Skip to content

Commit f3837e2

Browse files
isPANNclaude
andauthored
feat: add SchedulingToMinimizeWeightedCompletionTime model + ILP rule (#809)
* feat: add SchedulingToMinimizeWeightedCompletionTime model and ILP rule (#505) Implement the multiprocessor scheduling optimization problem (SS13 from Garey & Johnson) that minimizes total weighted completion time with Smith's rule ordering on each processor, plus its direct ILP reduction. - Model: Min<u64> value type, dims=[m; n], Smith's rule for per-processor ordering - ILP rule: binary assignment + completion time + ordering variables with big-M constraints - 16 model tests + 6 ILP tests, all passing - CLI: pred create support, schema registration - Paper: problem-def + reduction-rule entries with bibliography Closes #505 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix paper definition: replace undefined σ(t) with explicit completion time description Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add C_t <= M upper bounds to ILP reduction per issue #783 spec Adds completion-time upper bound constraints (C_t <= M where M = Σ l(t)) to tighten the LP relaxation. Updates overhead formula from 2*n to 3*n bound constraints, matching the 75-constraint canonical example. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a4b8656 commit f3837e2

File tree

11 files changed

+947
-7
lines changed

11 files changed

+947
-7
lines changed

docs/paper/reductions.typ

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@
183183
"RectilinearPictureCompression": [Rectilinear Picture Compression],
184184
"ResourceConstrainedScheduling": [Resource Constrained Scheduling],
185185
"RootedTreeStorageAssignment": [Rooted Tree Storage Assignment],
186+
"SchedulingToMinimizeWeightedCompletionTime": [Scheduling to Minimize Weighted Completion Time],
186187
"SchedulingWithIndividualDeadlines": [Scheduling With Individual Deadlines],
187188
"SequencingToMinimizeMaximumCumulativeCost": [Sequencing to Minimize Maximum Cumulative Cost],
188189
"SequencingToMinimizeWeightedCompletionTime": [Sequencing to Minimize Weighted Completion Time],
@@ -5743,6 +5744,104 @@ A classical NP-complete problem from Garey and Johnson @garey1979[Ch.~3, p.~76],
57435744
]
57445745
]
57455746
}
5747+
#{
5748+
let x = load-model-example("SchedulingToMinimizeWeightedCompletionTime")
5749+
let ntasks = x.instance.lengths.len()
5750+
let m = x.instance.num_processors
5751+
let lengths = x.instance.lengths
5752+
let weights = x.instance.weights
5753+
let sigma = x.optimal_config
5754+
// Group tasks by processor
5755+
let tasks-by-proc = range(m).map(p =>
5756+
range(ntasks).filter(i => sigma.at(i) == p)
5757+
)
5758+
[
5759+
#problem-def("SchedulingToMinimizeWeightedCompletionTime")[
5760+
Given a finite set $T$ of tasks with processing lengths $ell: T -> ZZ^+$ and weights $w: T -> ZZ^+$, and a number $m in ZZ^+$ of identical processors, find an assignment $p: T -> {1, dots, m}$ that minimizes the total weighted completion time $sum_(t in T) w(t) dot C(t)$, where on each processor tasks are ordered by Smith's rule (non-decreasing $ell(t) "/" w(t)$ ratio) and $C(t)$ is the completion time of task $t$ (i.e., the cumulative processing time up to and including $t$ on its assigned processor).
5761+
][
5762+
Scheduling to Minimize Weighted Completion Time is problem A5 SS13 in Garey & Johnson @garey1979. NP-complete for $m = 2$ by reduction from Partition @lenstra1977, and NP-complete in the strong sense for arbitrary $m$. For a fixed assignment of tasks to processors, Smith's rule gives the optimal ordering on each processor, reducing the search space to $m^n$ processor assignments @smith1956. The problem is solvable in polynomial time when all lengths are equal or when all weights are equal @conway1967 @horn1973.
5763+
5764+
*Example.* Let $T = {t_1, dots, t_#ntasks}$ with lengths $(#lengths.map(str).join(", "))$, weights $(#weights.map(str).join(", "))$, and $m = #m$ processors. The optimal assignment $(#sigma.map(v => str(v + 1)).join(", "))$ achieves total weighted completion time #x.optimal_value:
5765+
#for p in range(m) [
5766+
- Processor #(p + 1): ${#tasks-by-proc.at(p).map(i => $t_#(i + 1)$).join(", ")}$#if tasks-by-proc.at(p).len() > 0 {
5767+
let proc-tasks = tasks-by-proc.at(p)
5768+
let elapsed = 0
5769+
let contributions = ()
5770+
for t in proc-tasks {
5771+
elapsed = elapsed + lengths.at(t)
5772+
contributions.push($#elapsed times #(weights.at(t)) = #(elapsed * weights.at(t))$)
5773+
}
5774+
[ -- contributions: #contributions.join(", ")]
5775+
}
5776+
]
5777+
5778+
#pred-commands(
5779+
"pred create --example " + problem-spec(x) + " -o scheduling-wct.json",
5780+
"pred solve scheduling-wct.json --solver brute-force",
5781+
"pred evaluate scheduling-wct.json --config " + x.optimal_config.map(str).join(","),
5782+
)
5783+
5784+
#figure({
5785+
canvas(length: 1cm, {
5786+
import draw: *
5787+
let scale = 0.2
5788+
let width = 1.2
5789+
let gap = 0.8
5790+
let colors = (
5791+
rgb("#4e79a7"),
5792+
rgb("#e15759"),
5793+
rgb("#76b7b2"),
5794+
rgb("#f28e2b"),
5795+
rgb("#59a14f"),
5796+
)
5797+
5798+
for p in range(m) {
5799+
let x0 = p * (width + gap)
5800+
let max-time = tasks-by-proc.at(p).fold(0, (acc, t) => acc + lengths.at(t))
5801+
rect((x0, 0), (x0 + width, max-time * scale), stroke: 0.8pt + black)
5802+
let y = 0
5803+
for task in tasks-by-proc.at(p) {
5804+
let len = lengths.at(task)
5805+
let col = colors.at(task)
5806+
rect(
5807+
(x0, y),
5808+
(x0 + width, y + len * scale),
5809+
fill: col.transparentize(25%),
5810+
stroke: 0.4pt + col,
5811+
)
5812+
content(
5813+
(x0 + width / 2, y + len * scale / 2),
5814+
text(7pt, fill: white)[$t_#(task + 1)$],
5815+
)
5816+
y += len * scale
5817+
}
5818+
content((x0 + width / 2, -0.3), text(8pt)[$P_#(p + 1)$])
5819+
}
5820+
})
5821+
},
5822+
caption: [Canonical Scheduling to Minimize Weighted Completion Time instance with #ntasks tasks on #m processors. Tasks are ordered on each processor by Smith's rule.],
5823+
) <fig:scheduling-wct>
5824+
]
5825+
]
5826+
}
5827+
5828+
// Reduction: SchedulingToMinimizeWeightedCompletionTime -> ILP
5829+
#reduction-rule("SchedulingToMinimizeWeightedCompletionTime", "ILP",
5830+
example: false,
5831+
)[
5832+
This $O(n^2 m)$ reduction constructs an ILP with binary assignment variables $x_(t,p)$, integer completion-time variables $C_t$, and binary ordering variables $y_(i,j)$ for task pairs. Big-M disjunctive constraints enforce non-overlapping execution on shared processors.
5833+
][
5834+
_Construction._ Let $n = |T|$ and $m$ be the number of processors. Create $n m$ binary assignment variables $x_(t,p) in {0, 1}$ (task $t$ on processor $p$), $n$ integer completion-time variables $C_t$, and $n(n-1)/2$ binary ordering variables $y_(i,j)$ for $i < j$. The constraints are:
5835+
(1) Assignment: $sum_p x_(t,p) = 1$ for each $t$.
5836+
(2) Completion bounds: $C_t >= ell(t)$ for each $t$.
5837+
(3) Disjunctive: for each pair $(i,j)$ with $i < j$ and each processor $p$, big-M constraints ensure that if both tasks are on processor $p$, one must complete before the other starts.
5838+
The objective minimizes $sum_t w(t) dot C_t$.
5839+
5840+
_Correctness._ ($arrow.r.double$) Any valid schedule gives a feasible ILP solution with the same objective. ($arrow.l.double$) Any ILP solution encodes a valid assignment and non-overlapping schedule.
5841+
5842+
_Solution extraction._ For each task $t$, find the processor $p$ with $x_(t,p) = 1$.
5843+
]
5844+
57465845
#{
57475846
let x = load-model-example("SequencingWithinIntervals")
57485847
let ntasks = x.instance.lengths.len()

