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
23 changes: 23 additions & 0 deletions docs/paper/reductions.typ
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
"FlowShopScheduling": [Flow Shop Scheduling],
"StaffScheduling": [Staff Scheduling],
"MultiprocessorScheduling": [Multiprocessor Scheduling],
"PrecedenceConstrainedScheduling": [Precedence Constrained Scheduling],
"MinimumTardinessSequencing": [Minimum Tardiness Sequencing],
"SumOfSquaresPartition": [Sum of Squares Partition],
"SequencingWithinIntervals": [Sequencing Within Intervals],
Expand Down Expand Up @@ -2809,6 +2810,28 @@ NP-completeness was established by Garey, Johnson, and Stockmeyer @gareyJohnsonS
]
}

#{
let x = load-model-example("PrecedenceConstrainedScheduling")
let n = x.instance.num_tasks
let m = x.instance.num_processors
let D = x.instance.deadline
let precs = x.instance.precedences
let sigma = x.optimal_config
// Group tasks by assigned slot
let tasks-by-slot = range(D).map(s =>
range(n).filter(i => sigma.at(i) == s)
)
[
#problem-def("PrecedenceConstrainedScheduling")[
Given a set $T$ of $n$ unit-length tasks, a partial order $prec$ on $T$, a number $m in ZZ^+$ of processors, and a deadline $D in ZZ^+$, determine whether there exists a schedule $sigma: T -> {0, dots, D-1}$ such that (i) for every time slot $t$, at most $m$ tasks are assigned to $t$, and (ii) for every precedence $t_i prec t_j$, we have $sigma(t_j) >= sigma(t_i) + 1$.
][
Precedence Constrained Scheduling is problem SS9 in Garey & Johnson @garey1979. NP-complete via reduction from 3SAT @ullman1975. Remains NP-complete even for $D = 3$ @lenstra1978. Solvable in polynomial time for $m = 2$ by the Coffman--Graham algorithm @coffman1972, for forest-structured precedences @hu1961, and for chordal complement precedences @papadimitriou1979. A subset dynamic programming approach solves the general case in $O(2^n dot n)$ time by enumerating subsets of completed tasks at each time step.

*Example.* Let $n = #n$ tasks, $m = #m$ processors, $D = #D$. Precedences: #precs.map(p => $t_#(p.at(0)) prec t_#(p.at(1))$).join(", "). A feasible schedule assigns $sigma = (#sigma.map(s => str(s)).join(", "))$: #range(D).map(s => [slot #s has ${#tasks-by-slot.at(s).map(i => $t_#i$).join(", ")}$]).join(", "). All precedences are satisfied and no slot exceeds $m = #m$.
]
]
}

#{
let x = load-model-example("SequencingWithinIntervals")
let ntasks = x.instance.lengths.len()
Expand Down
53 changes: 53 additions & 0 deletions docs/paper/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -851,3 +851,56 @@ @article{boothlueker1976
year = {1976},
doi = {10.1016/S0022-0000(76)80045-1}
}

@article{ullman1975,
author = {Jeffrey D. Ullman},
title = {NP-Complete Scheduling Problems},
journal = {Journal of Computer and System Sciences},
volume = {10},
number = {3},
pages = {384--393},
year = {1975},
doi = {10.1016/S0022-0000(75)80008-0}
}

@article{lenstra1978,
author = {Jan Karel Lenstra and Alexander H. G. Rinnooy Kan},
title = {Complexity of Scheduling under Precedence Constraints},
journal = {Operations Research},
volume = {26},
number = {1},
pages = {22--35},
year = {1978},
doi = {10.1287/opre.26.1.22}
}

@article{coffman1972,
author = {Edward G. Coffman and Ronald L. Graham},
title = {Optimal Scheduling for Two-Processor Systems},
journal = {Acta Informatica},
volume = {1},
number = {3},
pages = {200--213},
year = {1972},
doi = {10.1007/BF00288685}
}

@article{hu1961,
author = {Te Chiang Hu},
title = {Parallel Sequencing and Assembly Line Problems},
journal = {Operations Research},
volume = {9},
number = {6},
pages = {841--848},
year = {1961},
doi = {10.1287/opre.9.6.841}
}

