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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Or build from source:
```bash
git clone https://github.com/CodingThrust/problem-reductions
cd problem-reductions
make cli # builds target/release/pred
make cli # installs `pred` from the local workspace
```

See the [Getting Started](https://codingthrust.github.io/problem-reductions/getting-started.html) guide for usage examples, the reduction workflow, and [CLI usage](https://codingthrust.github.io/problem-reductions/cli.html).
Expand Down
77 changes: 77 additions & 0 deletions docs/paper/reductions.typ
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
"SubgraphIsomorphism": [Subgraph Isomorphism],
"PartitionIntoTriangles": [Partition Into Triangles],
"FlowShopScheduling": [Flow Shop Scheduling],
"SchedulingWithIndividualDeadlines": [Scheduling With Individual Deadlines],
"MinimumTardinessSequencing": [Minimum Tardiness Sequencing],
"SequencingWithinIntervals": [Sequencing Within Intervals],
"DirectedTwoCommodityIntegralFlow": [Directed Two-Commodity Integral Flow],
Expand Down Expand Up @@ -2109,6 +2110,82 @@ NP-completeness was established by Garey, Johnson, and Stockmeyer @gareyJohnsonS
) <fig:flowshop>
]

#{
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 @@ -64,6 +64,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{gareyJohnsonStockmeyer1976,
author = {Michael R. Garey and David S. Johnson and Larry Stockmeyer},
title = {Some Simplified {NP}-Complete Graph Problems},
Expand Down
30 changes: 27 additions & 3 deletions docs/src/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,11 +298,16 @@ pred create Factoring --target 15 --bits-m 4 --bits-n 4 -o factoring.json
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 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
```

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 Expand Up @@ -372,12 +377,31 @@ pred create MIS --graph 0-1,1-2,2-3 | pred evaluate - --config 1,0,1,0

### `pred inspect` — Inspect a problem file

Show a summary of what's inside a problem JSON or reduction bundle:
Show JSON metadata about what's inside a problem JSON or reduction bundle:

```bash
$ pred inspect problem.json
Type: MaximumIndependentSet {graph=SimpleGraph, weight=i32}
Size: 5 vertices, 5 edges
{
"kind": "problem",
"num_variables": 4,
"reduces_to": [
"MaximumSetPacking",
"MinimumVertexCover"
],
"size_fields": [
"num_vertices",
"num_edges"
],
"solvers": [
"ilp",
"brute-force"
],
"type": "MaximumIndependentSet",
"variant": {
"graph": "SimpleGraph",
"weight": "i32"
}
}
```

Works with reduction bundles and stdin:
Expand Down
44 changes: 40 additions & 4 deletions problemreductions-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ Flags by problem type:
FVS --arcs [--weights] [--num-vertices]
FlowShopScheduling --task-lengths, --deadline [--num-processors]
MinimumTardinessSequencing --n, --deadlines [--precedence-pairs]
SchedulingWithIndividualDeadlines --n, --num-processors/--m, --deadlines [--precedence-pairs]
SCS --strings, --bound [--alphabet-size]
D2CIF --arcs, --capacities, --source-1, --sink-1, --source-2, --sink-2, --requirement-1, --requirement-2
ILP, CircuitSAT (via reduction only)
Expand Down Expand Up @@ -342,7 +343,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 @@ -438,10 +439,10 @@ pub struct CreateArgs {
/// Directed arcs for directed graph problems (e.g., 0>1,1>2,2>0)
#[arg(long)]
pub arcs: Option<String>,
/// Deadlines for MinimumTardinessSequencing (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>,
/// Task lengths for FlowShopScheduling (semicolon-separated rows: "3,4,2;2,3,5;4,1,3")
Expand All @@ -450,7 +451,7 @@ pub struct CreateArgs {
/// Deadline for FlowShopScheduling
#[arg(long)]
pub deadline: Option<u64>,
/// Number of processors/machines for FlowShopScheduling
/// Number of processors/machines for FlowShopScheduling or SchedulingWithIndividualDeadlines
#[arg(long)]
pub num_processors: Option<usize>,
/// Alphabet size for SCS (optional; inferred from max symbol + 1 if omitted)
Expand Down Expand Up @@ -573,3 +574,38 @@ pub fn print_subcommand_help_hint(error_msg: &str) {
}
}
}

#[cfg(test)]
mod tests {
use super::Cli;
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 or SchedulingWithIndividualDeadlines"
),
"create help should describe --num-processors for both scheduling models"
);
assert!(help.contains(
"SchedulingWithIndividualDeadlines --n, --num-processors/--m, --deadlines [--precedence-pairs]"
));
}
}
Loading
Loading