Skip to content
Open
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
82 changes: 82 additions & 0 deletions docs/paper/reductions.typ
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
"PartitionIntoTriangles": [Partition Into Triangles],
"FlowShopScheduling": [Flow Shop Scheduling],
"MinimumTardinessSequencing": [Minimum Tardiness Sequencing],
"SequencingToMinimizeWeightedTardiness": [Sequencing to Minimize Weighted Tardiness],
)

// Definition label: "def:<ProblemName>" — each definition block must have a matching label
Expand Down Expand Up @@ -1977,6 +1978,87 @@ NP-completeness was established by Garey, Johnson, and Stockmeyer @gareyJohnsonS
]
}

#{
let x = load-model-example("SequencingToMinimizeWeightedTardiness")
let lengths = x.instance.lengths
let weights = x.instance.weights
let deadlines = x.instance.deadlines
let bound = x.instance.bound
let njobs = lengths.len()
let sol = x.optimal.at(0)
let lehmer = sol.config
let schedule = {
let avail = range(njobs)
let result = ()
for c in lehmer {
result.push(avail.at(c))
avail = avail.enumerate().filter(((i, v)) => i != c).map(((i, v)) => v)
}
result
}
let completions = {
let t = 0
let result = ()
for job in schedule {
t += lengths.at(job)
result.push(t)
}
result
}
let tardiness = schedule.enumerate().map(((pos, job)) => calc.max(0, completions.at(pos) - deadlines.at(job)))
let weighted = schedule.enumerate().map(((pos, job)) => tardiness.at(pos) * weights.at(job))
let total-weighted = weighted.fold(0, (acc, v) => acc + v)
let tardy-jobs = schedule.enumerate().filter(((pos, job)) => tardiness.at(pos) > 0).map(((pos, job)) => job)
[
#problem-def("SequencingToMinimizeWeightedTardiness")[
Given a set $J$ of $n$ jobs, processing times $ell_j in ZZ^+$, tardiness weights $w_j in ZZ^+$, deadlines $d_j in ZZ^+$, and a bound $K in ZZ^+$, determine whether there exists a one-machine schedule whose total weighted tardiness
$sum_(j in J) w_j max(0, C_j - d_j)$
is at most $K$, where $C_j$ is the completion time of job $j$.
][
Sequencing to Minimize Weighted Tardiness is the classical single-machine scheduling problem $1 || sum w_j T_j$, where $T_j = max(0, C_j - d_j)$. It appears as SS5 in Garey & Johnson @garey1979 and is strongly NP-complete via transformation from 3-Partition, which rules out pseudo-polynomial algorithms in general. When all weights are equal, the special case reduces to ordinary total tardiness and admits a pseudo-polynomial dynamic program @lawler1977. Garey & Johnson also note that the equal-length case is polynomial-time solvable by bipartite matching @garey1979.

Exact algorithms remain exponential in the worst case. Brute-force over all $n!$ schedules evaluates the implementation's decision encoding in $O(n! dot n)$ time. More refined exact methods include the branch-and-bound algorithm of Potts and Van Wassenhove @potts1985 and the dynamic-programming style exact algorithm of Tanaka, Fujikuma, and Araki @tanaka2009.

*Example.* Consider the five jobs with processing times $ell = (#lengths.map(v => str(v)).join(", "))$, weights $w = (#weights.map(v => str(v)).join(", "))$, deadlines $d = (#deadlines.map(v => str(v)).join(", "))$, and bound $K = #bound$. The unique satisfying schedule is $(#schedule.map(job => $t_#(job + 1)$).join(", "))$, with completion times $(#completions.map(v => str(v)).join(", "))$. Only job $t_#(tardy-jobs.at(0) + 1)$ is tardy; the per-job weighted tardiness contributions are $(#weighted.map(v => str(v)).join(", "))$, so the total weighted tardiness is $#total-weighted <= K$.

#figure(
canvas(length: 1cm, {
import draw: *
let colors = (rgb("#4e79a7"), rgb("#e15759"), rgb("#76b7b2"), rgb("#f28e2b"), rgb("#59a14f"))
let scale = 0.34
let row-h = 0.7
let y = 0

for (pos, job) in schedule.enumerate() {
let start = if pos == 0 { 0 } else { completions.at(pos - 1) }
let end = completions.at(pos)
let is-tardy = tardiness.at(pos) > 0
let fill = colors.at(calc.rem(job, colors.len())).transparentize(if is-tardy { 70% } else { 30% })
let stroke = colors.at(calc.rem(job, colors.len()))
rect((start * scale, y - row-h / 2), (end * scale, y + row-h / 2),
fill: fill, stroke: 0.4pt + stroke)
content(((start + end) * scale / 2, y), text(7pt, $t_#(job + 1)$))

let dl = deadlines.at(job)
line((dl * scale, y + row-h / 2 + 0.05), (dl * scale, y + row-h / 2 + 0.2),
stroke: (paint: if is-tardy { red } else { green.darken(20%) }, thickness: 0.6pt))
}

let axis-y = -row-h / 2 - 0.25
line((0, axis-y), (completions.at(completions.len() - 1) * scale, axis-y), stroke: 0.4pt)
for t in range(completions.at(completions.len() - 1) + 1) {
let x = t * scale
line((x, axis-y), (x, axis-y - 0.08), stroke: 0.4pt)
content((x, axis-y - 0.22), text(6pt, str(t)))
}
content((completions.at(completions.len() - 1) * scale / 2, axis-y - 0.42), text(7pt)[time])
}),
caption: [Single-machine schedule for the canonical weighted-tardiness example. The faded job is tardy; colored ticks mark the individual deadlines $d_j$.],
) <fig:weighted-tardiness>
]
]
}

