Skip to content
Blog

Mixed Models in Timefold Solver: Combining Planning Variable Types in One Solution

Timefold Solver supports both basic and list planning variables… and you can mix them! Learn all three patterns and when each one makes sense.

Most planning problems fit neatly into one of two camps: you're either using @PlanningVariable to assign a single value to an entity, or @PlanningListVariable to build an ordered sequence. But real-world problems aren't always that tidy.

Sometimes a production line needs both an assigned operator and a queue of jobs to process. Sometimes you've got two different kinds of planning entities that each need their own variable type. And sometimes, the items inside a list are complex enough to need their own variables too. That's where mixed models come in.

# A quick refresher on variable types

Before we get into mixed models, it helps to understand what we're mixing.

A basic planning variable holds a single value. Think assigning a Shift to an Employee, or a Room to a Meeting. It maps one entity to one value from a defined value range.

A list planning variable holds an ordered sequence of planning values. Think building a route for a Vehicle by assembling an ordered list of Visits. The solver decides both which values go in the list and in what order.

Mixed models let you use both in the same solution.

# Pattern 1: Both variables on the same entity

The simplest form of a mixed model is a single planning entity that carries both a basic variable and a list variable.

@PlanningEntity
public class Line {

    @PlanningVariable
    private Operator operator;
    
    @PlanningListVariable
    private List<Job> jobs;
    // ...
}

Here, Line needs to know two things: which Operator is running it, and which Jobs it's processing (and in what order). Neither variable type alone could express this — you'd lose either the assignment or the sequence.

This pattern is useful any time a single resource needs both an assignment and a workload. A manufacturing floor is the obvious example: each production line is staffed by a single operator, but processes a variable list of jobs throughout the day. The operator assignment affects skill-based constraints (can this operator run this type of job?), while the job list drives throughput and scheduling constraints (do we have enough time? are dependencies respected?).

The same structure applies to a surgical theatre that needs an assigned anaesthetist and an ordered list of procedures for the day, or a cloud VM assigned to a physical host and running an ordered queue of batch jobs. One entity, two concerns, two variable types.

# Pattern 2: Multiple entities, each with their own variable

The second pattern splits things across multiple planning entities. Each entity defines its own variable type independently.

@PlanningEntity
public class Line {

    @PlanningListVariable
    private List<Job> jobs;
    // ...
}

@PlanningEntity
public class LineOperation {

    @PlanningVariable
    private Operator operator;
    // ...
}

This works well when two decisions have genuinely different cadences or ownership: different lifecycles, different constraints, or different parts of the model that don't need to be tightly coupled on a single object.

Weekly staff rostering (assigning nurses to wards) combined with daily task scheduling (ordering tasks within a shift) is a natural fit. The roster changes weekly, the task list changes daily, and keeping them as separate entities means you can re-solve either without touching the other. Fleet assignment (which truck covers which region) paired with route planning (which stops that truck makes) follows the same logic: one decision is strategic, the other is operational, and conflating them in a single entity makes both harder to reason about.

# Pattern 3: A child entity with its own variable

The third pattern goes one level deeper: the items inside a list variable are themselves planning entities, each carrying their own basic variable.

@PlanningEntity
public class Vehicle {

    @PlanningListVariable
    private List<Visit> visits;
    // ...
}

@PlanningEntity
public class Visit {

    @PlanningVariable
    private DropoffPoint dropOffBefore;
    // ...
}

Here, Vehicle owns an ordered list of Visits, that's your standard vehicle routing setup. But each Visit is also a @PlanningEntity in its own right, carrying a dropOffBefore variable that links it to a DropoffPoint.

This is what makes the Capacitated Vehicle Routing Problem with intermediate stops expressible. The parent shapes the route; the child refines each stop. That separation is what makes Pattern 3 so powerful, and what makes it the hardest to model without native support.

The common thread across use cases is decisions that are scoped to an element of a list rather than to the list as a whole. Consider last-mile delivery with parcel lockers: each Visit carries a @PlanningVariable for which locker to use as a fallback if no one is home. The route is solved at the vehicle level; the fallback assignment is solved at the visit level. Two decisions, two scopes, one clean model.

Multi-modal transport works the same way. An itinerary is a list of legs on a Traveller entity, but each leg independently decides its TransportMode (train, bus, or taxi) based on time, cost, and availability constraints. Without Pattern 3, you'd be encoding that per-leg decision somewhere it doesn't belong.

# Wrapping up

Mixed models are one of those features that quietly unlocks a whole class of problems that used to require awkward workarounds. If you've been shoehorning your domain into a single variable type, it's worth asking whether your model is actually mixed and if so, just saying so explicitly.

Check out the Timefold Solver docs for the full reference on planning variables and mixed model configuration.

Planned, planning education for real world stuff

Sign up for our monthly newsletter. 

Continue reading