Skip to content

Commit 3618171

Browse files
GiggleLiuisPANNclaude
authored
Fix #497: [Model] SequencingToMinimizeWeightedCompletionTime (#669)
* Add plan for #497: [Model] SequencingToMinimizeWeightedCompletionTime * Implement #497: [Model] SequencingToMinimizeWeightedCompletionTime * chore: remove plan file after implementation * Fix late review findings for SequencingToMinimizeWeightedCompletionTime * chore: retrigger CI for PR #669 * Fix compilation after merge: update example specs to new API - Remove duplicate `lengths` field in CLI args - Update ModelExampleSpec to use new struct fields (instance, optimal_config, optimal_value) instead of removed `build` closure - Replace removed `direct_ilp_example` helper with `rule_example_with_witness` pattern Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Merge origin/main and fix formatting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Revert unrelated variant-hint feature from graph.rs Remove the pred-path variant suggestion code and its test, as they are outside the scope of this model/rule PR. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix precedence-pairs help text to list all three scheduling problems The help string was generalized to "scheduling problems" but the test expected specific problem names. List all three explicitly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix cli test to match updated precedence-pairs help text Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Xiwei Pan <xiwei.pan@connect.hkust-gz.edu.cn> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2228c61 commit 3618171

14 files changed

Lines changed: 1185 additions & 12 deletions

docs/paper/reductions.typ

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@
139139
"ConsecutiveBlockMinimization": [Consecutive Block Minimization],
140140
"ConsecutiveOnesSubmatrix": [Consecutive Ones Submatrix],
141141
"SequencingToMinimizeMaximumCumulativeCost": [Sequencing to Minimize Maximum Cumulative Cost],
142+
"SequencingToMinimizeWeightedCompletionTime": [Sequencing to Minimize Weighted Completion Time],
142143
"SequencingToMinimizeWeightedTardiness": [Sequencing to Minimize Weighted Tardiness],
143144
"SequencingWithinIntervals": [Sequencing Within Intervals],
144145
"SumOfSquaresPartition": [Sum of Squares Partition],
@@ -3716,6 +3717,72 @@ A classical NP-complete problem from Garey and Johnson @garey1979[Ch.~3, p.~76],
37163717
]
37173718
}
37183719

3720+
#{
3721+
let x = load-model-example("SequencingToMinimizeWeightedCompletionTime")
3722+
let lengths = x.instance.lengths
3723+
let weights = x.instance.weights
3724+
let precs = x.instance.precedences
3725+
let ntasks = lengths.len()
3726+
let sol = x.optimal.at(0)
3727+
let opt = sol.metric.Valid
3728+
let lehmer = sol.config
3729+
let schedule = {
3730+
let avail = range(ntasks)
3731+
let result = ()
3732+
for c in lehmer {
3733+
result.push(avail.at(c))
3734+
avail = avail.enumerate().filter(((i, v)) => i != c).map(((i, v)) => v)
3735+
}
3736+
result
3737+
}
3738+
let starts = ()
3739+
let finishes = ()
3740+
let elapsed = 0
3741+
for task in schedule {
3742+
starts.push(elapsed)
3743+
elapsed += lengths.at(task)
3744+
finishes.push(elapsed)
3745+
}
3746+
let total-time = elapsed
3747+
[
3748+
#problem-def("SequencingToMinimizeWeightedCompletionTime")[
3749+
Given a set $T$ of $n$ tasks, a processing-time function $l: T -> ZZ^+$, a weight function $w: T -> ZZ^+$, and a partial order $prec.eq$ on $T$, find a one-machine schedule minimizing $sum_(t in T) w(t) C(t)$, where $C(t)$ is the completion time of task $t$ and every precedence relation $t_i prec.eq t_j$ requires task $t_i$ to complete before task $t_j$ starts.
3750+
][
3751+
Sequencing to Minimize Weighted Completion Time is the single-machine precedence-constrained scheduling problem catalogued as SS4 in Garey & Johnson @garey1979, usually written $1 | "prec" | sum w_j C_j$. Lawler showed that arbitrary precedence constraints make the problem NP-complete, while series-parallel precedence orders admit an $O(n log n)$ algorithm @lawler1978. Without precedence constraints, Smith's ratio rule orders jobs by non-increasing $w_j / l_j$ and is optimal @smith1956.
3752+
3753+
*Example.* Consider tasks with lengths $l = (#lengths.map(v => str(v)).join(", "))$, weights $w = (#weights.map(v => str(v)).join(", "))$, and precedence constraints #{precs.map(p => [$t_#(p.at(0)) prec.eq t_#(p.at(1))$]).join(", ")}. An optimal schedule is $(#schedule.map(t => $t_#t$).join(", "))$, with completion times $(#finishes.map(v => str(v)).join(", "))$ along the machine timeline and objective value $#opt$.
3754+
3755+
#figure(
3756+
canvas(length: 1cm, {
3757+
import draw: *
3758+
let colors = (rgb("#4e79a7"), rgb("#e15759"), rgb("#76b7b2"), rgb("#f28e2b"), rgb("#59a14f"))
3759+
let scale = 0.55
3760+
let row-h = 0.7
3761+
3762+
for (pos, task) in schedule.enumerate() {
3763+
let x0 = starts.at(pos) * scale
3764+
let x1 = finishes.at(pos) * scale
3765+
let color = colors.at(calc.rem(task, colors.len()))
3766+
rect((x0, -row-h / 2), (x1, row-h / 2),
3767+
fill: color.transparentize(30%), stroke: 0.4pt + color)
3768+
content(((x0 + x1) / 2, 0), text(7pt, $t_#task$))
3769+
}
3770+
3771+
let y-axis = -row-h / 2 - 0.22
3772+
line((0, y-axis), (total-time * scale, y-axis), stroke: 0.4pt)
3773+
for t in range(total-time + 1) {
3774+
let x = t * scale
3775+
line((x, y-axis), (x, y-axis - 0.08), stroke: 0.4pt)
3776+
content((x, y-axis - 0.22), text(6pt, str(t)))
3777+
}
3778+
content((total-time * scale / 2, y-axis - 0.45), text(7pt)[time])
3779+
}),
3780+
caption: [Optimal single-machine schedule for the canonical weighted-completion-time instance. Each block width equals the processing time $l_j$.],
3781+
) <fig:stmwct>
3782+
]
3783+
]
3784+
}
3785+
37193786
#{
37203787
let x = load-model-example("SequencingToMinimizeWeightedTardiness")
37213788
let lengths = x.instance.lengths
@@ -4898,6 +4965,28 @@ The following reductions to Integer Linear Programming are straightforward formu
48984965
_Solution extraction._ For each item $i$, find the unique $j$ with $x_(i j) = 1$; assign item $i$ to bin $j$.
48994966
]
49004967

