spring-bootoptaplannertimefold

TimeFold Variable Listener. How do I update start time based on the adjusted end time if a condition met (end time is between breaktime). Solving fail


Currently I'm using adjustedEndTime to keep track of when end time was updated in @PlanningEntity class

  @ShadowVariable(variableListenerClass = TaskChangeListener.class, sourceVariableName = "previousTask")
  @ShadowVariable(variableListenerClass = TaskChangeListener.class, sourceVariableName = "adjustEndTime")
  @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
  @JsonDeserialize(using = LocalDateTimeDeserializer.class)
  @JsonSerialize(using = LocalDateTimeSerializer.class)
  private LocalDateTime startTime;

  @PiggybackShadowVariable(shadowVariableName = "startTime")
  @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
  @JsonDeserialize(using = LocalDateTimeDeserializer.class)
  @JsonSerialize(using = LocalDateTimeSerializer.class)
  private LocalDateTime endTime;

   @JsonIgnore
   @ShadowVariable(variableListenerClass = TaskChangeListener.class, sourceVariableName = "machine")
   @ShadowVariable(variableListenerClass = TaskChangeListener.class, sourceVariableName = "previousTask")
   private LocalTime adjustEndTime;

  @PiggybackShadowVariable(shadowVariableName = "startTime")
  @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
  @JsonDeserialize(using = LocalDateTimeDeserializer.class)
  @JsonSerialize(using = LocalDateTimeSerializer.class)
  private LocalDateTime endTimePlusChangeOver;

In variable listener class, I set it to update end time if it is bewteen break. I set adjustEndTime here for the next start time to update.

public Map<String, LocalTime> skipBreakTime(ScoreDirector score, Task task, LocalTime start, LocalTime end, LocalTime endWithChangeover, BreakTime breakTime) {
        Map<String, LocalTime> planningTime = new HashMap<>();
        Duration breakDuration = Duration.between(breakTime.getFromTime(), breakTime.getToTime());

        // Case 1: Task starts before break time and ends during break time
        if (start.isBefore(breakTime.getFromTime())
                && end.isAfter(breakTime.getFromTime())
                && (end.isBefore(breakTime.getToTime()) || end.equals(breakTime.getToTime())  ) ) {
            end = end.plus(breakDuration);
            endWithChangeover = endWithChangeover.plus(breakDuration);
            score.beforeVariableChanged(task, "adjustEndTime");
            task.setAdjustEndTime(end);
            score.afterVariableChanged(task, "adjustEndTime");

            // Case 2: Task starts during the break time
        } else if (!start.isBefore(breakTime.getFromTime())
                //&& start.isAfter(task.getPreviousTask().getEndTimePlusChangeOver().toLocalTime())
                && !start.isAfter(breakTime.getToTime())) {
            start = breakTime.getToTime();
            end = task.calculateEndTime(start);

            // Case 3: Task starts before and ends after the break time
        } else if (start.isBefore(breakTime.getFromTime()) && end.isAfter(breakTime.getToTime())) {
            end = end.plus(breakDuration);
            score.beforeVariableChanged(task, "adjustEndTime");
            task.setAdjustEndTime(end);
            score.afterVariableChanged(task, "adjustEndTime");
        }
//        log.info(task.getPreviousTask().getMachine().getMachine_name() + " " + task.getMachine().getMachine_name());


        planningTime.put("startTime", start);
        planningTime.put("endTime", end);
        return planningTime;
    }

After the solving ends, I got error. Before , when I mistakenly add at to cas 'Start time between break time, it runs no have no error.

