From bf8ec065b5938cb98ea7ed7a677b3bb4392ab04c Mon Sep 17 00:00:00 2001 From: Timefold Release Bot Date: Thu, 12 Mar 2026 12:34:41 +0000 Subject: [PATCH 1/3] build: release version 2.0.0-beta-1 --- build/bom/pom.xml | 2 +- build/build-parent/pom.xml | 2 +- build/ide-config/pom.xml | 2 +- core/pom.xml | 2 +- docs/pom.xml | 2 +- docs/src/antora.yml | 18 +++++++++--------- persistence/jackson/pom.xml | 2 +- persistence/jaxb/pom.xml | 2 +- persistence/jpa/pom.xml | 2 +- persistence/pom.xml | 2 +- pom.xml | 2 +- quarkus-integration/pom.xml | 2 +- .../quarkus-benchmark/deployment/pom.xml | 2 +- .../quarkus-benchmark/integration-test/pom.xml | 2 +- quarkus-integration/quarkus-benchmark/pom.xml | 2 +- .../quarkus-benchmark/runtime/pom.xml | 2 +- .../quarkus-jackson/deployment/pom.xml | 2 +- .../quarkus-jackson/integration-test/pom.xml | 2 +- quarkus-integration/quarkus-jackson/pom.xml | 2 +- .../quarkus-jackson/runtime/pom.xml | 2 +- quarkus-integration/quarkus/deployment/pom.xml | 2 +- .../quarkus/devui-integration-test/pom.xml | 2 +- .../quarkus/integration-test/pom.xml | 2 +- quarkus-integration/quarkus/pom.xml | 2 +- .../reflection-integration-test/pom.xml | 2 +- quarkus-integration/quarkus/runtime/pom.xml | 2 +- spring-integration/pom.xml | 2 +- .../spring-boot-autoconfigure/pom.xml | 2 +- .../spring-boot-integration-test/pom.xml | 2 +- spring-integration/spring-boot-starter/pom.xml | 2 +- tools/benchmark-aggregator/pom.xml | 2 +- tools/benchmark/pom.xml | 2 +- tools/migration/pom.xml | 2 +- tools/pom.xml | 2 +- 34 files changed, 42 insertions(+), 42 deletions(-) diff --git a/build/bom/pom.xml b/build/bom/pom.xml index 04ef47840a5..375ee5b8fc2 100644 --- a/build/bom/pom.xml +++ b/build/bom/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-parent - 999-SNAPSHOT + 2.0.0-beta-1 ../../pom.xml diff --git a/build/build-parent/pom.xml b/build/build-parent/pom.xml index 43fbe1f6129..03b2ed7a542 100644 --- a/build/build-parent/pom.xml +++ b/build/build-parent/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-parent - 999-SNAPSHOT + 2.0.0-beta-1 ../../pom.xml diff --git a/build/ide-config/pom.xml b/build/ide-config/pom.xml index 5e52944a076..d48d76486d7 100644 --- a/build/ide-config/pom.xml +++ b/build/ide-config/pom.xml @@ -5,7 +5,7 @@ ai.timefold.solver timefold-solver-parent - 999-SNAPSHOT + 2.0.0-beta-1 ../../pom.xml timefold-solver-ide-config diff --git a/core/pom.xml b/core/pom.xml index dbd7122f6a8..7e74cb7e3d0 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-build-parent - 999-SNAPSHOT + 2.0.0-beta-1 ../build/build-parent/pom.xml diff --git a/docs/pom.xml b/docs/pom.xml index 8a8341808d1..407c5682522 100755 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -7,7 +7,7 @@ ai.timefold.solver timefold-solver-build-parent - 999-SNAPSHOT + 2.0.0-beta-1 ../build/build-parent/pom.xml diff --git a/docs/src/antora.yml b/docs/src/antora.yml index 3f2a450dff9..ad12bbb5a7e 100644 --- a/docs/src/antora.yml +++ b/docs/src/antora.yml @@ -3,17 +3,17 @@ # That file is then copied to src/modules/antora.yml and committed to Git on the release branch. # The timefold.ai website can then be refreshed from the release branch and/or tag. name: timefold-solver -title: Timefold Solver ${project.version} +title: Timefold Solver 2.0.0-beta-1 version: latest asciidoc: attributes: - timefold-solver-version: ${project.version} - java-version: ${maven.compiler.release} - maven-version: ${maven.min.version} - quarkus-version: ${version.io.quarkus} - spring-boot-version: ${version.org.springframework.boot} - logback-version: ${version.ch.qos.logback} - exec-maven-plugin-version: ${version.exec.plugin} - rewrite-maven-plugin-version: ${version.rewrite.plugin} + timefold-solver-version: 2.0.0-beta-1 + java-version: 21 + maven-version: 3.9.11 + quarkus-version: 3.32.3 + spring-boot-version: 4.0.3 + logback-version: 1.5.32 + exec-maven-plugin-version: 3.6.3 + rewrite-maven-plugin-version: 6.28.1 nav: - modules/ROOT/nav.adoc diff --git a/persistence/jackson/pom.xml b/persistence/jackson/pom.xml index 1e0e9611f00..ab4fe9f6eea 100644 --- a/persistence/jackson/pom.xml +++ b/persistence/jackson/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-persistence-parent - 999-SNAPSHOT + 2.0.0-beta-1 timefold-solver-jackson diff --git a/persistence/jaxb/pom.xml b/persistence/jaxb/pom.xml index 4a7741aae4b..90ba7787a53 100644 --- a/persistence/jaxb/pom.xml +++ b/persistence/jaxb/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-persistence-parent - 999-SNAPSHOT + 2.0.0-beta-1 timefold-solver-jaxb diff --git a/persistence/jpa/pom.xml b/persistence/jpa/pom.xml index 6979344a60f..1228ad0b011 100644 --- a/persistence/jpa/pom.xml +++ b/persistence/jpa/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-persistence-parent - 999-SNAPSHOT + 2.0.0-beta-1 timefold-solver-jpa diff --git a/persistence/pom.xml b/persistence/pom.xml index d18ba0875b1..a7649a4bac0 100644 --- a/persistence/pom.xml +++ b/persistence/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-build-parent - 999-SNAPSHOT + 2.0.0-beta-1 ../build/build-parent/pom.xml diff --git a/pom.xml b/pom.xml index e08cfa0156d..93b91860aa0 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-parent pom - 999-SNAPSHOT + 2.0.0-beta-1 Timefold Solver multiproject parent diff --git a/quarkus-integration/pom.xml b/quarkus-integration/pom.xml index 31194accda2..fdf2a4ee742 100644 --- a/quarkus-integration/pom.xml +++ b/quarkus-integration/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-build-parent - 999-SNAPSHOT + 2.0.0-beta-1 ../build/build-parent/pom.xml diff --git a/quarkus-integration/quarkus-benchmark/deployment/pom.xml b/quarkus-integration/quarkus-benchmark/deployment/pom.xml index 5b30e5dd342..cd1a16a318d 100644 --- a/quarkus-integration/quarkus-benchmark/deployment/pom.xml +++ b/quarkus-integration/quarkus-benchmark/deployment/pom.xml @@ -5,7 +5,7 @@ ai.timefold.solver timefold-solver-quarkus-benchmark-parent - 999-SNAPSHOT + 2.0.0-beta-1 timefold-solver-quarkus-benchmark-deployment diff --git a/quarkus-integration/quarkus-benchmark/integration-test/pom.xml b/quarkus-integration/quarkus-benchmark/integration-test/pom.xml index 356ebaa872a..f2cf6487a29 100644 --- a/quarkus-integration/quarkus-benchmark/integration-test/pom.xml +++ b/quarkus-integration/quarkus-benchmark/integration-test/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-quarkus-benchmark-parent - 999-SNAPSHOT + 2.0.0-beta-1 timefold-solver-quarkus-benchmark-integration-test diff --git a/quarkus-integration/quarkus-benchmark/pom.xml b/quarkus-integration/quarkus-benchmark/pom.xml index abacfea5619..daafcff510f 100644 --- a/quarkus-integration/quarkus-benchmark/pom.xml +++ b/quarkus-integration/quarkus-benchmark/pom.xml @@ -5,7 +5,7 @@ ai.timefold.solver timefold-solver-quarkus-integration - 999-SNAPSHOT + 2.0.0-beta-1 4.0.0 diff --git a/quarkus-integration/quarkus-benchmark/runtime/pom.xml b/quarkus-integration/quarkus-benchmark/runtime/pom.xml index 512287d2c1e..63308bf47b6 100644 --- a/quarkus-integration/quarkus-benchmark/runtime/pom.xml +++ b/quarkus-integration/quarkus-benchmark/runtime/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-quarkus-benchmark-parent - 999-SNAPSHOT + 2.0.0-beta-1 timefold-solver-quarkus-benchmark diff --git a/quarkus-integration/quarkus-jackson/deployment/pom.xml b/quarkus-integration/quarkus-jackson/deployment/pom.xml index 35a86130262..32e7b3d5fc6 100644 --- a/quarkus-integration/quarkus-jackson/deployment/pom.xml +++ b/quarkus-integration/quarkus-jackson/deployment/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-quarkus-jackson-parent - 999-SNAPSHOT + 2.0.0-beta-1 timefold-solver-quarkus-jackson-deployment diff --git a/quarkus-integration/quarkus-jackson/integration-test/pom.xml b/quarkus-integration/quarkus-jackson/integration-test/pom.xml index 8aa386bb97b..7e82f0cc764 100644 --- a/quarkus-integration/quarkus-jackson/integration-test/pom.xml +++ b/quarkus-integration/quarkus-jackson/integration-test/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-quarkus-jackson-parent - 999-SNAPSHOT + 2.0.0-beta-1 timefold-solver-quarkus-jackson-integration-test diff --git a/quarkus-integration/quarkus-jackson/pom.xml b/quarkus-integration/quarkus-jackson/pom.xml index 457a252ea29..27650c69f4e 100644 --- a/quarkus-integration/quarkus-jackson/pom.xml +++ b/quarkus-integration/quarkus-jackson/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-quarkus-integration - 999-SNAPSHOT + 2.0.0-beta-1 timefold-solver-quarkus-jackson-parent diff --git a/quarkus-integration/quarkus-jackson/runtime/pom.xml b/quarkus-integration/quarkus-jackson/runtime/pom.xml index b26078f9fb5..28612eb1303 100644 --- a/quarkus-integration/quarkus-jackson/runtime/pom.xml +++ b/quarkus-integration/quarkus-jackson/runtime/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-quarkus-jackson-parent - 999-SNAPSHOT + 2.0.0-beta-1 timefold-solver-quarkus-jackson diff --git a/quarkus-integration/quarkus/deployment/pom.xml b/quarkus-integration/quarkus/deployment/pom.xml index f02a9e7e0c1..f499749e6da 100644 --- a/quarkus-integration/quarkus/deployment/pom.xml +++ b/quarkus-integration/quarkus/deployment/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-quarkus-parent - 999-SNAPSHOT + 2.0.0-beta-1 timefold-solver-quarkus-deployment diff --git a/quarkus-integration/quarkus/devui-integration-test/pom.xml b/quarkus-integration/quarkus/devui-integration-test/pom.xml index 641fc019e83..9edd8c67516 100644 --- a/quarkus-integration/quarkus/devui-integration-test/pom.xml +++ b/quarkus-integration/quarkus/devui-integration-test/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-quarkus-parent - 999-SNAPSHOT + 2.0.0-beta-1 timefold-solver-quarkus-devui-integration-test diff --git a/quarkus-integration/quarkus/integration-test/pom.xml b/quarkus-integration/quarkus/integration-test/pom.xml index dead8201847..63b6e3786a8 100644 --- a/quarkus-integration/quarkus/integration-test/pom.xml +++ b/quarkus-integration/quarkus/integration-test/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-quarkus-parent - 999-SNAPSHOT + 2.0.0-beta-1 timefold-solver-quarkus-integration-test diff --git a/quarkus-integration/quarkus/pom.xml b/quarkus-integration/quarkus/pom.xml index 8a918ba56f7..d2be8eda198 100644 --- a/quarkus-integration/quarkus/pom.xml +++ b/quarkus-integration/quarkus/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-quarkus-integration - 999-SNAPSHOT + 2.0.0-beta-1 timefold-solver-quarkus-parent diff --git a/quarkus-integration/quarkus/reflection-integration-test/pom.xml b/quarkus-integration/quarkus/reflection-integration-test/pom.xml index ba56b300fc3..d6dc76a57b3 100644 --- a/quarkus-integration/quarkus/reflection-integration-test/pom.xml +++ b/quarkus-integration/quarkus/reflection-integration-test/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-quarkus-parent - 999-SNAPSHOT + 2.0.0-beta-1 timefold-solver-quarkus-reflection-integration-test diff --git a/quarkus-integration/quarkus/runtime/pom.xml b/quarkus-integration/quarkus/runtime/pom.xml index 67764b7c1cc..01c329e6238 100644 --- a/quarkus-integration/quarkus/runtime/pom.xml +++ b/quarkus-integration/quarkus/runtime/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-quarkus-parent - 999-SNAPSHOT + 2.0.0-beta-1 timefold-solver-quarkus diff --git a/spring-integration/pom.xml b/spring-integration/pom.xml index 3efbe4a80a5..630289a36f4 100644 --- a/spring-integration/pom.xml +++ b/spring-integration/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-build-parent - 999-SNAPSHOT + 2.0.0-beta-1 ../build/build-parent/pom.xml diff --git a/spring-integration/spring-boot-autoconfigure/pom.xml b/spring-integration/spring-boot-autoconfigure/pom.xml index a90838c3e3f..72348ae478d 100644 --- a/spring-integration/spring-boot-autoconfigure/pom.xml +++ b/spring-integration/spring-boot-autoconfigure/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-spring-integration - 999-SNAPSHOT + 2.0.0-beta-1 timefold-solver-spring-boot-autoconfigure diff --git a/spring-integration/spring-boot-integration-test/pom.xml b/spring-integration/spring-boot-integration-test/pom.xml index 2b946fde7c7..9334738e2bd 100644 --- a/spring-integration/spring-boot-integration-test/pom.xml +++ b/spring-integration/spring-boot-integration-test/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-spring-integration - 999-SNAPSHOT + 2.0.0-beta-1 spring-boot-integration-test diff --git a/spring-integration/spring-boot-starter/pom.xml b/spring-integration/spring-boot-starter/pom.xml index d2917847a3c..cc946b6fbdd 100644 --- a/spring-integration/spring-boot-starter/pom.xml +++ b/spring-integration/spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-spring-integration - 999-SNAPSHOT + 2.0.0-beta-1 timefold-solver-spring-boot-starter diff --git a/tools/benchmark-aggregator/pom.xml b/tools/benchmark-aggregator/pom.xml index 7844202af70..5bbcea0be22 100644 --- a/tools/benchmark-aggregator/pom.xml +++ b/tools/benchmark-aggregator/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-build-parent - 999-SNAPSHOT + 2.0.0-beta-1 ../../build/build-parent/pom.xml diff --git a/tools/benchmark/pom.xml b/tools/benchmark/pom.xml index e72c337cfde..060dd35fed3 100644 --- a/tools/benchmark/pom.xml +++ b/tools/benchmark/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-build-parent - 999-SNAPSHOT + 2.0.0-beta-1 ../../build/build-parent/pom.xml diff --git a/tools/migration/pom.xml b/tools/migration/pom.xml index 92324ed396f..6c1148afd9d 100644 --- a/tools/migration/pom.xml +++ b/tools/migration/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-build-parent - 999-SNAPSHOT + 2.0.0-beta-1 ../../build/build-parent/pom.xml diff --git a/tools/pom.xml b/tools/pom.xml index f5d53727d47..400e9f17eda 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-build-parent - 999-SNAPSHOT + 2.0.0-beta-1 ../build/build-parent/pom.xml From ee73c6efcfce7c6ed1e19056488c98228c041d08 Mon Sep 17 00:00:00 2001 From: Timefold Release Bot Date: Thu, 12 Mar 2026 13:02:02 +0000 Subject: [PATCH 2/3] build: move back to 999-SNAPSHOT --- build/bom/pom.xml | 2 +- build/build-parent/pom.xml | 2 +- build/ide-config/pom.xml | 2 +- core/pom.xml | 2 +- docs/pom.xml | 2 +- persistence/jackson/pom.xml | 2 +- persistence/jaxb/pom.xml | 2 +- persistence/jpa/pom.xml | 2 +- persistence/pom.xml | 2 +- pom.xml | 2 +- quarkus-integration/pom.xml | 2 +- quarkus-integration/quarkus-benchmark/deployment/pom.xml | 2 +- quarkus-integration/quarkus-benchmark/integration-test/pom.xml | 2 +- quarkus-integration/quarkus-benchmark/pom.xml | 2 +- quarkus-integration/quarkus-benchmark/runtime/pom.xml | 2 +- quarkus-integration/quarkus-jackson/deployment/pom.xml | 2 +- quarkus-integration/quarkus-jackson/integration-test/pom.xml | 2 +- quarkus-integration/quarkus-jackson/pom.xml | 2 +- quarkus-integration/quarkus-jackson/runtime/pom.xml | 2 +- quarkus-integration/quarkus/deployment/pom.xml | 2 +- quarkus-integration/quarkus/devui-integration-test/pom.xml | 2 +- quarkus-integration/quarkus/integration-test/pom.xml | 2 +- quarkus-integration/quarkus/pom.xml | 2 +- quarkus-integration/quarkus/reflection-integration-test/pom.xml | 2 +- quarkus-integration/quarkus/runtime/pom.xml | 2 +- spring-integration/pom.xml | 2 +- spring-integration/spring-boot-autoconfigure/pom.xml | 2 +- spring-integration/spring-boot-integration-test/pom.xml | 2 +- spring-integration/spring-boot-starter/pom.xml | 2 +- tools/benchmark-aggregator/pom.xml | 2 +- tools/benchmark/pom.xml | 2 +- tools/migration/pom.xml | 2 +- tools/pom.xml | 2 +- 33 files changed, 33 insertions(+), 33 deletions(-) diff --git a/build/bom/pom.xml b/build/bom/pom.xml index 375ee5b8fc2..04ef47840a5 100644 --- a/build/bom/pom.xml +++ b/build/bom/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-parent - 2.0.0-beta-1 + 999-SNAPSHOT ../../pom.xml diff --git a/build/build-parent/pom.xml b/build/build-parent/pom.xml index 03b2ed7a542..43fbe1f6129 100644 --- a/build/build-parent/pom.xml +++ b/build/build-parent/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-parent - 2.0.0-beta-1 + 999-SNAPSHOT ../../pom.xml diff --git a/build/ide-config/pom.xml b/build/ide-config/pom.xml index d48d76486d7..5e52944a076 100644 --- a/build/ide-config/pom.xml +++ b/build/ide-config/pom.xml @@ -5,7 +5,7 @@ ai.timefold.solver timefold-solver-parent - 2.0.0-beta-1 + 999-SNAPSHOT ../../pom.xml timefold-solver-ide-config diff --git a/core/pom.xml b/core/pom.xml index 7e74cb7e3d0..dbd7122f6a8 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-build-parent - 2.0.0-beta-1 + 999-SNAPSHOT ../build/build-parent/pom.xml diff --git a/docs/pom.xml b/docs/pom.xml index 407c5682522..8a8341808d1 100755 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -7,7 +7,7 @@ ai.timefold.solver timefold-solver-build-parent - 2.0.0-beta-1 + 999-SNAPSHOT ../build/build-parent/pom.xml diff --git a/persistence/jackson/pom.xml b/persistence/jackson/pom.xml index ab4fe9f6eea..1e0e9611f00 100644 --- a/persistence/jackson/pom.xml +++ b/persistence/jackson/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-persistence-parent - 2.0.0-beta-1 + 999-SNAPSHOT timefold-solver-jackson diff --git a/persistence/jaxb/pom.xml b/persistence/jaxb/pom.xml index 90ba7787a53..4a7741aae4b 100644 --- a/persistence/jaxb/pom.xml +++ b/persistence/jaxb/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-persistence-parent - 2.0.0-beta-1 + 999-SNAPSHOT timefold-solver-jaxb diff --git a/persistence/jpa/pom.xml b/persistence/jpa/pom.xml index 1228ad0b011..6979344a60f 100644 --- a/persistence/jpa/pom.xml +++ b/persistence/jpa/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-persistence-parent - 2.0.0-beta-1 + 999-SNAPSHOT timefold-solver-jpa diff --git a/persistence/pom.xml b/persistence/pom.xml index a7649a4bac0..d18ba0875b1 100644 --- a/persistence/pom.xml +++ b/persistence/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-build-parent - 2.0.0-beta-1 + 999-SNAPSHOT ../build/build-parent/pom.xml diff --git a/pom.xml b/pom.xml index 93b91860aa0..e08cfa0156d 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-parent pom - 2.0.0-beta-1 + 999-SNAPSHOT Timefold Solver multiproject parent diff --git a/quarkus-integration/pom.xml b/quarkus-integration/pom.xml index fdf2a4ee742..31194accda2 100644 --- a/quarkus-integration/pom.xml +++ b/quarkus-integration/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-build-parent - 2.0.0-beta-1 + 999-SNAPSHOT ../build/build-parent/pom.xml diff --git a/quarkus-integration/quarkus-benchmark/deployment/pom.xml b/quarkus-integration/quarkus-benchmark/deployment/pom.xml index cd1a16a318d..5b30e5dd342 100644 --- a/quarkus-integration/quarkus-benchmark/deployment/pom.xml +++ b/quarkus-integration/quarkus-benchmark/deployment/pom.xml @@ -5,7 +5,7 @@ ai.timefold.solver timefold-solver-quarkus-benchmark-parent - 2.0.0-beta-1 + 999-SNAPSHOT timefold-solver-quarkus-benchmark-deployment diff --git a/quarkus-integration/quarkus-benchmark/integration-test/pom.xml b/quarkus-integration/quarkus-benchmark/integration-test/pom.xml index f2cf6487a29..356ebaa872a 100644 --- a/quarkus-integration/quarkus-benchmark/integration-test/pom.xml +++ b/quarkus-integration/quarkus-benchmark/integration-test/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-quarkus-benchmark-parent - 2.0.0-beta-1 + 999-SNAPSHOT timefold-solver-quarkus-benchmark-integration-test diff --git a/quarkus-integration/quarkus-benchmark/pom.xml b/quarkus-integration/quarkus-benchmark/pom.xml index daafcff510f..abacfea5619 100644 --- a/quarkus-integration/quarkus-benchmark/pom.xml +++ b/quarkus-integration/quarkus-benchmark/pom.xml @@ -5,7 +5,7 @@ ai.timefold.solver timefold-solver-quarkus-integration - 2.0.0-beta-1 + 999-SNAPSHOT 4.0.0 diff --git a/quarkus-integration/quarkus-benchmark/runtime/pom.xml b/quarkus-integration/quarkus-benchmark/runtime/pom.xml index 63308bf47b6..512287d2c1e 100644 --- a/quarkus-integration/quarkus-benchmark/runtime/pom.xml +++ b/quarkus-integration/quarkus-benchmark/runtime/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-quarkus-benchmark-parent - 2.0.0-beta-1 + 999-SNAPSHOT timefold-solver-quarkus-benchmark diff --git a/quarkus-integration/quarkus-jackson/deployment/pom.xml b/quarkus-integration/quarkus-jackson/deployment/pom.xml index 32e7b3d5fc6..35a86130262 100644 --- a/quarkus-integration/quarkus-jackson/deployment/pom.xml +++ b/quarkus-integration/quarkus-jackson/deployment/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-quarkus-jackson-parent - 2.0.0-beta-1 + 999-SNAPSHOT timefold-solver-quarkus-jackson-deployment diff --git a/quarkus-integration/quarkus-jackson/integration-test/pom.xml b/quarkus-integration/quarkus-jackson/integration-test/pom.xml index 7e82f0cc764..8aa386bb97b 100644 --- a/quarkus-integration/quarkus-jackson/integration-test/pom.xml +++ b/quarkus-integration/quarkus-jackson/integration-test/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-quarkus-jackson-parent - 2.0.0-beta-1 + 999-SNAPSHOT timefold-solver-quarkus-jackson-integration-test diff --git a/quarkus-integration/quarkus-jackson/pom.xml b/quarkus-integration/quarkus-jackson/pom.xml index 27650c69f4e..457a252ea29 100644 --- a/quarkus-integration/quarkus-jackson/pom.xml +++ b/quarkus-integration/quarkus-jackson/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-quarkus-integration - 2.0.0-beta-1 + 999-SNAPSHOT timefold-solver-quarkus-jackson-parent diff --git a/quarkus-integration/quarkus-jackson/runtime/pom.xml b/quarkus-integration/quarkus-jackson/runtime/pom.xml index 28612eb1303..b26078f9fb5 100644 --- a/quarkus-integration/quarkus-jackson/runtime/pom.xml +++ b/quarkus-integration/quarkus-jackson/runtime/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-quarkus-jackson-parent - 2.0.0-beta-1 + 999-SNAPSHOT timefold-solver-quarkus-jackson diff --git a/quarkus-integration/quarkus/deployment/pom.xml b/quarkus-integration/quarkus/deployment/pom.xml index f499749e6da..f02a9e7e0c1 100644 --- a/quarkus-integration/quarkus/deployment/pom.xml +++ b/quarkus-integration/quarkus/deployment/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-quarkus-parent - 2.0.0-beta-1 + 999-SNAPSHOT timefold-solver-quarkus-deployment diff --git a/quarkus-integration/quarkus/devui-integration-test/pom.xml b/quarkus-integration/quarkus/devui-integration-test/pom.xml index 9edd8c67516..641fc019e83 100644 --- a/quarkus-integration/quarkus/devui-integration-test/pom.xml +++ b/quarkus-integration/quarkus/devui-integration-test/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-quarkus-parent - 2.0.0-beta-1 + 999-SNAPSHOT timefold-solver-quarkus-devui-integration-test diff --git a/quarkus-integration/quarkus/integration-test/pom.xml b/quarkus-integration/quarkus/integration-test/pom.xml index 63b6e3786a8..dead8201847 100644 --- a/quarkus-integration/quarkus/integration-test/pom.xml +++ b/quarkus-integration/quarkus/integration-test/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-quarkus-parent - 2.0.0-beta-1 + 999-SNAPSHOT timefold-solver-quarkus-integration-test diff --git a/quarkus-integration/quarkus/pom.xml b/quarkus-integration/quarkus/pom.xml index d2be8eda198..8a918ba56f7 100644 --- a/quarkus-integration/quarkus/pom.xml +++ b/quarkus-integration/quarkus/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-quarkus-integration - 2.0.0-beta-1 + 999-SNAPSHOT timefold-solver-quarkus-parent diff --git a/quarkus-integration/quarkus/reflection-integration-test/pom.xml b/quarkus-integration/quarkus/reflection-integration-test/pom.xml index d6dc76a57b3..ba56b300fc3 100644 --- a/quarkus-integration/quarkus/reflection-integration-test/pom.xml +++ b/quarkus-integration/quarkus/reflection-integration-test/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-quarkus-parent - 2.0.0-beta-1 + 999-SNAPSHOT timefold-solver-quarkus-reflection-integration-test diff --git a/quarkus-integration/quarkus/runtime/pom.xml b/quarkus-integration/quarkus/runtime/pom.xml index 01c329e6238..67764b7c1cc 100644 --- a/quarkus-integration/quarkus/runtime/pom.xml +++ b/quarkus-integration/quarkus/runtime/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-quarkus-parent - 2.0.0-beta-1 + 999-SNAPSHOT timefold-solver-quarkus diff --git a/spring-integration/pom.xml b/spring-integration/pom.xml index 630289a36f4..3efbe4a80a5 100644 --- a/spring-integration/pom.xml +++ b/spring-integration/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-build-parent - 2.0.0-beta-1 + 999-SNAPSHOT ../build/build-parent/pom.xml diff --git a/spring-integration/spring-boot-autoconfigure/pom.xml b/spring-integration/spring-boot-autoconfigure/pom.xml index 72348ae478d..a90838c3e3f 100644 --- a/spring-integration/spring-boot-autoconfigure/pom.xml +++ b/spring-integration/spring-boot-autoconfigure/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-spring-integration - 2.0.0-beta-1 + 999-SNAPSHOT timefold-solver-spring-boot-autoconfigure diff --git a/spring-integration/spring-boot-integration-test/pom.xml b/spring-integration/spring-boot-integration-test/pom.xml index 9334738e2bd..2b946fde7c7 100644 --- a/spring-integration/spring-boot-integration-test/pom.xml +++ b/spring-integration/spring-boot-integration-test/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-spring-integration - 2.0.0-beta-1 + 999-SNAPSHOT spring-boot-integration-test diff --git a/spring-integration/spring-boot-starter/pom.xml b/spring-integration/spring-boot-starter/pom.xml index cc946b6fbdd..d2917847a3c 100644 --- a/spring-integration/spring-boot-starter/pom.xml +++ b/spring-integration/spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-spring-integration - 2.0.0-beta-1 + 999-SNAPSHOT timefold-solver-spring-boot-starter diff --git a/tools/benchmark-aggregator/pom.xml b/tools/benchmark-aggregator/pom.xml index 5bbcea0be22..7844202af70 100644 --- a/tools/benchmark-aggregator/pom.xml +++ b/tools/benchmark-aggregator/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-build-parent - 2.0.0-beta-1 + 999-SNAPSHOT ../../build/build-parent/pom.xml diff --git a/tools/benchmark/pom.xml b/tools/benchmark/pom.xml index 060dd35fed3..e72c337cfde 100644 --- a/tools/benchmark/pom.xml +++ b/tools/benchmark/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-build-parent - 2.0.0-beta-1 + 999-SNAPSHOT ../../build/build-parent/pom.xml diff --git a/tools/migration/pom.xml b/tools/migration/pom.xml index 6c1148afd9d..92324ed396f 100644 --- a/tools/migration/pom.xml +++ b/tools/migration/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-build-parent - 2.0.0-beta-1 + 999-SNAPSHOT ../../build/build-parent/pom.xml diff --git a/tools/pom.xml b/tools/pom.xml index 400e9f17eda..f5d53727d47 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -6,7 +6,7 @@ ai.timefold.solver timefold-solver-build-parent - 2.0.0-beta-1 + 999-SNAPSHOT ../build/build-parent/pom.xml From c55ed8427b659db0de594e8449a5c6b77c288052 Mon Sep 17 00:00:00 2001 From: Christopher Chianelli Date: Wed, 25 Mar 2026 15:04:03 -0400 Subject: [PATCH 3/3] perf: make Random splittable --- .../core/config/solver/SolverConfig.java | 38 +--- .../core/config/solver/random/RandomType.java | 23 --- .../config/solver/random/package-info.java | 9 - .../DefaultConstructionHeuristicPhase.java | 13 +- .../DefaultExhaustiveSearchPhase.java | 5 +- .../localsearch/DefaultLocalSearchPhase.java | 31 +-- .../core/impl/solver/AbstractSolver.java | 14 ++ .../core/impl/solver/DefaultSolver.java | 13 +- .../impl/solver/DefaultSolverFactory.java | 33 +-- .../solver/random/DefaultRandomFactory.java | 60 ------ .../DelegatingSplittableRandomGenerator.java | 189 ++++++++++++++++++ .../impl/solver/random/RandomFactory.java | 15 -- .../core/impl/solver/scope/SolverScope.java | 7 +- core/src/main/java/module-info.java | 3 +- core/src/main/resources/solver.xsd | 28 --- .../config/solver/EnvironmentModeTest.java | 27 +-- .../impl/solver/DefaultSolverFactoryTest.java | 12 -- .../core/impl/solver/DefaultSolverTest.java | 11 +- .../core/impl/solver/SolverMetricsIT.java | 2 +- .../running-the-solver.adoc | 24 +-- .../src/test/resources/solver-full.xml | 2 - .../src/main/resources/benchmark.xsd | 42 ---- .../result/PlannerBenchmarkResultTest.java | 7 +- 23 files changed, 281 insertions(+), 327 deletions(-) delete mode 100644 core/src/main/java/ai/timefold/solver/core/config/solver/random/RandomType.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/config/solver/random/package-info.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/solver/random/DefaultRandomFactory.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/solver/random/DelegatingSplittableRandomGenerator.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/solver/random/RandomFactory.java diff --git a/core/src/main/java/ai/timefold/solver/core/config/solver/SolverConfig.java b/core/src/main/java/ai/timefold/solver/core/config/solver/SolverConfig.java index b9fa907fdc9..edbd1554371 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/solver/SolverConfig.java +++ b/core/src/main/java/ai/timefold/solver/core/config/solver/SolverConfig.java @@ -41,7 +41,6 @@ import ai.timefold.solver.core.config.score.director.ScoreDirectorFactoryConfig; import ai.timefold.solver.core.config.solver.monitoring.MonitoringConfig; import ai.timefold.solver.core.config.solver.monitoring.SolverMetric; -import ai.timefold.solver.core.config.solver.random.RandomType; import ai.timefold.solver.core.config.solver.termination.TerminationConfig; import ai.timefold.solver.core.config.util.ConfigUtils; import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessor; @@ -49,7 +48,6 @@ import ai.timefold.solver.core.impl.io.jaxb.SolverConfigIO; import ai.timefold.solver.core.impl.io.jaxb.TimefoldXmlSerializationException; import ai.timefold.solver.core.impl.phase.PhaseFactory; -import ai.timefold.solver.core.impl.solver.random.RandomFactory; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; @@ -63,9 +61,7 @@ "enablePreviewFeatureSet", "environmentMode", "daemon", - "randomType", "randomSeed", - "randomFactoryClass", "moveThreadCount", "moveThreadBufferSize", "threadFactoryClass", @@ -215,9 +211,7 @@ public final class SolverConfig extends AbstractConfig { private Set enablePreviewFeatureSet = null; private EnvironmentMode environmentMode = null; private Boolean daemon = null; - private RandomType randomType = null; private Long randomSeed = null; - private Class randomFactoryClass = null; private String moveThreadCount = null; private Integer moveThreadBufferSize = null; private Class threadFactoryClass = null; @@ -332,14 +326,6 @@ public void setDaemon(@Nullable Boolean daemon) { this.daemon = daemon; } - public @Nullable RandomType getRandomType() { - return randomType; - } - - public void setRandomType(@Nullable RandomType randomType) { - this.randomType = randomType; - } - public @Nullable Long getRandomSeed() { return randomSeed; } @@ -348,14 +334,6 @@ public void setRandomSeed(@Nullable Long randomSeed) { this.randomSeed = randomSeed; } - public @Nullable Class getRandomFactoryClass() { - return randomFactoryClass; - } - - public void setRandomFactoryClass(@Nullable Class randomFactoryClass) { - this.randomFactoryClass = randomFactoryClass; - } - public @Nullable String getMoveThreadCount() { return moveThreadCount; } @@ -471,21 +449,11 @@ public void setMonitoringConfig(@Nullable MonitoringConfig monitoringConfig) { return this; } - public @NonNull SolverConfig withRandomType(@NonNull RandomType randomType) { - this.randomType = randomType; - return this; - } - public @NonNull SolverConfig withRandomSeed(@NonNull Long randomSeed) { this.randomSeed = randomSeed; return this; } - public @NonNull SolverConfig withRandomFactoryClass(@NonNull Class randomFactoryClass) { - this.randomFactoryClass = randomFactoryClass; - return this; - } - public @NonNull SolverConfig withMoveThreadCount(@NonNull String moveThreadCount) { this.moveThreadCount = moveThreadCount; return this; @@ -648,7 +616,7 @@ public boolean canTerminate() { // ************************************************************************ public void offerRandomSeedFromSubSingleIndex(long subSingleIndex) { - if ((environmentMode == null || environmentMode.isReproducible()) && randomFactoryClass == null && randomSeed == null) { + if ((environmentMode == null || environmentMode.isReproducible()) && randomSeed == null) { randomSeed = subSingleIndex; } } @@ -665,10 +633,7 @@ public void offerRandomSeedFromSubSingleIndex(long subSingleIndex) { inheritedConfig.getEnablePreviewFeatureSet()); environmentMode = ConfigUtils.inheritOverwritableProperty(environmentMode, inheritedConfig.getEnvironmentMode()); daemon = ConfigUtils.inheritOverwritableProperty(daemon, inheritedConfig.getDaemon()); - randomType = ConfigUtils.inheritOverwritableProperty(randomType, inheritedConfig.getRandomType()); randomSeed = ConfigUtils.inheritOverwritableProperty(randomSeed, inheritedConfig.getRandomSeed()); - randomFactoryClass = ConfigUtils.inheritOverwritableProperty(randomFactoryClass, - inheritedConfig.getRandomFactoryClass()); moveThreadCount = ConfigUtils.inheritOverwritableProperty(moveThreadCount, inheritedConfig.getMoveThreadCount()); moveThreadBufferSize = ConfigUtils.inheritOverwritableProperty(moveThreadBufferSize, @@ -700,7 +665,6 @@ public void offerRandomSeedFromSubSingleIndex(long subSingleIndex) { @Override public void visitReferencedClasses(@NonNull Consumer> classVisitor) { - classVisitor.accept(randomFactoryClass); classVisitor.accept(threadFactoryClass); classVisitor.accept(solutionClass); if (entityClassList != null) { diff --git a/core/src/main/java/ai/timefold/solver/core/config/solver/random/RandomType.java b/core/src/main/java/ai/timefold/solver/core/config/solver/random/RandomType.java deleted file mode 100644 index 4b8c20b06ae..00000000000 --- a/core/src/main/java/ai/timefold/solver/core/config/solver/random/RandomType.java +++ /dev/null @@ -1,23 +0,0 @@ -package ai.timefold.solver.core.config.solver.random; - -import jakarta.xml.bind.annotation.XmlEnum; - -/** - * Defines the pseudo random number generator. - * See the PRNG - * documentation in commons-math. - */ -@XmlEnum -public enum RandomType { - /** - * This is the default. - */ - JDK, - MERSENNE_TWISTER, - WELL512A, - WELL1024A, - WELL19937A, - WELL19937C, - WELL44497A, - WELL44497B; -} diff --git a/core/src/main/java/ai/timefold/solver/core/config/solver/random/package-info.java b/core/src/main/java/ai/timefold/solver/core/config/solver/random/package-info.java deleted file mode 100644 index c1998dd1769..00000000000 --- a/core/src/main/java/ai/timefold/solver/core/config/solver/random/package-info.java +++ /dev/null @@ -1,9 +0,0 @@ -@XmlSchema( - namespace = SolverConfig.XML_NAMESPACE, - elementFormDefault = XmlNsForm.QUALIFIED) -package ai.timefold.solver.core.config.solver.random; - -import jakarta.xml.bind.annotation.XmlNsForm; -import jakarta.xml.bind.annotation.XmlSchema; - -import ai.timefold.solver.core.config.solver.SolverConfig; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhase.java b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhase.java index 4695f0bde28..cae734d261f 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhase.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhase.java @@ -104,9 +104,9 @@ public void solve(SolverScope solverScope) { stepScope.getPhaseScope().calculateSolverTimeMillisSpentUpToNow()); } } else { - throw new IllegalStateException("The step index (" + stepScope.getStepIndex() - + ") has selected move count (" + stepScope.getSelectedMoveCount() - + ") but failed to pick a nextStep (" + stepScope.getStep() + ")."); + throw new IllegalStateException( + "The step index (%d) has selected move count (%d) but failed to pick a nextStep (%s).".formatted( + stepScope.getStepIndex(), stepScope.getSelectedMoveCount(), stepScope.getStep())); } // Although stepStarted has been called, stepEnded is not called for this step. earlyTerminationStatus = TerminationStatus.early(phaseScope.getNextStepIndex()); @@ -190,11 +190,16 @@ public void phaseEnded(ConstructionHeuristicPhaseScope phaseScope) { phaseScope.endingNow(); if (decider.isLoggingEnabled() && logger.isInfoEnabled()) { logger.info( - "{}Construction Heuristic phase ({}) ended: time spent ({}), best score ({}), move evaluation speed ({}/sec), step total ({}).", + """ + {}Construction Heuristic phase ({}) ended: time spent ({}), best score ({}), \ + {}move evaluation speed ({}/sec), step total ({}).""", logIndentation, phaseIndex, phaseScope.calculateSolverTimeMillisSpentUpToNow(), phaseScope.getBestScore().raw(), + // Multithreaded solving uses "effective" move evaluation speed, since not all evaluated moves + // are foraged + (decider.getClass().equals(ConstructionHeuristicDecider.class)) ? "" : "effective ", phaseScope.getPhaseMoveEvaluationSpeed(), phaseScope.getNextStepIndex()); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/DefaultExhaustiveSearchPhase.java b/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/DefaultExhaustiveSearchPhase.java index 8aad9aea447..8b0e8a694cd 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/DefaultExhaustiveSearchPhase.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/DefaultExhaustiveSearchPhase.java @@ -101,8 +101,9 @@ private void phaseEnded(ExhaustiveSearchPhaseScope phaseScope) { super.phaseEnded(phaseScope); decider.phaseEnded(phaseScope); phaseScope.endingNow(); - logger.info("{}Exhaustive Search phase ({}) ended: time spent ({}), best score ({})," - + " move evaluation speed ({}/sec), step total ({}).", + logger.info(""" + {}Exhaustive Search phase ({}) ended: time spent ({}), best score ({}),\ + move evaluation speed ({}/sec), step total ({}).""", logIndentation, phaseIndex, phaseScope.calculateSolverTimeMillisSpentUpToNow(), diff --git a/core/src/main/java/ai/timefold/solver/core/impl/localsearch/DefaultLocalSearchPhase.java b/core/src/main/java/ai/timefold/solver/core/impl/localsearch/DefaultLocalSearchPhase.java index 66132466227..c89d26e6d2b 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/localsearch/DefaultLocalSearchPhase.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/localsearch/DefaultLocalSearchPhase.java @@ -94,16 +94,17 @@ public void solve(SolverScope solverScope) { stepScope.getStepIndex(), stepScope.getPhaseScope().calculateSolverTimeMillisSpentUpToNow()); } else if (stepScope.getSelectedMoveCount() == 0L) { - logger.warn("{} No doable selected move at step index ({}), time spent ({})." - + " Terminating phase early.", + logger.warn(""" + {} No doable selected move at step index ({}), time spent ({}). \ + Terminating phase early.""", logIndentation, stepScope.getStepIndex(), stepScope.getPhaseScope().calculateSolverTimeMillisSpentUpToNow()); } else { - throw new IllegalStateException("The step index (" + stepScope.getStepIndex() - + ") has accepted/selected move count (" + stepScope.getAcceptedMoveCount() + "/" - + stepScope.getSelectedMoveCount() - + ") but failed to pick a nextStep (" + stepScope.getStep() + ")."); + throw new IllegalStateException( + "The step index (%d) has accepted/selected move count (%d/%d) but failed to pick a nextStep (%s)." + .formatted(stepScope.getStepIndex(), stepScope.getAcceptedMoveCount(), + stepScope.getSelectedMoveCount(), stepScope.getStep())); } // Although stepStarted has been called, stepEnded is not called for this step break; @@ -151,8 +152,9 @@ public void stepEnded(LocalSearchStepScope stepScope) { if (logger.isDebugEnabled()) { if (stepScope.getAcceptedMoveCount() == 0 && phaseTermination.isPhaseTerminated(phaseScope)) { // Terminated early - logger.debug("{} LS step ({}), time spent ({}), score ({}), {} best score ({})," + - " terminated prematurely after selecting {} moves.", + logger.debug(""" + {} LS step ({}), time spent ({}), score ({}), {} best score ({}), \ + terminated prematurely after selecting {} moves.""", logIndentation, stepScope.getStepIndex(), phaseScope.calculateSolverTimeMillisSpentUpToNow(), @@ -160,8 +162,9 @@ public void stepEnded(LocalSearchStepScope stepScope) { (stepScope.getBestScoreImproved() ? "new" : " "), phaseScope.getBestScore().raw(), stepScope.getSelectedMoveCount()); } else { - logger.debug("{} LS step ({}), time spent ({}), score ({}), {} best score ({})," + - " accepted/selected move count ({}/{}), picked move ({}).", + logger.debug(""" + {} LS step ({}), time spent ({}), score ({}), {} best score ({}), \ + accepted/selected move count ({}/{}), picked move ({}).""", logIndentation, stepScope.getStepIndex(), phaseScope.calculateSolverTimeMillisSpentUpToNow(), @@ -224,12 +227,16 @@ public void phaseEnded(LocalSearchPhaseScope phaseScope) { super.phaseEnded(phaseScope); decider.phaseEnded(phaseScope); phaseScope.endingNow(); - logger.info("{}Local Search phase ({}) ended: time spent ({}), best score ({})," - + " move evaluation speed ({}/sec), step total ({}).", + logger.info(""" + {}Local Search phase ({}) ended: time spent ({}), best score ({}), \ + {}move evaluation speed ({}/sec), step total ({}).""", logIndentation, phaseIndex, phaseScope.calculateSolverTimeMillisSpentUpToNow(), phaseScope.getBestScore().raw(), + // Multithreaded solving uses "effective" move evaluation speed, since not all evaluated moves + // are foraged + (decider.getClass().equals(LocalSearchDecider.class)) ? "" : "effective ", phaseScope.getPhaseMoveEvaluationSpeed(), phaseScope.getNextStepIndex()); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/solver/AbstractSolver.java b/core/src/main/java/ai/timefold/solver/core/impl/solver/AbstractSolver.java index a94d8ac841c..d2b1b96afc3 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/solver/AbstractSolver.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/solver/AbstractSolver.java @@ -2,6 +2,8 @@ import java.util.Iterator; import java.util.List; +import java.util.Objects; +import java.util.random.RandomGenerator; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.solver.Solver; @@ -12,11 +14,13 @@ import ai.timefold.solver.core.impl.phase.scope.AbstractPhaseScope; import ai.timefold.solver.core.impl.phase.scope.AbstractStepScope; import ai.timefold.solver.core.impl.solver.event.SolverEventSupport; +import ai.timefold.solver.core.impl.solver.random.DelegatingSplittableRandomGenerator; import ai.timefold.solver.core.impl.solver.recaller.BestSolutionRecaller; import ai.timefold.solver.core.impl.solver.scope.SolverScope; import ai.timefold.solver.core.impl.solver.termination.UniversalTermination; import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,6 +48,8 @@ public abstract class AbstractSolver implements Solver { protected final UniversalTermination globalTermination; protected final List> phaseList; + private RandomGenerator.@Nullable SplittableGenerator savedRandom; + // ************************************************************************ // Constructors and simple getters/setters // ************************************************************************ @@ -123,10 +129,18 @@ public void stepStarted(AbstractStepScope stepScope) { bestSolutionRecaller.stepStarted(stepScope); phaseLifecycleSupport.fireStepStarted(stepScope); globalTermination.stepStarted(stepScope); + // To ensure reproducibility even when the number of random calls is not deterministic, + // split the random at step start. + var delegatingRandom = ((DelegatingSplittableRandomGenerator) stepScope.getWorkingRandom()); + savedRandom = delegatingRandom.getDelegate(); + delegatingRandom.setDelegate(delegatingRandom.split()); // Do not propagate to phases; the active phase does that for itself and they should not propagate further. } public void stepEnded(AbstractStepScope stepScope) { + // Restore from the split random + var delegatingRandom = ((DelegatingSplittableRandomGenerator) stepScope.getWorkingRandom()); + delegatingRandom.setDelegate(Objects.requireNonNull(savedRandom)); bestSolutionRecaller.stepEnded(stepScope); phaseLifecycleSupport.fireStepEnded(stepScope); globalTermination.stepEnded(stepScope); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolver.java b/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolver.java index 8f9b11b94ff..8541e0e6c01 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolver.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolver.java @@ -5,6 +5,8 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; +import java.util.random.RandomGenerator; import ai.timefold.solver.core.api.domain.common.PlanningId; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; @@ -16,7 +18,6 @@ import ai.timefold.solver.core.impl.phase.Phase; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; import ai.timefold.solver.core.impl.score.director.ScoreDirectorFactory; -import ai.timefold.solver.core.impl.solver.random.RandomFactory; import ai.timefold.solver.core.impl.solver.recaller.BestSolutionRecaller; import ai.timefold.solver.core.impl.solver.scope.SolverScope; import ai.timefold.solver.core.impl.solver.termination.BasicPlumbingTermination; @@ -38,7 +39,7 @@ public class DefaultSolver extends AbstractSolver { protected final EnvironmentMode environmentMode; - protected final RandomFactory randomFactory; + protected final Supplier randomFactory; protected final BasicPlumbingTermination basicPlumbingTermination; protected final AtomicBoolean solving = new AtomicBoolean(false); protected final SolverScope solverScope; @@ -48,7 +49,7 @@ public class DefaultSolver extends AbstractSolver { // Constructors and simple getters/setters // ************************************************************************ - public DefaultSolver(EnvironmentMode environmentMode, RandomFactory randomFactory, + public DefaultSolver(EnvironmentMode environmentMode, Supplier randomFactory, BestSolutionRecaller bestSolutionRecaller, BasicPlumbingTermination basicPlumbingTermination, UniversalTermination termination, List> phaseList, SolverScope solverScope, String moveThreadCountDescription) { @@ -65,8 +66,8 @@ public EnvironmentMode getEnvironmentMode() { return environmentMode; } - public RandomFactory getRandomFactory() { - return randomFactory; + public RandomGenerator getRandomGenerator() { + return randomFactory.get(); } public ScoreDirectorFactory getScoreDirectorFactory() { @@ -187,7 +188,7 @@ public void outerSolvingStarted(SolverScope solverScope) { solving.set(true); basicPlumbingTermination.resetTerminateEarly(); solverScope.setStartingSolverCount(0); - solverScope.setWorkingRandom(randomFactory.createRandom()); + solverScope.setWorkingRandom(randomFactory.get()); } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactory.java index 0a568175e4e..e391f41136f 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactory.java @@ -7,6 +7,8 @@ import java.util.List; import java.util.Objects; import java.util.OptionalInt; +import java.util.function.Supplier; +import java.util.random.RandomGenerator; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.score.Score; @@ -21,7 +23,6 @@ import ai.timefold.solver.core.config.solver.PreviewFeature; import ai.timefold.solver.core.config.solver.SolverConfig; import ai.timefold.solver.core.config.solver.monitoring.SolverMetric; -import ai.timefold.solver.core.config.solver.random.RandomType; import ai.timefold.solver.core.config.solver.termination.TerminationConfig; import ai.timefold.solver.core.config.util.ConfigUtils; import ai.timefold.solver.core.impl.AbstractFromConfigFactory; @@ -36,8 +37,7 @@ import ai.timefold.solver.core.impl.score.director.ScoreDirectorFactory; import ai.timefold.solver.core.impl.score.director.ScoreDirectorFactoryFactory; import ai.timefold.solver.core.impl.solver.change.DefaultProblemChangeDirector; -import ai.timefold.solver.core.impl.solver.random.DefaultRandomFactory; -import ai.timefold.solver.core.impl.solver.random.RandomFactory; +import ai.timefold.solver.core.impl.solver.random.DelegatingSplittableRandomGenerator; import ai.timefold.solver.core.impl.solver.recaller.BestSolutionRecaller; import ai.timefold.solver.core.impl.solver.recaller.BestSolutionRecallerFactory; import ai.timefold.solver.core.impl.solver.scope.SolverScope; @@ -133,7 +133,7 @@ public Solver buildSolver(SolverConfigOverride configOverr var moveThreadCount = resolveMoveThreadCount(true); var bestSolutionRecaller = BestSolutionRecallerFactory.create(). buildBestSolutionRecaller(environmentMode); - var randomFactory = buildRandomFactory(environmentMode); + var randomFactory = buildRandomSupplier(environmentMode); var previewFeaturesEnabled = solverConfig.getEnablePreviewFeatureSet(); var scoreDirectorFactoryConfig = solverConfig.getScoreDirectorFactoryConfig(); @@ -153,7 +153,7 @@ public Solver buildSolver(SolverConfigOverride configOverr .withMoveThreadBufferSize(solverConfig.getMoveThreadBufferSize()) .withThreadFactoryClass(solverConfig.getThreadFactoryClass()) .withNearbyDistanceMeterClass(solverConfig.getNearbyDistanceMeterClass()) - .withRandom(randomFactory.createRandom()) + .withRandom(randomFactory.get()) .withInitializingScoreTrend(scoreDirectorFactory.getInitializingScoreTrend()) .withSolutionDescriptor(solutionDescriptor) .withClassInstanceCache(ClassInstanceCache.create()) @@ -212,25 +212,14 @@ private > ScoreDirectorFactory b return scoreDirectorFactoryFactory.buildScoreDirectorFactory(environmentMode, solutionDescriptor); } - public RandomFactory buildRandomFactory(EnvironmentMode environmentMode_) { - var randomFactoryClass = solverConfig.getRandomFactoryClass(); - if (randomFactoryClass != null) { - var randomType = solverConfig.getRandomType(); - var randomSeed = solverConfig.getRandomSeed(); - if (randomType != null || randomSeed != null) { - throw new IllegalArgumentException( - "The solverConfig with randomFactoryClass (%s) has a non-null randomType (%s) or a non-null randomSeed (%s)." - .formatted(randomFactoryClass, randomType, randomSeed)); - } - return ConfigUtils.newInstance(solverConfig, "randomFactoryClass", randomFactoryClass); + public Supplier buildRandomSupplier(EnvironmentMode environmentMode_) { + var randomSeed_ = solverConfig.getRandomSeed(); + if (randomSeed_ == null && environmentMode_ != EnvironmentMode.NON_REPRODUCIBLE) { + randomSeed_ = DEFAULT_RANDOM_SEED; } else { - var randomType_ = Objects.requireNonNullElse(solverConfig.getRandomType(), RandomType.JDK); - var randomSeed_ = solverConfig.getRandomSeed(); - if (solverConfig.getRandomSeed() == null && environmentMode_ != EnvironmentMode.NON_REPRODUCIBLE) { - randomSeed_ = DEFAULT_RANDOM_SEED; - } - return new DefaultRandomFactory(randomType_, randomSeed_); + randomSeed_ = RandomGenerator.getDefault().nextLong(); } + return DelegatingSplittableRandomGenerator.getSupplier(randomSeed_); } public List> buildPhaseList(HeuristicConfigPolicy configPolicy, diff --git a/core/src/main/java/ai/timefold/solver/core/impl/solver/random/DefaultRandomFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/solver/random/DefaultRandomFactory.java deleted file mode 100644 index d1a06ef2113..00000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/solver/random/DefaultRandomFactory.java +++ /dev/null @@ -1,60 +0,0 @@ -package ai.timefold.solver.core.impl.solver.random; - -import java.util.Random; -import java.util.random.RandomGenerator; - -import ai.timefold.solver.core.config.solver.random.RandomType; - -import org.apache.commons.math3.random.MersenneTwister; -import org.apache.commons.math3.random.RandomAdaptor; -import org.apache.commons.math3.random.Well1024a; -import org.apache.commons.math3.random.Well19937a; -import org.apache.commons.math3.random.Well19937c; -import org.apache.commons.math3.random.Well44497a; -import org.apache.commons.math3.random.Well44497b; -import org.apache.commons.math3.random.Well512a; - -public class DefaultRandomFactory implements RandomFactory { - - protected final RandomType randomType; - protected final Long randomSeed; - - /** - * @param randomType never null - * @param randomSeed null if no seed - */ - public DefaultRandomFactory(RandomType randomType, Long randomSeed) { - this.randomType = randomType; - this.randomSeed = randomSeed; - } - - @Override - public RandomGenerator createRandom() { - switch (randomType) { - case JDK: - return randomSeed == null ? new Random() : new Random(randomSeed); - case MERSENNE_TWISTER: - return new RandomAdaptor(randomSeed == null ? new MersenneTwister() : new MersenneTwister(randomSeed)); - case WELL512A: - return new RandomAdaptor(randomSeed == null ? new Well512a() : new Well512a(randomSeed)); - case WELL1024A: - return new RandomAdaptor(randomSeed == null ? new Well1024a() : new Well1024a(randomSeed)); - case WELL19937A: - return new RandomAdaptor(randomSeed == null ? new Well19937a() : new Well19937a(randomSeed)); - case WELL19937C: - return new RandomAdaptor(randomSeed == null ? new Well19937c() : new Well19937c(randomSeed)); - case WELL44497A: - return new RandomAdaptor(randomSeed == null ? new Well44497a() : new Well44497a(randomSeed)); - case WELL44497B: - return new RandomAdaptor(randomSeed == null ? new Well44497b() : new Well44497b(randomSeed)); - default: - throw new IllegalStateException("The randomType (" + randomType + ") is not implemented."); - } - } - - @Override - public String toString() { - return randomType.name() + (randomSeed == null ? "" : " with seed " + randomSeed); - } - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/solver/random/DelegatingSplittableRandomGenerator.java b/core/src/main/java/ai/timefold/solver/core/impl/solver/random/DelegatingSplittableRandomGenerator.java new file mode 100644 index 00000000000..d6a62185fa1 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/solver/random/DelegatingSplittableRandomGenerator.java @@ -0,0 +1,189 @@ +package ai.timefold.solver.core.impl.solver.random; + +import java.util.SplittableRandom; +import java.util.function.Supplier; +import java.util.random.RandomGenerator; + +import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; +import ai.timefold.solver.core.impl.solver.AbstractSolver; + +import org.jspecify.annotations.NullMarked; + +/** + * A {@link RandomGenerator} that delegates to another {@link RandomGenerator.SplittableGenerator} + * instance. This allows us to change the {@link RandomGenerator} used even when + * {@link MoveSelector} and other classes to cache the {@link RandomGenerator} in a field. + *

