Closed simontiffert closed 2 months ago
@simontiffert Thanks for reporting! The behavior is correct; we can not auto-configure the selectors in cases with more than one planning variable. I'm not exactly sure why it only started happening recently for you; as far as I know, this has always been the case.
Also, your report mentions 1.7.0 in places, and 1.8.0 in others. Which is the first release where you see this break? If 1.7.0, it is even stranger; that was a small release, and we didn't really touch anything that could do this. 1.8.0, on the other hand, was large and we were touching related code.
I'm assuming from the quickstart that you're not using Enterprise edition, and therefore not using Nearby selection?
(Side note: any reason why you're using chains as opposed to list variable? Chains will one day be deprecated. I'm assuming it's precisely because you need two vars.)
@triceo It is working with releases <= 1.7.0 and starts to throw the error message starting with version >= 1.8.0.
Yes, you are right, I have multiple planning variables which is not supported for list variables. Next to the assignment of visits to a "vehicle" - the duration of my visits is flexible, but there is a constraint for the overall duration of visits of a specific kind per week.
What would be the work around? Defining the selectors manually in the configuration?
What would be the work around? Defining the selectors manually in the configuration?
Exactly. As far as I know, we've always required it for situations with more than 1 variable. For example, the documentation for swap moves shows how to configure the move for two variables.
When time permits, we'll look into why it started happening suddenly in this case.
Hypothesis: it's happening because we've added TailChainSwapMove
to the list of default moves. Assuming it wasn't there before, this could explain the sudden change.
To prove or disprove, configure your move selectors to only include change and swap, without any additional selector configuration. (So, do not rely on the default config.) If it works without an exception, then the hypothesis is correct; it's failing now, because it has one more default move now, and out of the box that move doesn't like two variables.
@triceo The manual configuration seems to work:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<solver xmlns="https://timefold.ai/xsd/solver">
<solutionClass>org.acme.vehiclerouting.domain.VehicleRoutePlan</solutionClass>
<entityClass>org.acme.vehiclerouting.domain.Visit</entityClass>
<entityClass>org.acme.vehiclerouting.domain.Standstill</entityClass>
<constructionHeuristic/>
<localSearch>
<unionMoveSelector>
<changeMoveSelector/>
<swapMoveSelector/>
<tailChainSwapMoveSelector>
<entitySelector>
<entityClass>org.acme.vehiclerouting.domain.Visit</entityClass>
</entitySelector>
<valueSelector variableName="previousVisit">
</valueSelector>
</tailChainSwapMoveSelector>
</unionMoveSelector>
</localSearch>
</solver>
This confirms my hypothesis that the problem is caused by us introducing tailChainSwapMove
without realizing it will blow up in situations with more than 1 variable.
Describe the bug Chained model and one additional planning variable is no longer working with Timefold versions > 1.7.0
Expected behavior Auto-configuration working for PlanningVariableGraphType.CHAINED with additional PlanningVariables.
Actual behavior Error message after start of solving:
2024-07-03 08:35:12,652 ERROR [io.qua.ver.htt.run.QuarkusErrorHandler] (executor-thread-1) HTTP Request to /route-plans failed, error id: 1f3b37cd-2730-4a3c-b613-e77be28d6b25-1: org.jboss.resteasy.spi.UnhandledException: java.lang.IllegalArgumentException: The config (ValueSelectorConfig(null)) has no configured variableName for entityClass (class org.acme.vehiclerouting.domain.Visit) and because there are multiple variableNames ([count, previousVisit]), it cannot be deduced automatically. at org.jboss.resteasy.core.ExceptionHandler.handleException(ExceptionHandler.java:357) at org.jboss.resteasy.core.SynchronousDispatcher.writeException(SynchronousDispatcher.java:205) at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:452) at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invokePropagateNotFound$6(SynchronousDispatcher.java:275) at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:154) at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:321) at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:157) at org.jboss.resteasy.core.SynchronousDispatcher.invokePropagateNotFound(SynchronousDispatcher.java:260) at io.quarkus.resteasy.runtime.standalone.RequestDispatcher.service(RequestDispatcher.java:84) at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.dispatch(VertxRequestHandler.java:151) at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler$1.run(VertxRequestHandler.java:97) at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:599) at org.jboss.threads.EnhancedQueueExecutor$Task.doRunWith(EnhancedQueueExecutor.java:2516) at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2495) at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1521) at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:11) at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:11) at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) at java.base/java.lang.Thread.run(Thread.java:1583) Caused by: java.lang.IllegalArgumentException: The config (ValueSelectorConfig(null)) has no configured variableName for entityClass (class org.acme.vehiclerouting.domain.Visit) and because there are multiple variableNames ([count, previousVisit]), it cannot be deduced automatically. at ai.timefold.solver.core.impl.AbstractFromConfigFactory.getTheOnlyVariableDescriptor(AbstractFromConfigFactory.java:100) at ai.timefold.solver.core.impl.AbstractFromConfigFactory.deduceGenuineVariableDescriptor(AbstractFromConfigFactory.java:76) at ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelectorFactory.buildValueSelector(ValueSelectorFactory.java:89) at ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelectorFactory.buildValueSelector(ValueSelectorFactory.java:81) at ai.timefold.solver.core.impl.heuristic.selector.move.generic.chained.TailChainSwapMoveSelectorFactory.buildBaseMoveSelector(TailChainSwapMoveSelectorFactory.java:36) at ai.timefold.solver.core.impl.heuristic.selector.move.AbstractMoveSelectorFactory.buildMoveSelector(AbstractMoveSelectorFactory.java:70) at ai.timefold.solver.core.impl.heuristic.selector.move.composite.AbstractCompositeMoveSelectorFactory.lambda$buildInnerMoveSelectors$0(AbstractCompositeMoveSelectorFactory.java:28) at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) at java.base/java.util.LinkedList$LLSpliterator.forEachRemaining(LinkedList.java:1249) at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921) at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682) at ai.timefold.solver.core.impl.heuristic.selector.move.composite.AbstractCompositeMoveSelectorFactory.buildInnerMoveSelectors(AbstractCompositeMoveSelectorFactory.java:29) at ai.timefold.solver.core.impl.heuristic.selector.move.composite.UnionMoveSelectorFactory.buildBaseMoveSelector(UnionMoveSelectorFactory.java:46) at ai.timefold.solver.core.impl.heuristic.selector.move.AbstractMoveSelectorFactory.buildMoveSelector(AbstractMoveSelectorFactory.java:70) at ai.timefold.solver.core.impl.localsearch.DefaultLocalSearchPhaseFactory.buildMoveSelector(DefaultLocalSearchPhaseFactory.java:184) at ai.timefold.solver.core.impl.localsearch.DefaultLocalSearchPhaseFactory.buildDecider(DefaultLocalSearchPhaseFactory.java:67) at ai.timefold.solver.core.impl.localsearch.DefaultLocalSearchPhaseFactory.buildPhase(DefaultLocalSearchPhaseFactory.java:53) at ai.timefold.solver.core.impl.localsearch.DefaultLocalSearchPhaseFactory.buildPhase(DefaultLocalSearchPhaseFactory.java:40) at ai.timefold.solver.core.impl.phase.PhaseFactory.buildPhases(PhaseFactory.java:59) at ai.timefold.solver.core.impl.solver.DefaultSolverFactory.buildPhaseList(DefaultSolverFactory.java:249) at ai.timefold.solver.core.impl.solver.DefaultSolverFactory.buildSolver(DefaultSolverFactory.java:125) at ai.timefold.solver.core.api.solver.SolverFactory.buildSolver(SolverFactory.java:119) at ai.timefold.solver.core.impl.solver.DefaultSolverManager.validateSolverFactory(DefaultSolverManager.java:65) at ai.timefold.solver.core.impl.solver.DefaultSolverManager.<init>(DefaultSolverManager.java:49) at ai.timefold.solver.core.api.solver.SolverManager.create(SolverManager.java:98) at ai.timefold.solver.quarkus.bean.DefaultTimefoldBeanProvider.solverManager(DefaultTimefoldBeanProvider.java:62) at ai.timefold.solver.quarkus.bean.DefaultTimefoldBeanProvider_ProducerMethod_solverManager_T5OWUir0G727pboehAmaToghWS8_Bean.doCreate(Unknown Source) at ai.timefold.solver.quarkus.bean.DefaultTimefoldBeanProvider_ProducerMethod_solverManager_T5OWUir0G727pboehAmaToghWS8_Bean.create(Unknown Source) at ai.timefold.solver.quarkus.bean.DefaultTimefoldBeanProvider_ProducerMethod_solverManager_T5OWUir0G727pboehAmaToghWS8_Bean.get(Unknown Source) at ai.timefold.solver.quarkus.bean.DefaultTimefoldBeanProvider_ProducerMethod_solverManager_T5OWUir0G727pboehAmaToghWS8_Bean.get(Unknown Source) at org.acme.vehiclerouting.rest.VehicleRoutePlanResource_Bean.doCreate(Unknown Source) at org.acme.vehiclerouting.rest.VehicleRoutePlanResource_Bean.create(Unknown Source) at org.acme.vehiclerouting.rest.VehicleRoutePlanResource_Bean.create(Unknown Source) at io.quarkus.arc.impl.AbstractSharedContext.createInstanceHandle(AbstractSharedContext.java:119) at io.quarkus.arc.impl.AbstractSharedContext$1.get(AbstractSharedContext.java:38) at io.quarkus.arc.impl.AbstractSharedContext$1.get(AbstractSharedContext.java:35) at io.quarkus.arc.impl.LazyValue.get(LazyValue.java:32) at io.quarkus.arc.impl.ComputingCache.computeIfAbsent(ComputingCache.java:69) at io.quarkus.arc.impl.ComputingCacheContextInstances.computeIfAbsent(ComputingCacheContextInstances.java:19) at io.quarkus.arc.impl.AbstractSharedContext.get(AbstractSharedContext.java:35) at org.acme.vehiclerouting.rest.VehicleRoutePlanResource_Bean.get(Unknown Source) at org.acme.vehiclerouting.rest.VehicleRoutePlanResource_Bean.get(Unknown Source) at io.quarkus.arc.impl.ArcContainerImpl.beanInstanceHandle(ArcContainerImpl.java:559) at io.quarkus.arc.impl.ArcContainerImpl.beanInstanceHandle(ArcContainerImpl.java:539) at io.quarkus.arc.impl.ArcContainerImpl.beanInstanceHandle(ArcContainerImpl.java:572) at io.quarkus.arc.impl.ArcContainerImpl$3.get(ArcContainerImpl.java:331) at io.quarkus.arc.impl.ArcContainerImpl$3.get(ArcContainerImpl.java:328) at io.quarkus.resteasy.common.runtime.QuarkusConstructorInjectoruct(QuarkusConstructorInjector.java:52) at org.jboss.resteasy.plugins.server.resourcefactory.POJOResourceFactory.createResource(POJOResourceFactory.java:64) at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:349) at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:70) at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:429) ... 16 more
To Reproduce Go to branch https://github.com/simontiffert/timefold-quickstarts/tree/chainedMultiVariable and run it (start planning in the UI) with Timefold 1.11.0 (default) or Timefold 1.7.0. There is one additional planning variable count in the class Visit. Please note that the branch is just used to show case the error and is not meant to be a full working example.
Environment
Timefold Solver Version or Git ref: 1.11.0 (but can reproduce in 1.8.0 as well)
Output of
java -version
: openjdk version "21.0.1" 2023-10-17 OpenJDK Runtime Environment (build 21.0.1+12-29) OpenJDK 64-Bit Server VM (build 21.0.1+12-29, mixed mode, sharing)