title: "Job Scheduling" description: "Use CP-SAT to solve job scheduling problems, including machine allocation, sequence dependencies, and due date constraints." order: 2

Job Scheduling

Problem Description

Imagine you run a factory with 3 machines and 8 jobs to process. Each job requires a specific machine, a specific processing time, and some jobs have precedence relationships. How do you schedule everything to complete all jobs in the shortest possible time?

from ortools.sat.python import cp_model
import matplotlib.pyplot as plt

# === Define Jobs ===
# (JobID, MachineID, Duration)
jobs = [
    (0, 0, 3),  # Job 0, needs machine 0 for 3 hours
    (1, 1, 2),  # Job 1, needs machine 1 for 2 hours
    (2, 2, 5),  # Job 2, needs machine 2 for 5 hours
    (3, 0, 4),  # Job 3, needs machine 0 for 4 hours
    (4, 1, 3),  # Job 4, needs machine 1 for 3 hours
    (5, 2, 2),  # Job 5, needs machine 2 for 2 hours
    (6, 0, 2),  # Job 6, needs machine 0 for 2 hours
    (7, 1, 4),  # Job 7, needs machine 1 for 4 hours
]

# Precedence constraints: (JobA, JobB) means A must finish before B starts
precedences = [(0, 3), (1, 4), (2, 5)]

num_machines = 3
# Jobs on each machine
machine_jobs = {m: [] for m in range(num_machines)}
for jid, mid, dur in jobs:
    machine_jobs[mid].append((jid, dur))

# === Build Model ===
model = cp_model.CpModel()

horizon = sum(dur for _, _, dur in jobs)
start_times = {}
end_times = {}
intervals = {}

for jid, mid, dur in jobs:
    start = model.NewIntVar(0, horizon, f"start_{jid}")
    end = model.NewIntVar(0, horizon, f"end_{jid}")
    interval = model.NewIntervalVar(start, dur, end, f"interval_{jid}")
    start_times[jid] = start
    end_times[jid] = end
    intervals[jid] = interval

# Constraint: jobs on same machine cannot overlap
for m in range(num_machines):
    model.AddNoOverlap([intervals[jid] for jid, _ in machine_jobs[m]])

# Constraint: precedence order
for before, after in precedences:
    model.Add(start_times[after] >= end_times[before])

# Objective: minimize makespan (max completion time)
makespan = model.NewIntVar(0, horizon, "makespan")
model.AddMaxEquality(makespan, [end_times[jid] for jid, _, _ in jobs])
model.Minimize(makespan)

# === Solve ===
solver = cp_model.CpSolver()
solver.parameters.max_time_in_seconds = 10
status = solver.Solve(model)

if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
    print(f"
=== Scheduling Results ===")
    print(f"Min Makespan: {solver.ObjectiveValue()}")

    result = []
    for jid, mid, dur in jobs:
        s = solver.Value(start_times[jid])
        e = solver.Value(end_times[jid])
        print(f"Job {jid} (Machine {mid}, {dur}h): {s} -> {e}")
        result.append((jid, mid, dur, s, e))

    # Draw Gantt chart
    fig, ax = plt.subplots(figsize=(12, 6))
    colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7',
              '#DDA0DD', '#98D8C8', '#F7DC6F']

    for i, (jid, mid, dur, s, e) in enumerate(result):
        ax.barh(mid, e - s, left=s, height=0.5, color=colors[i],
                edgecolor='black', label=f"Job {jid}")
        ax.text((s + e) / 2, mid, f"J{jid}", ha='center', va='center',
                fontweight='bold', fontsize=10)

    ax.set_yticks(range(num_machines))
    ax.set_yticklabels([f"Machine {m}" for m in range(num_machines)])
    ax.set_xlabel("Time")
    ax.set_title("Job Scheduling Gantt Chart")
    ax.grid(True, alpha=0.3)
    plt.tight_layout()
    plt.show()
else:
    print("No solution found")

Scheduling: Assigning the Right Work to the Right People

Job scheduling is a problem you solve every day without realizing it:

  • Factory Scheduling: 3 machines, 5 orders, each order requires specific operations. How to arrange for fastest completion?
  • Staff Scheduling: 10 employees, 7-day schedule, each has shift preferences. How to satisfy everyone?
  • Cloud Task Orchestration: 100 batch tasks, 20 servers. How to minimize total completion time?

These are all scheduling problems - and CP-SAT is Google OR-Tools core solver for these problems.

Key Elements of Scheduling Problems