@inproceedings{papadimitriou1979,
author = {Christos H. Papadimitriou and Mihalis Yannakakis},
title = {Scheduling Interval-Ordered Tasks},
booktitle = {Proceedings of the 10th Annual ACM Symposium on Theory of Computing (STOC)},
pages = {1--7},
year = {1979},
doi = {10.1145/800135.804393}
}
4 changes: 4 additions & 0 deletions src/models/misc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
//! - [`LongestCommonSubsequence`]: Longest Common Subsequence
//! - [`MinimumTardinessSequencing`]: Minimize tardy tasks in single-machine scheduling
//! - [`PaintShop`]: Minimize color switches in paint shop scheduling
//! - [`PrecedenceConstrainedScheduling`]: Schedule unit tasks on processors by deadline
//! - [`SequencingWithReleaseTimesAndDeadlines`]: Single-machine scheduling feasibility
//! - [`SequencingWithinIntervals`]: Schedule tasks within time windows
//! - [`ShortestCommonSupersequence`]: Find a common supersequence of bounded length
Expand All @@ -24,6 +25,7 @@ mod longest_common_subsequence;
mod minimum_tardiness_sequencing;
mod multiprocessor_scheduling;
pub(crate) mod paintshop;
mod precedence_constrained_scheduling;
mod sequencing_with_release_times_and_deadlines;
mod sequencing_within_intervals;
pub(crate) mod shortest_common_supersequence;
Expand All @@ -40,6 +42,7 @@ pub use longest_common_subsequence::LongestCommonSubsequence;
pub use minimum_tardiness_sequencing::MinimumTardinessSequencing;
pub use multiprocessor_scheduling::MultiprocessorScheduling;
pub use paintshop::PaintShop;
pub use precedence_constrained_scheduling::PrecedenceConstrainedScheduling;
pub use sequencing_with_release_times_and_deadlines::SequencingWithReleaseTimesAndDeadlines;
pub use sequencing_within_intervals::SequencingWithinIntervals;
pub use shortest_common_supersequence::ShortestCommonSupersequence;
Expand All @@ -61,6 +64,7 @@ pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::M
specs.extend(string_to_string_correction::canonical_model_example_specs());
specs.extend(minimum_tardiness_sequencing::canonical_model_example_specs());
specs.extend(sum_of_squares_partition::canonical_model_example_specs());
specs.extend(precedence_constrained_scheduling::canonical_model_example_specs());
specs.extend(sequencing_with_release_times_and_deadlines::canonical_model_example_specs());
specs
}
192 changes: 192 additions & 0 deletions src/models/misc/precedence_constrained_scheduling.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
//! Precedence Constrained Scheduling problem implementation.
//!
//! Given unit-length tasks with precedence constraints, m processors, and a
//! deadline D, determine whether all tasks can be scheduled to meet D while
//! respecting precedences. NP-complete via reduction from 3SAT (Ullman, 1975).

use crate::registry::{FieldInfo, ProblemSchemaEntry};
use crate::traits::{Problem, SatisfactionProblem};
use serde::{Deserialize, Serialize};

inventory::submit! {
ProblemSchemaEntry {
name: "PrecedenceConstrainedScheduling",
display_name: "Precedence Constrained Scheduling",
aliases: &[],
dimensions: &[],
module_path: module_path!(),
description: "Schedule unit-length tasks on m processors by deadline D respecting precedence constraints",
fields: &[
FieldInfo { name: "num_tasks", type_name: "usize", description: "Number of tasks n = |T|" },
FieldInfo { name: "num_processors", type_name: "usize", description: "Number of processors m" },
FieldInfo { name: "deadline", type_name: "usize", description: "Global deadline D" },
FieldInfo { name: "precedences", type_name: "Vec<(usize, usize)>", description: "Precedence pairs (i, j) meaning task i must finish before task j starts" },
],
}
}

/// The Precedence Constrained Scheduling problem.
///
/// Given `n` unit-length tasks with precedence constraints (a partial order),
/// `m` processors, and a deadline `D`, determine whether there exists a schedule
/// assigning each task to a time slot in `{0, ..., D-1}` such that:
/// - At most `m` tasks are assigned to any single time slot
/// - For each precedence `(i, j)`: task `j` starts after task `i` completes,
/// i.e., `slot(j) >= slot(i) + 1`
///
/// # Representation
///
/// Each task has a variable in `{0, ..., D-1}` representing its assigned time slot.
///
/// # Example
///
/// ```
/// use problemreductions::models::misc::PrecedenceConstrainedScheduling;
/// use problemreductions::{Problem, Solver, BruteForce};
///
/// // 4 tasks, 2 processors, deadline 3, with t0 < t2 and t1 < t3
/// let problem = PrecedenceConstrainedScheduling::new(4, 2, 3, vec![(0, 2), (1, 3)]);
/// let solver = BruteForce::new();
/// let solution = solver.find_satisfying(&problem);
/// assert!(solution.is_some());
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PrecedenceConstrainedScheduling {
num_tasks: usize,
num_processors: usize,
deadline: usize,
precedences: Vec<(usize, usize)>,
}