4968+
#reduction-rule("SequencingToMinimizeWeightedCompletionTime", "ILP")[
4969+
Completion times are natural integer variables, precedence constraints compare those completion times directly, and one binary order variable per task pair enforces that a single machine cannot overlap two jobs.
4970+
][
4971+
_Construction._ For each task $j$, introduce an integer completion-time variable $C_j$. For each unordered pair $i < j$, introduce a binary order variable $y_(i j)$ with $y_(i j) = 1$ meaning task $i$ finishes before task $j$. Let $M = sum_h l_h$.
4972+
4973+
_Bounds._ $l_j <= C_j <= M$ for every task $j$, and $y_(i j) in {0, 1}$.
4974+
4975+
_Precedence constraints._ If $i prec.eq j$, require $C_j - C_i >= l_j$.
4976+
4977+
_Single-machine disjunction._ For every pair $i < j$, require
4978+
$C_j - C_i + M (1 - y_(i j)) >= l_j$
4979+
and
4980+
$C_i - C_j + M y_(i j) >= l_i$.
4981+
Exactly one of the two orderings is therefore active.
4982+
4983+
_Objective._ Minimize $sum_j w_j C_j$.
4984+
4985+
_Correctness._ ($arrow.r.double$) Any feasible schedule defines completion times and pairwise order values satisfying the bounds, precedence inequalities, and disjunctive machine constraints; its weighted completion time is exactly the ILP objective. ($arrow.l.double$) Any feasible ILP solution assigns a strict order to every task pair and forbids overlap, so the completion times correspond to a valid single-machine schedule that respects all precedences. Minimizing the ILP objective therefore minimizes the original weighted completion-time objective.
4986+
4987+
_Solution extraction._ Sort tasks by their completion times $C_j$ and encode that order back into the source schedule representation.
4988+
]
4989+
49014990
#reduction-rule("TravelingSalesman", "ILP",
49024991
example: true,
49034992
example-caption: [Weighted $K_4$: the optimal tour $0 arrow 1 arrow 3 arrow 2 arrow 0$ with cost 80 is found by position-based ILP.],

docs/paper/references.bib

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,27 @@ @article{moore1968
3939
doi = {10.1287/mnsc.15.1.102}
4040
}
4141

