Configuring Timefold Solver
1. Solver configuration by XML
Build a Solver
instance with the SolverFactory
.
Configure the SolverFactory
with a solver configuration XML file, provided as a classpath resource (as defined by ClassLoader.getResource()
):
-
Java
-
Python
SolverFactory<Timetable> solverFactory = SolverFactory.createFromXmlResource(
"org/acme/schooltimetabling/solverConfig.xml");
Solver<Timetable> solver = solverFactory.buildSolver();
solver_factory = SolverFactory.create(SolverConfig.create_from_xml_resource(
Path(...) / "solver_config.xml"))
solver = solver_factory.build_solver()
In a typical project (following the Maven directory structure), that solverConfig XML file would be located at $PROJECT_DIR/src/main/resources/org/acme/schooltimetabling/solverConfig.xml
.
Alternatively, a SolverFactory
can be created from a File
with SolverFactory.createFromXmlFile()
.
However, for portability reasons, a classpath resource is recommended.
Both a Solver
and a SolverFactory
have a generic type called Solution_
, which is the class
representing a planning problem and solution.
A solver configuration XML file looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<solver xmlns="https://timefold.ai/xsd/solver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://timefold.ai/xsd/solver https://timefold.ai/xsd/solver/solver.xsd">
<!-- Define the model -->
<solutionClass>org.acme.schooltimetabling.domain.Timetable</solutionClass>
<entityClass>org.acme.schooltimetabling.domain.Lesson</entityClass>
<!-- Define the score function -->
<scoreDirectorFactory>
<constraintProviderClass>org.acme.schooltimetabling.solver.TimetableConstraintProvider</constraintProviderClass>
</scoreDirectorFactory>
<!-- Configure the optimization algorithms (optional) -->
<termination>
...
</termination>
<constructionHeuristic>
...
</constructionHeuristic>
<localSearch>
...
</localSearch>
</solver>
In Timefold Solver for Python, use qualified names when referencing functions and classes. |
Notice the three parts in it:
-
Define the model.
-
Define the score function.
-
Optionally configure the optimization algorithm(s).
These various parts of a configuration are explained further in this manual.
Timefold Solver makes it relatively easy to switch optimization algorithm(s) just by changing the configuration. There is even a Benchmarker which allows you to play out different configurations against each other and report the most appropriate configuration for your use case.
2. Solver configuration as code
A solver configuration can also be configured with the SolverConfig
API.
This is especially useful to change some values dynamically at runtime.
For example, to change the running time based on system property, before building the Solver
:
-
Java
-
Python
SolverConfig solverConfig = SolverConfig.createFromXmlResource("org/acme/schooltimetabling/solverConfig.xml");
solverConfig.withTerminationConfig(new TerminationConfig()
.withMinutesSpentLimit(userInput));
SolverFactory<Timetable> solverFactory = SolverFactory.create(solverConfig);
Solver<Timetable> solver = solverFactory.buildSolver();
solver_config = SolverConfig.create_from_xml_resource(
Path(...) / "solver_config.xml")
termination_config = TerminationConfig(
spent_limit=Duration(minutes=user_input)
)
solver_config.termination_config = termination_config
solver_factory = SolverFactory.create(solver_config)
solver = solver_factory.build_solver()
Every element in the solver configuration XML is available as a *Config
class
or a property on a *Config
class in the package namespace ai.timefold.solver.core.config
.
These *Config
classes are the Java representation of the XML format.
They build the runtime components (of the package namespace ai.timefold.solver.core.impl
)
and assemble them into an efficient Solver
.
To configure a
|
3. Annotation alternatives
Timefold Solver needs to be told which classes in your domain model are planning entities, which properties are planning variables, etc. There are several ways to deliver this information:
-
Java
-
Python
-
Add class annotations and JavaBean property annotations on the domain model (recommended). The property annotations must be on the getter method, not on the setter method. Such a getter does not need to be public.
-
Add class annotations and field annotations on the domain model. Such a field does not need to be public.
-
Add class decorators and annotations on attributes in the domain model (recommended).
-
Add class decorators and annotations on the return type of methods in the domain model.
This manual focuses on the first manner, but every feature supports both, even if it’s not explicitly mentioned.
4. Domain access
Timefold Solver by default accesses your domain using reflection, which will always work, but is slow compared to direct access. Alternatively, you can configure Timefold Solver to access your domain using Gizmo, which will generate bytecode that directly access the fields/methods of your domain without reflection. However, it comes with some restrictions:
-
All fields in the domain must be public.
-
The planning annotations can only be on public fields and public getters.
-
io.quarkus.gizmo:gizmo must be on the classpath.
These restrictions do not apply when using Timefold Solver with Quarkus, where Gizmo is the default domain access type.
To use Gizmo outside of Quarkus, set the domainAccessType
in the
Solver Configuration:
<solver>
<domainAccessType>GIZMO</domainAccessType>
</solver>
5. Custom properties configuration
This feature is not supported in Timefold Solver for Python. |
Solver configuration elements, that instantiate classes and explicitly mention it, support custom properties.
Custom properties are useful to tweak dynamic values through the Benchmarker.
For example, presume your EasyScoreCalculator
has heavy calculations (which are cached)
and you want to increase the cache size in one benchmark:
<scoreDirectorFactory>
<easyScoreCalculatorClass>...MyEasyScoreCalculator</easyScoreCalculatorClass>
<easyScoreCalculatorCustomProperties>
<property name="myCacheSize" value="1000"/><!-- Override value -->
</easyScoreCalculatorCustomProperties>
</scoreDirectorFactory>
Add a public setter for each custom property, which is called when a Solver
is built.
public class MyEasyScoreCalculator extends EasyScoreCalculator<MySolution, SimpleScore> {
private int myCacheSize = 500; // Default value
@SuppressWarnings("unused")
public void setMyCacheSize(int myCacheSize) {
this.myCacheSize = myCacheSize;
}
...
}
Most value types are supported (including boolean
, int
, double
, BigDecimal
, String
and enums).