diff --git a/docs/src/modules/ROOT/nav.adoc b/docs/src/modules/ROOT/nav.adoc index 7e75cc46a8e..cbd6ac2d898 100644 --- a/docs/src/modules/ROOT/nav.adoc +++ b/docs/src/modules/ROOT/nav.adoc @@ -1,12 +1,12 @@ -* xref:introduction.adoc[leveloffset=+1] -* Getting started -** xref:quickstart/overview.adoc[Overview] -** xref:quickstart/service/getting-started.adoc[Build as a service] -** Embed as a library -*** xref:quickstart/hello-world/hello-world-quickstart.adoc[Hello World guide] -*** xref:quickstart/quarkus/quarkus-quickstart.adoc[Quarkus guide] -*** xref:quickstart/spring-boot/spring-boot-quickstart.adoc[Spring Boot guide] +.Getting started +* xref:quickstart/overview.adoc[Overview] +* xref:quickstart/service/getting-started.adoc[Build as a service] +* Embed as a library +** xref:quickstart/hello-world/hello-world-quickstart.adoc[Hello World guide] +** xref:quickstart/quarkus/quarkus-quickstart.adoc[Quarkus guide] +** xref:quickstart/spring-boot/spring-boot-quickstart.adoc[Spring Boot guide] +.Build with Timefold * Domain modeling ** xref:domain-modeling/domain-modeling.adoc[Guide] ** xref:domain-modeling/modeling-planning-problems.adoc[Building blocks] @@ -25,7 +25,6 @@ *** xref:running-timefold-solver/service/rest-api.adoc[leveloffset=+1] *** xref:running-timefold-solver/service/model-config-overrides.adoc[Model configuration overrides] *** xref:running-timefold-solver/service/model-enrichment.adoc[leveloffset=+1] - *** xref:running-timefold-solver/service/demo-data.adoc[leveloffset=+1] *** xref:running-timefold-solver/service/exposing-metrics.adoc[leveloffset=+1] *** xref:running-timefold-solver/service/consumer-guide.adoc[Service consumer guide] @@ -37,58 +36,63 @@ *** xref:running-timefold-solver/library/spring-boot.adoc[leveloffset=+1] *** xref:running-timefold-solver/library/jpa-jaxb-json-integration.adoc[leveloffset=+1] +* Responding to change +** xref:responding-to-change/continuous-planning.adoc[leveloffset=+1] +** xref:responding-to-change/real-time-planning.adoc[leveloffset=+1] +** xref:responding-to-change/non-disruptive-replanning.adoc[leveloffset=+1] +** xref:responding-to-change/recommendation-api.adoc[leveloffset=+1] + * Diagnosing the Solver ** xref:running-timefold-solver/benchmarking-and-tweaking.adoc[leveloffset=+1] ** xref:running-timefold-solver/solver-diagnostics.adoc[leveloffset=+1] -* Deploying to the Timefold Platform -** xref:deploying-to-platform/introduction.adoc[Overview] -** xref:deploying-to-platform/guide.adoc[Guide] -** xref:deploying-to-platform/model-metadata.adoc[leveloffset=+1] -** xref:deploying-to-platform/metrics.adoc[leveloffset=+1] +* Use cases +** xref:quickstart/quarkus-vehicle-routing/quarkus-vehicle-routing-quickstart.adoc[Vehicle routing (guide)] +** https://github.com/TimefoldAI/timefold-quickstarts[More examples on GitHub^] * Optimization algorithms ** xref:optimization-algorithms/overview.adoc[Overview] ** xref:optimization-algorithms/construction-heuristics.adoc[leveloffset=+1] ** xref:optimization-algorithms/local-search.adoc[leveloffset=+1] ** xref:optimization-algorithms/exhaustive-search.adoc[leveloffset=+1] -** Custom moves -*** xref:optimization-algorithms/neighborhoods.adoc[Neighborhoods API] -*** xref:optimization-algorithms/move-selector-reference.adoc[leveloffset=+1] -* Responding to change -** xref:responding-to-change/continuous-planning.adoc[leveloffset=+1] -** xref:responding-to-change/real-time-planning.adoc[leveloffset=+1] -** xref:responding-to-change/non-disruptive-replanning.adoc[leveloffset=+1] -** xref:responding-to-change/recommendation-api.adoc[leveloffset=+1] - -* Example use cases -** xref:quickstart/quarkus-vehicle-routing/quarkus-vehicle-routing-quickstart.adoc[Vehicle routing (guide)] -** https://github.com/TimefoldAI/timefold-quickstarts[More examples on GitHub^] +* Moves +** xref:optimization-algorithms/move-selector-reference.adoc[leveloffset=+1] +** Custom Moves +*** xref:optimization-algorithms/custom-moves.adoc[Move definition] +*** xref:optimization-algorithms/neighborhoods.adoc[Neighborhoods API] -* xref:frequently-asked-questions.adoc[leveloffset=+1] -* https://github.com/TimefoldAI/timefold-solver/releases[New and noteworthy][leveloffset=+1] +.Deploy to Timefold Platform +* xref:deploying-to-platform/introduction.adoc[Overview] +* xref:deploying-to-platform/guide.adoc[Guide] +* xref:deploying-to-platform/model-metadata.adoc[leveloffset=+1] +* xref:deploying-to-platform/metrics.adoc[leveloffset=+1] -* Upgrading Timefold Solver +.Upgrading +* https://github.com/TimefoldAI/timefold-solver/releases[New and noteworthy] +* Upgrading ** xref:upgrading-timefold-solver/overview.adoc[leveloffset=+1] -** xref:upgrading-timefold-solver/upgrade-to-latest.adoc[leveloffset=+1] ** xref:upgrading-timefold-solver/upgrade-from-v1.adoc[leveloffset=+1] -** https://docs.timefold.ai/timefold-solver/1.x/upgrading-timefold-solver/upgrade-from-optaplanner[Upgrading from OptaPlanner][leveloffset=+1] -** xref:upgrading-timefold-solver/backwards-compatibility.adoc[leveloffset=+1] -** Migration guides -*** xref:upgrading-timefold-solver/migration-guides/variable-listeners-to-custom-shadow-variables.adoc[leveloffset=+1] -*** xref:upgrading-timefold-solver/migration-guides/chained-variables-to-planning-list-variable.adoc[leveloffset=+1] -* Commercial editions -** xref:commercial-editions/commercial-editions.adoc[Overview] -** xref:commercial-editions/installation.adoc[Installation] -** xref:commercial-editions/performance-improvements.adoc[leveloffset=+1] -** xref:constraints-and-score/understanding-the-score.adoc[Score analysis] -** xref:responding-to-change/recommendation-api.adoc#assignmentRecommendationAPI[Recommendation API] -** xref:optimization-algorithms/move-selector-reference.adoc#nearbySelection[Nearby selection] -** xref:running-timefold-solver/multithreaded-solving.adoc#multithreadedIncrementalSolving[Multithreaded solving] -** xref:running-timefold-solver/multithreaded-solving.adoc#partitionedSearch[Partitioned search] -** xref:constraints-and-score/performance.adoc#constraintProfiling[Constraint profiling] -** xref:commercial-editions/multistage-moves.adoc[leveloffset=+1] -** xref:running-timefold-solver/library/library-integration.adoc#throttlingBestSolutionEvents[Throttling best solution events] -** xref:commercial-editions/license-management.adoc[License management] +** xref:upgrading-timefold-solver/upgrade-from-optaplanner.adoc[leveloffset=+1] +* Migration guides +** xref:upgrading-timefold-solver/migration-guides/variable-listeners-to-custom-shadow-variables.adoc[leveloffset=+1] +** xref:upgrading-timefold-solver/migration-guides/chained-variables-to-planning-list-variable.adoc[leveloffset=+1] + +.Commercial editions +* xref:commercial-editions/commercial-editions.adoc[Overview] +* xref:commercial-editions/installation.adoc[Installation] +* xref:commercial-editions/performance-improvements.adoc[leveloffset=+1] +* xref:constraints-and-score/understanding-the-score.adoc[Score analysis] +* xref:responding-to-change/recommendation-api.adoc#assignmentRecommendationAPI[Recommendation API] +* xref:optimization-algorithms/move-selector-reference.adoc#nearbySelection[Nearby selection] +* xref:running-timefold-solver/multithreaded-solving.adoc#multithreadedIncrementalSolving[Multithreaded solving] +* xref:running-timefold-solver/multithreaded-solving.adoc#partitionedSearch[Partitioned search] +* xref:constraints-and-score/performance.adoc#constraintProfiling[Constraint profiling] +* xref:commercial-editions/multistage-moves.adoc[leveloffset=+1] +* xref:running-timefold-solver/library/library-integration.adoc#throttlingBestSolutionEvents[Throttling best solution events] +* xref:commercial-editions/license-management.adoc[License management] + +.Additional resources +* xref:frequently-asked-questions.adoc[leveloffset=+1] +* link:https://github.com/TimefoldAI/timefold-solver[GitHub] diff --git a/docs/src/modules/ROOT/pages/constraints-and-score/understanding-the-score.adoc b/docs/src/modules/ROOT/pages/constraints-and-score/understanding-the-score.adoc index 8dac6b805ad..76d4ebfc65b 100644 --- a/docs/src/modules/ROOT/pages/constraints-and-score/understanding-the-score.adoc +++ b/docs/src/modules/ROOT/pages/constraints-and-score/understanding-the-score.adoc @@ -234,7 +234,7 @@ In that case, use `ScoreAnalysisFetchPolicy.FETCH_MATCH_COUNT` instead of the de == Solution Diff: What changed between now and then? IMPORTANT: The solution diff is exclusive to Timefold Solver Enterprise Edition. -It is also only available as a xref:upgrading-timefold-solver/backwards-compatibility.adoc#previewFeatures[preview feature]. +It is also only available as a xref:upgrading-timefold-solver/overview.adoc#previewFeatures[preview feature]. It may be subject to change and must be enabled in the solver configuration by setting: `PLANNING_SOLUTION_DIFF` Using the `SolutionManager` API, you can compare two solutions provided by the solver, and find out what changed between them: diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/.optimization-algorithms.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/.optimization-algorithms.adoc index ddc55f0ef47..dc21935ee71 100644 --- a/docs/src/modules/ROOT/pages/optimization-algorithms/.optimization-algorithms.adoc +++ b/docs/src/modules/ROOT/pages/optimization-algorithms/.optimization-algorithms.adoc @@ -3,10 +3,13 @@ :doctype: book :sectnums: :icons: font +// Hides the compatibility anchors in move-selector-reference.adoc, which would otherwise duplicate the IDs in custom-moves.adoc. +:custom-moves-included: include::overview.adoc[leveloffset=+1] include::construction-heuristics.adoc[leveloffset=+1] include::local-search.adoc[leveloffset=+1] include::exhaustive-search.adoc[leveloffset=+1] include::neighborhoods.adoc[leveloffset=+1] -include::move-selector-reference.adoc[leveloffset=+1] \ No newline at end of file +include::move-selector-reference.adoc[leveloffset=+1] +include::custom-moves.adoc[leveloffset=+1] diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/custom-moves.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/custom-moves.adoc new file mode 100644 index 00000000000..b07999b6a97 --- /dev/null +++ b/docs/src/modules/ROOT/pages/optimization-algorithms/custom-moves.adoc @@ -0,0 +1,241 @@ +[#customMovesIntroduction] += Custom moves +:doctype: book +:sectnums: +:icons: font + +Instead of using the generic ``Move``s (such as ``ChangeMove``) you can also implement your own ``Move``. +Generic and custom ``MoveSelector``s can be xref:optimization-algorithms/move-selector-reference.adoc#combiningMultipleMoveSelectors[combined] as desired. + +A custom `Move` can be tailored to work to the advantage of your constraints. +For example, in examination scheduling, changing the period of an exam A +would also change the period of all the other exams that need to coincide with exam A. + +A custom `Move` is far more work to implement and much harder to avoid bugs than a generic ``Move``. +After implementing a custom ``Move``, turn on `environmentMode` ``TRACED_FULL_ASSERT`` to check for score corruptions. + +For information on the `Move` interface, check out the +xref:optimization-algorithms/neighborhoods.adoc#neighborhoodsMove[Move Anatomy] section of the Neighborhoods chapter. +Even though Neighborhoods is an entirely different mechanism than move selectors, +they share the `Move` interface and therefore the same custom `Move` can be used in both mechanisms. + + +[#generatingCustomMoves] +== Generating custom moves + +Now, let's generate instances of this custom ``Move`` class. +There are 2 ways: + +[#moveListFactory] +=== `MoveListFactory`: the easy way to generate custom moves + +The easiest way to generate custom moves is by implementing the interface ``MoveListFactory``: + +[source,java,options="nowrap"] +---- +public interface MoveListFactory { + + List createMoveList(Solution_ solution); + +} +---- + +Simple configuration (which can be nested in a `unionMoveSelector` just like any other ``MoveSelector``): + +[source,xml,options="nowrap"] +---- + + ...MyMoveFactory + +---- + +Advanced configuration: + +[source,xml,options="nowrap"] +---- + + ... + ...MyMoveFactory + + ... + + +---- + +Because the `MoveListFactory` generates all moves at once in a ``List``, +it does not support `cacheType` ``JUST_IN_TIME``. +Therefore, `moveListFactory` uses `cacheType` ``STEP`` by default and it scales badly. + +To configure values of a `MoveListFactory` dynamically in the solver configuration +(so the xref:running-timefold-solver/benchmarking-and-tweaking.adoc#benchmarker[Benchmarker] can tweak those parameters), +add the `moveListFactoryCustomProperties` element and use xref:running-timefold-solver/library/configuration.adoc#customPropertiesConfiguration[custom properties]. + +[WARNING] +==== +A custom `MoveListFactory` implementation must ensure that it does not move xref:domain-modeling/modeling-planning-problems.adoc#pinnedPlanningEntities[pinned entities]. +==== + + +[#moveIteratorFactory] +=== ``MoveIteratorFactory``: generate Custom moves just in time + +Use this advanced form to generate custom moves Just In Time +by implementing the `MoveIteratorFactory` interface: + +[source,java,options="nowrap"] +---- +public interface MoveIteratorFactory { + + long getSize(ScoreDirector scoreDirector); + + Iterator createOriginalMoveIterator(ScoreDirector scoreDirector); + + Iterator createRandomMoveIterator(ScoreDirector scoreDirector, Random workingRandom); + +} +---- + +The `getSize()` method must return an estimation of the size. +It doesn't need to be correct, but it's better too big than too small. +The `createOriginalMoveIterator` method is called if the `selectionOrder` is `ORIGINAL` or if it is cached. +The `createRandomMoveIterator` method is called for `selectionOrder` ``RANDOM`` combined with cacheType ``JUST_IN_TIME``. + +[IMPORTANT] +==== +Don't create a collection (array, list, set or map) of ``Move``s when creating the ``Iterator``: +the whole purpose of `MoveIteratorFactory` over `MoveListFactory` is to create a `Move` just in time +in a custom ``Iterator.next()``. +==== + +For example: + +[source,java,options="nowrap"] +---- +public class PossibleAssignmentsOnlyMoveIteratorFactory implements MoveIteratorFactory { + @Override + public long getSize(ScoreDirector scoreDirector) { + // In this case, we return the exact size, but an estimate can be used + // if it too expensive to calculate or unknown + long totalSize = 0L; + var solution = scoreDirector.getWorkingSolution(); + for (MyEntity entity : solution.getEntities()) { + for (MyPlanningValue value : solution.getValues()) { + if (entity.canBeAssigned(value)) { + totalSize++; + } + } + } + return totalSize; + } + + @Override + public Iterator createOriginalMoveIterator(ScoreDirector scoreDirector) { + // Only needed if selectionOrder is ORIGINAL or if it is cached + var solution = scoreDirector.getWorkingSolution(); + var entities = solution.getEntities(); + var values = solution.getValues(); + // Assumes each entity has at least one assignable value + var firstEntityIndex = 0; + var firstValueIndex = 0; + while (!entities.get(firstEntityIndex).canBeAssigned(values.get(firstValueIndex))) { + firstValueIndex++; + } + + + return new Iterator<>() { + int nextEntityIndex = firstEntityIndex; + int nextValueIndex = firstValueIndex; + + @Override + public boolean hasNext() { + return nextEntityIndex < entities.size(); + } + + @Override + public MyChangeMove next() { + var selectedEntity = entities.get(nextEntityIndex); + var selectedValue = values.get(nextValueIndex); + nextValueIndex++; + while (nextValueIndex < values.size() && !selectedEntity.canBeAssigned(values.get(nextValueIndex))) { + nextValueIndex++; + } + if (nextValueIndex >= values.size()) { + // value list exhausted, go to next entity + nextEntityIndex++; + if (nextEntityIndex < entities.size()) { + nextValueIndex = 0; + while (nextValueIndex < values.size() && !entities.get(nextEntityIndex).canBeAssigned(values.get(nextValueIndex))) { + // Assumes each entity has at least one assignable value + nextValueIndex++; + } + } + } + return new MyChangeMove(selectedEntity, selectedValue); + } + }; + } + + @Override + public Iterator createRandomMoveIterator(ScoreDirector scoreDirector, + Random workingRandom) { + // Not needed if selectionOrder is ORIGINAL or if it is cached + var solution = scoreDirector.getWorkingSolution(); + var entities = solution.getEntities(); + var values = solution.getValues(); + + return new Iterator<>() { + @Override + public boolean hasNext() { + return !entities.isEmpty(); + } + + @Override + public MyChangeMove next() { + var selectedEntity = entities.get(workingRandom.nextInt(entities.size())); + var selectedValue = values.get(workingRandom.nextInt(values.size())); + while (!selectedEntity.canBeAssigned(selectedValue)) { + // This assumes there at least one value that can be assigned to the selected entity + selectedValue = values.get(workingRandom.nextInt(values.size())); + } + return new MyChangeMove(selectedEntity, selectedValue); + } + }; + } +} +---- + +[NOTE] +==== +The same effect can also be achieved using xref:optimization-algorithms/move-selector-reference.adoc#filteredSelection[filtered selection]. +==== + +Simple configuration (which can be nested in a `unionMoveSelector` just like any other ``MoveSelector``): + +[source,xml,options="nowrap"] +---- + + ... + +---- + +Advanced configuration: + +[source,xml,options="nowrap"] +---- + + ... + ... + + ... + + +---- + +To configure values of a `MoveIteratorFactory` dynamically in the solver configuration +(so the xref:running-timefold-solver/benchmarking-and-tweaking.adoc#benchmarker[Benchmarker] can tweak those parameters), +add the `moveIteratorFactoryCustomProperties` element and use xref:running-timefold-solver/library/configuration.adoc#customPropertiesConfiguration[custom properties]. + +[WARNING] +==== +A custom `MoveIteratorFactory` implementation must ensure that it does not move xref:domain-modeling/modeling-planning-problems.adoc#pinnedPlanningEntities[pinned entities]. +==== \ No newline at end of file diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/local-search.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/local-search.adoc index 4eca347e6a1..1735bba84d5 100644 --- a/docs/src/modules/ROOT/pages/optimization-algorithms/local-search.adoc +++ b/docs/src/modules/ROOT/pages/optimization-algorithms/local-search.adoc @@ -562,7 +562,7 @@ Advanced configuration: [IMPORTANT] ==== -The new acceptor is available as a xref:upgrading-timefold-solver/backwards-compatibility.adoc#previewFeatures[preview feature] +The new acceptor is available as a xref:upgrading-timefold-solver/overview.adoc#previewFeatures[preview feature] It may be subject to change and must be enabled in the solver configuration by setting: `DIVERSIFIED_LATE_ACCEPTANCE` ==== diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/move-selector-reference.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/move-selector-reference.adoc index 052303f5e0b..1022a670cd4 100644 --- a/docs/src/modules/ROOT/pages/optimization-algorithms/move-selector-reference.adoc +++ b/docs/src/modules/ROOT/pages/optimization-algorithms/move-selector-reference.adoc @@ -1774,241 +1774,19 @@ xref:optimization-algorithms/move-selector-reference.adoc#listSwapMoveSelector[S xref:optimization-algorithms/move-selector-reference.adoc#kOptListMoveSelector[K-OPT]. ==== -[#customMovesIntroduction] -== Custom moves - -Instead of using the generic ``Move``s (such as ``ChangeMove``) you can also implement your own ``Move``. -Generic and custom ``MoveSelector``s can be <> as desired. - -A custom `Move` can be tailored to work to the advantage of your constraints. -For example, in examination scheduling, changing the period of an exam A -would also change the period of all the other exams that need to coincide with exam A. - -A custom `Move` is far more work to implement and much harder to avoid bugs than a generic ``Move``. -After implementing a custom ``Move``, turn on `environmentMode` ``TRACED_FULL_ASSERT`` to check for score corruptions. - -For information on the `Move` interface, check out the -xref:optimization-algorithms/neighborhoods.adoc#neighborhoodsMove[Move Anatomy] section of the Neighborhoods chapter. -Even though Neighborhoods is an entirely different mechanism than move selectors, -they share the `Move` interface and therefore the same custom `Move` can be used in both mechanisms. - - -[#generatingCustomMoves] -=== Generating custom moves - -Now, let's generate instances of this custom ``Move`` class. -There are 2 ways: - -[#moveListFactory] -==== `MoveListFactory`: the easy way to generate custom moves - -The easiest way to generate custom moves is by implementing the interface ``MoveListFactory``: - -[source,java,options="nowrap"] ----- -public interface MoveListFactory { - - List createMoveList(Solution_ solution); - -} ----- - -Simple configuration (which can be nested in a `unionMoveSelector` just like any other ``MoveSelector``): - -[source,xml,options="nowrap"] ----- - - ...MyMoveFactory - ----- - -Advanced configuration: - -[source,xml,options="nowrap"] ----- - - ... - ...MyMoveFactory - - ... - - ----- - -Because the `MoveListFactory` generates all moves at once in a ``List``, -it does not support `cacheType` ``JUST_IN_TIME``. -Therefore, `moveListFactory` uses `cacheType` ``STEP`` by default and it scales badly. - -To configure values of a `MoveListFactory` dynamically in the solver configuration -(so the xref:running-timefold-solver/benchmarking-and-tweaking.adoc#benchmarker[Benchmarker] can tweak those parameters), -add the `moveListFactoryCustomProperties` element and use xref:running-timefold-solver/library/configuration.adoc#customPropertiesConfiguration[custom properties]. - -[WARNING] -==== -A custom `MoveListFactory` implementation must ensure that it does not move xref:domain-modeling/modeling-planning-problems.adoc#pinnedPlanningEntities[pinned entities]. -==== - - -[#moveIteratorFactory] -==== ``MoveIteratorFactory``: generate Custom moves just in time - -Use this advanced form to generate custom moves Just In Time -by implementing the `MoveIteratorFactory` interface: - -[source,java,options="nowrap"] ----- -public interface MoveIteratorFactory { - - long getSize(ScoreDirector scoreDirector); - - Iterator createOriginalMoveIterator(ScoreDirector scoreDirector); - - Iterator createRandomMoveIterator(ScoreDirector scoreDirector, Random workingRandom); -} ----- - -The `getSize()` method must return an estimation of the size. -It doesn't need to be correct, but it's better too big than too small. -The `createOriginalMoveIterator` method is called if the `selectionOrder` is `ORIGINAL` or if it is cached. -The `createRandomMoveIterator` method is called for `selectionOrder` ``RANDOM`` combined with cacheType ``JUST_IN_TIME``. - -[IMPORTANT] -==== -Don't create a collection (array, list, set or map) of ``Move``s when creating the ``Iterator``: -the whole purpose of `MoveIteratorFactory` over `MoveListFactory` is to create a `Move` just in time -in a custom ``Iterator.next()``. -==== - -For example: - -[source,java,options="nowrap"] ----- -public class PossibleAssignmentsOnlyMoveIteratorFactory implements MoveIteratorFactory { - @Override - public long getSize(ScoreDirector scoreDirector) { - // In this case, we return the exact size, but an estimate can be used - // if it too expensive to calculate or unknown - long totalSize = 0L; - var solution = scoreDirector.getWorkingSolution(); - for (MyEntity entity : solution.getEntities()) { - for (MyPlanningValue value : solution.getValues()) { - if (entity.canBeAssigned(value)) { - totalSize++; - } - } - } - return totalSize; - } - - @Override - public Iterator createOriginalMoveIterator(ScoreDirector scoreDirector) { - // Only needed if selectionOrder is ORIGINAL or if it is cached - var solution = scoreDirector.getWorkingSolution(); - var entities = solution.getEntities(); - var values = solution.getValues(); - // Assumes each entity has at least one assignable value - var firstEntityIndex = 0; - var firstValueIndex = 0; - while (!entities.get(firstEntityIndex).canBeAssigned(values.get(firstValueIndex))) { - firstValueIndex++; - } +ifndef::custom-moves-included[] +// Compatibility anchors: this content moved to custom-moves.adoc. +// Keep these IDs so that existing deep links into this page still land somewhere useful. +[#customMovesIntroduction] +== Custom moves - return new Iterator<>() { - int nextEntityIndex = firstEntityIndex; - int nextValueIndex = firstValueIndex; - - @Override - public boolean hasNext() { - return nextEntityIndex < entities.size(); - } - - @Override - public MyChangeMove next() { - var selectedEntity = entities.get(nextEntityIndex); - var selectedValue = values.get(nextValueIndex); - nextValueIndex++; - while (nextValueIndex < values.size() && !selectedEntity.canBeAssigned(values.get(nextValueIndex))) { - nextValueIndex++; - } - if (nextValueIndex >= values.size()) { - // value list exhausted, go to next entity - nextEntityIndex++; - if (nextEntityIndex < entities.size()) { - nextValueIndex = 0; - while (nextValueIndex < values.size() && !entities.get(nextEntityIndex).canBeAssigned(values.get(nextValueIndex))) { - // Assumes each entity has at least one assignable value - nextValueIndex++; - } - } - } - return new MyChangeMove(selectedEntity, selectedValue); - } - }; - } - - @Override - public Iterator createRandomMoveIterator(ScoreDirector scoreDirector, - Random workingRandom) { - // Not needed if selectionOrder is ORIGINAL or if it is cached - var solution = scoreDirector.getWorkingSolution(); - var entities = solution.getEntities(); - var values = solution.getValues(); - - return new Iterator<>() { - @Override - public boolean hasNext() { - return !entities.isEmpty(); - } - - @Override - public MyChangeMove next() { - var selectedEntity = entities.get(workingRandom.nextInt(entities.size())); - var selectedValue = values.get(workingRandom.nextInt(values.size())); - while (!selectedEntity.canBeAssigned(selectedValue)) { - // This assumes there at least one value that can be assigned to the selected entity - selectedValue = values.get(workingRandom.nextInt(values.size())); - } - return new MyChangeMove(selectedEntity, selectedValue); - } - }; - } -} ----- - -[NOTE] -==== -The same effect can also be accomplished using <>. -==== - -Simple configuration (which can be nested in a `unionMoveSelector` just like any other ``MoveSelector``): - -[source,xml,options="nowrap"] ----- - - ... - ----- - -Advanced configuration: - -[source,xml,options="nowrap"] ----- - - ... - ... - - ... - - ----- - -To configure values of a `MoveIteratorFactory` dynamically in the solver configuration -(so the xref:running-timefold-solver/benchmarking-and-tweaking.adoc#benchmarker[Benchmarker] can tweak those parameters), -add the `moveIteratorFactoryCustomProperties` element and use xref:running-timefold-solver/library/configuration.adoc#customPropertiesConfiguration[custom properties]. +[[generatingCustomMoves]][[moveListFactory]][[moveIteratorFactory]] +Custom moves, including generating them with a `MoveListFactory` or a `MoveIteratorFactory`, +are described in xref:optimization-algorithms/custom-moves.adoc[Custom moves]: -[WARNING] -==== -A custom `MoveIteratorFactory` implementation must ensure that it does not move xref:domain-modeling/modeling-planning-problems.adoc#pinnedPlanningEntities[pinned entities]. -==== +* xref:optimization-algorithms/custom-moves.adoc#generatingCustomMoves[Generating custom moves] +* xref:optimization-algorithms/custom-moves.adoc#moveListFactory[`MoveListFactory`] +* xref:optimization-algorithms/custom-moves.adoc#moveIteratorFactory[`MoveIteratorFactory`] +endif::[] diff --git a/docs/src/modules/ROOT/pages/upgrading-timefold-solver/.upgrading-timefold-solver.adoc b/docs/src/modules/ROOT/pages/upgrading-timefold-solver/.upgrading-timefold-solver.adoc index 04724062baf..ab872b524c7 100644 --- a/docs/src/modules/ROOT/pages/upgrading-timefold-solver/.upgrading-timefold-solver.adoc +++ b/docs/src/modules/ROOT/pages/upgrading-timefold-solver/.upgrading-timefold-solver.adoc @@ -5,6 +5,5 @@ :icons: font include::overview.adoc[leveloffset=+1] -include::upgrade-to-latest.adoc[leveloffset=+1] include::upgrade-from-v1.adoc[leveloffset=+1] -include::backwards-compatibility.adoc[leveloffset=+1] \ No newline at end of file +include::upgrade-from-optaplanner.adoc[leveloffset=+1] \ No newline at end of file diff --git a/docs/src/modules/ROOT/pages/upgrading-timefold-solver/backwards-compatibility.adoc b/docs/src/modules/ROOT/pages/upgrading-timefold-solver/backwards-compatibility.adoc deleted file mode 100644 index b700c4b1e4d..00000000000 --- a/docs/src/modules/ROOT/pages/upgrading-timefold-solver/backwards-compatibility.adoc +++ /dev/null @@ -1,55 +0,0 @@ -[#backwardsCompatibility] -= Backwards compatibility -:doctype: book -:icons: font - -Timefold Solver separates its API from its implementation: - -* **Public API**: All classes under these `api` and `config` namespaces are 100% *backwards compatible* in future minor and hotfix releases: -** `ai.timefold.solver.core.api` -** `ai.timefold.solver.benchmark.api` -** `ai.timefold.solver.test.api` -** `ai.timefold.solver.*.api` -** `ai.timefold.solver.core.config` -** `ai.timefold.solver.benchmark.config` -* **Implementation classes**: All other classes are _not_ backwards compatible. -They will change in future major or minor releases, -but probably not in hotfix releases. - -Backwards incompatible changes for a new major version are clearly documented in xref:upgrading-timefold-solver/upgrade-from-v1.adoc#manualUpgrade1to2[the upgrade recipe]. - - -[#previewFeatures] -== Preview features - -Timefold Solver includes several components which are only available as preview features. -We use preview features as means of sharing our work with the community early -and to get feedback on how to develop them further, -without being hamstrung by our strict backwards compatibility guarantees. - -We deliver preview features to the same standard of quality as the rest of Timefold Solver. -However, their APIs and behavior are not yet considered stable, pending user feedback. -Any class, method or field related to these features may change or be removed without prior notice, -although we strive to avoid this as much as possible. - -Preview features need to be activated in the solverConfig.xml file. - -[source,xml,options="nowrap"] ----- - - DIVERSIFIED_LATE_ACCEPTANCE - PLANNING_SOLUTION_DIFF - ----- - -Preview features often live in the `preview.api` package, and they are: - -- xref:optimization-algorithms/local-search.adoc#diversifiedLateAcceptance[Diversified Late Acceptance] acceptor, -- xref:constraints-and-score/understanding-the-score.adoc#solutionDiff[Solution diff API] -in the `ai.timefold.solver.core.preview.api.domain.solution.diff` package and in the `SolutionManager`, -- Neighborhoods API. - -We encourage you to try these preview features and give us feedback on your experience with them. -Please direct your feedback to -https://github.com/TimefoldAI/timefold-solver/discussions[Timefold Solver GitHub] -or to https://discord.com/channels/1413420192213631086/1414521616955605003[Timefold Discord] \ No newline at end of file diff --git a/docs/src/modules/ROOT/pages/upgrading-timefold-solver/overview.adoc b/docs/src/modules/ROOT/pages/upgrading-timefold-solver/overview.adoc index adb83cbafcf..ce447ffb741 100644 --- a/docs/src/modules/ROOT/pages/upgrading-timefold-solver/overview.adoc +++ b/docs/src/modules/ROOT/pages/upgrading-timefold-solver/overview.adoc @@ -15,3 +15,54 @@ in our GitHub repository, read https://timefold.ai/blog[our blog], or subscribe to our newsletter. +== Backwards compatibility + +Timefold Solver separates its API from its implementation: + +* **Public API**: All classes under these `api` and `config` namespaces are 100% *backwards compatible* in future minor and hotfix releases: +** `ai.timefold.solver.core.api` +** `ai.timefold.solver.benchmark.api` +** `ai.timefold.solver.test.api` +** `ai.timefold.solver.*.api` +** `ai.timefold.solver.core.config` +** `ai.timefold.solver.benchmark.config` +* **Implementation classes**: All other classes are _not_ backwards compatible. +They will change in future major or minor releases, +but probably not in hotfix releases. + +Backwards incompatible changes for a new major version are clearly documented in xref:upgrading-timefold-solver/upgrade-from-v1.adoc#manualUpgrade[the upgrade recipe]. + +[#previewFeatures] +== Preview features + +Timefold Solver includes several components which are only available as preview features. +We use preview features as means of sharing our work with the community early +and to get feedback on how to develop them further, +without being hamstrung by our strict backwards compatibility guarantees. + +We deliver preview features to the same standard of quality as the rest of Timefold Solver. +However, their APIs and behavior are not yet considered stable, pending user feedback. +Any class, method or field related to these features may change or be removed without prior notice, +although we strive to avoid this as much as possible. + +Preview features need to be activated in the solverConfig.xml file. + +[source,xml,options="nowrap"] +---- + + DIVERSIFIED_LATE_ACCEPTANCE + PLANNING_SOLUTION_DIFF + +---- + +Preview features often live in the `preview.api` package, and they are: + +- xref:optimization-algorithms/local-search.adoc#diversifiedLateAcceptance[Diversified Late Acceptance] acceptor, +- xref:constraints-and-score/understanding-the-score.adoc#solutionDiff[Solution diff API] +in the `ai.timefold.solver.core.preview.api.domain.solution.diff` package and in the `SolutionManager`, +- Neighborhoods API. + +We encourage you to try these preview features and give us feedback on your experience with them. +Please direct your feedback to +https://github.com/TimefoldAI/timefold-solver/discussions[Timefold Solver GitHub] +or to https://discord.com/channels/1413420192213631086/1414521616955605003[Timefold Discord] \ No newline at end of file diff --git a/docs/src/modules/ROOT/pages/upgrading-timefold-solver/upgrade-from-optaplanner.adoc b/docs/src/modules/ROOT/pages/upgrading-timefold-solver/upgrade-from-optaplanner.adoc new file mode 100644 index 00000000000..4aaff21942a --- /dev/null +++ b/docs/src/modules/ROOT/pages/upgrading-timefold-solver/upgrade-from-optaplanner.adoc @@ -0,0 +1,71 @@ +[#upgradeFromOptaPlanner] += Upgrade from OptaPlanner +:page-aliases: upgrade-and-migration/migrate-from-optaplanner.adoc +:doctype: book +:sectnums: +:icons: font + +In spring of 2024, Red Hat announced https://access.redhat.com/articles/7060671[end of life for OptaPlanner]. +Timefold Solver is a faster, feature-rich, and actively developed fork of OptaPlanner by the same team. + +== Automatic upgrade + +Upgrading from OptaPlanner to Timefold Solver only takes two minutes. +Run the command below to upgrade your java, build and other code automatically. + +NOTE: The script below upgrades your OptaPlanner project to the latest Timefold Solver version, which is {timefold-solver-version}. +This is a significant jump and might require additional migrations. Check out our other guide for more details on how to upgrade to the xref:upgrading-timefold-solver/upgrade-from-v1.adoc[2.x range]. + +[tabs] +==== +Maven:: ++ +-- +[source,shell,subs=attributes+] +---- +mvn org.openrewrite.maven:rewrite-maven-plugin:{rewrite-maven-plugin-version}:run -Drewrite.recipeArtifactCoordinates=ai.timefold.solver:timefold-solver-migration:{timefold-solver-version} -Drewrite.activeRecipes=ai.timefold.solver.migration.ToLatest +---- +-- + +Gradle:: ++ +-- +[source,shell,subs=attributes+] +---- +curl https://raw.githubusercontent.com/TimefoldAI/timefold-solver/refs/tags/v{timefold-solver-version}/tools/migration/upgrade-timefold.gradle > upgrade-timefold.gradle ; gradle -Dorg.gradle.jvmargs=-Xmx2G --init-script upgrade-timefold.gradle rewriteRun -DtimefoldSolverVersion={timefold-solver-version} ; rm upgrade-timefold.gradle +---- +-- +==== + +include::framework-version-warning.adoc[leveloffset=+1] + +Having done that, do a test run of the solver and commit the changes. +If it doesn't work, just revert it instead and +https://github.com/timefoldai/timefold-solver/issues[submit an issue]. +We'll fix it with the highest priority. + +Timefold Solver 1.x does not support `scoreDRL`, nor is it upgraded automatically. +If you're still using `scoreDRL` from OptaPlanner 7.x, +please link:https://timefold.ai/blog/migrating-score-drl-to-constraint-streams[upgrade to Constraint Streams first]. + +== Manual upgrade + +Timefold Solver 1.x is backward compatible with OptaPlanner 8.x, +except for the following changes: + +* Java 17 is the minimum, and Java 21 and 25 are also supported. +* The Maven/Gradle GAVs changed: +** The groupId changed from `org.optaplanner` to `ai.timefold.solver`. +** The artifactIds changed from `optaplanner-\*` to `timefold-solver-*`. +** ArtifactIds containing `persistence-` changed from `optaplanner-persistence-\*` to `timefold-solver-*`. +*** For example, `optaplanner-persistence-jackson` changed to `timefold-solver-jackson`. +* The import statements changed accordingly: +** `import org.optaplanner...;` changed to `import ai.timefold.solver...;`. +** `import org.optaplanner.persistence...;` changed to `import ai.timefold.solver...;` too. +* The JEE dependencies changed from `javax` to `jakarta` to accommodate Spring 3 and Quarkus 3. +** This is the same difference as between OptaPlanner 8.x and OptaPlanner 9.x. +* The `OptaPlannerJacksonModule` class is now called `TimefoldJacksonModule`. +* The deprecated `scoreDRL` support is removed, because Drools with its transitive dependencies have been removed entirely. +* The unsecure module `persistence-xstream` is removed, because of old, unresolved CVEs in XStream. +* The deprecated, undocumented `ScoreHibernateType` has been removed because of Jakarta. +Use JPA's `ScoreConverter` instead.