Swap move selector applies entity value range filtering to entity classes without entity-level ranges
Maintainers usually reply within 1 day
@zepfred is already working on this.
Since Oct 15, 2026.
Assessment
This issue has not been assessed yet.
Description
Summary
Once any basic variable in the model has an entity-level value range, the default swap move selector
turns on entity value range filtering for every entity class with basic variables, including classes
whose variables only have solution-level ranges. Local search then crashes when it starts.
List variables are not affected: only basic variables are checked.
Error
java.lang.IllegalStateException: Impossible state: no basic variable found for the entity class org.acme.projectjobschedule.domain.ProjectLead.
at ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.FilteringEntityByEntitySelector.phaseStarted(FilteringEntityByEntitySelector.java:104)
...
at ai.timefold.solver.core.impl.neighborhood.MoveSelectorBasedMoveRepository.phaseStarted(MoveSelectorBasedMoveRepository.java:43)
at ai.timefold.solver.core.impl.localsearch.decider.LocalSearchDecider.phaseStarted(LocalSearchDecider.java:78)
at ai.timefold.solver.core.impl.localsearch.DefaultLocalSearchPhase.phaseStarted(DefaultLocalSearchPhase.java:136)
at ai.timefold.solver.core.impl.localsearch.DefaultLocalSearchPhase.solve(DefaultLocalSearchPhase.java:76)
Root cause
SwapMoveSelectorFactory.java (lines 44-47) decides whether to enable entity value range filtering by
looking at all basic variables in the solution, not just those of the entity class being swapped:
var entityDescriptor = deduceEntityDescriptor(configPolicy, entitySelectorConfig.getEntityClass());
var enableEntityValueRangeFilter = entityDescriptor.getSolutionDescriptor().getBasicVariableDescriptorList()
.stream()
.anyMatch(v -> !v.canExtractValueRangeFromSolution());
The default swap move selector is unfolded into one child selector per entity class with basic variables.
For an entity class whose variables all have solution-level ranges, the check above still returns true
because of another entity class, so a recorder ID is set and the right entity selector is wrapped in
FilteringEntityByEntitySelector. That selector then finds no entity-level range on this class and throws.
Suggested fix
Scope the check to the entity class being swapped (ideally to the variables the selector actually uses,
honoring variableNameIncludeList):
var enableEntityValueRangeFilter = deduceBasicVariableDescriptorList(entityDescriptor, config.getVariableNameIncludeList())
.stream()
.anyMatch(v -> !v.canExtractValueRangeFromSolution());
Reproducer
Quickstart use-cases/project-job-scheduling, branch bug/reproduce-multi-entity-value-range:
Allocation(existing) has basic variables with entity-level ranges (executionMode,delay).ProjectLead(new) has a basic variableresourcewhose range comes from the solution
(@ValueRangeProvideronProjectJobSchedule.resources).MultiEntityValueRangeReproducerTestsolves for 5 seconds without a best score limit, so the
local search phase runs and crashes.
Note: the existing SolverManagerTest does not catch this because the test profile's best score limit
ends the solve before local search starts.
Workaround
Give every other entity class with basic variables an entity-level range too, even if it just lists every
value:
@ValueRangeProvider
public List<Resource> getResourceRange() {
return resourceRange; // all resources
}
With this in place, the reproducer passes and local search runs normally.
Environment
- Reproduced with the local
timefold-solver999-SNAPSHOT(built 2026-10-12); the same code is on
solvermain. Reported against 2.7.
- Dominant language
- Java
- Stars
- 1.8k
- Forks
- 238
- Avg merge
- 3d 1h
- Merged PRs (30d)
- 55
Getting set up
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from TimefoldAI/timefold-solver
-
component/docs
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
TimefoldAI/timefold-solver#2671 ·
Maintainers usually reply within 1 day
-
process/needs triage
Difficulty 5/5 Over a week Newbie friendliness 12/100
TimefoldAI/timefold-solver#2763 · 2 comments ·
Maintainers usually reply within 1 day
-
Docs: Add keywords for search.Possibly taken @TomCools claimed this 2 days ago. Opencomponent/docs
TimefoldAI/timefold-solver#2762 · 1 assignee ·
Maintainers usually reply within 1 day
-
SDK: Waypoints class has not OpenAPI Spec annotations.Possibly taken @TomCools claimed this 3 days ago. Opencomponent/service
TimefoldAI/timefold-solver#2759 · 1 assignee ·
Maintainers usually reply within 1 day
-
process/needs triage
Difficulty 5/5 Over a week Newbie friendliness 30/100
TimefoldAI/timefold-solver#2755 ·
Maintainers usually reply within 1 day
All issues in TimefoldAI/timefold-solver
Similar issues
-
enhancement Improvement
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
zowe/zowe-client-java-sdk#647 ·
-
NullPointerException when a `DeserializationProblemHandler` returns `null` for `byte[]` (single value unwrapped) and `char[]` elementsPossibly taken @Marti2203 claimed this today. Open
Difficulty 2/5 Half a day Newbie friendliness 88/100
FasterXML/jackson-databind#6300 ·
Maintainers usually reply within 1 day
-
content good first issue
Difficulty 1/5 Under an hour Newbie friendliness 95/100
cpinitiative/usaco-guide#6699 ·
Maintainers usually reply within 1 day
-
BC remittance upload: a claim MSP refused (S03) is saved with a blank status instead of refused (F)Possibly taken A pull request linked to this issue is open or already merged. Opentype: bug
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
carlos-emr/carlos#4933 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
apache/rocketmq-dashboard#6925 ·
Maintainers usually reply within 3 days