docs/paper/references.bib

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1447,6 +1447,33 @@ @article{edmondsjohnson1973
14471447
year = {1973}
14481448
}
14491449

1450+
@article{lenstra1977,
1451+
author = {J. K. Lenstra and A. H. G. Rinnooy Kan and P. Brucker},
1452+
title = {Complexity of Machine Scheduling Problems},
1453+
journal = {Annals of Discrete Mathematics},
1454+
volume = {1},
1455+
pages = {343--362},
1456+
year = {1977},
1457+
doi = {10.1016/S0167-5060(08)70743-X}
1458+
}
1459+
1460+
@book{conway1967,
1461+
author = {Richard W. Conway and William L. Maxwell and Louis W. Miller},
1462+
title = {Theory of Scheduling},
1463+
publisher = {Addison-Wesley},
1464+
year = {1967}
1465+
}
1466+
1467+
@article{horn1973,
1468+
author = {W. A. Horn},
1469+
title = {Minimizing Average Flow Time with Parallel Machines},
1470+
journal = {Operations Research},
1471+
volume = {21},
1472+
number = {3},
1473+
pages = {846--847},
1474+
year = {1973}
1475+
}
1476+
14501477
@techreport{plaisted1976,
14511478
author = {David A. Plaisted},
14521479
title = {Some Polynomial and Integer Divisibility Problems Are {NP}-Hard},

problemreductions-cli/src/cli.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ Flags by problem type:
280280
AcyclicPartition --arcs [--weights] [--arc-costs] --weight-bound --cost-bound [--num-vertices]
281281
CVP --basis, --target-vec [--bounds]
282282
MultiprocessorScheduling --lengths, --num-processors, --deadline
283+
SchedulingToMinimizeWeightedCompletionTime --lengths, --weights, --num-processors
283284
SequencingWithinIntervals --release-times, --deadlines, --lengths
284285
OptimalLinearArrangement --graph
285286
RootedTreeArrangement --graph, --bound
@@ -345,6 +346,7 @@ Examples:
345346
pred create MIS/UnitDiskGraph --positions \"0,0;1,0;0.5,0.8\" --radius 1.5
346347
pred create MIS --random --num-vertices 10 --edge-prob 0.3
347348
pred create MultiprocessorScheduling --lengths 4,5,3,2,6 --num-processors 2 --deadline 10
349+
pred create SchedulingToMinimizeWeightedCompletionTime --lengths 1,2,3,4,5 --weights 6,4,3,2,1 --num-processors 2
348350
pred create UndirectedFlowLowerBounds --graph 0-1,0-2,1-3,2-3,1-4,3-5,4-5 --capacities 2,2,2,2,1,3,2 --lower-bounds 1,1,0,0,1,0,1 --source 0 --sink 5 --requirement 3
349351
pred create ConsistencyOfDatabaseFrequencyTables --num-objects 6 --attribute-domains \"2,3,2\" --frequency-tables \"0,1:1,1,1|1,1,1;1,2:1,1|0,2|1,1\" --known-values \"0,0,0;3,0,1;1,2,1\"
350352
pred create BiconnectivityAugmentation --graph 0-1,1-2,2-3 --potential-edges 0-2:3,0-3:4,1-3:2 --budget 5
@@ -668,7 +670,7 @@ pub struct CreateArgs {
668670
/// Deadline for FlowShopScheduling, MultiprocessorScheduling, or ResourceConstrainedScheduling
669671
#[arg(long)]
670672
pub deadline: Option<u64>,
671-
/// Number of processors/machines for FlowShopScheduling, JobShopScheduling, MultiprocessorScheduling, ResourceConstrainedScheduling, or SchedulingWithIndividualDeadlines
673+
/// Number of processors/machines for FlowShopScheduling, JobShopScheduling, MultiprocessorScheduling, ResourceConstrainedScheduling, SchedulingToMinimizeWeightedCompletionTime, or SchedulingWithIndividualDeadlines
672674
#[arg(long)]
673675
pub num_processors: Option<usize>,
674676
/// Binary schedule patterns for StaffScheduling (semicolon-separated rows, e.g., "1,1,0;0,1,1")
@@ -919,7 +921,7 @@ mod tests {
919921
));
920922
assert!(
921923
help.contains(
922-
"Number of processors/machines for FlowShopScheduling, JobShopScheduling, MultiprocessorScheduling, ResourceConstrainedScheduling, or SchedulingWithIndividualDeadlines"
924+
"Number of processors/machines for FlowShopScheduling, JobShopScheduling, MultiprocessorScheduling, ResourceConstrainedScheduling, SchedulingToMinimizeWeightedCompletionTime, or SchedulingWithIndividualDeadlines"
923925
),
924926
"create help should describe --num-processors for both scheduling models"
925927
);

problemreductions-cli/src/commands/create.rs

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@ use problemreductions::models::misc::{
2727
JobShopScheduling, KnownValue, KthLargestMTuple, LongestCommonSubsequence,
2828
MinimumTardinessSequencing, MultiprocessorScheduling, PaintShop, PartiallyOrderedKnapsack,
2929
ProductionPlanning, QueryArg, RectilinearPictureCompression, ResourceConstrainedScheduling,
30-
SchedulingWithIndividualDeadlines, SequencingToMinimizeMaximumCumulativeCost,
31-
SequencingToMinimizeWeightedCompletionTime, SequencingToMinimizeWeightedTardiness,
32-
SequencingWithReleaseTimesAndDeadlines, SequencingWithinIntervals, ShortestCommonSupersequence,
33-
StringToStringCorrection, SubsetSum, SumOfSquaresPartition, ThreePartition, TimetableDesign,
30+
SchedulingToMinimizeWeightedCompletionTime, SchedulingWithIndividualDeadlines,
31+
SequencingToMinimizeMaximumCumulativeCost, SequencingToMinimizeWeightedCompletionTime,
32+
SequencingToMinimizeWeightedTardiness, SequencingWithReleaseTimesAndDeadlines,
33+
SequencingWithinIntervals, ShortestCommonSupersequence, StringToStringCorrection, SubsetSum,
34+
SumOfSquaresPartition, ThreePartition, TimetableDesign,
3435
};
3536
use problemreductions::models::BiconnectivityAugmentation;
3637
use problemreductions::prelude::*;
@@ -677,6 +678,9 @@ fn example_for(canonical: &str, graph_type: Option<&str>) -> &'static str {
677678
"--num-periods 6 --demands 5,3,7,2,8,5 --capacities 12,12,12,12,12,12 --setup-costs 10,10,10,10,10,10 --production-costs 1,1,1,1,1,1 --inventory-costs 1,1,1,1,1,1 --cost-bound 80"
678679
}
679680
"MultiprocessorScheduling" => "--lengths 4,5,3,2,6 --num-processors 2 --deadline 10",
681+
"SchedulingToMinimizeWeightedCompletionTime" => {
682+
"--lengths 1,2,3,4,5 --weights 6,4,3,2,1 --num-processors 2"
683+
}
680684
"JobShopScheduling" => {
681685
"--job-tasks \"0:3,1:4;1:2,0:3,1:2;0:4,1:3;1:5,0:2;0:2,1:3,0:1\" --num-processors 2"
682686
}
@@ -3283,6 +3287,37 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> {
32833287
)
32843288
}
32853289

