抽象条理非常好,广义优化工具。用于排产没有复杂的落地示例
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
安装
设置
- xml设置
- solutionClass
- entityClass
- constraintProviderClass
- termination: 5min
- constructionHeuristic: 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
- planning entity
- planing variable
- 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
约束
比如排产中的工序依赖关系
- import org.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore;
- import org.timefold.solver.core.api.score.stream.ConstraintProvider;
- import org.timefold.solver.core.api.score.stream.Constraint;
- import org.timefold.solver.core.api.score.stream.ConstraintStream;
- import org.timefold.solver.core.api.score.stream.Joiners;
- public class JobShopConstraintProvider implements ConstraintProvider {
- @Override
- public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
- return new Constraint[] {
- // Ensure operations follow the sequence within each job
- constraintFactory.from(Operation.class)
- .join(Operation.class, Joiners.filteringEach(otherOp ->
- otherOp.getJob().equals(op.getJob()) &&
- otherOp.getSequence() == op.getSequence() + 1
- ))
- .penalize("Operations must follow sequence",
- HardSoftScore.ONE_HARD,
- (op, otherOp) -> 1),
-
- // Ensure machine constraints are respected
- constraintFactory.from(Operation.class)
- .join(Operation.class, Joiners.filteringEach((op1, op2) ->
- op1.getMachine().equals(op2.getMachine()) &&
- op1.getEndTime() > op2.getStartTime() &&
- op1.getStartTime() < op2.getEndTime() &&
- !op1.equals(op2))
- )
- .penalize("Machine cannot process two operations at once",
- HardSoftScore.ONE_HARD,
- (op1, op2) -> 1)
- };
- }
- }
复制代码 官方示例
入口在APP的main
- public static void main(String[] args) {
- prepareSwingEnvironment();
- new ProjectJobSchedulingApp().init();
- }
复制代码 init
- public void init() {
- init(null, true);
- }
- public void init(Component centerForComponent, boolean exitOnClose) {
- solutionBusiness = createSolutionBusiness();
- solverAndPersistenceFrame = new SolverAndPersistenceFrame<>(solutionBusiness, createSolutionPanel(),
- createExtraActions());
- solverAndPersistenceFrame
- .setDefaultCloseOperation(exitOnClose ? WindowConstants.EXIT_ON_CLOSE : WindowConstants.DISPOSE_ON_CLOSE);
- solverAndPersistenceFrame.init(centerForComponent);
- solverAndPersistenceFrame.setVisible(true);
- }
复制代码 其中,solution business
- SolverFactory.createFromXmlResource建立了solver
- public SolutionBusiness<Solution_, ?> createSolutionBusiness() {
- SolutionBusiness<Solution_, ?> solutionBusiness = new SolutionBusiness<>(this,
- SolverFactory.createFromXmlResource(solverConfigResource));
- solutionBusiness.setDataDir(determineDataDir(dataDirName));
- solutionBusiness.setSolutionFileIO(createSolutionFileIO());
- solutionBusiness.setImporters(createSolutionImporters());
- solutionBusiness.setExporters(createSolutionExporters());
- solutionBusiness.updateDataDirs();
- return solutionBusiness;
- }
复制代码 在APP类继承的solution中,示例采用的是schedule,也就是planningsolution,作为题目和排产效果
Timefold 示例
Solver job接受到problem,开始run
- @Deprecated(forRemoval = true, since = "1.6.0")
- default SolverJob<Solution_, ProblemId_> solve(ProblemId_ problemId,
- Solution_ problem, Consumer<? super Solution_> finalBestSolutionConsumer,
- BiConsumer<? super ProblemId_, ? super Throwable> exceptionHandler) {
- SolverJobBuilder<Solution_, ProblemId_> builder = solveBuilder()
- .withProblemId(problemId)
- .withProblem(problem);
- if (finalBestSolutionConsumer != null) {
- builder.withFinalBestSolutionConsumer(finalBestSolutionConsumer);
- }
- if (exceptionHandler != null) {
- builder.withExceptionHandler(exceptionHandler);
- }
- return builder.run();
- }
复制代码- solverStatus = SolverStatus.SOLVING_ACTIVE;
- // Create the consumer thread pool only when this solver job is active.
- consumerSupport = new ConsumerSupport<>(getProblemId(), bestSolutionConsumer, finalBestSolutionConsumer,
- firstInitializedSolutionConsumer, exceptionHandler, bestSolutionHolder);
- Solution_ problem = problemFinder.apply(problemId);
- // add a phase lifecycle listener that unlock the solver status lock when solving started
- solver.addPhaseLifecycleListener(new UnlockLockPhaseLifecycleListener());
- // add a phase lifecycle listener that consumes the first initialized solution
- solver.addPhaseLifecycleListener(new FirstInitializedSolutionPhaseLifecycleListener(consumerSupport));
- solver.addEventListener(this::onBestSolutionChangedEvent);
- final Solution_ finalBestSolution = solver.solve(problem);
- consumerSupport.consumeFinalBestSolution(finalBestSolution);
- return finalBestSolution;
复制代码 理解
- https://www.optaplanner.org/docs/optaplanner/latest/shadow-variable/shadow-variable.html
- build_solver/ default_solver_factory
- public Solver<Solution_> buildSolver(SolverConfigOverride<Solution_> configOverride) {
- Objects.requireNonNull(configOverride, "Invalid configOverride (null) given to SolverFactory.");
- var isDaemon = Objects.requireNonNullElse(solverConfig.getDaemon(), false);
- var solverScope = new SolverScope<Solution_>();
- var monitoringConfig = solverConfig.determineMetricConfig();
- solverScope.setMonitoringTags(Tags.empty());
- var metricsRequiringConstraintMatchSet = Collections.<SolverMetric> emptyList();
- if (!monitoringConfig.getSolverMetricList().isEmpty()) {
- solverScope.setSolverMetricSet(EnumSet.copyOf(monitoringConfig.getSolverMetricList()));
- metricsRequiringConstraintMatchSet = solverScope.getSolverMetricSet().stream()
- .filter(SolverMetric::isMetricConstraintMatchBased)
- .filter(solverScope::isMetricEnabled)
- .toList();
- } else {
- solverScope.setSolverMetricSet(EnumSet.noneOf(SolverMetric.class));
- }
- var environmentMode = solverConfig.determineEnvironmentMode();
- var constraintMatchEnabled = !metricsRequiringConstraintMatchSet.isEmpty() || environmentMode.isAsserted();
- if (constraintMatchEnabled && !environmentMode.isAsserted()) {
- LOGGER.info(
- "Enabling constraint matching as required by the enabled metrics ({}). This will impact solver performance.",
- metricsRequiringConstraintMatchSet);
- }
- var innerScoreDirector = scoreDirectorFactory.buildScoreDirector(true, constraintMatchEnabled);
- solverScope.setScoreDirector(innerScoreDirector);
- solverScope.setProblemChangeDirector(new DefaultProblemChangeDirector<>(innerScoreDirector));
- var moveThreadCount = resolveMoveThreadCount(true);
- var bestSolutionRecaller = BestSolutionRecallerFactory.create().<Solution_> buildBestSolutionRecaller(environmentMode);
- var randomFactory = buildRandomFactory(environmentMode);
- var configPolicy = new HeuristicConfigPolicy.Builder<>(
- environmentMode,
- moveThreadCount,
- solverConfig.getMoveThreadBufferSize(),
- solverConfig.getThreadFactoryClass(),
- solverConfig.getNearbyDistanceMeterClass(),
- randomFactory.createRandom(),
- scoreDirectorFactory.getInitializingScoreTrend(),
- solutionDescriptor,
- ClassInstanceCache.create()).build();
- var basicPlumbingTermination = new BasicPlumbingTermination<Solution_>(isDaemon);
- var termination = buildTerminationConfig(basicPlumbingTermination, configPolicy, configOverride);
- var phaseList = buildPhaseList(configPolicy, bestSolutionRecaller, termination);
- return new DefaultSolver<>(environmentMode, randomFactory, bestSolutionRecaller, basicPlumbingTermination,
- termination, phaseList, solverScope,
- moveThreadCount == null ? SolverConfig.MOVE_THREAD_COUNT_NONE : Integer.toString(moveThreadCount));
- }
复制代码 solver的主流程
- @Override
- public final Solution_ solve(Solution_ problem) {
- if (problem == null) {
- throw new IllegalArgumentException("The problem (" + problem + ") must not be null.");
- }
- // No tags for these metrics; they are global
- LongTaskTimer solveLengthTimer = Metrics.more().longTaskTimer(SolverMetric.SOLVE_DURATION.getMeterId());
- Counter errorCounter = Metrics.counter(SolverMetric.ERROR_COUNT.getMeterId());
- solverScope.setBestSolution(problem);
- solverScope.setSolver(this);
- outerSolvingStarted(solverScope);
- boolean restartSolver = true;
- while (restartSolver) {
- LongTaskTimer.Sample sample = solveLengthTimer.start();
- try {
- // solvingStarted will call registerSolverSpecificMetrics(), since
- // the solverScope need to be fully initialized to calculate the
- // problem's scale metrics
- solvingStarted(solverScope);
- runPhases(solverScope);
- solvingEnded(solverScope);
- } catch (Exception e) {
- errorCounter.increment();
- solvingError(solverScope, e);
- throw e;
- } finally {
- sample.stop();
- unregisterSolverSpecificMetrics();
- }
- restartSolver = checkProblemFactChanges();
- }
- outerSolvingEnded(solverScope);
- return solverScope.getBestSolution();
- }
复制代码
- run_phase /abstract_solver
- protected void runPhases(SolverScope<Solution_> solverScope) {
- if (!solverScope.getSolutionDescriptor().hasMovableEntities(solverScope.getScoreDirector())) {
- logger.info("Skipped all phases ({}): out of {} planning entities, none are movable (non-pinned).",
- phaseList.size(), solverScope.getWorkingEntityCount());
- return;
- }
- Iterator<Phase<Solution_>> it = phaseList.iterator();
- while (!solverTermination.isSolverTerminated(solverScope) && it.hasNext()) {
- Phase<Solution_> phase = it.next();
- phase.solve(solverScope);
- // If there is a next phase, it starts from the best solution, which might differ from the working solution.
- // If there isn't, no need to planning clone the best solution to the working solution.
- if (it.hasNext()) {
- solverScope.setWorkingSolutionFromBestSolution();
- }
- }
- }
复制代码
- solver表面的phase, PhaseFactory
- dostep
局部搜索在当前解上尝试多个移动,并选择最佳的被接受的移动作为这一步。A step is the winning Move。在每一步,它尝试全部选定的移动,除非是选定的step,否则它不会进一步研究谁人解。这就是局部搜索具有很高可扩展性的原因之一。
- private void doStep(CustomStepScope<Solution_> stepScope, CustomPhaseCommand<Solution_> customPhaseCommand) {
- InnerScoreDirector<Solution_, ?> scoreDirector = stepScope.getScoreDirector();
- customPhaseCommand.changeWorkingSolution(scoreDirector);
- calculateWorkingStepScore(stepScope, customPhaseCommand);
- solver.getBestSolutionRecaller().processWorkingSolutionDuringStep(stepScope);
- }
复制代码
- 决定下一步
- 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.
- <localSearch>
- <unionMoveSelector>
- ...
- </unionMoveSelector>
- <acceptor>
- ...
- </acceptor>
- <forager>
- ...
- </forager>
- </localSearch>
复制代码
从底向上看,理解大概的move。如果是entity+value组合,或者是entity和entity进行新的组合。也许这就是叫做组合优化的原因?
chatgpt产生的一些简单代码
- // File: pom.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <groupId>org.example</groupId>
- <artifactId>jobshop-scheduler</artifactId>
- <version>1.0-SNAPSHOT</version>
- <properties>
- <maven.compiler.source>17</maven.compiler.source>
- <maven.compiler.target>17</maven.compiler.target>
- <timefold.version>1.6.0</timefold.version>
- </properties>
- <dependencies>
- <dependency>
- <groupId>ai.timefold.solver</groupId>
- <artifactId>timefold-solver-core</artifactId>
- <version>${timefold.version}</version>
- </dependency>
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <version>1.18.30</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>ch.qos.logback</groupId>
- <artifactId>logback-classic</artifactId>
- <version>1.4.11</version>
- </dependency>
- </dependencies>
- </project>
- // File: src/main/resources/logback.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <configuration>
- <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
- <encoder>
- <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
- </encoder>
- </appender>
- <logger name="ai.timefold.solver" level="info"/>
- <root level="warn">
- <appender-ref ref="STDOUT"/>
- </root>
- </configuration>
- // File: src/main/resources/solver-config.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <solver xmlns="https://timefold.ai/xsd/solver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="https://timefold.ai/xsd/solver https://timefold.ai/xsd/solver/solver.xsd">
- <solutionClass>org.example.jobshop.domain.JobShopSchedule</solutionClass>
- <entityClass>org.example.jobshop.domain.JobAllocation</entityClass>
- <scoreDirectorFactory>
- <constraintProviderClass>org.example.jobshop.solver.JobShopConstraintProvider</constraintProviderClass>
- </scoreDirectorFactory>
- <termination>
- <minutesSpentLimit>5</minutesSpentLimit>
- </termination>
- <constructionHeuristic>
- <constructionHeuristicType>FIRST_FIT_DECREASING</constructionHeuristicType>
- </constructionHeuristic>
- <localSearch>
- <unionMoveSelector>
- <changeMoveSelector/>
- <swapMoveSelector/>
- <moveListFactory>
- <moveListFactoryClass>org.example.jobshop.solver.JobAllocationMoveFactory</moveListFactoryClass>
- </moveListFactory>
- </unionMoveSelector>
- <acceptor>
- <lateAcceptanceSize>400</lateAcceptanceSize>
- </acceptor>
- <forager>
- <acceptedCountLimit>4</acceptedCountLimit>
- </forager>
- </localSearch>
- </solver>
- // File: src/main/java/org/example/jobshop/domain/Project.java
- package org.example.jobshop.domain;
- @lombok.Data
- @lombok.NoArgsConstructor
- @lombok.AllArgsConstructor
- public class Project {
- private String id;
- private String name;
- private LocalDateTime releaseDate;
- private LocalDateTime dueDate;
- private List<Job> jobs;
- }
- // File: src/main/java/org/example/jobshop/domain/Job.java
- package org.example.jobshop.domain;
- @lombok.Data
- @lombok.NoArgsConstructor
- @lombok.AllArgsConstructor
- public class Job {
- private String id;
- private String name;
- private Project project;
- private Duration processingTime;
- private List<Resource> compatibleResources;
- private Job previousJob;
- private Job nextJob;
- }
- // File: src/main/java/org/example/jobshop/domain/Resource.java
- package org.example.jobshop.domain;
- @lombok.Data
- @lombok.NoArgsConstructor
- @lombok.AllArgsConstructor
- public class Resource {
- private String id;
- private String name;
- private ResourceType type;
- private LocalDateTime availableFrom;
- private double costPerHour;
- }
- // File: src/main/java/org/example/jobshop/domain/JobAllocation.java
- package org.example.jobshop.domain;
- @PlanningEntity
- @lombok.Data
- @lombok.NoArgsConstructor
- @lombok.AllArgsConstructor
- public class JobAllocation {
- private String id;
- private Job job;
-
- @PlanningVariable(valueRangeProvider = "resourceRange")
- private Resource resource;
-
- @PlanningVariable(valueRangeProvider = "allocationRange")
- private JobAllocation previousAllocation;
-
- @CustomShadowVariable(
- variableListenerClass = StartTimeUpdatingVariableListener.class,
- sources = {@PlanningVariableReference(variableName = "previousAllocation"),
- @PlanningVariableReference(variableName = "resource")})
- private LocalDateTime startTime;
-
- private LocalDateTime endTime;
-
- // ... other methods from previous versions
- }
- // File: src/main/java/org/example/jobshop/solver/JobShopSolver.java
- package org.example.jobshop.solver;
- import ai.timefold.solver.core.api.solver.SolverFactory;
- import ai.timefold.solver.core.config.solver.SolverConfig;
- public class JobShopSolver {
- private final SolverFactory<JobShopSchedule> solverFactory;
- public JobShopSolver() {
- SolverConfig solverConfig = SolverConfig.createFromXmlResource(
- "solver-config.xml");
- this.solverFactory = SolverFactory.create(solverConfig);
- }
- public JobShopSchedule solve(JobShopSchedule problem) {
- return solverFactory.buildSolver().solve(problem);
- }
- }
- // File: src/main/java/org/example/jobshop/Main.java
- package org.example.jobshop;
- public class Main {
- public static void main(String[] args) {
- // Create sample problem
- JobShopSchedule problem = createSampleProblem();
-
- // Create and run solver
- JobShopSolver solver = new JobShopSolver();
- JobShopSchedule solution = solver.solve(problem);
-
- // Print solution
- printSolution(solution);
- }
- private static JobShopSchedule createSampleProblem() {
- // Create resources
- List<Resource> resources = new ArrayList<>();
- resources.add(new Resource("R1", "Machine 1", ResourceType.MACHINE,
- LocalDateTime.now(), 100.0));
- resources.add(new Resource("R2", "Machine 2", ResourceType.MACHINE,
- LocalDateTime.now(), 150.0));
-
- // Create projects and jobs
- List<Project> projects = new ArrayList<>();
- List<JobAllocation> allocations = new ArrayList<>();
-
- Project project1 = new Project("P1", "Project 1",
- LocalDateTime.now(), LocalDateTime.now().plusDays(5), new ArrayList<>());
-
- // Create jobs for project 1
- Job job1 = new Job("J1", "Job 1", project1, Duration.ofHours(4),
- resources, null, null);
- Job job2 = new Job("J2", "Job 2", project1, Duration.ofHours(3),
- resources, job1, null);
- job1.setNextJob(job2);
-
- project1.setJobs(Arrays.asList(job1, job2));
- projects.add(project1);
-
- // Create allocations
- allocations.add(new JobAllocation("A1", job1, null, null, null, null));
- allocations.add(new JobAllocation("A2", job2, null, null, null, null));
-
- return new JobShopSchedule(projects, allocations, resources);
- }
- private static void printSolution(JobShopSchedule solution) {
- System.out.println("\nSolution found:");
-
- // Print by resource
- for (Resource resource : solution.getResources()) {
- System.out.println("\nResource: " + resource.getName());
-
- // Find all allocations for this resource and sort by start time
- List<JobAllocation> resourceAllocations = solution.getAllocations().stream()
- .filter(a -> a.getResource() == resource)
- .sorted(Comparator.comparing(JobAllocation::getStartTime))
- .collect(Collectors.toList());
-
- for (JobAllocation allocation : resourceAllocations) {
- System.out.printf(" %s: %s -> %s (%s)\n",
- allocation.getJob().getName(),
- allocation.getStartTime().format(DateTimeFormatter.ISO_LOCAL_TIME),
- allocation.getEndTime().format(DateTimeFormatter.ISO_LOCAL_TIME),
- allocation.getJob().getProject().getName());
- }
- }
-
- // Print by project
- for (Project project : solution.getProjects()) {
- System.out.println("\nProject: " + project.getName());
-
- List<JobAllocation> projectAllocations = solution.getAllocations().stream()
- .filter(a -> a.getJob().getProject() == project)
- .sorted(Comparator.comparing(JobAllocation::getStartTime))
- .collect(Collectors.toList());
-
- for (JobAllocation allocation : projectAllocations) {
- System.out.printf(" %s: %s on %s\n",
- allocation.getJob().getName(),
- allocation.getStartTime().format(DateTimeFormatter.ISO_LOCAL_TIME),
- allocation.getResource().getName());
- }
- }
- }
- }
复制代码- // Domain classes
- @PlanningEntity
- public class Allocation {
- private Job job;
-
- @PlanningVariable(valueRangeProviderRefs = "resourceRange")
- private Resource resource;
-
- // This is the anchor planning variable
- @PlanningVariable(valueRangeProviderRefs = "allocationRange")
- private Allocation previousAllocation;
-
- // Shadow variables that get updated automatically
- @CustomShadowVariable(
- variableListenerClass = StartTimeUpdatingVariableListener.class,
- sources = {@PlanningVariableReference(variableName = "previousAllocation"),
- @PlanningVariableReference(variableName = "resource")})
- private Integer startTime;
-
- @CustomShadowVariable(
- variableListenerClass = EndTimeUpdatingVariableListener.class,
- sources = {@PlanningVariableReference(variableName = "startTime")})
- private Integer endTime;
- }
- // Move implementation for changing resources
- public class ResourceChangeMove extends AbstractMove<JobShopSchedule> {
- private final Allocation allocation;
- private final Resource toResource;
-
- @Override
- protected void doMoveOnGenuineVariables(ScoreDirector<JobShopSchedule> scoreDirector) {
- // Step 1: Change the resource
- scoreDirector.beforeVariableChanged(allocation, "resource");
- allocation.setResource(toResource);
- scoreDirector.afterVariableChanged(allocation, "resource");
-
- // Step 2: Update the chain
- // The allocation becomes the last in the new resource's chain
- Allocation lastInResource = findLastAllocationInResource(toResource);
-
- scoreDirector.beforeVariableChanged(allocation, "previousAllocation");
- allocation.setPreviousAllocation(lastInResource);
- scoreDirector.afterVariableChanged(allocation, "previousAllocation");
- }
- }
- // Move implementation for sequence changes
- public class SequenceChangeMove extends AbstractMove<JobShopSchedule> {
- private final Allocation allocation;
- private final Allocation beforeAllocation;
-
- @Override
- protected void doMoveOnGenuineVariables(ScoreDirector<JobShopSchedule> scoreDirector) {
- // Step 1: Update the chain links
- Allocation oldNextAllocation = allocation.getNextAllocation();
- Allocation oldPreviousAllocation = allocation.getPreviousAllocation();
-
- // Connect old previous and next allocations
- if (oldNextAllocation != null) {
- scoreDirector.beforeVariableChanged(oldNextAllocation, "previousAllocation");
- oldNextAllocation.setPreviousAllocation(oldPreviousAllocation);
- scoreDirector.afterVariableChanged(oldNextAllocation, "previousAllocation");
- }
-
- // Step 2: Insert allocation in new position
- Allocation newNextAllocation = beforeAllocation.getNextAllocation();
-
- scoreDirector.beforeVariableChanged(allocation, "previousAllocation");
- allocation.setPreviousAllocation(beforeAllocation);
- scoreDirector.afterVariableChanged(allocation, "previousAllocation");
-
- if (newNextAllocation != null) {
- scoreDirector.beforeVariableChanged(newNextAllocation, "previousAllocation");
- newNextAllocation.setPreviousAllocation(allocation);
- scoreDirector.afterVariableChanged(newNextAllocation, "previousAllocation");
- }
- }
- }
- // Variable Listener for updating start times
- public class StartTimeUpdatingVariableListener
- implements VariableListener<JobShopSchedule, Allocation> {
-
- @Override
- public void afterEntityAdded(ScoreDirector<JobShopSchedule> scoreDirector,
- Allocation allocation) {
- updateStartTime(scoreDirector, allocation);
- }
-
- @Override
- public void afterVariableChanged(ScoreDirector<JobShopSchedule> scoreDirector,
- Allocation allocation) {
- updateStartTime(scoreDirector, allocation);
- }
-
- private void updateStartTime(ScoreDirector<JobShopSchedule> scoreDirector,
- Allocation allocation) {
- Allocation previousAllocation = allocation.getPreviousAllocation();
- Integer newStartTime;
-
- if (previousAllocation == null) {
- // First in resource - can start at 0
- newStartTime = 0;
- } else {
- // Must wait for previous allocation to finish
- newStartTime = previousAllocation.getEndTime();
- }
-
- // Consider job dependencies (if this job must wait for other jobs)
- for (Job prerequisite : allocation.getJob().getPrerequisites()) {
- Allocation prerequisiteAllocation = findAllocationForJob(prerequisite);
- if (prerequisiteAllocation != null) {
- newStartTime = Math.max(newStartTime,
- prerequisiteAllocation.getEndTime());
- }
- }
-
- scoreDirector.beforeVariableChanged(allocation, "startTime");
- allocation.setStartTime(newStartTime);
- scoreDirector.afterVariableChanged(allocation, "startTime");
- }
- }
复制代码- package org.example.jobshop.solver;
- import ai.timefold.solver.core.api.score.director.ScoreDirector;
- import ai.timefold.solver.core.impl.phase.custom.CustomPhaseCommand;
- import java.util.*;
- public class JobShopInitializer implements CustomPhaseCommand<JobShopSchedule> {
- @Override
- public void changeWorkingSolution(ScoreDirector<JobShopSchedule> scoreDirector) {
- JobShopSchedule schedule = scoreDirector.getWorkingSolution();
-
- // Step 1: Sort jobs by priority and dependencies
- List<JobAllocation> sortedAllocations = prioritizeAllocations(schedule);
-
- // Step 2: Initialize all allocations to unassigned
- for (JobAllocation allocation : schedule.getAllocations()) {
- scoreDirector.beforeVariableChanged(allocation, "resource");
- allocation.setResource(null);
- scoreDirector.afterVariableChanged(allocation, "resource");
-
- scoreDirector.beforeVariableChanged(allocation, "previousAllocation");
- allocation.setPreviousAllocation(null);
- scoreDirector.afterVariableChanged(allocation, "previousAllocation");
- }
- // Step 3: Assign jobs considering both resource and sequence constraints
- Map<Resource, JobAllocation> lastAllocationByResource = new HashMap<>();
- Map<Job, LocalDateTime> jobEndTimes = new HashMap<>();
- for (JobAllocation allocation : sortedAllocations) {
- // Find best resource and position
- ResourceAssignment bestAssignment = findBestAssignment(
- allocation,
- schedule.getResources(),
- lastAllocationByResource,
- jobEndTimes,
- schedule);
- // Apply the assignment
- if (bestAssignment != null) {
- // Assign resource
- scoreDirector.beforeVariableChanged(allocation, "resource");
- allocation.setResource(bestAssignment.resource);
- scoreDirector.afterVariableChanged(allocation, "resource");
- // Assign previous allocation
- scoreDirector.beforeVariableChanged(allocation, "previousAllocation");
- allocation.setPreviousAllocation(bestAssignment.previousAllocation);
- scoreDirector.afterVariableChanged(allocation, "previousAllocation");
- // Update tracking maps
- lastAllocationByResource.put(bestAssignment.resource, allocation);
- jobEndTimes.put(allocation.getJob(), bestAssignment.endTime);
- }
- }
- }
- private List<JobAllocation> prioritizeAllocations(JobShopSchedule schedule) {
- List<JobAllocation> sortedAllocations = new ArrayList<>(schedule.getAllocations());
-
- // Create job dependency graph
- Map<Job, Set<Job>> dependencies = new HashMap<>();
- Map<Job, Integer> inDegree = new HashMap<>();
-
- for (Project project : schedule.getProjects()) {
- for (Job job : project.getJobs()) {
- dependencies.putIfAbsent(job, new HashSet<>());
- inDegree.putIfAbsent(job, 0);
-
- if (job.getNextJob() != null) {
- dependencies.get(job).add(job.getNextJob());
- inDegree.merge(job.getNextJob(), 1, Integer::sum);
- }
- }
- }
- // Topological sort with additional priority factors
- sortedAllocations.sort((a1, a2) -> {
- Job job1 = a1.getJob();
- Job job2 = a2.getJob();
-
- // First priority: dependency order
- int dep1 = inDegree.getOrDefault(job1, 0);
- int dep2 = inDegree.getOrDefault(job2, 0);
- if (dep1 != dep2) return dep1 - dep2;
-
- // Second priority: project due date
- int dueDate = job1.getProject().getDueDate()
- .compareTo(job2.getProject().getDueDate());
- if (dueDate != 0) return dueDate;
-
- // Third priority: processing time (longer first)
- return job2.getProcessingTime().compareTo(job1.getProcessingTime());
- });
-
- return sortedAllocations;
- }
- @lombok.Data
- @lombok.AllArgsConstructor
- private static class ResourceAssignment {
- private Resource resource;
- private JobAllocation previousAllocation;
- private LocalDateTime startTime;
- private LocalDateTime endTime;
- private double score;
- }
- private ResourceAssignment findBestAssignment(
- JobAllocation allocation,
- List<Resource> resources,
- Map<Resource, JobAllocation> lastAllocationByResource,
- Map<Job, LocalDateTime> jobEndTimes,
- JobShopSchedule schedule) {
-
- ResourceAssignment bestAssignment = null;
- double bestScore = Double.NEGATIVE_INFINITY;
- Job job = allocation.getJob();
-
- // Get earliest start time based on job dependencies
- LocalDateTime earliestStart = job.getPreviousJob() != null ?
- jobEndTimes.getOrDefault(job.getPreviousJob(), LocalDateTime.MIN) :
- LocalDateTime.now();
- // Try each compatible resource
- for (Resource resource : job.getCompatibleResources()) {
- JobAllocation lastAllocation = lastAllocationByResource.get(resource);
-
- // Calculate possible start time
- LocalDateTime startTime = calculateStartTime(
- earliestStart,
- lastAllocation,
- resource);
-
- LocalDateTime endTime = startTime.plus(job.getProcessingTime());
-
- // Calculate assignment score
- double score = calculateAssignmentScore(
- resource,
- startTime,
- endTime,
- job,
- schedule);
- if (score > bestScore) {
- bestScore = score;
- bestAssignment = new ResourceAssignment(
- resource, lastAllocation, startTime, endTime, score);
- }
- }
- return bestAssignment;
- }
- private LocalDateTime calculateStartTime(
- LocalDateTime earliestStart,
- JobAllocation lastAllocation,
- Resource resource) {
-
- LocalDateTime resourceAvailable = lastAllocation != null ?
- lastAllocation.getEndTime() :
- resource.getAvailableFrom();
-
- return earliestStart.isAfter(resourceAvailable) ?
- earliestStart : resourceAvailable;
- }
- private double calculateAssignmentScore(
- Resource resource,
- LocalDateTime startTime,
- LocalDateTime endTime,
- Job job,
- JobShopSchedule schedule) {
-
- double score = 0.0;
-
- // Factor 1: Resource utilization balance
- score -= getResourceUtilization(resource, schedule) * 2;
-
- // Factor 2: Start time (earlier is better)
- score -= startTime.until(job.getProject().getDueDate(), ChronoUnit.HOURS);
-
- // Factor 3: Resource cost
- score -= resource.getCostPerHour() *
- job.getProcessingTime().toHours();
-
- // Factor 4: Project critical path consideration
- if (isOnCriticalPath(job)) {
- score += 1000; // Prioritize critical path jobs
- }
-
- return score;
- }
- private double getResourceUtilization(Resource resource, JobShopSchedule schedule) {
- return schedule.getAllocations().stream()
- .filter(a -> a.getResource() == resource)
- .mapToDouble(a -> a.getJob().getProcessingTime().toHours())
- .sum();
- }
- private boolean isOnCriticalPath(Job job) {
- // Simple critical path detection
- Job current = job;
- while (current.getNextJob() != null) {
- current = current.getNextJob();
- }
- return current.getProject().getDueDate()
- .minusHours(calculatePathDuration(job))
- .isBefore(LocalDateTime.now());
- }
- private long calculatePathDuration(Job startJob) {
- long duration = 0;
- Job current = startJob;
- while (current != null) {
- duration += current.getProcessingTime().toHours();
- current = current.getNextJob();
- }
- return duration;
- }
- }
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |