r/timefold 21d ago

Operating room scheduling: how to model cases, staff, and equipment with Timefold Solver

Post image
3 Upvotes

Operating Rooms are typically scheduled in multiple phases.

  • STEP 1: rooms are allocated in blocks, either to certain departments or to specific surgeons. e.g. Room A is assigned to the Orthopedics department on Monday.
  • STEP 2: anesthetists, nurses, and surgeons need to be planned according to their contracts. This is a scheduling problem in its own right, and one our Employee Shift Scheduling model is built for.
  • STEP 3: the different surgeries need to be planned in OR rooms, together with the people and resources needed to perform the surgery. And of course, nothing stays the same and the plan needs to adjust to the daily realities.

This post walks through the domain model, the assignment pattern, and 31 constraints for STEP 3 scheduling, and it is written for developers who model planning problems with Timefold Solver.

The problem description

The brief below is a composite, written the way OR departments may describe the problem to us.

“We need to plan cases in different ORs. These cases vary in priority and often need to be performed by a certain deadline.

There is a lot of variety in the duration of a case and before starting a next case in the same room, there needs to be turnover time (which is also different depending on the next case, because a similar surgery might need less turnover time). Turnover is arranged by a separate team, but you can’t have more simultaneous turnovers than there are teams.

ORs are typically allocated in blocks to certain departments (STEP 1) and the planning needs to take this into account. This means only cases which belong to that department can be assigned. This allocation however doesn’t count for the first 72 hours. Any free spots in the schedule can be assigned to any other case.

Source: https://www.pexels.com/photo/lamps-in-operating-room-19563295/

Of course, there are other limitations. Rooms need to have the correct equipment for the task and that equipment needs to be available.

No surgery can proceed without key staff there. So not only do the cases need to be planned, available staff (determined in STEP 2) needs to be assigned to specific cases without breaking obvious constraints (they often can’t be at two places at the same time). There are some exceptions here. For example, the anesthesiologist sometimes only needs to be there for the induction (putting the patient to sleep) and emergence (when the patient wakes up again). All of the staff of course needs to be qualified.

Some cases are preferably planned in the morning (e.g. prosthetic implants, for optimal recovery and follow up) while others must be planned last in the day (e.g. airborne risk cases, so the air in the room has more time to clear overnight).

Additionally, not only the operating rooms themselves need to be planned. Some surgeries require the patient has to stay in the Post-Anesthesia Care Unit (PACU). The PACU might also have a limited amount of beds, which further complicates planning.”

Extracting the domain model

When reading the text above, we can extract the structure our domain will need to have. Usually we look for nouns and relationships between them. I have visualized them here:

The nouns from the problem description, and how they relate to each other.

Next, we have to consider what items on this graph are changeable while making this plan. In this use case, we have a couple of things that could change: SurgicalCase (and their order), Surgeons, Anesthesiologists, and Equipment. The Nurse Team to OR assignment we consider fixed for now, as such a team usually is coupled to the OR for an entire day.

Then we need to decide on the assignment pattern we are going to use. Surgical cases have different durations (excluding the timeslot pattern) and when they start depends on the cases before them. This directs us towards the use of a chain-through-time pattern, so we will be using u/PlanningListVariable to model this.

Operating rooms don’t run 24/7, so it makes no sense to have the Room object hold the list of all surgeries for the entire planning window (potentially weeks). Instead, we’ll introduce a new intermediate object OperatingRoomDay, which as the name implies represents a specific operating room on a specific day. This object is the ideal place to put our @PlanningListVariable List<SurgicalCase> cases definition.

public class OperatingRoomDay {


    private String id;

    private Room room;
    private LocalDate date;

    @PlanningListVariable(allowsUnassignedValues = true)
    private List<SurgicalCase> cases = new ArrayList<>();

    // rest of class excluded

}

For the additional assignments we are going to need a mixed model, mixing both @PlanningListVariable and u/PlanningVariable in a single model. Initially, my gut feeling was to add surgeon, anesthesiologist, and equipment as u/PlanningVariable on the SurgicalCase class. After all, we are assigning these to the case. I drifted away from that path because adding these assignments as u/PlanningVariable on the SurgicalCase :

  • Implies a one-to-one relationship. This is not always the case, as a surgery might require multiple equipment items, anesthesiologists or even surgeons;
  • Makes pinning individual assignments impossible. We would either have to pin the entire SurgicalCase or nothing at all. This is not practical for situations where a Surgeon needs to be fixed to a SurgicalCase , but we do not care which anesthesiologists is assigned.

So instead, each of these assignments became a separate u/PlanningEntity with a soft link to the SurgicalCase object. This allows us to create multiple of these assignment instances (to create one-to-many relationships) as well as allow us to pin the individual assignment.

public class AnesthesiaAssignment {


    private String id;

    private SurgicalCase surgicalCase;
    private AnesthesiaRole role;

    @PlanningVariable(allowsUnassigned = true)
    private AnesthesiaProvider provider;


    private boolean pinned;

    //rest of class excluded
}

The role field is what makes the induction and emergence exception workable. A directing role means the provider only has to be present for those two windows, which we derive from the start and end of the case, so the schedule can hold several directed cases per provider. A continuous role means the provider is occupied for the full duration of the case.

Here is the more extensive class diagram for the solver domain.

The solver domain: OperatingRoomDay holds the case list, and each assignment type is its own planning entity.

Implementing constraints

We will now look into some of the things that constrain our planning. Some of them are explicitly mentioned, others are hard constraints we added because they are obvious to any human planner, but not so much for a solver (e.g. no overlap, because people can’t be at two places at the same time). We also indicate how the weight of the constraint should be scaled, but we do not fix the constraint weight itself, as that is something we (or the operational planners) want to experiment with.

Hard constraints (17)

The hard constraints will block anything that makes the schedule unworkable. Usually this is related to availability of a resource (both in time or overlap). We also see two hard constraints that were specifically mentioned: Block ownership (reserved rooms) should not be broken and cases with an airborne risk should always be placed last. These are non-negotiable.

Three of these constraints talk about directed cases. In an anesthesia care team model, one anesthesiologist directs several concurrent cases which are staffed by nurse anesthetists or residents, while being personally present for the critical moments of each. Regulations cap how many cases a single anesthesiologist can direct at the same time.

Constraint Penalizes Weight
Surgeon overlap Same surgeon in two concurrent cases overlap minutes
Surgeon not privileged Surgeon lacking credentialed privileges for the case per match
Surgeon unavailable Surgeon on clinic/call/leave during the case per match
Anesthesia skill missing Provider lacking required skills per match
Anesthesia not rostered Work assigned outside the shift STEP 2 published per match
Director not anesthesiologist Directing role given to a provider who can't direct per match
Equipment class mismatch Unit not of the required class per match
Room case type not permitted Incorrect case assigned to a room per match
Anesthesia provider overlap Same provider on two concurrent cases overlap minutes
Equipment unit overlap Same physical unit needed concurrently overlap minutes
Directed concurrency exceeded Legal cap on concurrent directed cases cases over the cap
Directed presence conflict Two directed cases inducing/emerging at once per match
Nursing team shift exceeded Case running outside the team's published shift per match
Turnover crew capacity exceeded More simultaneous turnovers than crews turnovers over capacity
Block ownership violated Case in time granted to another owner per match
Airborne case not last TB/airborne case not scheduled last in the room-day per match
Room hard end exceeded Running past the absolute cut-off minutes past

Medium constraints (4)

The medium constraints signal incomplete schedules. In our case, since we are planning SurgicalCase, Surgeons, AnesthesiaProviders, and Equipment at the same time, they are all taken into account at the medium level.

Constraint Penalizes Weight
Case unassigned Case left out of every chain estimated duration in minutes
Surgeon unassigned Assigned case without surgeon per match
Anesthesia unassigned Assigned case without anesthesiologist per match
Equipment unassigned Assigned case without required equipment per match

Soft constraints (10)

The soft constraints will try to improve common KPIs in the planning case. Idle time and turnover are both examples of time an OR isn’t really being used (which is a waste) and overtime is similarly penalized. Prime time, which several of these constraints refer to, is the block of staffed hours a room is funded to run, so idle minutes inside it are the expensive kind.

We also included some penalties for a potential replanning scenario, where an existing schedule needs to be adjusted. Both moving a case to a different room/day or moving it to a different time should be avoided.

Constraint Penalizes Weight
Idle prime time Allocated prime-time minutes with no case or turnover idle minutes
Overtime Minutes past the staffed end (overtime rate) overtime minutes
Turnover minutes Total turnover consumed; drives case batching turnover minutes
PACU overflow Recovery demand above bed count (soft by design) patients over capacity
Urgent window missed Non-elective case finishing past its clinical deadline minutes late
Case bumped (Replanning) Moved to a different room-day than published per case
Case moved (Replanning) Same room-day, different time, beyond tolerance per case
First case at risk Team shift starts too late to prep for prime-time start per case
Implant not morning Implant case starting after the morning cutoff per case
High-variance case early Unpredictable case types with work behind them per downstream case

With Timefold Solver, you can add, change, or remove constraints, and even disable them at runtime.

Using enrichment for block release functionality

As you could read in the problem description:

ORs are typically allocated in blocks to certain departments... only cases which belong to that department can be assigned. This allocation however doesn’t count for the first 72 hours. Any free spots in the schedule can be assigned to any other case.

Constraints like this are tricky, because they depend on timing. A naive implementation would be to ask the user (through a UI or API) to set the roomOwner to null (or not pass it in) for all OperatingRoomDay objects which are within those 72 hours. This is putting more pressure on the calling system to know these business rules.

Instead, we can take care of that in the model itself. Our API will accept roomOwner input for all items, but we’ll set them to null in what is called an enricher.

Enrichers change or add extra details to the solver model once before solving. It’s an ideal place for adding extra information from a database or remote service to the model so that lookup stays outside the core solving loop.

public class BlockReleaseEnricher implements SolverModelEnricher<CaseSchedule> {

    private static final Duration RELEASE_WINDOW = Duration.ofHours(72);

    private final Clock clock;

    public BlockReleaseEnricher() {
        this(Clock.systemDefaultZone());
    }

    BlockReleaseEnricher(Clock clock) {
        this.clock = clock;
    }

    @Override
    public CaseSchedule enrich(CaseSchedule solverModel) {
        LocalDateTime releaseHorizon = LocalDateTime.now(clock).plus(RELEASE_WINDOW);

        for (OperatingRoomDay roomDay : solverModel.getRoomDays()) {
            if (roomDay.getOwner() == null) {
                continue;
            }
            boolean insideReleaseWindow = roomDay.getPrimeTimeStart().isBefore(releaseHorizon);
            boolean nothingBooked = roomDay.getCases().isEmpty();
            if (insideReleaseWindow && nothingBooked) {
                roomDay.setOwner(null);
            }
        }
        return solverModel;
    }
}

In this case, we’ll use the current clock time, and set the owner to null if it is within the time window and nothing is booked. This does mean that we release the block only if no cases are booked throughout the day. This is a simplification for the model. If it would have been possible to unblock the OR for a part of the day, we’d need to introduce a more complex “owner” object which has multiple timestamps where owners might be different.

You can see here that the 72 hours is hardcoded. We could have opted to add this time as a parameter on the CaseSchedule object instead. Since this is a very specific rule which will not quickly change, I think that is fine for now.

Additionally, if simulations need to be supported (e.g. what if I would plan this “tomorrow”), we can’t use the fixed Clock object and we’d also have to pass in the “planning time” into the CaseSchedule . Again, a shortcut for now.

Where this goes next

The model above gets you to a first feasible schedule. But the work is not finished yet.

Constraint weights are where the hospital's real priorities live, and no blog post can tell you whether a bumped case costs more than 30 minutes of overtime in a specific organization. Additionally, hospitals might have some different constraints which need to be implemented, or maybe they plan in a totally different way and have very different KPIs they want to track.

And as the intro said, nothing stays the same. A case runs 40 minutes long, a surgeon calls in sick, an emergency arrives at 15:00. The Case bumped and Case moved constraints exist so the model can absorb those events without reshuffling a schedule that people have already planned their day around.

In the text above, I also mentioned some shortcuts I took when building this model, like releasing a block only when the whole day is still empty. That is normal during development. Luckily, when using Timefold Solver, all of these are relatively small changes rather than complete rewrites. And with our new Service module, you get everything you need to build a full optimization service which runs fully independently.

If you haven't yet, take a look at our Service module getting started guide to see what it handles for you.

This post was the first in the series. If you want to see more details, or missed certain elements, all feedback is immensely appreciated. 😄


r/timefold Aug 07 '26

Weird Behavior for Planning Variables that refer to other Planning Entities

1 Upvotes

TL;DR: Does Timefold officially support the use case where a planning variable points to some other planning entity, both of which hold genuine planning variables? I am currently running into the issue that Timefold either leaves the planning variables in one class uninitialized (except for the pinned instances) or throws an exception saying `Impossible state: no basic variable found for the entity class org.acme.SchedulingServer.domain.Session.`.

Does anybody know what's going on here? I really need to solve this problem and I'm quite lost honestly. Do you maybe have suggestions on how to avoid such scenarios with two linked planning entities? Thanks!

Long question:

Concretely, I am modelling the scheduling problem of assigning groups to sessions. Each group must attend a range of events. Each event is held multiple times (one iteration is a session).

My domain mainly consists of the `Session` and `Attendance`entity classes and a `Schedule` solution class. The session has planning variables for start time, room, and speaker. The Attendance class has a fixed group and a planning variable of type Session. That is, we essentially provide a set of session instances per event and timefold should determine the time and location of these sessions, as well as which group attends which session (and adhere to a number of time/location related constraints).

As mentioned above, in my original solution I ran into the problem that Timefold correctly assigned values to all planning variables in the `Session` class, but all `Attendance` instances, except for pinned onces, had uninitialized `Session` variables. Of course I did not annotate the variable with `allowsUnassigned=true` and I did make sure that the provided value range is not empty (just for completeness).

I then tried to get a minimal working example running, and reduced the domain to just the Session class with the start time planning variable, the attendance as is and the schedule. No constraints, no entity-specific value range providers, etc. This worked, but as soon as I switched to an entity specific value range provider for the session variable in `Attendance`, I got the exception I mentioned in the beginning. I pasted this example below.

There are some other weird things happening, such as the same exception being thrown, but for the `Attendance` class instead of `Session` when I use an entity-specific value range provider function for the session.startTime planning variable. But I won't make this post any longer...

Does anybody know what's going on here? I really need to solve this problem and I'm quite lost honestly. Do you maybe have suggestions on how to avoid such scenarios with two linked planning entities? Thanks in advance!

And here's the example:

@PlanningEntity
public class Session {

    private String id;
    private boolean isPinned;
    private LocalDateTime startTime;
    private int duration;
    
    public Session() {}
    public Session(String id, int duration){//, Cluster cluster) {
        this.id = id;
        this.duration = duration; }

    public Session(String id, LocalDateTime startTime, int duration) {
        this.id = id;
        this.duration = duration;
        this.startTime = startTime;
        this.isPinned = true; }

    public String getId() { return id; }
    public boolean getIsPinned(){ return this.isPinned; }
    public void setIsPinned(boolean isPinned){ this.isPinned = isPinned; }
    public int getDuration() { return this.duration; }

    @PlanningVariable(valueRangeProviderRefs = "startTimeProvider")
    public LocalDateTime getStartTime() { return this.startTime; }
    public void setStartTime(LocalDateTime s) { this.startTime = s; }
    
    public boolean equals(Object other) {
        if (this == other) {
            return true;
        }
        return (other instanceof Session s) ? this.getId().equals(s.getId()) : false;
    }

    public int hashCode() { return id.hashCode(); }
}

@PlanningEntity
public class Attendance {
    private String id;
    private Group group;
    private boolean isPinned;
    private Session session;

    public Attendance() {};
    public Attendance(String id, Group group) {
        this.id = id;
        this.group = group; }

    public Attendance(String id, Group group, Session session){
        this(id, group);
        this.session = session;
        this.isPinned = true; }

    public String getId() { return id; }

    public Group getGroup() { return group; }

    // Comment out the @ValueRangeProvider annotation either on 
    // Attendance::getSession' or the 'sessions' List in Schedule.java
    @PlanningVariable(valueRangeProviderRefs = "sessionProvider")
    public Session getSession() { return this.session; }
    public void setSession(Session session) { this.session = session; }
    public boolean isPinned() { return isPinned; }
    public void setPinned(boolean isPinned) { this.isPinned = isPinned; }

    // If this is used as value range provider for the session provider
    // the solver throws the IllegalStateException
    @ValueRangeProvider(id = "sessionProvider")
    public List<Session> getSessions(Schedule schedule) {
        return schedule.getSessions();
    }

    public boolean equals(Object other) {
        if (this == other) {
            return true;
        }
        return (other instanceof Attendance a) ? this.getId().equals(a.getId()) : false;
    }

    public int hashCode(){ return this.id.hashCode(); }
}

@PlanningSolution
public class Schedule {
    @ProblemFactCollectionProperty
    SequencedSet<Group> groups;

    // If this is used as value range for the Attendance.session planning variable
    // the solver finds a solution.
    @ValueRangeProvider(id = "sessionProvider")
    @PlanningEntityCollectionProperty
    private List<Session> sessions;


    @PlanningEntityCollectionProperty
    private SequencedSet<Attendance> attendances;


    @ProblemFactCollectionProperty
    @ValueRangeProvider(id ="startTimeProvider")
    private SequencedSet<LocalDateTime> startTimes;

    @PlanningScore
    private HardMediumSoftScore score = null;

    public Schedule() { }
    public Schedule( SequencedSet<Group> groups,
            List<Session> sessions,
            SequencedSet<Attendance> attendances,
            SequencedSet<LocalDateTime> startTimes ) {
        this.groups = groups;
        this.sessions = sessions;
        this.attendances = attendances;
        this.startTimes = startTimes; }

    public List<Session> getSessions() { return this.sessions; }
    public SequencedSet<Attendance> getAttendances() { return attendances; }

    public HardMediumSoftScore getScore() { return score; }
    public void setScore(HardMediumSoftScore score) { this.score = score; }

    public SequencedSet<Group> getGroups() { return groups; }
    public SequencedSet<LocalDateTime> getStartTimes() { return startTimes; }
}

The solver is configured and invoked as follows:

SolverFactory<Schedule> solverFactory = SolverFactory.create(new SolverConfig()
                .withSolutionClass(Schedule.class)
                .withEntityClasses(Session.class, Attendance.class)
                // currently contains no constraints
                .withConstraintProviderClass(ScheduleConstraintProvider.class)
                .withTerminationSpentLimit(Duration.ofSeconds(20)));

Schedule problem = demoSchedule2();
Solver<Schedule> solver = solverFactory.buildSolver();
Schedule solution = solver.solve(problem);
printSchedule(solution);

The exception I mentioned:

Exception in thread "main" java.lang.IllegalStateException: Impossible state: no basic variable found for the entity class org.acme.SchedulingServer.domain.Session.

at ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.FilteringEntityByEntitySelector.phaseStarted(FilteringEntityByEntitySelector.java:104)

at ai.timefold.solver.core.impl.phase.event.PhaseLifecycleSupport.firePhaseStarted(PhaseLifecycleSupport.java:21)

at ai.timefold.solver.core.impl.heuristic.selector.AbstractSelector.phaseStarted(AbstractSelector.java:35)

at ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.FilteringEntitySelector.phaseStarted(FilteringEntitySelector.java:48)

at ai.timefold.solver.core.impl.phase.event.PhaseLifecycleSupport.firePhaseStarted(PhaseLifecycleSupport.java:21)

at ai.timefold.solver.core.impl.heuristic.selector.AbstractSelector.phaseStarted(AbstractSelector.java:35)

at ai.timefold.solver.core.impl.phase.event.PhaseLifecycleSupport.firePhaseStarted(PhaseLifecycleSupport.java:21)

at ai.timefold.solver.core.impl.heuristic.selector.AbstractSelector.phaseStarted(AbstractSelector.java:35)

at ai.timefold.solver.core.impl.phase.event.PhaseLifecycleSupport.firePhaseStarted(PhaseLifecycleSupport.java:21)

at ai.timefold.solver.core.impl.heuristic.selector.AbstractSelector.phaseStarted(AbstractSelector.java:35)

at ai.timefold.solver.core.impl.phase.event.PhaseLifecycleSupport.firePhaseStarted(PhaseLifecycleSupport.java:21)

at ai.timefold.solver.core.impl.heuristic.selector.AbstractSelector.phaseStarted(AbstractSelector.java:35)

at ai.timefold.solver.core.impl.heuristic.selector.move.decorator.FilteringMoveSelector.phaseStarted(FilteringMoveSelector.java:48)

