Gurobi versus Timefold comparison
Compare two solvers for mathematical optimization: Timefold and Gurobi.
Gurobi and Timefold are mathematical optimization solvers. Both are fast and scalable solvers, used across the globe by a large user base in production. They have similarities, but also differences.
This comparison is written by the Timefold team to guide you through the fundamental differences in these technologies. We aim to present correct and truthful information. However, it’s important to acknowledge our natural bias towards Timefold.
License and community
Both Gurobi and Timefold are created by companies with dedicated team of optimization experts. Both have a large community, high-quality documentation and a rich set of examples.
Gurobi
Gurobi is paid, proprietary software.
To get started, contact Hexaly for a free evaluation license, then read the docs or start from the examples.
Timefold
Timefold Solver Community is open source software under the Apache License, which allows free usage in commercial software.
To get started, read the docs or clone the quickstarts repo on GitHub.
Timefold Enterprise is paid, proprietary software, that adds support and high-scalability features. This comparison covers only the features in the free, open source edition.
Define a model
Both Gurobi and Timefold require you to define your model with optimization variables, so the mathematical optimization software knows which decisions it needs to make.
Gurobi
Gurobi supports several types of optimization variables, such as booleans, integers and floating point numbers. You can define your planning model in those types.
For example, you can use addVar(BINARY)
to model the assignment of a particular employee to a particular shift:
// Input
Model model = ...
Variable[][] assignments = new Variable[shifts.size()][employees.size()];
for (int s = 0; s < shifts.size(); s++) {
for (int e = 0; e < employees.size(); e++) {
assignments[s][e] = model.addVar(BINARY);
}
}
... // Add constraints to enforce no shift is assigned to multiple employees
// Solve
model.solve();
// Output
for (int s = 0; s < shifts.size(); s++) {
for (int e = 0; e < employees.size(); e++) {
if (assignments[s][e].get() > 0.99) {
print(shifts[s] + " is assigned to " + employees[e]);
}
}
}
Timefold
Timefold supports any type of optimization variables,
including your custom classes (Employee
, Vehicle
, …) and standard classes (Integer
, BigDecimal
, LocalDate
, …).
You typically define your planning model in your own domain classes.
For example, you can use @PlanningVariable
on your Shift.employee
field to model the assignment of an employee to a shift:
@PlanningEntity
class Shift { // User defined class
... // 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);
}
None of these classes (Shift
, Employee
and Timetable
) exist in Timefold itself: you define and shape them.
Your code naturally represents employees as instances of the Employee
class,
shifts as instances of the Shift
class, and so forth.
Timefold supports Object-Oriented Programming (OOP) input, including polymorphism.
Search space comparison
In the code above for employee scheduling, Gurobi uses boolean variables and Timefold uses employee variables.
This has an effect on the search space:
-
Gurobi 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 model naturally enforces the one shift per employee hard constraint by design, the Gurobi model implements it.
Constraints
Both Gurobi and Timefold have a rich API to define your constraints.
Gurobi
Gurobi 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++) {
Expression expr = ...
for (int s = 0; s < shifts.size(); s++) {
// If the shift is on the date
if (shifts[s].date == dates[d])) {
expr.addTerm(1.0, assignments[s][e]);
}
}
model.addConstraint(expr, LESS_EQUAL, 1.0);
}
}
Timefold
Timefold constraints are implemented as programming code.
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");
Timefold’s ConstraintStreams API uses a Functional Programming (FP) approach, so it can run delta calculations under the hood for maximum scalability and performance.
Constraints can reuse existing code.
For example, because date
is an instance of LocalDate
(a Date and Time API),
you can 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 intricacies.
Timefold allows you to directly use any 3th-party API (such as LocalDate
) in your constraints.
Besides the equal()
joiner, Timefold provides lessThan()
, greaterThan()
, lessThanOrEqual()
, greaterThanOrEqual()
,
overlapping()
, etc.
It automatically applies indexing (hashtable techniques) on joiners for performance.
It has higher level abstractions. 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.
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 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()
.
You can mix positive and negative constraints.
Timefold supports weighting constraints dynamically per dataset.
Scoring
Gurobi
Gurobi supports 2 score levels: hard constraints as constraints and soft constraints as an objective function that returns a floating point number.
Gurobi uses floating point arithmetic internally to solve.
It minimizes rounding errors by ordering its arithmetic operations intelligently.
By default, Gurobi tolerates going over hard constraints by margin of a 0.000001
to ignore compounded rounding errors.
Timefold
Timefold supports extra score levels beyond hard and soft constraints.
For example, HardMediumSoftScore
allows to prioritize medium operational constraints (such as assign all shifts)
over soft financial constraints (such as reduce cost).
Without resorting to multiplication with a big number.
Timefold supports to weight constraints dynamically per dataset without rebuilding the entire model. For example to make the soft constraint fairness in dataset A twice as important as in dataset B.
Timefold includes a Score Analysis API to break down a score per constraint.
Timefold does not suffer from numerical instability because it doesn’t rely on floating point arithmetic internally for constraint validation.
Cloud and Integration
Gurobi
Gurobi Cloud offers a paid service to run models on Azure and AWS clouds.
Timefold
Timefold Solver has integration modules for Spring, Quarkus and JSON (de)serialization. It supports native compilation for serverless use cases that need to start up in milliseconds.
Timefold Cloud offers a paid Field Service Routing API and Employee Scheduling API, that runs all major clouds (including Amazon AWS, Microsoft Azure and Google Cloud).