Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions docs/paper/reductions.typ
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"MinimumSumMulticenter": [Minimum Sum Multicenter],
"SubgraphIsomorphism": [Subgraph Isomorphism],
"SubsetSum": [Subset Sum],
"FlowShopScheduling": [Flow Shop Scheduling],
)

// Definition label: "def:<ProblemName>" — each definition block must have a matching label
Expand Down Expand Up @@ -1107,6 +1108,77 @@ Biclique Cover is equivalent to factoring the biadjacency matrix $M$ of the bipa
*Example.* Consider $G$ with $V = {0, 1, 2, 3, 4, 5}$ and arcs $(0 arrow 1), (1 arrow 2), (2 arrow 0), (1 arrow 3), (3 arrow 4), (4 arrow 1), (2 arrow 5), (5 arrow 3), (3 arrow 0)$. This graph contains four directed cycles: $0 arrow 1 arrow 2 arrow 0$, $1 arrow 3 arrow 4 arrow 1$, $0 arrow 1 arrow 3 arrow 0$, and $2 arrow 5 arrow 3 arrow 0 arrow 1 arrow 2$. Removing $A' = {(0 arrow 1), (3 arrow 4)}$ breaks all four cycles (vertex 0 becomes a sink in the residual graph), giving a minimum FAS of size 2.
]

#problem-def("FlowShopScheduling")[
Given $m$ processors and a set $J$ of $n$ jobs, where each job $j in J$ consists of $m$ tasks $t_1 [j], t_2 [j], dots, t_m [j]$ with lengths $ell(t_i [j]) in ZZ^+_0$, and a deadline $D in ZZ^+$, determine whether there exists a permutation schedule $pi$ of the jobs such that all jobs complete by time $D$. Each job must be processed on machines $1, 2, dots, m$ in order, and job $j$ cannot start on machine $i+1$ until its task on machine $i$ is completed.
][
Flow Shop Scheduling is a classical NP-complete problem from Garey & Johnson (A5 SS15), strongly NP-hard for $m >= 3$ @garey1976. For $m = 2$, it is solvable in $O(n log n)$ by Johnson's rule @johnson1954. The problem is fundamental in operations research, manufacturing planning, and VLSI design. When restricted to permutation schedules (same job order on all machines), the search space is $n!$ orderings. The best known exact algorithm for $m = 3$ runs in $O^*(3^n)$ time @shang2018; for general $m$, brute-force over $n!$ permutations gives $O(n! dot m n)$.

*Example.* Let $m = 3$ machines, $n = 5$ jobs with task lengths:
$ ell = mat(
3, 4, 2;
2, 3, 5;
4, 1, 3;
1, 5, 4;
3, 2, 3;
) $
and deadline $D = 25$. The job order $pi = (j_4, j_1, j_5, j_3, j_2)$ (0-indexed: $3, 0, 4, 2, 1$) yields makespan $23 <= 25$, so a feasible schedule exists.

#figure(
canvas(length: 1cm, {
import draw: *
// Gantt chart for job order [3, 0, 4, 2, 1] on 3 machines
// Schedule computed greedily:
// M1: j3[0,1], j0[1,4], j4[4,7], j2[7,11], j1[11,13]
// M2: j3[1,6], j0[6,10], j4[10,12], j2[12,13], j1[13,16]
// M3: j3[6,10], j0[10,12], j4[12,15], j2[15,18], j1[18,23]
let colors = (rgb("#4e79a7"), rgb("#e15759"), rgb("#76b7b2"), rgb("#f28e2b"), rgb("#59a14f"))
let job-names = ("$j_1$", "$j_2$", "$j_3$", "$j_4$", "$j_5$")
let scale = 0.38
let row-h = 0.6
let gap = 0.15

// Machine labels
for (mi, label) in ("M1", "M2", "M3").enumerate() {
let y = -mi * (row-h + gap)
content((-0.8, y), text(8pt, label))
}

// Draw schedule blocks: (machine, job-index, start, end)
let blocks = (
(0, 3, 0, 1), (0, 0, 1, 4), (0, 4, 4, 7), (0, 2, 7, 11), (0, 1, 11, 13),
(1, 3, 1, 6), (1, 0, 6, 10), (1, 4, 10, 12), (1, 2, 12, 13), (1, 1, 13, 16),
(2, 3, 6, 10), (2, 0, 10, 12), (2, 4, 12, 15), (2, 2, 15, 18), (2, 1, 18, 23),
)

for (mi, ji, s, e) in blocks {
let x0 = s * scale
let x1 = e * scale
let y = -mi * (row-h + gap)
rect((x0, y - row-h / 2), (x1, y + row-h / 2),
fill: colors.at(ji).transparentize(30%), stroke: 0.4pt + colors.at(ji))
content(((x0 + x1) / 2, y), text(6pt, job-names.at(ji)))
}

// Time axis
let max-t = 23
let y-axis = -2 * (row-h + gap) - row-h / 2 - 0.2
line((0, y-axis), (max-t * scale, y-axis), stroke: 0.4pt)
for t in (0, 5, 10, 15, 20, 23) {
let x = t * scale
line((x, y-axis), (x, y-axis - 0.1), stroke: 0.4pt)
content((x, y-axis - 0.25), text(6pt, str(t)))
}
content((max-t * scale / 2, y-axis - 0.5), text(7pt)[$t$])

// Deadline marker
let dl-x = 25 * scale
line((dl-x, row-h / 2 + 0.1), (dl-x, y-axis), stroke: (paint: red, thickness: 0.8pt, dash: "dashed"))
content((dl-x, row-h / 2 + 0.25), text(6pt, fill: red)[$D = 25$])
}),
caption: [Flow shop schedule for 5 jobs on 3 machines. Job order $(j_4, j_1, j_5, j_3, j_2)$ achieves makespan 23, within deadline $D = 25$ (dashed red line).],
) <fig:flowshop>
]