3290+
// SchedulingToMinimizeWeightedCompletionTime
3291+
"SchedulingToMinimizeWeightedCompletionTime" => {
3292+
let usage = "Usage: pred create SchedulingToMinimizeWeightedCompletionTime --lengths 1,2,3,4,5 --weights 6,4,3,2,1 --num-processors 2";
3293+
let lengths_str = args.lengths.as_deref().ok_or_else(|| {
3294+
anyhow::anyhow!(
3295+
"SchedulingToMinimizeWeightedCompletionTime requires --lengths, --weights, and --num-processors\n\n{usage}"
3296+
)
3297+
})?;
3298+
let weights_str = args.weights.as_deref().ok_or_else(|| {
3299+
anyhow::anyhow!(
3300+
"SchedulingToMinimizeWeightedCompletionTime requires --weights\n\n{usage}"
3301+
)
3302+
})?;
3303+
let num_processors = args.num_processors.ok_or_else(|| {
3304+
anyhow::anyhow!("SchedulingToMinimizeWeightedCompletionTime requires --num-processors\n\n{usage}")
3305+
})?;
3306+
if num_processors == 0 {
3307+
bail!("SchedulingToMinimizeWeightedCompletionTime requires --num-processors > 0\n\n{usage}");
3308+
}
3309+
let lengths: Vec<u64> = util::parse_comma_list(lengths_str)?;
3310+
let weights: Vec<u64> = util::parse_comma_list(weights_str)?;
3311+
(
3312+
ser(SchedulingToMinimizeWeightedCompletionTime::new(
3313+
lengths,
3314+
weights,
3315+
num_processors,
3316+
))?,
3317+
resolved_variant.clone(),
3318+
)
3319+
}
3320+
32863321
"CapacityAssignment" => {
32873322
let usage = "Usage: pred create CapacityAssignment --capacities 1,2,3 --cost-matrix \"1,3,6;2,4,7;1,2,5\" --delay-matrix \"8,4,1;7,3,1;6,3,1\" --delay-budget 12";
32883323
let capacities_str = args.capacities.as_deref().ok_or_else(|| {

src/models/misc/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ mod precedence_constrained_scheduling;
8888
mod production_planning;
8989
mod rectilinear_picture_compression;
9090
pub(crate) mod resource_constrained_scheduling;
91+
mod scheduling_to_minimize_weighted_completion_time;
9192
mod scheduling_with_individual_deadlines;
9293
mod sequencing_to_minimize_maximum_cumulative_cost;
9394
mod sequencing_to_minimize_weighted_completion_time;
@@ -131,6 +132,7 @@ pub use precedence_constrained_scheduling::PrecedenceConstrainedScheduling;
131132
pub use production_planning::ProductionPlanning;
132133
pub use rectilinear_picture_compression::RectilinearPictureCompression;
133134
pub use resource_constrained_scheduling::ResourceConstrainedScheduling;
135+
pub use scheduling_to_minimize_weighted_completion_time::SchedulingToMinimizeWeightedCompletionTime;
134136
pub use scheduling_with_individual_deadlines::SchedulingWithIndividualDeadlines;
135137
pub use sequencing_to_minimize_maximum_cumulative_cost::SequencingToMinimizeMaximumCumulativeCost;
136138
pub use sequencing_to_minimize_weighted_completion_time::SequencingToMinimizeWeightedCompletionTime;
@@ -164,6 +166,7 @@ pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::M
164166
specs.extend(partition::canonical_model_example_specs());
165167
specs.extend(production_planning::canonical_model_example_specs());
166168
specs.extend(rectilinear_picture_compression::canonical_model_example_specs());
169+
specs.extend(scheduling_to_minimize_weighted_completion_time::canonical_model_example_specs());
167170
specs.extend(scheduling_with_individual_deadlines::canonical_model_example_specs());
168171
specs.extend(sequencing_within_intervals::canonical_model_example_specs());
169172
specs.extend(staff_scheduling::canonical_model_example_specs());

0 commit comments

Comments
 (0)