Skip to content

Commit

Permalink
feat: Overriding Solver termination configuration (#542)
Browse files Browse the repository at this point in the history
This pull request introduces a new feature that enables users to
override solver termination settings without recreating the
`SolverFactory` or `SolverManager`.

The `SolverManager` now has a builder API for job configuration and
execution. Additionally, `SolverFactory` can build a solver and override
the termination config settings.

Review done in #515
  • Loading branch information
zepfred authored Jan 5, 2024
1 parent 67d4448 commit a481fe1
Show file tree
Hide file tree
Showing 20 changed files with 982 additions and 89 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package ai.timefold.solver.core.api.solver;

import java.util.Objects;

import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
import ai.timefold.solver.core.config.solver.termination.TerminationConfig;

/**
* Includes settings to override default {@link ai.timefold.solver.core.api.solver.Solver} configuration.
*
* @param <Solution_> the solution type, the class with the {@link PlanningSolution} annotation
*/
public final class SolverConfigOverride<Solution_> {

private TerminationConfig terminationConfig = null;

public TerminationConfig getTerminationConfig() {
return terminationConfig;
}

/**
* Sets the solver {@link TerminationConfig}.
*
* @param terminationConfig allows overriding the default termination config of {@link Solver}
* @return this, never null
*/
public SolverConfigOverride<Solution_> withTerminationConfig(TerminationConfig terminationConfig) {
this.terminationConfig =
Objects.requireNonNull(terminationConfig, "Invalid terminationConfig (null) given to SolverConfigOverride.");
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,15 @@ static <Solution_> SolverFactory<Solution_> create(SolverConfig solverConfig) {
*
* @return never null
*/
Solver<Solution_> buildSolver();
default Solver<Solution_> buildSolver() {
return this.buildSolver(new SolverConfigOverride<>());
}

/**
* As defined by {@link #buildSolver()}.
*
* @param configOverride never null, includes settings that override the default configuration
* @return never null
*/
Solver<Solution_> buildSolver(SolverConfigOverride<Solution_> configOverride);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.function.Function;

import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
import ai.timefold.solver.core.api.solver.change.ProblemChange;
Expand All @@ -19,8 +18,8 @@
public interface SolverJob<Solution_, ProblemId_> {

/**
* @return never null, a value given to {@link SolverManager#solve(Object, Function, Consumer)}
* or {@link SolverManager#solveAndListen(Object, Function, Consumer)}
* @return never null, a value given to {@link SolverManager#solve(Object, Object, Consumer)}
* or {@link SolverManager#solveAndListen(Object, Object, Consumer)}
*/
ProblemId_ getProblemId();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package ai.timefold.solver.core.api.solver;

import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;

import ai.timefold.solver.core.api.domain.solution.PlanningSolution;

/**
* Provides a fluent contract that allows customization and submission of planning problems to solve.
* <p>
* A {@link SolverManager} can solve multiple planning problems and can be used across different threads.
* <p>
* Hence, it is possible to have multiple distinct build configurations that are scheduled to run by the {@link SolverManager}
* instance.
* <p>
* To solve a planning problem, set the problem configuration: {@link #withProblemId(Object)},
* {@link #withProblemFinder(Function)} and {@link #withProblem(Object)}.
* <p>
* Then solve it by calling {@link #run()}.
*
* @param <Solution_> the solution type, the class with the {@link PlanningSolution} annotation
* @param <ProblemId_> the ID type of submitted problem, such as {@link Long} or {@link UUID}.
*/
public interface SolverJobBuilder<Solution_, ProblemId_> {

/**
* Sets the problem id.
*
* @param problemId never null, a ID for each planning problem. This must be unique.
* @return this, never null
*/
SolverJobBuilder<Solution_, ProblemId_> withProblemId(ProblemId_ problemId);

/**
* Sets the problem definition.
*
* @param problem never null, a {@link PlanningSolution} usually with uninitialized planning variables
* @return this, never null
*/
default SolverJobBuilder<Solution_, ProblemId_> withProblem(Solution_ problem) {
return withProblemFinder(id -> problem);
}

/**
* Sets the mapping function to the problem definition.
*
* @param problemFinder never null, a function that returns a {@link PlanningSolution}, usually with uninitialized
* planning variables
* @return this, never null
*/
SolverJobBuilder<Solution_, ProblemId_> withProblemFinder(Function<? super ProblemId_, ? extends Solution_> problemFinder);

/**
* Sets the best solution consumer, which may be called multiple times during the solving process.
*
* @param bestSolutionConsumer never null, called multiple times for each new best solution on a consumer thread
* @return this, never null
*/
SolverJobBuilder<Solution_, ProblemId_> withBestSolutionConsumer(Consumer<? super Solution_> bestSolutionConsumer);

/**
* Sets the final best solution consumer, which is called at the end of the solving process and returns the final
* best solution.
*
* @param finalBestSolutionConsumer never null, called only once at the end of the solving process on a consumer thread
* @return this, never null
*/
SolverJobBuilder<Solution_, ProblemId_>
withFinalBestSolutionConsumer(Consumer<? super Solution_> finalBestSolutionConsumer);

/**
* Sets the custom exception handler.
*
* @param exceptionHandler never null, called if an exception or error occurs. If null it defaults to logging the
* exception as an error.
* @return this, never null
*/
SolverJobBuilder<Solution_, ProblemId_>
withExceptionHandler(BiConsumer<? super ProblemId_, ? super Throwable> exceptionHandler);

/**
* Sets the solver config override.
*
* @param solverConfigOverride never null, allows overriding the default behavior of {@link Solver}
* @return this, never null
*/
SolverJobBuilder<Solution_, ProblemId_> withConfigOverride(SolverConfigOverride<Solution_> solverConfigOverride);

/**
* Submits a planning problem to solve and returns immediately. The planning problem is solved on a solver {@link Thread},
* as soon as one is available.
*
* @return never null
*/
SolverJob<Solution_, ProblemId_> run();
}
Loading

0 comments on commit a481fe1

Please sign in to comment.