| Element | Description | Example | |:-------:|:-----------:|:-------:| | Job | A task to be completed | Manufacturing a product | | Machine | Resource executing the job | Lathe, welder | | Operation | A job may need multiple operations | Cut -> Weld -> Polish | | Duration | Time needed for each operation | 30 minutes | | Due Date | Deadline for job completion | Before end of day | | Precedence | Operation B must follow Operation A | Cut before welding |

Next Chapter: From Single to Multiple Resources

Single-machine scheduling has only one resource type. The next chapter Multi-Knapsack Problem introduces multiple resource constraints - closer to real-world complex problems.

Job Scheduling with CP-SAT

from ortools.sat.python import cp_model

def job_scheduling(jobs, num_machines):
    """
    Schedule jobs on machines to minimize makespan (completion time).
    
    Args:
        jobs: list of (duration, machine) tuples
        num_machines: number of parallel machines
    """
    model = cp_model.CpModel()
    horizon = sum(job[0] for job in jobs)  # upper bound

    # Variables
    start_times = [model.NewIntVar(0, horizon, f"start_{i}") for i in range(len(jobs))]
    end_times = [model.NewIntVar(0, horizon, f"end_{i}") for i in range(len(jobs))]
    intervals = []

    for i, (duration, machine) in enumerate(jobs):
        interval = model.NewIntervalVar(
            start_times[i], duration, end_times[i], f"interval_{i}"
        )
        intervals.append((interval, machine))

    # Each machine can only process one job at a time
    for m in range(num_machines):
        machine_intervals = [inv for inv, mac in intervals if mac == m]
        if len(machine_intervals) > 1:
            model.AddNoOverlap(machine_intervals)

    # Objective: minimize makespan (latest end time)
    makespan = model.NewIntVar(0, horizon, "makespan")
    model.AddMaxEquality(makespan, end_times)
    model.Minimize(makespan)

    solver = cp_model.CpSolver()
    status = solver.Solve(model)

    if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
        print(f"Makespan: {solver.Value(makespan)}")
        for i in range(len(jobs)):
            print(f"  Job {i}: start={solver.Value(start_times[i])}, "
                  f"end={solver.Value(end_times[i])}, "
                  f"duration={jobs[i][0]}, machine={jobs[i][1]}")
        return solver.Value(makespan)
    else:
        print("No solution found")
        return None

# Example: 6 jobs, 2 machines
jobs = [
    (3, 0),  # Job 0: duration 3, machine 0
    (4, 1),  # Job 1: duration 4, machine 1
    (2, 0),  # Job 2: duration 2, machine 0
    (5, 1),  # Job 3: duration 5, machine 1
    (3, 0),  # Job 4: duration 3, machine 0
    (2, 1),  # Job 5: duration 2, machine 1
]

job_scheduling(jobs, 2)

Real-World Applications

| Domain | Scheduling Problem | |--------|-------------------| | Manufacturing | Assign production orders to factory lines | | Cloud Computing | Schedule batch jobs on server clusters | | Healthcare | Schedule surgeries, appointments, staff shifts | | Transportation | Schedule deliveries, flights, train departures | | Education | Assign courses to rooms, schedule exams | | Event Planning | Schedule talks, sessions, and breaks at conferences | | Construction | Schedule tasks, workers, and equipment to phases |

Comparison with Other Approaches

| Method | Optimal? | Speed | Complexity | |--------|----------|-------|------------| | CP-SAT | ✅ Yes | Slower for large instances | Easy to model constraints | | Greedy (Earliest Deadline) | ❌ No | Very fast | Simple to implement | | Genetic Algorithm | ❌ No | Medium | Complex setup, good for large | | MILP (PuLP/CBC) | ✅ Yes | Similar to CP-SAT | Harder to model time constraints |

Summary

Job scheduling assigns tasks to machines to minimize total completion time (makespan) while respecting machine capacity constraints. Use CP-SAT for optimal solutions, greedy heuristics for fast approximations, and genetic algorithms for very large instances.

Key takeaways:

  • Scheduling = assign jobs to machines over time
  • Makespan = time when the last job finishes
  • CP-SAT uses IntervalVar for tasks and AddNoOverlap for resource constraints
  • Each machine processes one job at a time (no overlap)
  • Objective: minimize makespan (or maximize throughput, meet deadlines)
  • Real applications: manufacturing, cloud computing, healthcare, logistics
  • For large instances: use greedy heuristics or metaheuristics
  • Always consider: due dates, precedences, machine eligibility

What's Next: Algorithm — Graph Search

The next course covers graph search algorithms — BFS, DFS, Dijkstra, and A* for pathfinding and network traversal.

Member Exclusive Free Tutorial

This chapter is free exclusive content for registered members! Please login or register to unlock immediately.

Login / Register Now