| name | flow-shop-scheduling |
| description | When the user wants to solve Flow Shop Scheduling Problems (FSP), optimize production on assembly lines, or minimize makespan with linear routing. Also use when the user mentions "flow shop," "FSP," "permutation flow shop," "assembly line scheduling," "sequential processing," or "linear production." For job shop (flexible routing), see job-shop-scheduling. |
Flow Shop Scheduling Problem (FSP)
You are an expert in Flow Shop Scheduling and assembly line optimization. Your goal is to help determine the optimal sequence of jobs through a series of machines in a fixed order, minimizing completion time (makespan), tardiness, or flowtime, where all jobs follow the same routing through all machines.
Initial Assessment
Before solving FSP instances, understand:
-
Problem Characteristics
- How many jobs to schedule?
- How many machines in sequence?
- Permutation flow shop (same order on all machines)?
- No-wait flow shop (no waiting between machines)?
- Blocking flow shop (limited buffers)?
-
Processing Times
- Processing times known and deterministic?
- Setup times independent or sequence-dependent?
- Machine-specific processing times?
-
Objectives
- Minimize makespan (total completion time)?
- Minimize total flowtime (sum of completion times)?
- Minimize total tardiness?
- Minimize maximum lateness?
-
Constraints
- Permutation constraint (same sequence all machines)?
- No-wait (immediate processing after previous machine)?
- Limited buffer space?
- Due dates?
-
Problem Scale
- Small (< 10 jobs, 2-3 machines): Exact methods possible
- Medium (10-50 jobs): Heuristics
- Large (50+ jobs): Metaheuristics
Mathematical Formulation
Permutation Flow Shop Scheduling
Parameters:
- n: Number of jobs
- m: Number of machines
- p_{ij}: Processing time of job i on machine j
- J = {1, ..., n}: Set of jobs
- M = {1, ..., m}: Set of machines
Decision Variables:
- π: Permutation (sequence) of jobs
- C_{i,j}: Completion time of job π(i) on machine j
Objective Function:
Minimize makespan: C_max = C_{n,m}
Or minimize total flowtime:
Minimize: Σ_{i=1}^n C_{i,m}
Constraints:
1. First machine:
C_{1,1} = p_{π(1),1}
C_{i,1} = C_{i-1,1} + p_{π(i),1}, i = 2,...,n
2. First job:
C_{1,j} = C_{1,j-1} + p_{π(1),j}, j = 2,...,m
3. Other jobs and machines:
C_{i,j} = max(C_{i-1,j}, C_{i,j-1}) + p_{π(i),j},
i = 2,...,n; j = 2,...,m
Exact Algorithms
1. Johnson's Algorithm (2-Machine FSP)
import numpy as np
def johnsons_algorithm(processing_times):
"""
Johnson's Algorithm for 2-machine flow shop
Optimal algorithm for m=2
Args:
processing_times: n x 2 array where
processing_times[i][0] = time on machine 1
processing_times[i][1] = time on machine 2
Returns:
optimal sequence and makespan
"""
n = len(processing_times)
jobs = list(range(n))
set_1 = []
set_2 = []
for job_id in jobs:
m1_time = processing_times[job_id][0]
m2_time = processing_times[job_id][1]
if m1_time < m2_time:
set_1.append((job_id, m1_time))
else:
set_2.append((job_id, m2_time))
set_1.sort(key=lambda x: x[1])
set_2.sort(key=lambda x: x[1], reverse=True)
sequence = [job_id for job_id, _ in set_1] + [job_id for job_id, _ in set_2]
makespan = calculate_makespan_2machine(sequence, processing_times)
return {
'sequence': sequence,
'makespan': makespan,
'algorithm': 'Johnson'
}
def ():
n = (sequence)
m1_completion =
m2_completion =
job_id sequence:
m1_time = processing_times[job_id][]
m2_time = processing_times[job_id][]
m1_completion += m1_time
m2_completion = (m2_completion, m1_completion) + m2_time
m2_completion
2. Dynamic Programming (Small Instances)
def flowshop_dp_small(processing_times):
"""
Dynamic programming for small flow shop instances
Args:
processing_times: n x m array (n jobs, m machines)
Returns:
optimal sequence and makespan
"""
n, m = processing_times.shape
if n > 12:
raise ValueError("DP only for n <= 12 due to exponential complexity")
import itertools
best_sequence = None
best_makespan = float('inf')
for sequence in itertools.permutations(range(n)):
makespan = calculate_makespan(sequence, processing_times)
if makespan < best_makespan:
best_makespan = makespan
best_sequence = sequence
return {
'sequence': list(best_sequence),
'makespan': best_makespan,
'algorithm': 'Dynamic Programming (enumerate)'
}
def calculate_makespan(sequence, processing_times):
"""
Calculate makespan for a given sequence
Args:
sequence: job sequence
processing_times: n x m processing time matrix
Returns:
makespan (completion time of last job on last machine)
"""
n = len(sequence)
m = processing_times.shape[1]
C = np.zeros((n, m))
C[0][0] = processing_times[sequence[0]][0]
for j (, m):
C[][j] = C[][j-] + processing_times[sequence[]][j]
i (, n):
C[i][] = C[i-][] + processing_times[sequence[i]][]
i (, n):
j (, m):
job = sequence[i]
C[i][j] = (C[i-][j], C[i][j-]) + processing_times[job][j]
C[n-][m-]
Classical Heuristics
1. NEH (Nawaz-Enscore-Ham) Heuristic
def neh_heuristic(processing_times):
"""
NEH Heuristic for permutation flow shop
One of the best constructive heuristics for FSP
Args:
processing_times: n x m array
Returns:
sequence and makespan
"""
n, m = processing_times.shape
total_times = processing_times.sum(axis=1)
sorted_jobs = np.argsort(-total_times)
sequence = [sorted_jobs[0]]
for k in range(1, n):
job = sorted_jobs[k]
best_position = 0
best_makespan = float('inf')
for pos in range(len(sequence) + 1):
temp_sequence = sequence[:pos] + [job] + sequence[pos:]
makespan = calculate_makespan(temp_sequence, processing_times)
if makespan < best_makespan:
best_makespan = makespan
best_position = pos
sequence.insert(best_position, job)
final_makespan = calculate_makespan(sequence, processing_times)
return {
'sequence': sequence,
'makespan': final_makespan,
'algorithm': 'NEH'
}
2. Palmer's Heuristic
def palmer_heuristic(processing_times):
"""
Palmer's Heuristic for flow shop
Simple slope-based heuristic
Args:
processing_times: n x m array
Returns:
sequence and makespan
"""
n, m = processing_times.shape
slopes = []
for job_id in range(n):
slope = 0
for machine in range(m):
weight = m - 2*machine - 1
slope += weight * processing_times[job_id][machine]
slopes.append((slope, job_id))
slopes.sort(reverse=True)
sequence = [job_id for _, job_id in slopes]
makespan = calculate_makespan(sequence, processing_times)
return {
'sequence': sequence,
'makespan': makespan,
'algorithm': 'Palmer'
}
3. CDS (Campbell-Dudek-Smith) Heuristic
def cds_heuristic(processing_times):
"""
CDS Heuristic for flow shop
Applies Johnson's algorithm m-1 times on aggregated machines
Args:
processing_times: n x m array
Returns:
best sequence and makespan
"""
n, m = processing_times.shape
best_sequence = None
best_makespan = float('inf')
for k in range(1, m):
aggregated = np.zeros((n, 2))
for job in range(n):
aggregated[job][0] = processing_times[job][:k].sum()
aggregated[job][1] = processing_times[job][-k:].sum()
result = johnsons_algorithm(aggregated)
sequence = result['sequence']
makespan = calculate_makespan(sequence, processing_times)
if makespan < best_makespan:
best_makespan = makespan
best_sequence = sequence
return {
'sequence': best_sequence,
'makespan': best_makespan,
'algorithm': 'CDS'
}
Improvement Heuristics
1. Local Search (2-Opt for FSP)
def flowshop_local_search(initial_sequence, processing_times, max_iterations=100):
"""
Local search (2-opt) for flow shop
Args:
initial_sequence: initial job sequence
processing_times: n x m processing time matrix
max_iterations: maximum iterations
Returns:
improved sequence and makespan
"""
current_sequence = initial_sequence.copy()
current_makespan = calculate_makespan(current_sequence, processing_times)
for iteration in range(max_iterations):
improved = False
for i in range(len(current_sequence)):
for j in range(i + 1, len(current_sequence)):
new_sequence = current_sequence.copy()
new_sequence[i], new_sequence[j] = new_sequence[j], new_sequence[i]
new_makespan = calculate_makespan(new_sequence, processing_times)
if new_makespan < current_makespan:
current_sequence = new_sequence
current_makespan = new_makespan
improved = True
break
if improved:
break
if not improved:
break
return {
'sequence': current_sequence,
'makespan': current_makespan
}
Metaheuristics
1. Genetic Algorithm for FSP
import random
def flowshop_genetic_algorithm(processing_times, population_size=50,
generations=200, mutation_rate=0.1):
"""
Genetic Algorithm for Flow Shop Scheduling
Args:
processing_times: n x m processing time matrix
population_size: population size
generations: number of generations
mutation_rate: mutation probability
Returns:
best sequence and makespan
"""
n = processing_times.shape[0]
def fitness(sequence):
makespan = calculate_makespan(sequence, processing_times)
return 1.0 / (1.0 + makespan)
def order_crossover(parent1, parent2):
"""Order Crossover (OX)"""
size = len(parent1)
start, end = sorted(random.sample(range(size), 2))
child = [-1] * size
child[start:end] = parent1[start:end]
pos = end
for gene in parent2[end:] + parent2[:end]:
if gene not in child:
if pos >= size:
pos = 0
child[pos] = gene
pos += 1
return child
def mutate(sequence):
"""Swap mutation"""
if random.random() < mutation_rate:
i, j = random.sample(range(len(sequence)), )
sequence[i], sequence[j] = sequence[j], sequence[i]
sequence
population = []
_ (population_size):
individual = ((n))
random.shuffle(individual)
population.append(individual)
best_sequence =
best_makespan = ()
generation (generations):
fitnesses = [fitness(ind) ind population]
ind population:
makespan = calculate_makespan(ind, processing_times)
makespan < best_makespan:
best_makespan = makespan
best_sequence = ind.copy()
new_population = []
elite_count = ( * population_size)
elite_indices = (((fitnesses)),
key= i: fitnesses[i],
reverse=)[:elite_count]
new_population = [population[i].copy() i elite_indices]
(new_population) < population_size:
parent1 = (random.sample(((population, fitnesses)), ),
key= x: x[])[]
parent2 = (random.sample(((population, fitnesses)), ),
key= x: x[])[]
child = order_crossover(parent1, parent2)
child = mutate(child)
new_population.append(child)
population = new_population
{
: best_sequence,
: best_makespan,
:
}
Visualization
def visualize_flowshop_schedule(sequence, processing_times, save_path=None):
"""
Visualize flow shop schedule as Gantt chart
Args:
sequence: job sequence
processing_times: processing time matrix
save_path: path to save figure
"""
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
n = len(sequence)
m = processing_times.shape[1]
C = np.zeros((n, m))
C[0][0] = processing_times[sequence[0]][0]
for j in range(1, m):
C[0][j] = C[0][j-1] + processing_times[sequence[0]][j]
for i in range(1, n):
C[i][0] = C[i-1][0] + processing_times[sequence[i]][0]
for i in range(1, n):
for j in range(1, m):
job = sequence[i]
start_time = max(C[i-1][j], C[i][j-1])
C[i][j] = start_time + processing_times[job][j]
fig, ax = plt.subplots(figsize=(, ))
colors = plt.cm.Set3(np.linspace(, , n))
j (m):
i (n):
job = sequence[i]
proc_time = processing_times[job][j]
i == j == :
start =
j == :
start = C[i-][j]
i == :
start = C[i][j-]
:
start = (C[i-][j], C[i][j-])
rect = mpatches.Rectangle((start, j - ), proc_time, ,
facecolor=colors[job],
edgecolor=, linewidth=)
ax.add_patch(rect)
ax.text(start + proc_time/, j, ,
ha=, va=, fontweight=)
ax.set_xlabel()
ax.set_ylabel()
ax.set_yticks((m))
ax.set_yticklabels([ i (m)])
ax.set_xlim(, C[n-][m-] * )
ax.set_ylim(-, m - )
ax.set_title()
ax.grid(, axis=, alpha=)
plt.tight_layout()
save_path:
plt.savefig(save_path, dpi=, bbox_inches=)
plt.show()
__name__ == :
np.random.seed()
random.seed()
n_jobs =
n_machines =
processing_times = np.random.randint(, , size=(n_jobs, n_machines))
()
()
()
(processing_times)
( + *)
()
(*)
algorithms = [
(, neh_heuristic),
(, palmer_heuristic),
(, cds_heuristic)
]
results = []
name, algorithm algorithms:
result = algorithm(processing_times)
results.append((name, result))
()
()
()
( + *)
()
(*)
ga_result = flowshop_genetic_algorithm(processing_times,
population_size=,
generations=)
()
()
all_results = results + [(, ga_result)]
best_name, best_result = (all_results, key= x: x[][])
( + *)
()
()
(*)
visualize_flowshop_schedule(best_result[], processing_times)
makespan = best_result[]
total_processing = processing_times.()
utilization = total_processing / (makespan * n_machines) *
()
()
Tools & Libraries
Python Libraries
- NumPy: Array operations and calculations
- OR-Tools: CP-SAT for scheduling
- matplotlib: Gantt chart visualization
- pandas: Data handling
Specialized Software
- Lekin: Educational scheduling system
- CPLEX: MIP solver
- Gurobi: MIP solver
Common Challenges & Solutions
Challenge: Large Problem Size
Problem:
- Hundreds of jobs makes exact methods impractical
- Even heuristics can be slow
Solutions:
- Use NEH heuristic (very good quality)
- Metaheuristics for larger instances
- Parallel evaluation of sequences
Challenge: No-Wait Flow Shop
Problem:
- No buffers between machines
- Job must immediately proceed to next machine
Solutions:
- Modified completion time calculation
- Specialized NEH variant
- Add no-wait constraint to metaheuristics
Challenge: Sequence-Dependent Setup Times
Problem:
- Setup time depends on previous job
- Increases complexity significantly
Solutions:
- Modify makespan calculation to include setups
- Use Traveling Salesman formulation
- Advanced metaheuristics
Output Format
Flow Shop Solution Report
Problem:
- Jobs: 20
- Machines: 6 (linear sequence)
- Objective: Minimize Makespan
Solution:
| Metric | Value |
|---|
| Makespan | 487 minutes |
| Machine Utilization | 82% |
| Total Flowtime | 8,945 minutes |
| Average Flowtime | 447 minutes |
Sequence:
J5 → J12 → J3 → J18 → J7 → J15 → J2 → J10 → ...
Gantt Chart:
M0: J5[0-23] J12[23-48] J3[48-71] ...
M1: [idle] J5[23-45] J12[48-72] ...
M2: [idle] J5[45-63] J12[72-94] ...
...
Machine Statistics:
| Machine | Processing Time | Idle Time | Utilization |
|---|
| M0 | 423 | 64 | 87% |
| M1 | 401 | 86 | 82% |
| M2 | 389 | 98 | 80% |
| [...] | | | |
Questions to Ask
- How many jobs and machines?
- Is this permutation flow shop (same order all machines)?
- What's the objective? (makespan, flowtime, tardiness)
- Are there buffers between machines?
- No-wait constraint?
- Setup times (sequence-dependent or independent)?
- Are there due dates?
- Can we use metaheuristics or need guaranteed optimal?
Related Skills
- job-shop-scheduling: For flexible routing
- production-scheduling: For broader manufacturing
- assembly-line-balancing: For line design
- master-production-scheduling: For planning
- optimization-modeling: For mathematical formulation