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
79 changes: 78 additions & 1 deletion docs/paper/reductions.typ
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,10 @@
"PartitionIntoTriangles": [Partition Into Triangles],
"PrimeAttributeName": [Prime Attribute Name],
"FlowShopScheduling": [Flow Shop Scheduling],
"StaffScheduling": [Staff Scheduling],
"MultiprocessorScheduling": [Multiprocessor Scheduling],
"PrecedenceConstrainedScheduling": [Precedence Constrained Scheduling],
"SchedulingWithIndividualDeadlines": [Scheduling With Individual Deadlines],
"StaffScheduling": [Staff Scheduling],
"MinimumTardinessSequencing": [Minimum Tardiness Sequencing],
"SequencingToMinimizeWeightedTardiness": [Sequencing to Minimize Weighted Tardiness],
"SequencingToMinimizeMaximumCumulativeCost": [Sequencing to Minimize Maximum Cumulative Cost],
Expand Down Expand Up @@ -3408,6 +3409,82 @@ A classical NP-complete problem from Garey and Johnson @garey1979[Ch.~3, p.~76],
]
}

#{
let x = load-model-example("SchedulingWithIndividualDeadlines")
let ntasks = x.instance.num_tasks
let nproc = x.instance.num_processors
let deadlines = x.instance.deadlines
let precs = x.instance.precedences
let sample = x.samples.at(0)
let start = sample.config
let horizon = deadlines.fold(0, (acc, d) => if d > acc { d } else { acc })
let slot-groups = range(horizon).map(slot => range(ntasks).filter(t => start.at(t) == slot))
let tight-tasks = range(ntasks).filter(t => start.at(t) + 1 == deadlines.at(t))
let start-label = start.map(v => str(v)).join(", ")
let deadline-pairs = deadlines.enumerate().map(((t, d)) => [$d(t_#(t + 1)) = #d$])
let slot-summaries = slot-groups.enumerate().map(((slot, tasks)) => [slot #slot: #tasks.map(task => $t_#(task + 1)$).join(", ")])
let tight-task-labels = tight-tasks.map(task => $t_#(task + 1)$)
[
#problem-def("SchedulingWithIndividualDeadlines")[
Given a set $T$ of $n$ unit-length tasks, a number $m in ZZ^+$ of identical processors, a deadline function $d: T -> ZZ^+$, and a partial order $prec.eq$ on $T$, determine whether there exists a schedule $sigma: T -> {0, 1, dots, D - 1}$, where $D = max_(t in T) d(t)$, such that every task meets its own deadline ($sigma(t) + 1 <= d(t)$), every precedence constraint is respected (if $t_i prec.eq t_j$ then $sigma(t_i) + 1 <= sigma(t_j)$), and at most $m$ tasks are scheduled in each time slot.
][
Scheduling With Individual Deadlines is the parallel-machine feasibility problem catalogued as A5 SS11 in Garey & Johnson @garey1979. Garey & Johnson record NP-completeness via reduction from Vertex Cover, and Brucker, Garey, and Johnson sharpen the complexity picture: the problem remains NP-complete for out-tree precedence constraints, but becomes polynomial-time solvable for in-trees @bruckerGareyJohnson1977. The two-processor case is also polynomial-time solvable @garey1979.

The direct encoding in this library uses one start-time variable per task, with each variable ranging over its allowable deadline window. If $D = max_t d(t)$, exhaustive search over that encoding yields an $O^*(D^n)$ brute-force bound.#footnote[This is the worst-case search bound induced by the implementation's configuration space; deadlines can be smaller on individual tasks, so practical instances may enumerate fewer than $D^n$ assignments.]

*Example.* Consider $n = #ntasks$ tasks on $m = #nproc$ processors with deadlines #{deadline-pairs.join(", ")} and precedence constraints #{precs.map(p => [$t_#(p.at(0) + 1) prec.eq t_#(p.at(1) + 1)$]).join(", ")}. The sample schedule $sigma = [#start-label]$ assigns #{slot-summaries.join("; ")}. Every slot uses at most #nproc processors, and the tight tasks #{tight-task-labels.join(", ")} finish exactly at their deadlines.

#figure(
canvas(length: 1cm, {
import draw: *
let colors = (
rgb("#4e79a7"),
rgb("#e15759"),
rgb("#76b7b2"),
rgb("#f28e2b"),
rgb("#59a14f"),
rgb("#edc948"),
rgb("#b07aa1"),
)
let scale = 1.25
let row-h = 0.58
let gap = 0.18

for lane in range(nproc) {
let y = -lane * (row-h + gap)
content((-0.8, y), text(7pt, "P" + str(lane + 1)))
}

for (slot, tasks) in slot-groups.enumerate() {
for (lane, task) in tasks.enumerate() {
let x0 = slot * scale
let x1 = (slot + 1) * scale
let y = -lane * (row-h + gap)
let color = colors.at(calc.rem(task, colors.len()))
rect(
(x0, y - row-h / 2),
(x1, y + row-h / 2),
fill: color.transparentize(30%),
stroke: 0.4pt + color,
)
content(((x0 + x1) / 2, y), text(7pt)[$t_#(task + 1)$])
}
}

let y-axis = -(nproc - 1) * (row-h + gap) - row-h / 2 - 0.2
line((0, y-axis), (horizon * scale, y-axis), stroke: 0.4pt)
for t in range(horizon + 1) {
let x = t * scale
line((x, y-axis), (x, y-axis - 0.1), stroke: 0.4pt)
content((x, y-axis - 0.24), text(6pt, str(t)))
}
content((horizon * scale / 2, y-axis - 0.46), text(7pt)[time slot])
}),
caption: [A feasible 3-processor schedule for Scheduling With Individual Deadlines. Tasks sharing a column run in the same unit-length time slot; the sample assignment uses slots $0, 1, 2$ and meets every deadline.],
) <fig:scheduling-with-individual-deadlines>
]
]
}
#{
let x = load-model-example("SequencingWithinIntervals")
let ntasks = x.instance.lengths.len()
Expand Down
10 changes: 10 additions & 0 deletions docs/paper/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,16 @@ @book{garey1979
year = {1979}
}

@article{bruckerGareyJohnson1977,
author = {Peter Brucker and Michael R. Garey and David S. Johnson},
title = {Scheduling equal-length tasks under tree-like precedence constraints to minimize maximum lateness},
journal = {Mathematics of Operations Research},
volume = {2},
number = {3},
pages = {275--284},
year = {1977}
}

@article{eswarantarjan1976,
author = {K. P. Eswaran and Robert E. Tarjan},
title = {Augmentation Problems},
Expand Down
5 changes: 5 additions & 0 deletions docs/src/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,13 +357,18 @@ pred create Factoring --target 21 --bits-m 3 --bits-n 3 -o factoring2.json
pred create X3C --universe 9 --sets "0,1,2;0,2,4;3,4,5;3,5,7;6,7,8;1,4,6;2,5,8" -o x3c.json
pred create MinimumCardinalityKey --num-attributes 6 --dependencies "0,1>2;0,2>3;1,3>4;2,4>5" --k 2 -o mck.json
pred create MinimumTardinessSequencing --n 5 --deadlines 5,5,5,3,3 --precedence-pairs "0>3,1>3,1>4,2>4" -o mts.json
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
pred solve swid.json --solver brute-force
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
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
```

For `LengthBoundedDisjointPaths`, the CLI flag `--bound` maps to the JSON field
`max_length`.

For problem-specific create help, run `pred create <PROBLEM>` with no additional flags.
The generic `pred create --help` output lists all flags across all problem types.

Canonical examples are useful when you want a known-good instance from the paper/example database.
For model examples, `pred create --example <PROBLEM_SPEC>` emits the canonical instance for that
graph node.
Expand Down
42 changes: 37 additions & 5 deletions problemreductions-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,9 +271,10 @@ Flags by problem type:
FlowShopScheduling --task-lengths, --deadline [--num-processors]
StaffScheduling --schedules, --requirements, --num-workers, --k
MinimumTardinessSequencing --n, --deadlines [--precedence-pairs]
RectilinearPictureCompression --matrix (0/1), --k
SchedulingWithIndividualDeadlines --n, --num-processors/--m, --deadlines [--precedence-pairs]
SequencingToMinimizeMaximumCumulativeCost --costs, --bound [--precedence-pairs]
SequencingToMinimizeWeightedTardiness --sizes, --weights, --deadlines, --bound
RectilinearPictureCompression --matrix (0/1), --k
SCS --strings, --bound [--alphabet-size]
StringToStringCorrection --source-string, --target-string, --bound [--alphabet-size]
D2CIF --arcs, --capacities, --source-1, --sink-1, --source-2, --sink-2, --requirement-1, --requirement-2
Expand Down Expand Up @@ -375,7 +376,7 @@ pub struct CreateArgs {
/// Target value (for Factoring and SubsetSum)
#[arg(long)]
pub target: Option<String>,
/// Bits for first factor (for Factoring)
/// Bits for first factor (for Factoring); also accepted as a processor-count alias for scheduling create commands
#[arg(long)]
pub m: Option<usize>,
/// Bits for second factor (for Factoring)
Expand Down Expand Up @@ -516,10 +517,10 @@ pub struct CreateArgs {
/// Storage costs for MultipleCopyFileAllocation (comma-separated, e.g., "1,1,1,1")
#[arg(long)]
pub storage: Option<String>,
/// Deadlines for scheduling problems (comma-separated, e.g., "5,5,5,3,3")
/// Deadlines for MinimumTardinessSequencing or SchedulingWithIndividualDeadlines (comma-separated, e.g., "5,5,5,3,3")
#[arg(long)]
pub deadlines: Option<String>,
/// Precedence pairs for MinimumTardinessSequencing (e.g., "0>3,1>3,1>4,2>4")
/// Precedence pairs for MinimumTardinessSequencing or SchedulingWithIndividualDeadlines (e.g., "0>3,1>3,1>4,2>4")
#[arg(long)]
pub precedence_pairs: Option<String>,
/// Resource bounds for ResourceConstrainedScheduling (comma-separated, e.g., "20,15")
Expand All @@ -534,7 +535,7 @@ pub struct CreateArgs {
/// Deadline for FlowShopScheduling, MultiprocessorScheduling, or ResourceConstrainedScheduling
#[arg(long)]
pub deadline: Option<u64>,
/// Number of processors/machines for FlowShopScheduling, MultiprocessorScheduling, or ResourceConstrainedScheduling
/// Number of processors/machines for FlowShopScheduling, MultiprocessorScheduling, ResourceConstrainedScheduling, or SchedulingWithIndividualDeadlines
#[arg(long)]
pub num_processors: Option<usize>,
/// Binary schedule patterns for StaffScheduling (semicolon-separated rows, e.g., "1,1,0;0,1,1")
Expand Down Expand Up @@ -708,7 +709,38 @@ pub fn print_subcommand_help_hint(error_msg: &str) {

#[cfg(test)]
mod tests {
use super::Cli;
use super::*;
use clap::CommandFactory;

#[test]
fn test_create_help_mentions_scheduling_with_individual_deadlines_shared_flags() {
let mut cmd = Cli::command();
let create = cmd
.find_subcommand_mut("create")
.expect("create subcommand");
let mut help = Vec::new();
create
.write_long_help(&mut help)
.expect("render create help");
let help = String::from_utf8(help).expect("utf8 help");

assert!(help.contains(
"Deadlines for MinimumTardinessSequencing or SchedulingWithIndividualDeadlines"
));
assert!(help.contains(
"Precedence pairs for MinimumTardinessSequencing or SchedulingWithIndividualDeadlines"
));
assert!(
help.contains(
"Number of processors/machines for FlowShopScheduling, MultiprocessorScheduling, ResourceConstrainedScheduling, or SchedulingWithIndividualDeadlines"
),
"create help should describe --num-processors for both scheduling models"
);
assert!(help.contains(
"SchedulingWithIndividualDeadlines --n, --num-processors/--m, --deadlines [--precedence-pairs]"
));
}

#[test]
fn test_create_parses_biconnectivity_augmentation_flags() {
Expand Down
Loading
Loading