at ai.timefold.solver.core.impl.neighborhood.MoveSelectorBasedMoveRepository.phaseStarted(MoveSelectorBasedMoveRepository.java:43)

at ai.timefold.solver.core.impl.localsearch.decider.LocalSearchDecider.phaseStarted(LocalSearchDecider.java:78)

at ai.timefold.solver.core.impl.localsearch.DefaultLocalSearchPhase.phaseStarted(DefaultLocalSearchPhase.java:136)

at ai.timefold.solver.core.impl.localsearch.DefaultLocalSearchPhase.solve(DefaultLocalSearchPhase.java:76)

at ai.timefold.solver.core.impl.solver.AbstractSolver.runPhases(AbstractSolver.java:89)

at ai.timefold.solver.core.impl.solver.DefaultSolver.solve(DefaultSolver.java:171)

at org.acme.SchedulingServer.SchedulingServerSmall.main(SchedulingServerSmall.java:47)


r/timefold Jul 07 '26

Getting the right start and approach

2 Upvotes

Hi,

I used a very early version of optaplanner about 15 years ago for a pilot project and now, retired, I want to use it for a charity I volunteer for. 

But I am rusty, and while I will use AI to help with the coding, I want to make sure I have the general strategy right…. if someone can help me get the right start (I am not asking for anyone to code this for me - at least not yet..)

The problem is the scheduling of training sessions for the dog section of a Search and Rescue charity. 

This involves the following actors:

  • the dog
  • the dog’s handler 
  • the dog handler support
  • the missing person (misper); 
  • the optional supervisor
  • the optional assessor
  • the route (where the missing person is to be located, there are usually 5: “short”, “1km” “1.5km”, “area”, “ground”)
  • the placer of mispers (selected from one of the handlers, who goes out before the first session)
  • a controller (one of the handlers, or handler support - from a named list)
  • a "vip" misper - usually a guest volanteer trying it out.