2024-09-14 11:20:58 ERROR a.t.s.c.i.s.DefaultSolverManager - Solving failed for problemId (d81ec522-24cd-4b0e-bf6c-7679f11ba30b).
java.util.ConcurrentModificationException: null
    at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1096)
    at java.base/java.util.ArrayList$Itr.next(ArrayList.java:1050)
    at ai.timefold.solver.core.impl.util.ListBasedScalingOrderedSet$1.next(ListBasedScalingOrderedSet.java:64)
    at ai.timefold.solver.core.impl.domain.variable.listener.support.AbstractNotifiable.triggerAllNotifications(AbstractNotifiable.java:86)
    at ai.timefold.solver.core.impl.domain.variable.listener.support.VariableListenerSupport.triggerVariableListenersInNotificationQueues(VariableListenerSupport.java:224)
    at ai.timefold.solver.core.impl.score.director.AbstractScoreDirector.triggerVariableListeners(AbstractScoreDirector.java:314)
    at ai.timefold.solver.core.impl.heuristic.move.AbstractMove.doMoveOnly(AbstractMove.java:28)
    at ai.timefold.solver.core.impl.score.director.AbstractScoreDirector.doAndProcessMove(AbstractScoreDirector.java:264)
    at ai.timefold.solver.core.impl.localsearch.decider.LocalSearchDecider.doMove(LocalSearchDecider.java:117)
    at ai.timefold.solver.core.impl.localsearch.decider.LocalSearchDecider.decideNextStep(LocalSearchDecider.java:98)
    at ai.timefold.solver.core.impl.localsearch.DefaultLocalSearchPhase.solve(DefaultLocalSearchPhase.java:72)
    at ai.timefold.solver.core.impl.solver.AbstractSolver.runPhases(AbstractSolver.java:82)
    at ai.timefold.solver.core.impl.solver.DefaultSolver.solve(DefaultSolver.java:200)
    at ai.timefold.solver.core.impl.solver.DefaultSolverJob.call(DefaultSolverJob.java:119)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
    at java.base/java.lang.Thread.run(Thread.java:1570)
2024-09-14 11:20:58 INFO  c.w.c.controller.TimetableController - /solve failed for plan d81ec522-24cd-4b0e-bf6c-7679f11ba30b
java.util.concurrent.ExecutionException: java.lang.IllegalStateException: Solving failed for problemId (d81ec522-24cd-4b0e-bf6c-7679f11ba30b).
    at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122)
    at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191)
    at ai.timefold.solver.core.impl.solver.DefaultSolverJob.getFinalBestSolution(DefaultSolverJob.java:209)
    at com.win.cuttingsop.controller.TimetableController.solve(TimetableController.java:69)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:255)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:188)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:926)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:831)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914)
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:389)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:904)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
    at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190)
    at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
    at java.base/java.lang.Thread.run(Thread.java:1570)
Caused by: java.lang.IllegalStateException: Solving failed for problemId (d81ec522-24cd-4b0e-bf6c-7679f11ba30b).
    at ai.timefold.solver.core.impl.solver.DefaultSolverJob.call(DefaultSolverJob.java:125)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
    ... 1 common frames omitted
Caused by: java.util.ConcurrentModificationException: null
    at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1096)
    at java.base/java.util.ArrayList$Itr.next(ArrayList.java:1050)
    at ai.timefold.solver.core.impl.util.ListBasedScalingOrderedSet$1.next(ListBasedScalingOrderedSet.java:64)
    at ai.timefold.solver.core.impl.domain.variable.listener.support.AbstractNotifiable.triggerAllNotifications(AbstractNotifiable.java:86)

Anyone know how can I notify start time to change after endtime changes?

When I just use shadow variable for start time, piggyback for end time, each time end time move 1 hour, the next task start at case 2 (next task start after break time is over), and overlap the previous task. It becomes:

break time: 11:30 - 12:30 (if start between break, move start to end of break. If end between break, add 60 more minutes)

before skip break time method: Task 2: start: 11:29 - end: 11:55

after skip break time method: Task 2: start : 11:29 - end: 12:55

finale result: Task 2: start : 11:29 - end: 12:55

next task by same employee: Task 5: start: 12:30 - end 12: 50 (overlap)

next task by same employee: Task 5: start: 12:50 - end 13: 20


Solution

  • Without looking deeply into your problem, I'm going to recommend you use the recently released cascading shadow variable to update your tail chains. This is a significantly simplified API (as compared to variable listeners) and is likely to remove whatever problem that arose in your variable listener.