+ * To ensure reproducibility, this class can only be used by the {@link Thread} + * that created it. Attempting to call any method from another thread will + * throw an {@link IllegalStateException}. + */ +@NullMarked +public final class DelegatingSplittableRandomGenerator implements RandomGenerator { + private RandomGenerator.SplittableGenerator delegate; + private final Thread ownerThread; + private final long seed; + + public DelegatingSplittableRandomGenerator(long seed) { + this.delegate = new SplittableRandom(seed); + this.ownerThread = Thread.currentThread(); + this.seed = seed; + } + + public DelegatingSplittableRandomGenerator(long seed, RandomGenerator.SplittableGenerator delegate) { + this.delegate = delegate; + this.ownerThread = Thread.currentThread(); + this.seed = seed; + } + + private void assertIsOwnedByCurrentThread() { + if (Thread.currentThread() != ownerThread) { + throw new IllegalStateException( + "The calling thread (%s) is not the owner thread (%s). Maybe create your own RandomGenerator instance?" + .formatted(Thread.currentThread(), ownerThread)); + } + } + + public RandomGenerator.SplittableGenerator split() { + assertIsOwnedByCurrentThread(); + return delegate.split(); + } + + public long getSeed() { + return seed; + } + + public RandomGenerator.SplittableGenerator getDelegate() { + assertIsOwnedByCurrentThread(); + return delegate; + } + + public void setDelegate(RandomGenerator.SplittableGenerator delegate) { + assertIsOwnedByCurrentThread(); + this.delegate = delegate; + } + + // ***************************************** + // RandomGenerator methods + // ***************************************** + + @Override + public long nextLong() { + assertIsOwnedByCurrentThread(); + return delegate.nextLong(); + } + + @Override + public int nextInt() { + assertIsOwnedByCurrentThread(); + return delegate.nextInt(); + } + + @Override + public int nextInt(int bound) { + assertIsOwnedByCurrentThread(); + return delegate.nextInt(bound); + } + + @Override + public int nextInt(int origin, int bound) { + assertIsOwnedByCurrentThread(); + return delegate.nextInt(origin, bound); + } + + @Override + public long nextLong(long bound) { + assertIsOwnedByCurrentThread(); + return delegate.nextLong(bound); + } + + @Override + public long nextLong(long origin, long bound) { + assertIsOwnedByCurrentThread(); + return delegate.nextLong(origin, bound); + } + + @Override + public double nextDouble() { + assertIsOwnedByCurrentThread(); + return delegate.nextDouble(); + } + + @Override + public double nextDouble(double bound) { + assertIsOwnedByCurrentThread(); + return delegate.nextDouble(bound); + } + + @Override + public double nextDouble(double origin, double bound) { + assertIsOwnedByCurrentThread(); + return delegate.nextDouble(origin, bound); + } + + @Override + public float nextFloat() { + assertIsOwnedByCurrentThread(); + return delegate.nextFloat(); + } + + @Override + public float nextFloat(float bound) { + assertIsOwnedByCurrentThread(); + return delegate.nextFloat(bound); + } + + @Override + public float nextFloat(float origin, float bound) { + assertIsOwnedByCurrentThread(); + return delegate.nextFloat(origin, bound); + } + + @Override + public double nextGaussian() { + assertIsOwnedByCurrentThread(); + return delegate.nextGaussian(); + } + + @Override + public boolean nextBoolean() { + assertIsOwnedByCurrentThread(); + return delegate.nextBoolean(); + } + + @Override + public void nextBytes(byte[] bytes) { + assertIsOwnedByCurrentThread(); + delegate.nextBytes(bytes); + } + + /** + * Return a supplier of {@link DelegatingSplittableRandomGenerator} with the specified + * seed with the seed included in {@link Object#toString()}. + *

+ * Required so the {@link RandomGenerator} is created in the thread {@link AbstractSolver#solve} + * is called. + * + * @param seed The seed to use for the {@link RandomGenerator}. + * @return A supplier include the seed in its {@link Object#toString()} + */ + public static Supplier getSupplier(long seed) { + return new Supplier<>() { + @Override + public RandomGenerator get() { + return new DelegatingSplittableRandomGenerator(seed); + } + + @Override + public String toString() { + return "seed %d".formatted(seed); + } + }; + } + + @Override + public String toString() { + return "%s (%s) with seed %d".formatted(delegate.getClass().getSimpleName(), + delegate, seed); + } +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/solver/random/RandomFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/solver/random/RandomFactory.java deleted file mode 100644 index cbe8c4ef724..00000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/solver/random/RandomFactory.java +++ /dev/null @@ -1,15 +0,0 @@ -package ai.timefold.solver.core.impl.solver.random; - -import java.util.random.RandomGenerator; - -/** - * @see DefaultRandomFactory - */ -public interface RandomFactory { - - /** - * @return never null - */ - RandomGenerator createRandom(); - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/solver/scope/SolverScope.java b/core/src/main/java/ai/timefold/solver/core/impl/solver/scope/SolverScope.java index 0f008e6f405..096557360de 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/solver/scope/SolverScope.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/solver/scope/SolverScope.java @@ -7,7 +7,6 @@ import java.util.EnumSet; import java.util.Map; import java.util.Objects; -import java.util.Random; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Semaphore; @@ -28,6 +27,7 @@ import ai.timefold.solver.core.impl.solver.AbstractSolver; import ai.timefold.solver.core.impl.solver.change.DefaultProblemChangeDirector; import ai.timefold.solver.core.impl.solver.monitoring.ScoreLevels; +import ai.timefold.solver.core.impl.solver.random.DelegatingSplittableRandomGenerator; import ai.timefold.solver.core.impl.solver.termination.PhaseTermination; import ai.timefold.solver.core.impl.solver.thread.ChildThreadType; @@ -353,9 +353,10 @@ public SolverScope createChildThreadSolverScope(ChildThreadType child childThreadSolverScope.monitoringTags = monitoringTags; childThreadSolverScope.solverMetricSet = solverMetricSet; childThreadSolverScope.startingSolverCount = startingSolverCount; - // TODO FIXME use RandomFactory // Experiments show that this trick to attain reproducibility doesn't break uniform distribution - childThreadSolverScope.workingRandom = new Random(workingRandom.nextLong()); + var delegatingRandom = (DelegatingSplittableRandomGenerator) workingRandom; + childThreadSolverScope.workingRandom = + new DelegatingSplittableRandomGenerator(delegatingRandom.getSeed(), delegatingRandom.split()); childThreadSolverScope.scoreDirector = scoreDirector.createChildThreadScoreDirector(childThreadType); childThreadSolverScope.startingSystemTimeMillis.set(startingSystemTimeMillis.get()); resetAtomicLongTimeMillis(childThreadSolverScope.endingSystemTimeMillis); diff --git a/core/src/main/java/module-info.java b/core/src/main/java/module-info.java index 10ad6fb09ef..6cb2b2c5f04 100644 --- a/core/src/main/java/module-info.java +++ b/core/src/main/java/module-info.java @@ -56,7 +56,6 @@ exports ai.timefold.solver.core.config.score.trend; exports ai.timefold.solver.core.config.solver; exports ai.timefold.solver.core.config.solver.monitoring; - exports ai.timefold.solver.core.config.solver.random; exports ai.timefold.solver.core.config.solver.termination; exports ai.timefold.solver.core.config.util; exports ai.timefold.solver.core.enterprise; @@ -204,6 +203,7 @@ exports ai.timefold.solver.core.impl.neighborhood to ai.timefold.solver.enterprise.core; exports ai.timefold.solver.core.impl.partitionedsearch to ai.timefold.solver.enterprise.core; exports ai.timefold.solver.core.impl.phase to ai.timefold.solver.enterprise.core; + exports ai.timefold.solver.core.impl.solver.random to ai.timefold.solver.enterprise.core; exports ai.timefold.solver.core.impl.solver.recaller to ai.timefold.solver.enterprise.core; exports ai.timefold.solver.core.impl.solver.event to ai.timefold.solver.enterprise.core; @@ -246,7 +246,6 @@ opens ai.timefold.solver.core.config.score.trend to jakarta.xml.bind, org.glassfish.jaxb.runtime; opens ai.timefold.solver.core.config.solver to jakarta.xml.bind, org.glassfish.jaxb.runtime; opens ai.timefold.solver.core.config.solver.monitoring to jakarta.xml.bind, org.glassfish.jaxb.runtime; - opens ai.timefold.solver.core.config.solver.random to jakarta.xml.bind, org.glassfish.jaxb.runtime; opens ai.timefold.solver.core.config.solver.termination to jakarta.xml.bind, org.glassfish.jaxb.runtime; opens ai.timefold.solver.core.config.util to jakarta.xml.bind, org.glassfish.jaxb.runtime; diff --git a/core/src/main/resources/solver.xsd b/core/src/main/resources/solver.xsd index 8e4c489f223..a5d10b20c96 100644 --- a/core/src/main/resources/solver.xsd +++ b/core/src/main/resources/solver.xsd @@ -17,12 +17,8 @@ - - - - @@ -1471,30 +1467,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/core/src/test/java/ai/timefold/solver/core/config/solver/EnvironmentModeTest.java b/core/src/test/java/ai/timefold/solver/core/config/solver/EnvironmentModeTest.java index fd9d131c42f..d22a080eae4 100644 --- a/core/src/test/java/ai/timefold/solver/core/config/solver/EnvironmentModeTest.java +++ b/core/src/test/java/ai/timefold/solver/core/config/solver/EnvironmentModeTest.java @@ -8,6 +8,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.random.RandomGenerator; import java.util.stream.IntStream; import ai.timefold.solver.core.api.score.SimpleScore; @@ -29,7 +30,6 @@ import ai.timefold.solver.core.impl.phase.event.PhaseLifecycleListenerAdapter; import ai.timefold.solver.core.impl.phase.scope.AbstractStepScope; import ai.timefold.solver.core.impl.solver.DefaultSolver; -import ai.timefold.solver.core.impl.solver.random.RandomFactory; import ai.timefold.solver.core.preview.api.move.builtin.Moves; import ai.timefold.solver.core.testdomain.TestdataEntity; import ai.timefold.solver.core.testdomain.TestdataSolution; @@ -186,14 +186,14 @@ void corruptedConstraints(EnvironmentMode environmentMode) { } private void assertReproducibility(Solver solver1, Solver solver2) { - assertGeneratingSameNumbers(((DefaultSolver) solver1).getRandomFactory(), - ((DefaultSolver) solver2).getRandomFactory()); + assertGeneratingSameNumbers(((DefaultSolver) solver1).getRandomGenerator(), + ((DefaultSolver) solver2).getRandomGenerator()); assertSameScoreSeries(solver1, solver2); } private void assertNonReproducibility(Solver solver1, Solver solver2) { - assertGeneratingDifferentNumbers(((DefaultSolver) solver1).getRandomFactory(), - ((DefaultSolver) solver2).getRandomFactory()); + assertGeneratingDifferentNumbers(((DefaultSolver) solver1).getRandomGenerator(), + ((DefaultSolver) solver2).getRandomGenerator()); assertDifferentScoreSeries(solver1, solver2); } @@ -241,10 +241,7 @@ private void assertDifferentScoreSeries(Solver solver1, Solver })); } - private void assertGeneratingSameNumbers(RandomFactory factory1, RandomFactory factory2) { - var random = factory1.createRandom(); - var random2 = factory2.createRandom(); - + private void assertGeneratingSameNumbers(RandomGenerator random, RandomGenerator random2) { assertSoftly(softly -> IntStream.range(0, NUMBER_OF_RANDOM_NUMBERS_GENERATED) .forEach(i -> softly.assertThat(random.nextInt()) .as("Random factories should generate the same results " @@ -252,15 +249,13 @@ private void assertGeneratingSameNumbers(RandomFactory factory1, RandomFactory f .isEqualTo(random2.nextInt()))); } - private void assertGeneratingDifferentNumbers(RandomFactory factory1, RandomFactory factory2) { - var random = factory1.createRandom(); - var random2 = factory2.createRandom(); - + private void assertGeneratingDifferentNumbers(RandomGenerator random, RandomGenerator random2) { assertSoftly(softly -> IntStream.range(0, NUMBER_OF_RANDOM_NUMBERS_GENERATED) .forEach(i -> softly.assertThat(random.nextInt()) - .as("Random factories should not generate exactly the same results " - + "in the non-reproducible environment mode. " - + "It can happen but the probability is very low. Run test again") + .as(""" + Random factories should not generate exactly the same results \ + in the non-reproducible environment mode. \ + It can happen but the probability is very low. Run test again""") .isNotEqualTo(random2.nextInt()))); } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactoryTest.java index 8d383755ddf..f150ad11765 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactoryTest.java @@ -10,7 +10,6 @@ import ai.timefold.solver.core.config.solver.SolverConfig; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; import ai.timefold.solver.core.impl.score.director.ScoreDirectorFactory; -import ai.timefold.solver.core.impl.solver.random.RandomFactory; import ai.timefold.solver.core.testdomain.TestdataConstraintProvider; import ai.timefold.solver.core.testdomain.TestdataEntity; import ai.timefold.solver.core.testdomain.TestdataSolution; @@ -121,17 +120,6 @@ void testNoEntityConfiguration() { "If you're using the Quarkus extension or Spring Boot starter, it should have been filled in already."); } - @Test - void testInvalidRandomConfiguration() { - SolverConfig solverConfig = - SolverConfig.createFromXmlResource("ai/timefold/solver/core/config/solver/testdataSolverConfig.xml") - .withRandomFactoryClass(RandomFactory.class) - .withRandomSeed(1000L); - assertThatCode(() -> new DefaultSolverFactory<>(solverConfig).buildSolver(new SolverConfigOverride<>())) - .hasMessageContaining("The solverConfig with randomFactoryClass ") - .hasMessageContaining("has a non-null randomType (null) or a non-null randomSeed (1000)."); - } - @Test void testInvalidMoveThreadCountConfiguration() { SolverConfig solverConfig = diff --git a/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java b/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java index c353855c571..5df6183c78a 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java @@ -1443,8 +1443,9 @@ void solveStaleBuiltinShadows() { var solution = PlannerTestUtils.solve(solverConfig, problem); - assertThat(solution.getEntityList().getFirst().getValue().getCode()).isEqualTo("v1"); - assertThat(solution.getEntityList().get(1).getValue().getCode()).isEqualTo("v2"); + assertThat(solution.getEntityList()) + .map(entity -> entity.getValue().getCode()) + .containsExactlyInAnyOrder("v1", "v2"); assertThat(solution.getScore()).isEqualTo(SimpleScore.of(-2)); } @@ -1484,9 +1485,9 @@ void solveStaleDeclarativeShadows() { var solution = PlannerTestUtils.solve(solverConfig, problem); - assertThat(solution.getEntities().getFirst().getValues()).map(TestdataConcurrentValue::getId).containsExactly("a1", - "b1"); - assertThat(solution.getEntities().get(1).getValues()).map(TestdataConcurrentValue::getId).containsExactly("a2", "b2"); + assertThat(solution.getEntities().getFirst().getValues()).map(TestdataConcurrentValue::getId).containsExactly("b1", + "a2"); + assertThat(solution.getEntities().get(1).getValues()).map(TestdataConcurrentValue::getId).containsExactly("b2", "a1"); assertThat(solution.getScore()).isEqualTo(HardSoftScore.of(0, -240)); } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/solver/SolverMetricsIT.java b/core/src/test/java/ai/timefold/solver/core/impl/solver/SolverMetricsIT.java index 7aa40f7d9b5..cd8203b55c6 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/solver/SolverMetricsIT.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/solver/SolverMetricsIT.java @@ -836,7 +836,7 @@ void lsWithListVariableAndCustomMetrics() { var solverConfig = PlannerTestUtils .buildSolverConfig(TestdataListSolution.class, TestdataListEntity.class, TestdataListValue.class); var phaseConfig = new LocalSearchPhaseConfig(); - phaseConfig.setTerminationConfig(new TerminationConfig().withScoreCalculationCountLimit(10L)); + phaseConfig.setTerminationConfig(new TerminationConfig().withScoreCalculationCountLimit(20L)); solverConfig.withPhases(phaseConfig) .withMonitoringConfig(new MonitoringConfig().withSolverMetricList(List.of(SolverMetric.MOVE_COUNT_PER_TYPE))); diff --git a/docs/src/modules/ROOT/pages/using-timefold-solver/running-the-solver.adoc b/docs/src/modules/ROOT/pages/using-timefold-solver/running-the-solver.adoc index 2c721153098..8ad37aaba7d 100644 --- a/docs/src/modules/ROOT/pages/using-timefold-solver/running-the-solver.adoc +++ b/docs/src/modules/ROOT/pages/using-timefold-solver/running-the-solver.adoc @@ -561,9 +561,9 @@ there are `timefold.solver.move.type.step.score.diff.hard.score` and `timefold.s [#randomNumberGenerator] == Random number generator -Many heuristics and metaheuristics depend on a pseudorandom number generator for move selection, to resolve score ties, probability based move acceptance, ... During solving, the same `Random` instance is reused to improve reproducibility, performance and uniform distribution of random values. +Many heuristics and metaheuristics depend on a pseudorandom number generator for move selection, to resolve score ties, probability based move acceptance, ... During solving, the same `RandomGenerator` instance is reused to improve reproducibility, performance and uniform distribution of random values. -To change the random seed of that `Random` instance, specify a ``randomSeed``: +To change the random seed of that `RandomGenerator` instance, specify a ``randomSeed``: [source,xml,options="nowrap"] ---- @@ -574,26 +574,6 @@ To change the random seed of that `Random` instance, specify a ``randomSeed``: ---- -To change the pseudorandom number generator implementation, specify a ``randomType``: - -[source,xml,options="nowrap"] ----- - - MERSENNE_TWISTER - ... - ----- - -The following types are supported: - -* `JDK` (default): Standard implementation (``java.util.Random``). -* ``MERSENNE_TWISTER``: Implementation by http://commons.apache.org/proper/commons-math/userguide/random.html[Commons Math]. -* ``WELL512A``, ``WELL1024A``, ``WELL19937A``, ``WELL19937C``, `WELL44497A` and ``WELL44497B``: Implementation by http://commons.apache.org/proper/commons-math/userguide/random.html[Commons Math]. - -For most use cases, the randomType has no significant impact on the average quality of the best solution on multiple datasets. -If you want to confirm this on your use case, use the xref:using-timefold-solver/benchmarking-and-tweaking.adoc#benchmarker[benchmarker]. - [#solverManager] == `SolverManager` diff --git a/spring-integration/spring-boot-integration-test/src/test/resources/solver-full.xml b/spring-integration/spring-boot-integration-test/src/test/resources/solver-full.xml index 136ebe5aeed..66be4df42be 100644 --- a/spring-integration/spring-boot-integration-test/src/test/resources/solver-full.xml +++ b/spring-integration/spring-boot-integration-test/src/test/resources/solver-full.xml @@ -1,9 +1,7 @@ TRACKED_FULL_ASSERT false - WELL1024A 10 - java.lang.Object 1 3 java.lang.Object diff --git a/tools/benchmark/src/main/resources/benchmark.xsd b/tools/benchmark/src/main/resources/benchmark.xsd index 0f9e9f1f713..87ef47a9121 100644 --- a/tools/benchmark/src/main/resources/benchmark.xsd +++ b/tools/benchmark/src/main/resources/benchmark.xsd @@ -326,15 +326,9 @@ - - - - - - @@ -2468,42 +2462,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tools/benchmark/src/test/java/ai/timefold/solver/benchmark/impl/result/PlannerBenchmarkResultTest.java b/tools/benchmark/src/test/java/ai/timefold/solver/benchmark/impl/result/PlannerBenchmarkResultTest.java index 14f87d29d15..55f7be87930 100644 --- a/tools/benchmark/src/test/java/ai/timefold/solver/benchmark/impl/result/PlannerBenchmarkResultTest.java +++ b/tools/benchmark/src/test/java/ai/timefold/solver/benchmark/impl/result/PlannerBenchmarkResultTest.java @@ -15,7 +15,6 @@ import ai.timefold.solver.core.api.score.SimpleScore; import ai.timefold.solver.core.api.score.calculator.IncrementalScoreCalculator; import ai.timefold.solver.core.config.solver.SolverConfig; -import ai.timefold.solver.core.config.solver.random.RandomType; import ai.timefold.solver.core.impl.heuristic.selector.common.nearby.NearbyDistanceMeter; import ai.timefold.solver.core.testdomain.TestdataEntity; import ai.timefold.solver.core.testdomain.TestdataSolution; @@ -37,19 +36,19 @@ void createMergedResult() { var p1SolverX = new SolverBenchmarkResult(p1); p1SolverX.setName("Solver X"); var p1SolverConfigX = new SolverConfig(); - p1SolverConfigX.setRandomType(RandomType.JDK); + p1SolverConfigX.setRandomSeed(0L); p1SolverX.setSolverConfig(p1SolverConfigX); p1SolverX.setSingleBenchmarkResultList(new ArrayList<>()); var p1SolverY = new SolverBenchmarkResult(p1); p1SolverY.setName("Solver Y"); var p1SolverConfigY = new SolverConfig(); - p1SolverConfigY.setRandomType(RandomType.MERSENNE_TWISTER); + p1SolverConfigY.setRandomSeed(1L); p1SolverY.setSolverConfig(p1SolverConfigY); p1SolverY.setSingleBenchmarkResultList(new ArrayList<>()); var p2SolverZ = new SolverBenchmarkResult(p2); p2SolverZ.setName("Solver Z"); var p2SolverConfigZ = new SolverConfig(); - p2SolverConfigZ.setRandomType(RandomType.WELL1024A); + p2SolverConfigZ.setRandomSeed(2L); p2SolverZ.setSolverConfig(p2SolverConfigZ); p2SolverZ.setSingleBenchmarkResultList(new ArrayList<>());