unifierv0.3.2

← All examples

Job-shop makespan

Three jobs on three machines, each job a fixed sequence of operations. Branch & Bound minimizes the makespan and proves 11 optimal.

Job-shop makespan

examples/job_shop.rs
//! A small job-shop scheduling problem as a constraint optimization problem (COP): three jobs,
//! three machines, minimize the makespan. Solved with `BranchAndBoundSolver`, which proves the
//! result optimal.
//!
//! Each job is a fixed sequence of operations (`Precedence`), each machine runs one operation at
//! a time (`NoOverlap`), and a makespan variable bounds every job's last end
//! (`LessThanOrEqual`) and is minimized.
//!
//! Run with `cargo run --example job_shop`.

use unifier::dsl::ModelBuilder;
use unifier::solver::{BranchAndBoundSolver, SolverOptions};
use unifier::{Interval, VariableId};

/// `(machine, duration)` per operation, in the order each job must run them.
const JOBS: [&[(usize, u64)]; 3] = [
    &[(0, 3), (1, 2), (2, 2)],
    &[(0, 2), (2, 1), (1, 4)],
    &[(1, 4), (2, 3)],
];
const MACHINES: usize = 3;

fn main() {
    let horizon: i64 = JOBS
        .iter()
        .flat_map(|job| job.iter())
        .map(|&(_, d)| d as i64)
        .sum();

    let mut builder = ModelBuilder::new();
    let mut per_machine: Vec<Vec<(usize, Interval, u64)>> = vec![Vec::new(); MACHINES];
    let mut last_ends: Vec<VariableId> = Vec::new();

    for (job, operations) in JOBS.iter().enumerate() {
        let mut previous: Option<Interval> = None;
        for (index, &(machine, duration)) in operations.iter().enumerate() {
            let op = builder.new_interval(
                &format!("j{job}_op{index}"),
                0..=horizon,
                duration,
                0..=horizon,
            );
            if let Some(previous) = &previous {
                builder.add_precedence(previous, &op, 0);
            }
            per_machine[machine].push((job, op.clone(), duration));
            previous = Some(op);
        }
        last_ends.push(previous.expect("every job has operations").end());
    }

    for operations in &per_machine {
        let intervals: Vec<Interval> = operations.iter().map(|(_, op, _)| op.clone()).collect();
        let durations: Vec<u64> = operations.iter().map(|&(_, _, d)| d).collect();
        builder.add_no_overlap(&intervals, &durations);
    }

    let makespan = builder.new_var("makespan", 0..=horizon);
    for &end in &last_ends {
        builder.add_less_than_or_equal(end, makespan, 0);
    }
    builder.add_minimize([makespan], 1);

    let graph = builder.build().expect("job-shop model validates");
    println!(
        "Job shop: {} jobs, {MACHINES} machines, horizon {horizon}",
        JOBS.len()
    );

    let outcome = BranchAndBoundSolver::new().solve(&graph, &SolverOptions::default());
    let Some(solution) = outcome.solution else {
        println!("No schedule: {:?}", outcome.status);
        return;
    };
    let length = solution.assignment[&makespan];
    println!("Status: {:?}", outcome.status);
    println!("Score: {}", solution.score);
    println!("Makespan: {length}\n");

    println!(
        "       {}",
        (0..length)
            .map(|t| (t % 10).to_string())
            .collect::<String>()
    );
    for (machine, operations) in per_machine.iter().enumerate() {
        let mut row = vec!['.'; length as usize];
        for (job, op, duration) in operations {
            let start = solution.assignment[&op.start()];
            for t in start..start + *duration as i64 {
                row[t as usize] = char::from_digit(*job as u32, 10).expect("single-digit job");
            }
        }
        println!("  M{machine}   {}", row.into_iter().collect::<String>());
    }
    println!("\nDigits are job numbers; each row is one machine.");
}
Job shop: 3 jobs, 3 machines, horizon 21
Status: Optimal
Score: Feasible(strong=0, medium=0, weak=-11)
Makespan: 11

       01234567890
  M0   00011......
  M1   2222001111.
  M2   .....122200

Digits are job numbers; each row is one machine.
  • COP
  • Branch & Bound
  • NoOverlap
  • Precedence