// Completeness check: warn about problem types in JSON but missing from paper
#{
let json-models = {
Expand Down
10 changes: 10 additions & 0 deletions problemreductions-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ Flags by problem type:
LCS --strings
FAS --arcs [--weights] [--num-vertices]
FVS --arcs [--weights] [--num-vertices]
FlowShopScheduling --task-lengths, --deadline [--num-processors]
SCS --strings, --bound [--alphabet-size]
ILP, CircuitSAT (via reduction only)

Expand Down Expand Up @@ -351,6 +352,15 @@ pub struct CreateArgs {
/// Directed arcs for directed graph problems (e.g., 0>1,1>2,2>0)
#[arg(long)]
pub arcs: Option<String>,
/// Task lengths for FlowShopScheduling (semicolon-separated rows: "3,4,2;2,3,5;4,1,3")
#[arg(long)]
pub task_lengths: Option<String>,
/// Deadline for FlowShopScheduling
#[arg(long)]
pub deadline: Option<u64>,
/// Number of processors/machines for FlowShopScheduling
#[arg(long)]
pub num_processors: Option<usize>,
/// Alphabet size for SCS (optional; inferred from max symbol + 1 if omitted)
#[arg(long)]
pub alphabet_size: Option<usize>,
Expand Down
49 changes: 48 additions & 1 deletion problemreductions-cli/src/commands/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use anyhow::{bail, Context, Result};
use problemreductions::models::algebraic::{ClosestVectorProblem, BMF};
use problemreductions::models::graph::{GraphPartitioning, HamiltonianPath};
use problemreductions::models::misc::{
BinPacking, LongestCommonSubsequence, PaintShop, ShortestCommonSupersequence, SubsetSum,
BinPacking, FlowShopScheduling, LongestCommonSubsequence, PaintShop,
ShortestCommonSupersequence, SubsetSum,
};
use problemreductions::prelude::*;
use problemreductions::registry::collect_schemas;
Expand Down Expand Up @@ -54,6 +55,9 @@ fn all_data_flags_empty(args: &CreateArgs) -> bool {
&& args.pattern.is_none()
&& args.strings.is_none()
&& args.arcs.is_none()
&& args.task_lengths.is_none()
&& args.deadline.is_none()
&& args.num_processors.is_none()
&& args.alphabet_size.is_none()
}

Expand Down Expand Up @@ -567,6 +571,49 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> {
)
}

// FlowShopScheduling
"FlowShopScheduling" => {
let task_str = args.task_lengths.as_deref().ok_or_else(|| {
anyhow::anyhow!(
"FlowShopScheduling requires --task-lengths and --deadline\n\n\
Usage: pred create FlowShopScheduling --task-lengths \"3,4,2;2,3,5;4,1,3\" --deadline 25 --num-processors 3"
)
})?;
let deadline = args.deadline.ok_or_else(|| {
anyhow::anyhow!(
"FlowShopScheduling requires --deadline\n\n\
Usage: pred create FlowShopScheduling --task-lengths \"3,4,2;2,3,5;4,1,3\" --deadline 25 --num-processors 3"
)
})?;
let task_lengths: Vec<Vec<u64>> = task_str
.split(';')
.map(|row| util::parse_comma_list(row.trim()))
.collect::<Result<Vec<_>>>()?;
let num_processors = if let Some(np) = args.num_processors {
np
} else if let Some(m) = args.m {
m
} else if let Some(first) = task_lengths.first() {
first.len()
} else {
bail!("Cannot infer num_processors from empty task list; use --num-processors");
};
for (j, row) in task_lengths.iter().enumerate() {
if row.len() != num_processors {
bail!(
"task_lengths row {} has {} entries, expected {} (num_processors)",
j,
row.len(),
num_processors
);
}
}
(
ser(FlowShopScheduling::new(num_processors, task_lengths, deadline))?,
resolved_variant.clone(),
)
Comment on lines +588 to +614
Comment on lines +574 to +614
}

// MinimumFeedbackArcSet
"MinimumFeedbackArcSet" => {
let arcs_str = args.arcs.as_deref().ok_or_else(|| {
Expand Down
5 changes: 4 additions & 1 deletion problemreductions-cli/src/dispatch.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use anyhow::{bail, Context, Result};
use problemreductions::models::algebraic::{ClosestVectorProblem, ILP};
use problemreductions::models::misc::{
BinPacking, Knapsack, LongestCommonSubsequence, ShortestCommonSupersequence, SubsetSum,
BinPacking, FlowShopScheduling, Knapsack, LongestCommonSubsequence,
ShortestCommonSupersequence, SubsetSum,
};
use problemreductions::prelude::*;
use problemreductions::rules::{MinimizeSteps, ReductionGraph};
Expand Down Expand Up @@ -255,6 +256,7 @@ pub fn load_problem(
"PartitionIntoTriangles" => deser_sat::<PartitionIntoTriangles<SimpleGraph>>(data),
"LongestCommonSubsequence" => deser_opt::<LongestCommonSubsequence>(data),
"MinimumFeedbackVertexSet" => deser_opt::<MinimumFeedbackVertexSet<i32>>(data),
"FlowShopScheduling" => deser_sat::<FlowShopScheduling>(data),
"SubsetSum" => deser_sat::<SubsetSum>(data),
"ShortestCommonSupersequence" => deser_sat::<ShortestCommonSupersequence>(data),
"MinimumFeedbackArcSet" => deser_opt::<MinimumFeedbackArcSet<i32>>(data),
Expand Down Expand Up @@ -326,6 +328,7 @@ pub fn serialize_any_problem(
"PartitionIntoTriangles" => try_ser::<PartitionIntoTriangles<SimpleGraph>>(any),
"LongestCommonSubsequence" => try_ser::<LongestCommonSubsequence>(any),
"MinimumFeedbackVertexSet" => try_ser::<MinimumFeedbackVertexSet<i32>>(any),
"FlowShopScheduling" => try_ser::<FlowShopScheduling>(any),
"SubsetSum" => try_ser::<SubsetSum>(any),
"ShortestCommonSupersequence" => try_ser::<ShortestCommonSupersequence>(any),
"MinimumFeedbackArcSet" => try_ser::<MinimumFeedbackArcSet<i32>>(any),
Expand Down
1 change: 1 addition & 0 deletions problemreductions-cli/src/problem_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ pub fn resolve_alias(input: &str) -> String {
"partitionintotriangles" => "PartitionIntoTriangles".to_string(),
"lcs" | "longestcommonsubsequence" => "LongestCommonSubsequence".to_string(),
"fvs" | "minimumfeedbackvertexset" => "MinimumFeedbackVertexSet".to_string(),
"flowshopscheduling" => "FlowShopScheduling".to_string(),
"fas" | "minimumfeedbackarcset" => "MinimumFeedbackArcSet".to_string(),
"minimumsummulticenter" | "pmedian" => "MinimumSumMulticenter".to_string(),
"subsetsum" => "SubsetSum".to_string(),
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ pub mod prelude {
RuralPostman, TravelingSalesman,
};
pub use crate::models::misc::{
BinPacking, Factoring, Knapsack, LongestCommonSubsequence, PaintShop,
BinPacking, Factoring, FlowShopScheduling, Knapsack, LongestCommonSubsequence, PaintShop,
ShortestCommonSupersequence, SubsetSum,
};
pub use crate::models::set::{MaximumSetPacking, MinimumSetCovering};
Expand Down
Loading
Loading