Skip to content

Commit

Permalink
fix: enable Nearby selection for Construction Heuristics (#767)
Browse files Browse the repository at this point in the history
Refactors construction heuristics configuration in order to properly
enable nearby selection.
Introduces documentation for configuring nearby in CH.
Also addresses some problems with the CI which were identified while
working on this PR.
  • Loading branch information
triceo authored Apr 4, 2024
1 parent 6da1776 commit 3ab0480
Show file tree
Hide file tree
Showing 32 changed files with 530 additions and 375 deletions.
2 changes: 2 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ updates:
directory: "/"
schedule:
interval: weekly
time: '05:00' # Otherwise it picks a random time.
open-pull-requests-limit: 10
target-branch: "main"
commit-message:
Expand All @@ -12,3 +13,4 @@ updates:
directory: "/"
schedule:
interval: weekly
time: '05:00' # Otherwise it picks a random time.
4 changes: 2 additions & 2 deletions .github/workflows/downstream_benchmarks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:
# Clone timefold-solver-enterprise
- name: Find the proper timefold-solver-enterprise repo and branch
env:
CHAIN_USER: ${{ github.actor }}
CHAIN_USER: "TimefoldAI" # No forks of the enterprise repo are allowed.
CHAIN_BRANCH: ${{ github.head_ref }}
CHAIN_REPO: "timefold-solver-enterprise"
CHAIN_DEFAULT_BRANCH: ${{ endsWith(github.head_ref, '.x') && github.head_ref || 'main' }}
Expand Down Expand Up @@ -77,7 +77,7 @@ jobs:
# Clone timefold-solver-benchmarks
- name: Find the proper timefold-solver-benchmarks repo and branch
env:
CHAIN_USER: ${{ github.actor }}
CHAIN_USER: "TimefoldAI" # No forks of the benchmarks repo are allowed.
CHAIN_BRANCH: ${{ github.head_ref }}
CHAIN_REPO: "timefold-solver-benchmarks"
CHAIN_DEFAULT_BRANCH: ${{ endsWith(github.head_ref, '.x') && github.head_ref || 'main' }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/downstream_enterprise.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
# Need to check for stale repo, since Github is not aware of the build chain and therefore doesn't automate it.
- name: Find the proper timefold-solver-enterprise repo and branch
env:
CHAIN_USER: ${{ github.actor }}
CHAIN_USER: "TimefoldAI" # No forks of the enterprise repo are allowed.
CHAIN_BRANCH: ${{ github.head_ref }}
CHAIN_REPO: "timefold-solver-enterprise"
CHAIN_DEFAULT_BRANCH: ${{ endsWith(github.head_ref, '.x') && github.head_ref || 'main' }}
Expand Down
35 changes: 7 additions & 28 deletions benchmark/src/main/resources/benchmark.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -1199,7 +1199,7 @@
<xs:complexContent>


<xs:extension base="tns:nearbyAutoConfigurationMoveSelectorConfig">
<xs:extension base="tns:moveSelectorConfig">


<xs:sequence>
Expand All @@ -1223,34 +1223,13 @@
</xs:complexType>


<xs:complexType abstract="true" name="nearbyAutoConfigurationMoveSelectorConfig">


<xs:complexContent>


<xs:extension base="tns:moveSelectorConfig">


<xs:sequence/>


</xs:extension>


</xs:complexContent>


</xs:complexType>


<xs:complexType name="kOptListMoveSelectorConfig">


<xs:complexContent>


<xs:extension base="tns:nearbyAutoConfigurationMoveSelectorConfig">
<xs:extension base="tns:moveSelectorConfig">


<xs:sequence>
Expand Down Expand Up @@ -1286,7 +1265,7 @@
<xs:complexContent>


<xs:extension base="tns:nearbyAutoConfigurationMoveSelectorConfig">
<xs:extension base="tns:moveSelectorConfig">


<xs:sequence>
Expand Down Expand Up @@ -1349,7 +1328,7 @@
<xs:complexContent>


<xs:extension base="tns:nearbyAutoConfigurationMoveSelectorConfig">
<xs:extension base="tns:moveSelectorConfig">


<xs:sequence>
Expand Down Expand Up @@ -1763,7 +1742,7 @@
<xs:complexContent>


<xs:extension base="tns:nearbyAutoConfigurationMoveSelectorConfig">
<xs:extension base="tns:moveSelectorConfig">


<xs:sequence>
Expand Down Expand Up @@ -1814,7 +1793,7 @@
<xs:complexContent>


<xs:extension base="tns:nearbyAutoConfigurationMoveSelectorConfig">
<xs:extension base="tns:moveSelectorConfig">


<xs:sequence>
Expand Down Expand Up @@ -1844,7 +1823,7 @@
<xs:complexContent>


<xs:extension base="tns:nearbyAutoConfigurationMoveSelectorConfig">
<xs:extension base="tns:moveSelectorConfig">


<xs:sequence>
Expand Down
56 changes: 0 additions & 56 deletions core/src/build/revapi-differences.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,62 +14,6 @@
"newValue": "{\"environmentMode\", \"daemon\", \"randomType\", \"randomSeed\", \"randomFactoryClass\", \"moveThreadCount\", \"moveThreadBufferSize\", \"threadFactoryClass\", \"monitoringConfig\", \"solutionClass\", \"entityClassList\", \"domainAccessType\", \"scoreDirectorFactoryConfig\", \"terminationConfig\", \"nearbyDistanceMeterClass\", \"phaseConfigList\"}",
"justification": "Adding new Nearby root property"
},
{
"ignore": true,
"code": "java.class.nonFinalClassInheritsFromNewClass",
"old": "class ai.timefold.solver.core.config.heuristic.selector.move.composite.UnionMoveSelectorConfig",
"new": "class ai.timefold.solver.core.config.heuristic.selector.move.composite.UnionMoveSelectorConfig",
"superClass": "ai.timefold.solver.core.config.heuristic.selector.move.NearbyAutoConfigurationMoveSelectorConfig<ai.timefold.solver.core.config.heuristic.selector.move.composite.UnionMoveSelectorConfig>",
"justification": "Adding support for Nearby Selection autoconfiguration"
},
{
"ignore": true,
"code": "java.class.nonFinalClassInheritsFromNewClass",
"old": "class ai.timefold.solver.core.config.heuristic.selector.move.generic.ChangeMoveSelectorConfig",
"new": "class ai.timefold.solver.core.config.heuristic.selector.move.generic.ChangeMoveSelectorConfig",
"superClass": "ai.timefold.solver.core.config.heuristic.selector.move.NearbyAutoConfigurationMoveSelectorConfig<ai.timefold.solver.core.config.heuristic.selector.move.generic.ChangeMoveSelectorConfig>",
"justification": "Adding support for Nearby Selection autoconfiguration"
},
{
"ignore": true,
"code": "java.class.nonFinalClassInheritsFromNewClass",
"old": "class ai.timefold.solver.core.config.heuristic.selector.move.generic.SwapMoveSelectorConfig",
"new": "class ai.timefold.solver.core.config.heuristic.selector.move.generic.SwapMoveSelectorConfig",
"superClass": "ai.timefold.solver.core.config.heuristic.selector.move.NearbyAutoConfigurationMoveSelectorConfig<ai.timefold.solver.core.config.heuristic.selector.move.generic.SwapMoveSelectorConfig>",
"justification": "Adding support for Nearby Selection autoconfiguration"
},
{
"ignore": true,
"code": "java.class.nonFinalClassInheritsFromNewClass",
"old": "class ai.timefold.solver.core.config.heuristic.selector.move.generic.chained.TailChainSwapMoveSelectorConfig",
"new": "class ai.timefold.solver.core.config.heuristic.selector.move.generic.chained.TailChainSwapMoveSelectorConfig",
"superClass": "ai.timefold.solver.core.config.heuristic.selector.move.NearbyAutoConfigurationMoveSelectorConfig<ai.timefold.solver.core.config.heuristic.selector.move.generic.chained.TailChainSwapMoveSelectorConfig>",
"justification": "Adding support for Nearby Selection autoconfiguration"
},
{
"ignore": true,
"code": "java.class.nonFinalClassInheritsFromNewClass",
"old": "class ai.timefold.solver.core.config.heuristic.selector.move.generic.list.ListChangeMoveSelectorConfig",
"new": "class ai.timefold.solver.core.config.heuristic.selector.move.generic.list.ListChangeMoveSelectorConfig",
"superClass": "ai.timefold.solver.core.config.heuristic.selector.move.NearbyAutoConfigurationMoveSelectorConfig<ai.timefold.solver.core.config.heuristic.selector.move.generic.list.ListChangeMoveSelectorConfig>",
"justification": "Adding support for Nearby Selection autoconfiguration"
},
{
"ignore": true,
"code": "java.class.nonFinalClassInheritsFromNewClass",
"old": "class ai.timefold.solver.core.config.heuristic.selector.move.generic.list.ListSwapMoveSelectorConfig",
"new": "class ai.timefold.solver.core.config.heuristic.selector.move.generic.list.ListSwapMoveSelectorConfig",
"superClass": "ai.timefold.solver.core.config.heuristic.selector.move.NearbyAutoConfigurationMoveSelectorConfig<ai.timefold.solver.core.config.heuristic.selector.move.generic.list.ListSwapMoveSelectorConfig>",
"justification": "Adding support for Nearby Selection autoconfiguration"
},
{
"ignore": true,
"code": "java.class.nonFinalClassInheritsFromNewClass",
"old": "class ai.timefold.solver.core.config.heuristic.selector.move.generic.list.kopt.KOptListMoveSelectorConfig",
"new": "class ai.timefold.solver.core.config.heuristic.selector.move.generic.list.kopt.KOptListMoveSelectorConfig",
"superClass": "ai.timefold.solver.core.config.heuristic.selector.move.NearbyAutoConfigurationMoveSelectorConfig<ai.timefold.solver.core.config.heuristic.selector.move.generic.list.kopt.KOptListMoveSelectorConfig>",
"justification": "Adding support for Nearby Selection autoconfiguration"
},
{
"ignore": true,
"code": "java.annotation.attributeValueChanged",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlType;

import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
import ai.timefold.solver.core.api.domain.variable.PlanningVariable;
import ai.timefold.solver.core.config.heuristic.selector.SelectorConfig;
import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType;
import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder;
Expand Down Expand Up @@ -239,56 +241,60 @@ public void validateNearby(SelectionCacheType resolvedCacheType, SelectionOrder
.filter(Objects::nonNull)
.count();
if (originSelectorCount == 0) {
throw new IllegalArgumentException("The nearbySelectorConfig (" + this
+ ") is nearby selection but lacks an origin selector config."
+ " Set one of originEntitySelectorConfig, originSubListSelectorConfig or originValueSelectorConfig.");
throw new IllegalArgumentException("""
The nearbySelectorConfig (%s) is nearby selection but lacks an origin selector config.
Set one of originEntitySelectorConfig, originSubListSelectorConfig or originValueSelectorConfig."""
.formatted(this));
} else if (originSelectorCount > 1) {
throw new IllegalArgumentException("The nearbySelectorConfig (" + this
+ ") has multiple origin selector configs but exactly one is expected."
+ " Set one of originEntitySelectorConfig, originSubListSelectorConfig or originValueSelectorConfig.");
throw new IllegalArgumentException("""
The nearbySelectorConfig (%s) has multiple origin selector configs but exactly one is expected.
Set one of originEntitySelectorConfig, originSubListSelectorConfig or originValueSelectorConfig."""
.formatted(this));
}
if (originEntitySelectorConfig != null &&
originEntitySelectorConfig.getMimicSelectorRef() == null) {
throw new IllegalArgumentException("The nearbySelectorConfig (" + this
+ ") has an originEntitySelectorConfig (" + originEntitySelectorConfig
+ ") which has no MimicSelectorRef (" + originEntitySelectorConfig.getMimicSelectorRef() + "). "
+ "A nearby's original entity should always be the same as an entity selected earlier in the move.");
if (originEntitySelectorConfig != null && originEntitySelectorConfig.getMimicSelectorRef() == null) {
throw new IllegalArgumentException("""
The nearbySelectorConfig (%s) has an originEntitySelectorConfig (%s) which has no mimicSelectorRef (%s).
Nearby selection's original entity should always be the same as an entity selected earlier in the move."""
.formatted(this, originEntitySelectorConfig, originEntitySelectorConfig.getMimicSelectorRef()));
}
if (originSubListSelectorConfig != null &&
originSubListSelectorConfig.getMimicSelectorRef() == null) {
throw new IllegalArgumentException("The nearbySelectorConfig (" + this
+ ") has an originSubListSelectorConfig (" + originSubListSelectorConfig
+ ") which has no MimicSelectorRef (" + originSubListSelectorConfig.getMimicSelectorRef() + "). "
+ "A nearby's original subList should always be the same as a subList selected earlier in the move.");
if (originSubListSelectorConfig != null && originSubListSelectorConfig.getMimicSelectorRef() == null) {
throw new IllegalArgumentException("""
The nearbySelectorConfig (%s) has an originSubListSelectorConfig (%s) which has no mimicSelectorRef (%s).
Nearby selection's original subList should always be the same as a subList selected earlier in the move."""
.formatted(this, originSubListSelectorConfig, originSubListSelectorConfig.getMimicSelectorRef()));
}
if (originValueSelectorConfig != null &&
originValueSelectorConfig.getMimicSelectorRef() == null) {
throw new IllegalArgumentException("The nearbySelectorConfig (" + this
+ ") has an originValueSelectorConfig (" + originValueSelectorConfig
+ ") which has no MimicSelectorRef (" + originValueSelectorConfig.getMimicSelectorRef() + "). "
+ "A nearby's original value should always be the same as a value selected earlier in the move.");
if (originValueSelectorConfig != null && originValueSelectorConfig.getMimicSelectorRef() == null) {
throw new IllegalArgumentException("""
The nearbySelectorConfig (%s) has an originValueSelectorConfig (%s) which has no mimicSelectorRef (%s).
Nearby selection's original value should always be the same as a value selected earlier in the move."""
.formatted(this, originValueSelectorConfig, originValueSelectorConfig.getMimicSelectorRef()));
}
if (nearbyDistanceMeterClass == null) {
throw new IllegalArgumentException("The nearbySelectorConfig (" + this
+ ") is nearby selection but lacks a nearbyDistanceMeterClass (" + nearbyDistanceMeterClass + ").");
throw new IllegalArgumentException(
"The nearbySelectorConfig (%s) enables nearby selection but lacks a nearbyDistanceMeterClass (%s)."
.formatted(this, nearbyDistanceMeterClass));
}
if (resolvedSelectionOrder != SelectionOrder.ORIGINAL && resolvedSelectionOrder != SelectionOrder.RANDOM) {
throw new IllegalArgumentException("The nearbySelectorConfig (" + this
+ ") with originEntitySelector (" + originEntitySelectorConfig
+ ") and originSubListSelector (" + originSubListSelectorConfig
+ ") and originValueSelector (" + originValueSelectorConfig
+ ") and nearbyDistanceMeterClass (" + nearbyDistanceMeterClass
+ ") has a resolvedSelectionOrder (" + resolvedSelectionOrder
+ ") that is not " + SelectionOrder.ORIGINAL + " or " + SelectionOrder.RANDOM + ".");
throw new IllegalArgumentException(
"""
The nearbySelectorConfig (%s) with originEntitySelector (%s), originSubListSelector (%s), originValueSelector (%s) and nearbyDistanceMeterClass (%s) \
has a resolvedSelectionOrder (%s) that is not %s or %s.
Maybe remove difficultyComparatorClass or difficultyWeightFactoryClass from your @%s annotation.
Maybe remove strengthComparatorClass or strengthWeightFactoryClass from your @%s annotation.
Maybe disable nearby selection.
"""
.formatted(this, originEntitySelectorConfig, originSubListSelectorConfig, originValueSelectorConfig,
nearbyDistanceMeterClass, resolvedSelectionOrder, SelectionOrder.ORIGINAL,
SelectionOrder.RANDOM, PlanningEntity.class.getSimpleName(),
PlanningVariable.class.getSimpleName()));
}
if (resolvedCacheType.isCached()) {
throw new IllegalArgumentException("The nearbySelectorConfig (" + this
+ ") with originEntitySelector (" + originEntitySelectorConfig
+ ") and originSubListSelector (" + originSubListSelectorConfig
+ ") and originValueSelector (" + originValueSelectorConfig
+ ") and nearbyDistanceMeterClass (" + nearbyDistanceMeterClass
+ ") has a resolvedCacheType (" + resolvedCacheType
+ ") that is cached.");
throw new IllegalArgumentException(
"""
The nearbySelectorConfig (%s) with originEntitySelector (%s), originSubListSelector (%s), originValueSelector (%s) and nearbyDistanceMeterClass (%s) \
has a resolvedCacheType (%s) that is cached."""
.formatted(this, originEntitySelectorConfig, originSubListSelectorConfig, originValueSelectorConfig,
nearbyDistanceMeterClass, resolvedCacheType));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package ai.timefold.solver.core.config.heuristic.selector.move;

import java.util.Random;

import ai.timefold.solver.core.impl.heuristic.selector.common.nearby.NearbyDistanceMeter;

/**
* For move selectors that support Nearby Selection autoconfiguration.
*/
public interface NearbyAutoConfigurationEnabled<Config_ extends MoveSelectorConfig<Config_>> {

/**
* @return new instance with the Nearby Selection settings properly configured
*/
Config_ enableNearbySelection(Class<? extends NearbyDistanceMeter<?, ?>> distanceMeter, Random random);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package ai.timefold.solver.core.config.heuristic.selector.move;

import java.util.Random;

import ai.timefold.solver.core.impl.heuristic.selector.common.nearby.NearbyDistanceMeter;

/**
* For move selectors that support Nearby Selection autoconfiguration in construction heuristics.
*/
public interface NearbyAutoConfigurationEnabledConstructionHeuristic<Config_ extends MoveSelectorConfig<Config_>> {

/**
* @return new instance with the Nearby Selection settings properly configured
*/
Config_ enableNearbySelectionForConstructionHeuristic(Class<? extends NearbyDistanceMeter<?, ?>> distanceMeter,
Random random, String recordingSelectorId);

}
Loading

0 comments on commit 3ab0480

Please sign in to comment.