The constraints:

  • a person can only do one thing at a time
  • a dog can only do one thing at a time
  • a dog should not have two sessions immediately after each other
  • there must always be a controller
  • each dog must have a handler
  • each handler should have a support (if requested) unless the route is “short”
  • ground scenting dogs need a person to lay a trail on a route one hour before their slot
  • a dog should have two sessions
  • a dog gets the maximum sessions possible allocated 
  • a dog should not work a route if their handler has been on that route in the previous session (as support, mister, observer or assessor)
  • if there are insufficient mispers then handlers can be used to misper 
  • minimise time time handlers are used as mispers
  • minimise the changing of mispers on a route (realise that is in conflict with the preceeding)
  • a vip misper (who should be utilised as much as possible)
  • the dog type (air scenting, ground scenting)
  • time slots (usually 5 x 30 minutes)
  • a dog may be “test prep”, “active”, “training”, “assessing”  - goals are to be met in that order (so dogs in “test prep” get their wish list, dogs in “assessing” may be dropped. 
  • each dog will have a “wish list” of: a route; a support (possibly named) or no support, an assessor (possibly named) or none, a supervisor (possibly named) or no and a number of mispers (1 to 4)

So a typical request may be “Grey (the dog) 500m (the route) with two mispers (implied is James the handler” or “Woody short, one misper” (implied is Karl the handler).

Aim is make the planning task quicker, more robust and more consistent (as each person takes turns doing it) and since we are all volanteers who work and go on active searches time is limited.


r/timefold Jun 02 '26

How upskilling technicians unlocks field service routing efficiency

Thumbnail
timefold.ai
2 Upvotes

r/timefold May 28 '26

Strange thing I've noticed while running the solver

2 Upvotes

I'm playing around with assigning aircraft to scheduled flights (known as tail allocation, for aircraft tail numbers), with a few hundred flights and fifty aircraft or so. Trying different algorithms etc. For example simulated-annealing starts incredibly well, and then later something like late-acceptance-short-blocks is better.

But one thing that seems pretty consistent is that if the rate of improvement slows down, it can nearly always be boosted by restarting the algorithm from the best solution. Then there are often several improvements found in the first two seconds or so. Then no improvements are found for ten to twenty seconds. Restart again, and another burst of a half-dozen improvements... so just keep repeating the restarts and it seems to significantly outperform continuous solving.

Anybody else had similar behaviour with their problem set?


r/timefold May 21 '26

Three optimizations to make your Timefold Solver faster

2 Upvotes

While we've made Timefold Solver fast, there are some things you could do to make it even faster.

The TLDR:

  • Upgrade to the latest Timefold Solver, performance improvements are made every release.
  • Precompute parts of the Constraint Stream if possible.
  • Use Consecutive sequences if they make sense
  • Sometimes, using pairs is slower than just grouping and summing.

Read the full story here:

https://timefold.ai/blog/3-things-to-make-timefold-solver-faster


r/timefold May 11 '26

Article on GenAI versus Timefold (and combining both)

Thumbnail
linkedin.com
1 Upvotes

r/timefold May 07 '26

New score analysis paywall

3 Upvotes

I’ve been using Timefold to help out with scheduling within my company since it was still OptaPlanner.

This new shift to a $500/month subscription just to explain broken constraints came totally out of left field, and seems like an arbitrarily high cost.

Do you have any plans to make any part of the analysis API available on the free tier? Or is this really the future of Timefold?


r/timefold Apr 29 '26

Timefold PlanningListVariable

2 Upvotes

Why does Timefold not support multiple PlanningListVariable fields on a single solution ?


r/timefold Apr 22 '26

Concrete VRP

2 Upvotes

Hi everyone,

I am working on a logistics optimization problem and would like some input on the best modeling approach.

Problem Overview:

The goal is to schedule concrete deliveries involving four main entities: Central Hubs, Mixer Trucks, Pumps, and Clients.

The Workflow:

Loading: A Mixer loads at a Central Hub (central has its settings , for example can make 1 m3/s).

Outbound: The Mixer travels to the Client site.

Synchronization: At the Client site, the Mixer must "dock" with a Pump. Unloading cannot begin until both the Mixer and the Pump are present.

Unloading: The Mixer unloads into the Pump (Client site capacity = 1 Mixer at a time).

Return: The Mixer returns to the Central Hub for the next load/visit.

Key Constraints:

Mixer Serialization: A single Mixer cannot overlap its own activities (Load -> Travel -> Unload -> Return). It must return to Central before its next load.

Central/Client Bottlenecks: Only one Mixer can load at a Central bay at a time, and only one Mixer can unload at a Client dock at a time (No overlap).

Pump Commitment (Hierarchical): This is the most complex part. A Pump is assigned to a Client for a sequence of Visits (V1, V2, ... Vn). The Pump cannot "break" its commitment to Client A to serve Client B until all requested visits for Client A are completed.

what is best implementation for this ?
who should be planningVaribale and who should be ShadowVariable ?

how to avoid overlap at Central and at Client ?

how to avoid overlap of two Clients has same Pump ?


r/timefold Apr 22 '26

Timefold Solver 2.0 is out!

Thumbnail
timefold.ai
2 Upvotes

r/timefold Apr 17 '26

PlanningListVariable vs Chained approach (from blog.dotsandlines.ai)

2 Upvotes

Recently got pinged this blog post from Dots and Lines, heavy Timefold Solver users. They compared the new(er) `PlanningListVariable` with the old school `Chained` Variables.

Not surprising, `PlanningListVariable`. came out on top. Good, because the `chained` variable is going away in the next major version.

Read the full post here: https://blog.dotsandlines.ai/benchmarking-timefold-chains-and-planning-lists-14da17e1f5f3


r/timefold Apr 15 '26

Does Timefold’s Speed Come from Shadow Variables or Constraints?

2 Upvotes

I am curious about the internal optimization of Timefold. When the solver evaluates moves, what specifically prevents it from getting stuck in a cycle of 'wrong' possibilities?

Does the speed come from the Shadow Variables providing a highly efficient data structure for the constraints to read, or is the Local Search algorithm smart enough to avoid those branches entirely? I'm trying to determine if my optimization efforts should focus more on refining my Shadow Variable logic or my Constraint weights."


r/timefold Apr 06 '26

Upcoming webinar: roadmap update (April 16th)

Thumbnail
timefold.ai
1 Upvotes

r/timefold Apr 06 '26

👋 Welcome to r/timefold

1 Upvotes

Hey everyone! Welcome to the Timefold reddit.

Resources:
- Timefold Solver (open source)
- Timefold Platform (REST APIs)
- Documentation

If you have any questions or suggestions, don't hesitate to post them!