42+
@article{lawler1978,
43+
author = {Eugene L. Lawler},
44+
title = {Sequencing Jobs to Minimize Total Weighted Completion Time Subject to Precedence Constraints},
45+
journal = {Annals of Discrete Mathematics},
46+
volume = {2},
47+
pages = {75--90},
48+
year = {1978},
49+
doi = {10.1016/S0167-5060(08)70356-7}
50+
}
51+
52+
@article{smith1956,
53+
author = {W. E. Smith},
54+
title = {Various Optimizers for Single-Stage Production},
55+
journal = {Naval Research Logistics Quarterly},
56+
volume = {3},
57+
number = {1--2},
58+
pages = {59--66},
59+
year = {1956},
60+
doi = {10.1002/nav.3800030106}
61+
}
62+
4263
@article{johnson1954,
4364
author = {Selmer M. Johnson},
4465
title = {Optimal two- and three-stage production schedules with setup times included},

docs/src/cli.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@ pred create MinimumCardinalityKey --num-attributes 6 --dependencies "0,1>2;0,2>3
366366
pred create MinimumTardinessSequencing --n 5 --deadlines 5,5,5,3,3 --precedence-pairs "0>3,1>3,1>4,2>4" -o mts.json
367367
pred create SchedulingWithIndividualDeadlines --n 7 --deadlines 2,1,2,2,3,3,2 --num-processors 3 --precedence-pairs "0>3,1>3,1>4,2>4,2>5" -o swid.json
368368
pred solve swid.json --solver brute-force
369+
pred create SequencingToMinimizeWeightedCompletionTime --lengths 2,1,3,1,2 --weights 3,5,1,4,2 --precedence-pairs "0>2,1>4" -o stmwct.json
369370
pred create StringToStringCorrection --source-string "0,1,2,3,1,0" --target-string "0,1,3,2,1" --bound 2 | pred solve - --solver brute-force
370371
pred create StrongConnectivityAugmentation --arcs "0>1,1>2,2>0,3>4,4>3,2>3,4>5,5>3" --candidate-arcs "3>0:5,3>1:3,3>2:4,4>0:6,4>1:2,4>2:7,5>0:4,5>1:3,5>2:1,0>3:8,0>4:3,0>5:2,1>3:6,1>4:4,1>5:5,2>4:3,2>5:7,1>0:2" --bound 1 -o sca.json
371372
```

problemreductions-cli/src/cli.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ Flags by problem type:
276276
RectilinearPictureCompression --matrix (0/1), --k
277277
SchedulingWithIndividualDeadlines --n, --num-processors/--m, --deadlines [--precedence-pairs]
278278
SequencingToMinimizeMaximumCumulativeCost --costs, --bound [--precedence-pairs]
279+
SequencingToMinimizeWeightedCompletionTime --lengths, --weights [--precedence-pairs]
279280
SequencingToMinimizeWeightedTardiness --sizes, --weights, --deadlines, --bound
280281
SCS --strings, --bound [--alphabet-size]
281282
StringToStringCorrection --source-string, --target-string, --bound [--alphabet-size]
@@ -524,7 +525,7 @@ pub struct CreateArgs {
524525
/// Deadlines for MinimumTardinessSequencing or SchedulingWithIndividualDeadlines (comma-separated, e.g., "5,5,5,3,3")
525526
#[arg(long)]
526527
pub deadlines: Option<String>,
527-
/// Precedence pairs for MinimumTardinessSequencing or SchedulingWithIndividualDeadlines (e.g., "0>3,1>3,1>4,2>4")
528+
/// Precedence pairs for MinimumTardinessSequencing, SchedulingWithIndividualDeadlines, or SequencingToMinimizeWeightedCompletionTime (e.g., "0>3,1>3,1>4,2>4")
528529
#[arg(long)]
529530
pub precedence_pairs: Option<String>,
530531
/// Resource bounds for ResourceConstrainedScheduling (comma-separated, e.g., "20,15")
@@ -735,7 +736,7 @@ mod tests {
735736
"Deadlines for MinimumTardinessSequencing or SchedulingWithIndividualDeadlines"
736737
));
737738
assert!(help.contains(
738-
"Precedence pairs for MinimumTardinessSequencing or SchedulingWithIndividualDeadlines"
739+
"Precedence pairs for MinimumTardinessSequencing, SchedulingWithIndividualDeadlines, or SequencingToMinimizeWeightedCompletionTime"
739740
));
740741
assert!(
741742
help.contains(

problemreductions-cli/src/commands/create.rs

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ use problemreductions::models::misc::{
2121
MultiprocessorScheduling, PaintShop, PartiallyOrderedKnapsack, QueryArg,
2222
RectilinearPictureCompression, ResourceConstrainedScheduling,
2323
SchedulingWithIndividualDeadlines, SequencingToMinimizeMaximumCumulativeCost,
24-
SequencingToMinimizeWeightedTardiness, SequencingWithReleaseTimesAndDeadlines,
25-
SequencingWithinIntervals, ShortestCommonSupersequence, StringToStringCorrection, SubsetSum,
26-
SumOfSquaresPartition,
24+
SequencingToMinimizeWeightedCompletionTime, SequencingToMinimizeWeightedTardiness,
25+
SequencingWithReleaseTimesAndDeadlines, SequencingWithinIntervals, ShortestCommonSupersequence,
26+
StringToStringCorrection, SubsetSum, SumOfSquaresPartition,
2727
};
2828
use problemreductions::models::BiconnectivityAugmentation;
2929
use problemreductions::prelude::*;
@@ -110,6 +110,7 @@ fn all_data_flags_empty(args: &CreateArgs) -> bool {
110110
&& args.potential_edges.is_none()
111111
&& args.budget.is_none()
112112
&& args.deadlines.is_none()
113+
&& args.lengths.is_none()
113114
&& args.precedence_pairs.is_none()
114115
&& args.resource_bounds.is_none()
115116
&& args.resource_requirements.is_none()
@@ -2214,6 +2215,70 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> {
22142215
)
22152216
}
22162217

2218+
// SequencingToMinimizeWeightedCompletionTime
2219+
"SequencingToMinimizeWeightedCompletionTime" => {
2220+
let lengths_str = args.lengths.as_deref().ok_or_else(|| {
2221+
anyhow::anyhow!(
2222+
"SequencingToMinimizeWeightedCompletionTime requires --lengths and --weights\n\n\
2223+
Usage: pred create SequencingToMinimizeWeightedCompletionTime --lengths 2,1,3,1,2 --weights 3,5,1,4,2 [--precedence-pairs \"0>2,1>4\"]"
2224+
)
2225+
})?;
2226+
let weights_str = args.weights.as_deref().ok_or_else(|| {
2227+
anyhow::anyhow!(
2228+
"SequencingToMinimizeWeightedCompletionTime requires --weights\n\n\
2229+
Usage: pred create SequencingToMinimizeWeightedCompletionTime --lengths 2,1,3,1,2 --weights 3,5,1,4,2"
2230+
)
2231+
})?;
2232+
let lengths: Vec<u64> = util::parse_comma_list(lengths_str)?;
2233+
let weights: Vec<u64> = util::parse_comma_list(weights_str)?;
2234+
anyhow::ensure!(
2235+
lengths.len() == weights.len(),
2236+
"lengths length ({}) must equal weights length ({})",
2237+
lengths.len(),
2238+
weights.len()
2239+
);
2240+
anyhow::ensure!(
2241+
lengths.iter().all(|&length| length > 0),
2242+
"task lengths must be positive"
2243+
);
2244+
let num_tasks = lengths.len();
2245+
let precedences: Vec<(usize, usize)> = match args.precedence_pairs.as_deref() {
2246+
Some(s) if !s.is_empty() => s
2247+
.split(',')
2248+
.map(|pair| {
2249+
let parts: Vec<&str> = pair.trim().split('>').collect();
2250+
anyhow::ensure!(
2251+
parts.len() == 2,
2252+
"Invalid precedence format '{}', expected 'u>v'",
2253+
pair.trim()
2254+
);
2255+
Ok((
2256+
parts[0].trim().parse::<usize>()?,
2257+
parts[1].trim().parse::<usize>()?,
2258+
))
2259+
})
2260+
.collect::<Result<Vec<_>>>()?,
2261+
_ => vec![],
2262+
};
2263+
for &(pred, succ) in &precedences {
2264+
anyhow::ensure!(
2265+
pred < num_tasks && succ < num_tasks,
2266+
"precedence index out of range: ({}, {}) but num_tasks = {}",
2267+
pred,
2268+
succ,
2269+
num_tasks
2270+
);
2271+
}
2272+
(
2273+
ser(SequencingToMinimizeWeightedCompletionTime::new(
2274+
lengths,
2275+
weights,
2276+
precedences,
2277+
))?,
2278+
resolved_variant.clone(),
2279+
)
2280+
}
2281+
22172282
// SequencingToMinimizeWeightedTardiness
22182283
"SequencingToMinimizeWeightedTardiness" => {
22192284
let sizes_str = args.sizes.as_deref().ok_or_else(|| {

0 commit comments

Comments
 (0)