Google OR-Tools and Timefold are open source mathematical optimization solvers. They are both used across the globe in production. But there are notable differences between Timefold and Google OR-Tools.
Timefold Solver is unified, customizable, fast, scalable and easy to use because of OO models, constraints as code and flexible scoring. Timefold is also integration friendly (including REST) and cloud ready. It runs on all operating systems and has enterprise support.
Try it yourself. Get started with Timefold today.
Timefold is a unified and consistent solver
Google OR-Tools has a separate solvers for Vehicle Routing (VRP), Constraint Programming (such as Employee Rostering), etc. Each solver has their own APIs, their own learning curve, their own limitations and their own specific use cases.
Timefold has a single, unified API that handles any use case with any combination of constraints. It can solve hybrid problems, such as a Vehicle Routing Problem with a constraint to distribute the workload fairly across drivers (a typical constraint of Employee Rostering).
Timefold is a customizable solver
Google OR-Tools’s VRP solver has a fixed list of constraints to choose from.
Most real-world VRP use cases share 80% of the constraints, such as capacity limits or time windows. But unless the other 20% - the business specific hard constraints - are implemented, your project will return infeasible solutions, making it 100% useless.
Timefold supports a flexible constraint API that can handle any type of constraint. VRP specific constraints - such as capacity limits or time windows - are implemented just like any other constraint. Therefore, it can handle any VRP variant, even those unique to your organization.
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 supports multi-threaded solving to take advantage of multiple CPU cores, even for a single algorithm on a single dataset. OR-Tools’s VRP solver doesn’t.
Timefold delivers better results, in the same amount of CPU time, even on a single CPU core. It also consumes less memory when scaling out. Why? Because:
Timefold uses a more efficient model
Under the covers, OR-Tools’s CP solver and Timefold use very different optimization technologies, which impacts your modeling flexibility, which in turn impacts performance and scalability.
For example, in employee rostering, to assign shifts to employees:
-
OR-Tools’s CP solver creates a
boolean
variable for every(shift, employee)
combination. Given2000
shifts and100
employees, that’s an array of200 000
elements. -
On the other hand, Timefold creates an
Employee
variable for everyShift
. Given2000
shifts and100
employees, that’s an array of2000
elements.
The Timefold approach doesn’t just scale better, it also omits the one shift per employee hard constraint which the model naturally enforces by design.
Ironically, OR-Tools’s VRP solver is far more akin to Timefold in this regard than OR-Tools’s CP solver.
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 Object-Oriented Programming and Functional Programming:
An Timefold model is Object-Oriented Programming (OOP) friendly
Both OR-Tools and Timefold require you to define your model, with optimization variables, so the mathematical optimization software knows which decisions it needs to make.
OR-Tools’s CP solver supports 3 types of optimization variables: booleans, integers and floating point numbers. You must transform your domain model into those types. For example:
// Input
CpModel model = ...
IntVar[][] assignments = new IntVar[shifts.size()][employees.size()];
for (int s = 0; s < shifts.size(); s++) {
for (int e = 0; e < employees.size(); e++) {
assignments[s][e] = model.newBoolVar(...);
}
}
... // Add constraints to enforce no shift is assigned to multiple employees
// Solve
solver.solve(model);
// Output
for (int s = 0; s < shifts.size(); s++) {
for (int e = 0; e < employees.size(); e++) {
if (value(assignments[s][e]) == 1L) {
print(shifts[s] + " is assigned to " + employees[e]);
}
}
}
OR-Tools’s VRP solver is even more limiting, because the type of the optimization variables are hard coded
in OR-Tools’s RoutingModel
class:
RoutingIndexManager manager = new RoutingIndexManager(locationListSize, vehicleListSize, 0);
RoutingModel routingModel = new RoutingModel(manager); // OR-Tools defined class
routingModel.setArcCostEvaluatorOfAllVehicles(...);
routingModel.addDimensionWithVehicleCapacity(...);
Assignment assignment = routingModel.solve();
long start = manager.indexToNode(routingModel.start(v));
long visit1 = assignment.value(routingModel.nextVar(internalIndex));
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
OR-Tools’s CP solver 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++) {
IntVar[] vars = new IntVar[...];
int i = 0;
for (int s = 0; s < shifts.size(); s++) {
// If the shift is on the date
if (shifts[s].date == dates[d])) {
vars[i++] = assignments[s][e];
}
}
model.addLessOrEqual(LinearExpr.sum(vars), 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 has flexible scoring
OR-Tools’s CP solver supports 2 score levels: hard constraints as constraints and soft constraints as an objective function that returns a floating point number.
If one soft constraint takes total priority over another soft constraint, for example service quality constraints over productivity constraints, OR-Tools’s CP solver multiplies the first soft constraint by a big weight and sums that with the second. This can lead to overflow or underflow.
Timefold supports any number of score levels:
-
2 levels (default): hard and soft constraints with
HardSoftScore
-
3 levels: hard, medium and soft constraints with
HardMediumSoftScore
-
n levels with
BendableScore
This allows users to prioritize operational constraints (such as assign all shifts) over financial constraints (such as reduce cost), without multiplication with a big number.
The Timefold constraint weights can use:
-
32-bit integer (
int
) arithmetic (default) withHardSoftScore
, etc. -
64-bit integer (
long
) arithmetic withHardSoftLongScore
, etc. -
Decimal number (
BigDecimal
) arithmetic withHardSoftBigDecimalScore
, etc.
Timefold actually no longer supports floating point (double
) arithmetic
because of the numerical instability issues involved for incremental score calculation.
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 on-premise.
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. There is no need to add a specific linux or Windows specific JAR in the classpath, depending on the operating system.
Timefold has enterprise support
Both Timefold Solver Community and OR-Tools are open source software under the Apache License, which allows free reuse for commercial purposes. In both cases, their source code is available on GitHub. Their community are open. Join us on StackOverflow or GitHub discussions.
A dedicated team of optimization experts works full-time 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 high-scalability features as part of Timefold Solver Enterprise for customers who want to take their Timefold implementation to the next level.
Get started
To get started with Timefold, read the quick start guide or copy-paste the source code of one of the Timefold Quickstarts.