LocalSolver and Timefold are mathematical optimization solvers. They are both used across the globe in production. But there are notable differences between Timefold and LocalSolver.
Timefold Solver is open source, fast, scalable and easy to use because of OO models and constraints as code. Timefold is also integration friendly (including REST) and cloud ready. It runs on all operating systems.
Try it yourself. No license needed.
Get started with Timefold today.
Timefold is Open Source
LocalSolver is closed source software.
Timefold Solver Community is open source software under the Apache License, which allows free reuse for commercial purposes. The source code is available on GitHub. Its community is open. Join us on StackOverflow or GitHub discussions.
A dedicated team of optimization experts works fulltime on Timefold Solver. Also, hundreds of external developers have contributed to Timefold Solver. Regardless of who wrote the code, an(other) Timefold core developer reviewed it before it was merged into the main repository.
Timefold offers support and highscalability features as part of Timefold Solver Enterprise for customers who want to take their Timefold implementation to the next level.
Timefold is fast and scalable
Timefold is both fast and scalable for planning and scheduling use cases, such as the Vehicle Routing Problem, Employee Rostering and Maintenance scheduling.
Timefold delivers better results, in the same amount of CPU time.
Timefold is easy to use
Any software developer can implement an optimization use case with Timefold. There’s no need to hire expensive consultants to write complex mathematical equations. It’s OOP friendly.
Later, when your business reality changes, you can quickly adjust the Timefold constraints. It is maintenance friendly.
Timefold supports both ObjectOriented Programming and Functional Programming:
An Timefold model is ObjectOriented Programming (OOP) friendly
Both LocalSolver and Timefold require you to define your model, with optimization variables, so the mathematical optimization software knows which decisions it needs to make.
LocalSolver supports 5 types of optimization variables: booleans, integers, floating point numbers and sets and lists. You must transform your domain model into those types. For example:
// Input
LSModel model = ...
LSExpression[][] assignments = new LSExpression[shifts.size()][employees.size()];
for (int s = 0; s < shifts.size(); s++) {
for (int e = 0; e < employees.size(); e++) {
assignments[s][e] = model.boolVar();
}
}
... // Add constraints to enforce no shift is assigned to multiple employees
// Solve
model.minimize(...);
// Output
for (int s = 0; s < shifts.size(); s++) {
for (int e = 0; e < employees.size(); e++) {
if (assignments[s][e].getValue() == 1) {
print(shifts[s] + " is assigned to " + employees[e]);
}
}
}
Timefold supports any type of optimization variables,
including your custom classes (Employee
, Vehicle
, …) or standard classes (Boolean
, Integer
, BigDecimal
, LocalDate
, …).
You can reuse your existing domain model, to avoid costly data transformations.
For example:
@PlanningEntity
class Shift { // User defined class
... // Shift id, date, start time, required skills, ...
@PlanningVariable
Employee employee;
}
@PlanningSolution
class TimeTable { // User defined class
List<Employee> employees;
List<Shift> shifts;
}
// Input
Timetable timetable = new Timetable(shifts, employees);
// Solve
timetable = Solver.solve(timetable);
// Output
for (Shift shift : timetable.shifts) {
print(shift + " is assigned to " + shift.employee);
}
Neither of these 2 classes (Shift
and Timetable
) exist in Timefold itself: you define and shape them.
Your code doesn’t deal with booleans and numbers, but uses Employee
, Shift
and DayOfRequest
instances.
Your code reads naturally.
Timefold even supports polymorphism.
Timefold constraints are code, not equations
LocalSolver constraints are implemented as mathematical equations.
For example, to assign at most one shift per day,
you add an equation s1 + s2 + s3 <= 1
for all shifts on day 1,
an equation s4 + s5 <= 1
for all shifts on day 2, and so forth:
for (int e = 0; e < employees.size(); e++) {
for (int d = 0; d < dates.size(); d++) {
LSExpression expr = ...
for (int s = 0; s < shifts.size(); s++) {
// If the shift is on the date
if (shifts[s].date == dates[d])) {
expr.addOperand(assignments[s][e]);
}
}
model.constraint(model.lq(expr, 1));
}
}
Timefold constraints are implemented as programming code. If you use ConstraintStreams, a Functional Programming (FP) approach, Timefold automatically applies incremental score calculation with deltas for maximum scalability and performance.
For example, to assign at most one shift per day,
select every pair of Shift
instances
that have the same date
and the same employee
,
to penalize matching pairs as a hard constraint:
// For every shift ...
constraintFactory.forEach(Shift.class)
// ... combined with any other shift ...
.join(Shift.class,
// ... on the same date ...
equal(shift > shift.date),
// ... assigned to the same employee ...
equal(shift > shift.employee))
// ... penalize one broken hard constraint per pair.
.penalize(HardSoftScore.ONE_HARD)
.asConstraint("One shift per day");
That equal()
method accepts any code as a parameter to return any type (not just booleans and numbers).
For example, because date
is an instance of LocalDate
(an advanced Date and Time API),
use LocalDate.isDayOfWeek()
to select 2 shifts on the same day of week:
// ... on the same day of week ...
equal(shift > shift.date.getDayOfWeek())
Date and times arithmetic is notoriously difficult,
because of Daylight Saving Time (DST), timezones, leap years and other semantics that only a few programmers on this planet actually understand.
Timefold empowers you to directly use their APIs (such as LocalDate
) in your constraints.
Besides the equal()
joiner, Timefold supplies lessThan()
, greaterThan()
, lessThanOrEqual()
, greaterThanOrEqual()
,
overlapping()
, etc.
Timefold automatically applies indexing (hashtable techniques) on joiners for performance.
For example, select two overlapping shifts with the overlapping()
joiner
(even if they start or end at different times):
// ... that overlap ...
overlapping(shift > shift.startDateTime, shift > shift.endDateTime)
Besides the join()
construct, Timefold supports filter()
, groupBy()
, ifExists()
, ifNotExists()
, map()
, etc.
This rich API empowers you to implement any constraint.
For example, allow employees that can work double shifts to work double shifts
by filtering out all employees that work double shifts with a filter()
:
// For every shift ...
constraintFactory.forEach(Shift.class)
// ... assigned to an employee that does not work double shifts ...
.filter(shift > !shift.employee.worksDoubleShifts)
// ... combined with any other shift ...
.join(Shift.class,
equal(shift > shift.date),
// ... assigned to that same employee that does not work double shifts ...
equal(shift > shift.employee))
.penalize(HardSoftScore.ONE_HARD)
.asConstraint("One shift per day");
The groupBy()
construct supports count()
, sum()
, average()
, min()
, max()
, toList()
, toSet()
, toMap()
, etc.
You can also plug in custom collectors.
For example, don’t assign more than 10 shifts to any employee by counting their shifts with count()
:
constraintFactory.forEach(Shift.class)
// Group shifts by employee and count the number of shifts per employee ...
.groupBy(shift > shift.employee, count())
// ... if more than 10 shifts for one employee ...
.filter((employee, shiftCount) > shiftCount > 10)
// ... penalize as a hard constraint ...
.penalize(HardSoftScore.ONE_HARD,
// ... multiplied by the number of excessive shifts.
(employee, shiftCount) > shiftCount  10)
.asConstraint("Too many shifts");
Timefold allows weighting constraints dynamically. It has no linear limitations.
For example, avoid overtime and distribute it fairly by penalizing the number of excessive hours squared:
constraintFactory.forEach(Shift.class)
// Group shifts by employee and sum the shift duration per employee ...
.groupBy(shift > shift.employee, sum(shift > shift.getDurationInHours()))
// ... if an employee is working more hours than his/her contract ...
.filter((employee, hoursTotal) > hoursTotal > employee.contract.maxHours)
// ... penalize as a soft constraint of weight 1000 ...
.penalize(HardSoftScore.ofSoft(1000),
// ... multiplied by the number of excessive hours squared.
(employee, hoursTotal) > {
int excessiveHours = hoursTotal  employee.contract.maxHours;
return excessiveHours * excessiveHours;
})
.asConstraint("Too many shifts");
This penalizes outliers more. It automatically load balances overtime in fair manner across the employees, whenever possible.
Timefold also supports positive constraints: use reward()
instead of penalize()
.
Timefold is easy to integrate with the REST, databases, etc
The Timefold Quickstarts show how to integrate Timefold with various technologies to quickly:

Expose a REST service and JSON data with Quarkus or Spring.

Connect to a relational database with Quarkus or Spring.

Load balance solvers across multiple nodes with ActiveMQ.
Timefold is cloud ready
Timefold runs on all major clouds, such as Amazon AWS, Microsoft Azure, Google Compute Engine and IBM Cloud. It works on all major cloud technologies, such as Kubernetes, OpenShift, Docker and Virtual Machines.
But it just runs as fine on your local development machine without internet access, or embedded in a process onpremise.
With Quarkus it can compile Java and Kotlin code natively for serverless use cases that need to start up in milliseconds.
Timefold runs on all major operating systems
Timefold runs on all major operating systems, such as Linux, Windows and macOS.
Get started
To get started with Timefold, read the quick start guide or copypaste the source code of one of the Timefold Quickstarts.