// Completeness check: warn about problem types in JSON but missing from paper
#{
let json-models = {
Expand Down
32 changes: 32 additions & 0 deletions docs/paper/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,38 @@ @article{shang2018
doi = {10.1016/j.cor.2017.10.015}
}

@article{lawler1977,
author = {Eugene L. Lawler},
title = {A pseudopolynomial algorithm for sequencing jobs to minimize total tardiness},
journal = {Annals of Discrete Mathematics},
volume = {1},
pages = {331--342},
year = {1977},
doi = {10.1016/S0167-5060(08)70742-8}
}

@article{potts1985,
author = {Chris N. Potts and Luk N. Van Wassenhove},
title = {A Branch and Bound Algorithm for the Total Weighted Tardiness Problem},
journal = {Operations Research},
volume = {33},
number = {2},
pages = {363--377},
year = {1985},
doi = {10.1287/opre.33.2.363}
}

@article{tanaka2009,
author = {Shunji Tanaka and Shuji Fujikuma and Mituhiko Araki},
title = {An exact algorithm for single-machine scheduling without machine idle time},
journal = {Journal of Scheduling},
volume = {12},
number = {6},
pages = {575--593},
year = {2009},
doi = {10.1007/s10951-008-0093-5}
}

@inproceedings{karp1972,
author = {Richard M. Karp},
title = {Reducibility among Combinatorial Problems},
Expand Down
26 changes: 26 additions & 0 deletions docs/src/reductions/problem_schemas.json
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,32 @@
}
]
},
{
"name": "SequencingToMinimizeWeightedTardiness",
"description": "Schedule jobs on one machine so total weighted tardiness is at most K",
"fields": [
{
"name": "lengths",
"type_name": "Vec<u64>",
"description": "Processing times l_j for each job"
},
{
"name": "weights",
"type_name": "Vec<u64>",
"description": "Tardiness weights w_j for each job"
},
{
"name": "deadlines",
"type_name": "Vec<u64>",
"description": "Deadlines d_j for each job"
},
{
"name": "bound",
"type_name": "u64",
"description": "Upper bound K on total weighted tardiness"
}
]
},
{
"name": "SetBasis",
"description": "Determine whether a collection of sets admits a basis of size k under union",
Expand Down
27 changes: 17 additions & 10 deletions docs/src/reductions/reduction_graph.json
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,13 @@
"doc_path": "models/formula/struct.Satisfiability.html",
"complexity": "2^num_variables"
},
{
"name": "SequencingToMinimizeWeightedTardiness",
"variant": {},
"category": "misc",
"doc_path": "models/misc/struct.SequencingToMinimizeWeightedTardiness.html",
"complexity": "factorial(num_tasks)"
},
{
"name": "SetBasis",
"variant": {},
Expand Down Expand Up @@ -576,7 +583,7 @@
},
{
"source": 4,
"target": 55,
"target": 56,
"overhead": [
{
"field": "num_spins",
Expand Down Expand Up @@ -740,7 +747,7 @@
},
{
"source": 21,
"target": 59,
"target": 60,
"overhead": [
{
"field": "num_elements",
Expand Down Expand Up @@ -796,7 +803,7 @@
},
{
"source": 25,
"target": 55,
"target": 56,
"overhead": [
{
"field": "num_spins",
Expand Down Expand Up @@ -1242,7 +1249,7 @@
},
{
"source": 49,
"target": 54,
"target": 55,
"overhead": [
{
"field": "num_spins",
Expand Down Expand Up @@ -1327,7 +1334,7 @@
"doc_path": "rules/sat_minimumdominatingset/index.html"
},
{
"source": 54,
"source": 55,
"target": 49,
"overhead": [
{
Expand All @@ -1338,7 +1345,7 @@
"doc_path": "rules/spinglass_qubo/index.html"
},
{
"source": 55,
"source": 56,
"target": 25,
"overhead": [
{
Expand All @@ -1353,8 +1360,8 @@
"doc_path": "rules/spinglass_maxcut/index.html"
},
{
"source": 55,
"target": 54,
"source": 56,
"target": 55,
"overhead": [
{
"field": "num_spins",
Expand All @@ -1368,7 +1375,7 @@
"doc_path": "rules/spinglass_casts/index.html"
},
{
"source": 60,
"source": 61,
"target": 12,
"overhead": [
{
Expand All @@ -1383,7 +1390,7 @@
"doc_path": "rules/travelingsalesman_ilp/index.html"
},
{
"source": 60,
"source": 61,
"target": 49,
"overhead": [
{
Expand Down
1 change: 1 addition & 0 deletions problemreductions-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ Flags by problem type:
FVS --arcs [--weights] [--num-vertices]
FlowShopScheduling --task-lengths, --deadline [--num-processors]
MinimumTardinessSequencing --n, --deadlines [--precedence-pairs]
SequencingToMinimizeWeightedTardiness --sizes, --weights, --deadlines, --bound
SCS --strings, --bound [--alphabet-size]
ILP, CircuitSAT (via reduction only)

Expand Down
61 changes: 60 additions & 1 deletion problemreductions-cli/src/commands/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use problemreductions::models::algebraic::{ClosestVectorProblem, BMF};
use problemreductions::models::graph::{GraphPartitioning, HamiltonianPath};
use problemreductions::models::misc::{
BinPacking, FlowShopScheduling, LongestCommonSubsequence, MinimumTardinessSequencing,
PaintShop, ShortestCommonSupersequence, SubsetSum,
PaintShop, SequencingToMinimizeWeightedTardiness, ShortestCommonSupersequence, SubsetSum,
};
use problemreductions::prelude::*;
use problemreductions::registry::collect_schemas;
Expand Down Expand Up @@ -262,6 +262,9 @@ fn example_for(canonical: &str, graph_type: Option<&str>) -> &'static str {
"--graph 0-1,1-2,2-3,3-0 --edge-weights 1,1,1,1 --required-edges 0,2 --bound 4"
}
"SubgraphIsomorphism" => "--graph 0-1,1-2,2-0 --pattern 0-1",
"SequencingToMinimizeWeightedTardiness" => {
"--sizes 3,4,2,5,3 --weights 2,3,1,4,2 --deadlines 5,8,4,15,10 --bound 13"
}
"SubsetSum" => "--sizes 3,7,1,8,2,4 --target 11",
"SetBasis" => "--universe 4 --sets \"0,1;1,2;0,2;0,1,2\" --k 3",
"ShortestCommonSupersequence" => "--strings \"0,1,2;1,2,0\" --bound 4",
Expand Down Expand Up @@ -914,6 +917,62 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> {
)
}

// SequencingToMinimizeWeightedTardiness
"SequencingToMinimizeWeightedTardiness" => {
let sizes_str = args.sizes.as_deref().ok_or_else(|| {
anyhow::anyhow!(
"SequencingToMinimizeWeightedTardiness requires --sizes, --weights, --deadlines, and --bound\n\n\
Usage: pred create SequencingToMinimizeWeightedTardiness --sizes 3,4,2,5,3 --weights 2,3,1,4,2 --deadlines 5,8,4,15,10 --bound 13"
)
Comment on lines +920 to +926
})?;
let weights_str = args.weights.as_deref().ok_or_else(|| {
anyhow::anyhow!(
"SequencingToMinimizeWeightedTardiness requires --weights (comma-separated tardiness weights)\n\n\
Usage: pred create SequencingToMinimizeWeightedTardiness --sizes 3,4,2,5,3 --weights 2,3,1,4,2 --deadlines 5,8,4,15,10 --bound 13"
)
})?;
let deadlines_str = args.deadlines.as_deref().ok_or_else(|| {
anyhow::anyhow!(
"SequencingToMinimizeWeightedTardiness requires --deadlines (comma-separated job deadlines)\n\n\
Usage: pred create SequencingToMinimizeWeightedTardiness --sizes 3,4,2,5,3 --weights 2,3,1,4,2 --deadlines 5,8,4,15,10 --bound 13"
)
})?;
let bound = args.bound.ok_or_else(|| {
anyhow::anyhow!(
"SequencingToMinimizeWeightedTardiness requires --bound\n\n\
Usage: pred create SequencingToMinimizeWeightedTardiness --sizes 3,4,2,5,3 --weights 2,3,1,4,2 --deadlines 5,8,4,15,10 --bound 13"
)
})?;
anyhow::ensure!(bound >= 0, "--bound must be non-negative");

let lengths: Vec<u64> = util::parse_comma_list(sizes_str)?;
let weights: Vec<u64> = util::parse_comma_list(weights_str)?;
let deadlines: Vec<u64> = util::parse_comma_list(deadlines_str)?;

anyhow::ensure!(
lengths.len() == weights.len(),
"sizes length ({}) must equal weights length ({})",
lengths.len(),
weights.len()
);
anyhow::ensure!(
lengths.len() == deadlines.len(),
"sizes length ({}) must equal deadlines length ({})",
lengths.len(),
deadlines.len()
);

(
ser(SequencingToMinimizeWeightedTardiness::new(
lengths,
weights,
deadlines,
bound as u64,
))?,
resolved_variant.clone(),
)
}

// OptimalLinearArrangement — graph + bound
"OptimalLinearArrangement" => {
let (graph, _) = parse_graph(args).map_err(|e| {
Expand Down
Loading
Loading