Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 52 additions & 48 deletions docs/src/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
@@ -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]
Expand All @@ -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]
Expand All @@ -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]
Comment thread
TomCools marked this conversation as resolved.
*** 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]
Original file line number Diff line number Diff line change
Expand Up @@ -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: `<enablePreviewFeature>PLANNING_SOLUTION_DIFF</enablePreviewFeature>`

Using the `SolutionManager` API, you can compare two solutions provided by the solver, and find out what changed between them:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
include::move-selector-reference.adoc[leveloffset=+1]
include::custom-moves.adoc[leveloffset=+1]
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
[#customMovesIntroduction]
Comment thread
TomCools marked this conversation as resolved.
= 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<Solution_> {

List<Move> createMoveList(Solution_ solution);

}
----

Simple configuration (which can be nested in a `unionMoveSelector` just like any other ``MoveSelector``):

[source,xml,options="nowrap"]
----
<moveListFactory>
<moveListFactoryClass>...MyMoveFactory</moveListFactoryClass>
</moveListFactory>
----

Advanced configuration:

[source,xml,options="nowrap"]
----
<moveListFactory>
... <!-- Normal moveSelector properties -->
<moveListFactoryClass>...MyMoveFactory</moveListFactoryClass>
<moveListFactoryCustomProperties>
...<!-- Custom properties -->
</moveListFactoryCustomProperties>
</moveListFactory>
----

Because the `MoveListFactory` generates all moves at once in a ``List<Move>``,
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<Solution_> {

long getSize(ScoreDirector<Solution_> scoreDirector);

Iterator<Move> createOriginalMoveIterator(ScoreDirector<Solution_> scoreDirector);

Iterator<Move> createRandomMoveIterator(ScoreDirector<Solution_> scoreDirector, Random workingRandom);
Comment on lines +87 to +93

}
----

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<Move>``:
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<MyPlanningSolution, MyChangeMove> {
@Override
public long getSize(ScoreDirector<MyPlanningSolution> 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<MyChangeMove> createOriginalMoveIterator(ScoreDirector<MyPlanningSolution> 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<MyChangeMove> createRandomMoveIterator(ScoreDirector<MyPlanningSolution> 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"]
----
<moveIteratorFactory>
<moveIteratorFactoryClass>...</moveIteratorFactoryClass>
</moveIteratorFactory>
----

Advanced configuration:

[source,xml,options="nowrap"]
----
<moveIteratorFactory>
... <!-- Normal moveSelector properties -->
<moveIteratorFactoryClass>...</moveIteratorFactoryClass>
<moveIteratorFactoryCustomProperties>
...<!-- Custom properties -->
</moveIteratorFactoryCustomProperties>
</moveIteratorFactory>
----

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].
====
Loading
Loading