impl PrecedenceConstrainedScheduling {
/// Create a new Precedence Constrained Scheduling instance.
///
/// # Panics
///
/// Panics if `num_processors` or `deadline` is zero (when `num_tasks > 0`),
/// or if any precedence index is out of bounds (>= num_tasks).
pub fn new(
num_tasks: usize,
num_processors: usize,
deadline: usize,
precedences: Vec<(usize, usize)>,
) -> Self {
if num_tasks > 0 {
assert!(
num_processors > 0,
"num_processors must be > 0 when there are tasks"
);
assert!(deadline > 0, "deadline must be > 0 when there are tasks");
}
for &(i, j) in &precedences {
assert!(
i < num_tasks && j < num_tasks,
"Precedence ({}, {}) out of bounds for {} tasks",
i,
j,
num_tasks
);
}
Self {
num_tasks,
num_processors,
deadline,
precedences,
}
}

/// Get the number of tasks.
pub fn num_tasks(&self) -> usize {
self.num_tasks
}

/// Get the number of processors.
pub fn num_processors(&self) -> usize {
self.num_processors
}

/// Get the deadline.
pub fn deadline(&self) -> usize {
self.deadline
}

/// Get the precedence constraints.
pub fn precedences(&self) -> &[(usize, usize)] {
&self.precedences
}
}

impl Problem for PrecedenceConstrainedScheduling {
const NAME: &'static str = "PrecedenceConstrainedScheduling";
type Metric = bool;

fn variant() -> Vec<(&'static str, &'static str)> {
crate::variant_params![]
}

fn dims(&self) -> Vec<usize> {
vec![self.deadline; self.num_tasks]
}

fn evaluate(&self, config: &[usize]) -> bool {
if config.len() != self.num_tasks {
return false;
}
// Check all values are valid time slots
if config.iter().any(|&v| v >= self.deadline) {
return false;
}
// Check processor capacity: at most num_processors tasks per time slot
let mut slot_count = vec![0usize; self.deadline];
for &slot in config {
slot_count[slot] += 1;
if slot_count[slot] > self.num_processors {
return false;
}
}
// Check precedence constraints: for (i, j), slot[j] >= slot[i] + 1
for &(i, j) in &self.precedences {
if config[j] < config[i] + 1 {
return false;
}
}
true
}
}

impl SatisfactionProblem for PrecedenceConstrainedScheduling {}

crate::declare_variants! {
default sat PrecedenceConstrainedScheduling => "2^num_tasks",
}

#[cfg(feature = "example-db")]
pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
vec![crate::example_db::specs::ModelExampleSpec {
id: "precedence_constrained_scheduling",
// Issue #501 example: 8 tasks, 3 processors, deadline 4
instance: Box::new(PrecedenceConstrainedScheduling::new(
8,
3,
4,
vec![
(0, 2),
(0, 3),
(1, 3),
(1, 4),
(2, 5),
(3, 6),
(4, 6),
(5, 7),
(6, 7),
],
)),
// Valid schedule: slot 0: {t0,t1}, slot 1: {t2,t3,t4}, slot 2: {t5,t6}, slot 3: {t7}
optimal_config: vec![0, 0, 1, 1, 1, 2, 2, 3],
optimal_value: serde_json::json!(true),
}]
}

#[cfg(test)]
#[path = "../../unit_tests/models/misc/precedence_constrained_scheduling.rs"]
mod tests;
5 changes: 3 additions & 2 deletions src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ pub use graph::{
pub use misc::{
BinPacking, Factoring, FlowShopScheduling, Knapsack, LongestCommonSubsequence,
MinimumTardinessSequencing, MultiprocessorScheduling, PaintShop,
SequencingWithReleaseTimesAndDeadlines, SequencingWithinIntervals, ShortestCommonSupersequence,
StaffScheduling, StringToStringCorrection, SubsetSum, SumOfSquaresPartition,
PrecedenceConstrainedScheduling, SequencingWithReleaseTimesAndDeadlines,
SequencingWithinIntervals, ShortestCommonSupersequence, StaffScheduling,
StringToStringCorrection, SubsetSum, SumOfSquaresPartition,
};
pub use set::{
ComparativeContainment, ConsecutiveSets, ExactCoverBy3Sets, MaximumSetPacking,
Expand Down
Loading
Loading