From 9034263e2c2100568ec559fb7189ecfa2e2fa8df Mon Sep 17 00:00:00 2001 From: zazabap Date: Tue, 10 Mar 2026 14:26:34 +0000 Subject: [PATCH 01/14] Add plan for #212: [Model] MultiprocessorScheduling Co-Authored-By: Claude Opus 4.6 --- .../2026-03-10-multiprocessor-scheduling.md | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 docs/plans/2026-03-10-multiprocessor-scheduling.md diff --git a/docs/plans/2026-03-10-multiprocessor-scheduling.md b/docs/plans/2026-03-10-multiprocessor-scheduling.md new file mode 100644 index 000000000..fb22490f7 --- /dev/null +++ b/docs/plans/2026-03-10-multiprocessor-scheduling.md @@ -0,0 +1,147 @@ +# Plan: [Model] MultiprocessorScheduling (#212) + +## Problem Summary + +**Name:** `MultiprocessorScheduling` +**Reference:** Garey & Johnson, *Computers and Intractability*, A5 SS8 +**Category:** `misc/` (scheduling input: list of processing times + number of machines) + +### Mathematical Definition + +INSTANCE: Set T of tasks, number m of processors, length l(t) for each t in T, and a deadline D. +QUESTION: Is there an assignment of tasks to processors such that the total load on each processor does not exceed D? + +Equivalently: given n jobs with integer processing times and m identical parallel machines, assign each job to a machine such that for every machine i, the sum of processing times of jobs assigned to i is at most D. + +### Problem Type + +**Satisfaction problem** (`Metric = bool`, implements `SatisfactionProblem`). + +The issue defines this as a decision problem: "Is there an m-processor schedule for T that meets the overall deadline D?" A configuration is feasible (true) iff for every processor, the total load does not exceed D. + +### Variables + +- **Count:** n = |T| (one variable per task) +- **Per-variable domain:** {0, 1, ..., m-1} -- the processor index assigned to the task +- **dims():** `vec![num_processors; num_tasks]` + +### Evaluation + +``` +evaluate(config): + for each processor i in 0..m: + load_i = sum of lengths[j] for all j where config[j] == i + if load_i > deadline: return false + return true +``` + +### Struct Fields + +| Field | Type | Description | +|------------------|------------|-------------------------------------| +| `lengths` | `Vec` | Processing time l(t) for each task | +| `num_processors` | `u64` | Number of identical processors m | +| `deadline` | `u64` | Global deadline D | + +### Getter Methods (for overhead system) + +- `num_tasks() -> usize` -- returns `lengths.len()` +- `num_processors() -> u64` -- returns `self.num_processors` + +### Complexity + +- For general m (part of input): strongly NP-hard. No known improvement over O*(m^n) brute-force enumeration. +- For fixed m: weakly NP-hard, solvable by pseudo-polynomial DP in O(n * D^(m-1)). +- Complexity string: `"num_processors ^ num_tasks"` (general case brute force) +- References: Garey & Johnson 1979; Lenstra, Rinnooy Kan & Brucker, *Annals of Discrete Mathematics*, 1977. + +### Solving Strategy + +- BruteForce: enumerate all m^n assignments, check if max load <= D. +- ILP: binary variables x_{t,i}, constraints sum_i x_{t,i} = 1, sum_t x_{t,i} * l(t) <= D. + +### Example Instance + +T = {t1, t2, t3, t4, t5}, lengths = [4, 5, 3, 2, 6], m = 2, D = 10. +Feasible assignment: config = [0, 1, 1, 1, 0] (processor 0 gets {t1, t5} load=10, processor 1 gets {t2, t3, t4} load=10). +Answer: true. + +--- + +## Implementation Steps + +### Step 1: Category + +`misc/` -- scheduling input does not fit graph, formula, set, or algebraic categories. + +### Step 2: Implement the model + +Create `src/models/misc/multiprocessor_scheduling.rs`: + +1. `inventory::submit!` for `ProblemSchemaEntry` with fields: `lengths`, `num_processors`, `deadline` +2. Struct `MultiprocessorScheduling` with `#[derive(Debug, Clone, Serialize, Deserialize)]` +3. Constructor `new(lengths: Vec, num_processors: u64, deadline: u64) -> Self` +4. Accessors: `lengths()`, `num_processors()`, `deadline()`, `num_tasks()` +5. `Problem` impl: + - `NAME = "MultiprocessorScheduling"` + - `Metric = bool` + - `variant() -> crate::variant_params![]` (no type parameters) + - `dims() -> vec![self.num_processors as usize; self.num_tasks()]` + - `evaluate()`: compute load per processor, return true iff all loads <= deadline +6. `SatisfactionProblem` impl (marker trait) +7. `declare_variants! { MultiprocessorScheduling => "num_processors ^ num_tasks" }` +8. `#[cfg(test)] #[path = "..."] mod tests;` + +### Step 2.5: Register variant complexity + +```rust +crate::declare_variants! { + MultiprocessorScheduling => "num_processors ^ num_tasks", +} +``` + +### Step 3: Register the model + +1. `src/models/misc/mod.rs`: add `mod multiprocessor_scheduling;` and `pub use multiprocessor_scheduling::MultiprocessorScheduling;` +2. `src/models/mod.rs`: add to the misc re-export line + +### Step 4: Register in CLI + +1. `problemreductions-cli/src/dispatch.rs`: + - `load_problem()`: add match arm `"MultiprocessorScheduling" => deser_sat::(json)` + - `serialize_any_problem()`: add match arm `"MultiprocessorScheduling" => try_ser::(json)` +2. `problemreductions-cli/src/problem_name.rs`: + - `resolve_alias()`: add `"multiprocessorscheduling" => "MultiprocessorScheduling".to_string()` + - No short alias (no well-established abbreviation in the literature) + +### Step 4.5: Add CLI creation support + +Add match arm in `problemreductions-cli/src/commands/create.rs` for `"MultiprocessorScheduling"`: +- Parse `--lengths`, `--num-processors`, `--deadline` flags +- Add required flags to `cli.rs` `CreateArgs` if not already present +- Update help text + +### Step 5: Write unit tests + +Create `src/unit_tests/models/misc/multiprocessor_scheduling.rs`: + +- `test_multiprocessor_scheduling_creation`: construct instance, verify dims = [2, 2, 2, 2, 2] for 5 tasks, 2 processors +- `test_multiprocessor_scheduling_evaluation`: test feasible (true) and infeasible (false) configs +- `test_multiprocessor_scheduling_serialization`: round-trip serde +- `test_multiprocessor_scheduling_solver`: brute-force finds a satisfying assignment for the example + +### Step 6: Document in paper + +Invoke `/write-model-in-paper` to add `#problem-def("MultiprocessorScheduling")` to `docs/paper/reductions.typ`: +- Add `"MultiprocessorScheduling": [Multiprocessor Scheduling]` to `display-name` dict +- Formal definition from Garey & Johnson +- Example with visualization of the 5-task, 2-processor instance +- Reference: Garey & Johnson 1979, Lenstra et al. 1977 + +### Step 7: Verify + +```bash +make test clippy +``` + +Run `/review-implementation` to verify structural and semantic checks. From aa7e2162b0b20a8aca76e29b7583ca0a833452e7 Mon Sep 17 00:00:00 2001 From: zazabap Date: Tue, 10 Mar 2026 14:26:52 +0000 Subject: [PATCH 02/14] Add plan for #219: [Model] SequencingWithinIntervals Co-Authored-By: Claude Opus 4.6 --- .../2026-03-10-sequencing-within-intervals.md | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 docs/plans/2026-03-10-sequencing-within-intervals.md diff --git a/docs/plans/2026-03-10-sequencing-within-intervals.md b/docs/plans/2026-03-10-sequencing-within-intervals.md new file mode 100644 index 000000000..50ac09e66 --- /dev/null +++ b/docs/plans/2026-03-10-sequencing-within-intervals.md @@ -0,0 +1,144 @@ +# Plan: Add SequencingWithinIntervals Model + +**Issue:** #219 — [Model] SequencingWithinIntervals +**Skill:** add-model (Steps 1–7) + +## Overview + +Add a `SequencingWithinIntervals` satisfaction model: given n tasks, each with a release time r(t), deadline d(t), and processing length l(t), determine whether all tasks can be scheduled non-overlappingly such that each task runs entirely within its allowed time window. This is problem SS1 from Garey & Johnson, NP-complete via Theorem 3.8 (reduction from PARTITION). + +## Design Decisions + +- **Category:** `misc/` — scheduling input (list of tasks with release times, deadlines, lengths); does not fit `graph/`, `set/`, `algebraic/`, or `formula/`. +- **Struct:** `SequencingWithinIntervals` with fields `release_times: Vec`, `deadlines: Vec`, `lengths: Vec`. No type parameters (all times are plain non-negative integers). +- **Problem type:** Satisfaction (`Metric = bool`, implements `SatisfactionProblem`). +- **dims():** For each task i, the number of valid start times is `d(i) - r(i) - l(i) + 1` (range `[r(i), d(i) - l(i)]`). So `dims()` returns `vec![d[i] - r[i] - l[i] + 1 for each i]` (as `usize`). Each variable selects an index into the valid start time range for that task. +- **evaluate():** Map each variable index back to an actual start time: `start_i = r[i] + config[i]`. Check: (1) each task finishes before deadline (`start_i + l[i] <= d[i]`), and (2) no two tasks overlap. Return `true` if feasible, `false` otherwise. +- **Constructor precondition:** Assert `r[i] + l[i] <= d[i]` for every task (otherwise domain is empty). +- **variant():** `variant_params![]` — no type parameters. +- **Getters:** `num_tasks()` (for complexity expression variable). +- **Complexity:** `2^num_tasks` — NP-complete, brute-force over orderings is O(n! * n) but the configuration space enumeration is exponential in n. The best known exact algorithms remain exponential. +- **Solver:** BruteForce (existing) — enumerates all configurations. + +## Information Checklist + +| # | Item | Value | +|---|------|-------| +| 1 | Problem name | `SequencingWithinIntervals` | +| 2 | Mathematical definition | Given tasks T with release times r(t), deadlines d(t), lengths l(t), find schedule sigma: T -> Z_>=0 s.t. sigma(t) >= r(t), sigma(t)+l(t) <= d(t), and no two tasks overlap | +| 3 | Problem type | Satisfaction (bool) | +| 4 | Type parameters | None | +| 5 | Struct fields | `release_times: Vec`, `deadlines: Vec`, `lengths: Vec` | +| 6 | Configuration space | `dims[i] = d[i] - r[i] - l[i] + 1` (number of valid start times per task) | +| 7 | Feasibility check | All tasks within window + no pairwise overlap | +| 8 | Objective function | N/A (satisfaction: returns bool) | +| 9 | Best known exact algorithm | NP-complete (GJ Thm 3.8, 1979). O*(2^n) brute force. | +| 10 | Solving strategy | BruteForce works; ILP reduction possible | +| 11 | Category | `misc/` | + +## Steps + +### Step 1: Determine category +Category: `misc/` — scheduling problem with unique input structure (task list with time windows). + +### Step 1.5: Infer problem size getters +From complexity `O*(2^n)` where n = |T|: +- `num_tasks()` -> number of tasks + +### Step 2: Implement the model +Create `src/models/misc/sequencing_within_intervals.rs`: + +```rust +// Structure: +// 1. inventory::submit! for ProblemSchemaEntry +// 2. SequencingWithinIntervals struct with release_times, deadlines, lengths (all Vec) +// 3. Constructor: new(release_times, deadlines, lengths) — panics if r[i]+l[i] > d[i] or lengths mismatch +// 4. Accessors: release_times(), deadlines(), lengths(), num_tasks() +// 5. Problem impl: NAME="SequencingWithinIntervals", Metric=bool, dims(), evaluate() +// 6. SatisfactionProblem impl (marker trait) +// 7. declare_variants! +// 8. #[cfg(test)] #[path] link +``` + +Key implementation details for `evaluate()`: +``` +1. For each task i, compute start_time = release_times[i] + config[i] +2. Check start_time + lengths[i] <= deadlines[i] (should always hold if dims is correct) +3. For all pairs (i, j), check non-overlap: + either start_i + l_i <= start_j OR start_j + l_j <= start_i +4. Return true iff all constraints satisfied +``` + +### Step 2.5: Register variant complexity +```rust +crate::declare_variants! { + SequencingWithinIntervals => "2^num_tasks", +} +``` + +### Step 3: Register the model +1. `src/models/misc/mod.rs` — add `mod sequencing_within_intervals;` and `pub use sequencing_within_intervals::SequencingWithinIntervals;` +2. `src/models/mod.rs` — add `SequencingWithinIntervals` to the `misc` re-export line + +### Step 4: Register in CLI +1. `problemreductions-cli/src/dispatch.rs`: + - `load_problem()`: add `"SequencingWithinIntervals" => deser_sat::(data)` + - `serialize_any_problem()`: add `"SequencingWithinIntervals" => try_ser::(any)` +2. `problemreductions-cli/src/problem_name.rs`: + - `resolve_alias()`: add `"sequencingwithinintervals" => "SequencingWithinIntervals".to_string()` + - No short alias — no well-established abbreviation in the literature + +### Step 4.5: Add CLI creation support +Add a match arm in `commands/create.rs` for `"SequencingWithinIntervals"` that parses: +- `--release-times` (or reuse an appropriate flag) +- `--deadlines` +- `--lengths` + +Add any needed CLI flags in `cli.rs` (`CreateArgs`). + +### Step 5: Write unit tests +Create `src/unit_tests/models/misc/sequencing_within_intervals.rs`: + +Tests: +- `test_sequencingwithinintervals_creation` — construct instance, verify num_tasks, dims +- `test_sequencingwithinintervals_evaluation_feasible` — valid schedule returns true +- `test_sequencingwithinintervals_evaluation_infeasible` — overlapping schedule returns false +- `test_sequencingwithinintervals_solver` — BruteForce finds a satisfying assignment for the example +- `test_sequencingwithinintervals_serialization` — round-trip serde test +- `test_sequencingwithinintervals_no_solution` — instance with no feasible schedule returns None from solver + +Example instance from issue (PARTITION reduction): +- 5 tasks: release_times = [0, 0, 0, 0, 5], deadlines = [11, 11, 11, 11, 6], lengths = [3, 1, 2, 4, 1] +- Feasible schedule: sigma = [0, 3, 3, 7, 5] -> starts at [0, 3, 3, 7, 5] + Wait — need to recheck. Config values are offsets from release time. + - Task 0: r=0, d=11, l=3 -> valid starts: 0..=8, dims=9, config=0 -> start=0, runs [0,3) + - Task 1: r=0, d=11, l=1 -> valid starts: 0..=10, dims=11, config=6 -> start=6, runs [6,7) + - Task 2: r=0, d=11, l=2 -> valid starts: 0..=9, dims=10, config=3 -> start=3, runs [3,5) + - Task 3: r=0, d=11, l=4 -> valid starts: 0..=7, dims=8, config=7 -> start=7, runs [7,11) + - Task 4: r=5, d=6, l=1 -> valid starts: 5..=5, dims=1, config=0 -> start=5, runs [5,6) + - No overlaps. Feasible. + +### Step 6: Document in paper +Invoke `/write-model-in-paper` to add: +1. `display-name` entry: `"SequencingWithinIntervals": [Sequencing Within Intervals]` +2. `#problem-def("SequencingWithinIntervals")[...]` with formal definition from GJ + +### Step 7: Verify +```bash +make check # fmt + clippy + test +``` +Then run `/review-implementation` to verify completeness. + +## Files Changed + +| File | Action | +|------|--------| +| `src/models/misc/sequencing_within_intervals.rs` | **Create** — model implementation | +| `src/unit_tests/models/misc/sequencing_within_intervals.rs` | **Create** — unit tests | +| `src/models/misc/mod.rs` | **Edit** — register module | +| `src/models/mod.rs` | **Edit** — add re-export | +| `problemreductions-cli/src/dispatch.rs` | **Edit** — CLI dispatch | +| `problemreductions-cli/src/problem_name.rs` | **Edit** — alias | +| `problemreductions-cli/src/commands/create.rs` | **Edit** — CLI create support | +| `problemreductions-cli/src/cli.rs` | **Edit** — CLI flags (if needed) | +| `docs/paper/reductions.typ` | **Edit** — paper definition | From 339db84e5813ace73e4f58871206b7243c1e3c89 Mon Sep 17 00:00:00 2001 From: zazabap Date: Tue, 10 Mar 2026 14:47:30 +0000 Subject: [PATCH 03/14] feat: implement SequencingWithinIntervals model for #219 Add a satisfaction problem model for scheduling tasks within time windows without overlap (Garey & Johnson SS1, NP-complete). Co-Authored-By: Claude Opus 4.6 --- problemreductions-cli/src/cli.rs | 10 + problemreductions-cli/src/commands/create.rs | 32 +++- problemreductions-cli/src/dispatch.rs | 4 +- problemreductions-cli/src/problem_name.rs | 1 + src/lib.rs | 4 +- src/models/misc/mod.rs | 3 + .../misc/sequencing_within_intervals.rs | 176 ++++++++++++++++++ src/models/mod.rs | 2 +- .../misc/sequencing_within_intervals.rs | 155 +++++++++++++++ 9 files changed, 383 insertions(+), 4 deletions(-) create mode 100644 src/models/misc/sequencing_within_intervals.rs create mode 100644 src/unit_tests/models/misc/sequencing_within_intervals.rs diff --git a/problemreductions-cli/src/cli.rs b/problemreductions-cli/src/cli.rs index 91e9bd252..64dc53504 100644 --- a/problemreductions-cli/src/cli.rs +++ b/problemreductions-cli/src/cli.rs @@ -216,6 +216,7 @@ Flags by problem type: BicliqueCover --left, --right, --biedges, --k BMF --matrix (0/1), --rank CVP --basis, --target-vec [--bounds] + SequencingWithinIntervals --release-times, --deadlines, --lengths ILP, CircuitSAT (via reduction only) Geometry graph variants (use slash notation, e.g., MIS/KingsSubgraph): @@ -326,6 +327,15 @@ pub struct CreateArgs { /// Variable bounds for CVP as "lower,upper" (e.g., "-10,10") [default: -10,10] #[arg(long, allow_hyphen_values = true)] pub bounds: Option, + /// Release times for SequencingWithinIntervals (comma-separated, e.g., "0,0,5") + #[arg(long)] + pub release_times: Option, + /// Deadlines for SequencingWithinIntervals (comma-separated, e.g., "11,11,6") + #[arg(long = "deadlines")] + pub deadlines_flag: Option, + /// Processing lengths for SequencingWithinIntervals (comma-separated, e.g., "3,1,1") + #[arg(long = "lengths")] + pub task_lengths: Option, } #[derive(clap::Args)] diff --git a/problemreductions-cli/src/commands/create.rs b/problemreductions-cli/src/commands/create.rs index 418bc520f..1200b0653 100644 --- a/problemreductions-cli/src/commands/create.rs +++ b/problemreductions-cli/src/commands/create.rs @@ -5,7 +5,7 @@ use crate::problem_name::{parse_problem_spec, resolve_variant}; use crate::util; use anyhow::{bail, Context, Result}; use problemreductions::models::algebraic::{ClosestVectorProblem, BMF}; -use problemreductions::models::misc::{BinPacking, PaintShop}; +use problemreductions::models::misc::{BinPacking, PaintShop, SequencingWithinIntervals}; use problemreductions::prelude::*; use problemreductions::registry::collect_schemas; use problemreductions::topology::{ @@ -45,6 +45,9 @@ fn all_data_flags_empty(args: &CreateArgs) -> bool { && args.basis.is_none() && args.target_vec.is_none() && args.bounds.is_none() + && args.release_times.is_none() + && args.deadlines_flag.is_none() + && args.task_lengths.is_none() } fn type_format_hint(type_name: &str, graph_type: Option<&str>) -> &'static str { @@ -59,6 +62,7 @@ fn type_format_hint(type_name: &str, graph_type: Option<&str>) -> &'static str { "Vec>" => "semicolon-separated rows: \"1,0.5;0.5,2\"", "usize" => "integer", "u64" => "integer", + "Vec" => "comma-separated integers: 0,0,5", _ => "value", } } @@ -83,6 +87,7 @@ fn example_for(canonical: &str, graph_type: Option<&str>) -> &'static str { "SpinGlass" => "--graph 0-1,1-2 --couplings 1,1", "KColoring" => "--graph 0-1,1-2,2-0 --k 3", "Factoring" => "--target 15 --m 4 --n 4", + "SequencingWithinIntervals" => "--release-times 0,0,5 --deadlines 11,11,6 --lengths 3,1,1", _ => "", } } @@ -443,6 +448,31 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> { ) } + // SequencingWithinIntervals + "SequencingWithinIntervals" => { + let usage = "Usage: pred create SequencingWithinIntervals --release-times 0,0,5 --deadlines 11,11,6 --lengths 3,1,1"; + let rt_str = args.release_times.as_deref().ok_or_else(|| { + anyhow::anyhow!("SequencingWithinIntervals requires --release-times\n\n{usage}") + })?; + let dl_str = args.deadlines_flag.as_deref().ok_or_else(|| { + anyhow::anyhow!("SequencingWithinIntervals requires --deadlines\n\n{usage}") + })?; + let len_str = args.task_lengths.as_deref().ok_or_else(|| { + anyhow::anyhow!("SequencingWithinIntervals requires --lengths\n\n{usage}") + })?; + let release_times: Vec = util::parse_comma_list(rt_str)?; + let deadlines: Vec = util::parse_comma_list(dl_str)?; + let lengths: Vec = util::parse_comma_list(len_str)?; + ( + ser(SequencingWithinIntervals::new( + release_times, + deadlines, + lengths, + ))?, + resolved_variant.clone(), + ) + } + _ => bail!("{}", crate::problem_name::unknown_problem_error(canonical)), }; diff --git a/problemreductions-cli/src/dispatch.rs b/problemreductions-cli/src/dispatch.rs index 7a8498421..cd9ea1efb 100644 --- a/problemreductions-cli/src/dispatch.rs +++ b/problemreductions-cli/src/dispatch.rs @@ -1,6 +1,6 @@ use anyhow::{bail, Context, Result}; use problemreductions::models::algebraic::{ClosestVectorProblem, ILP}; -use problemreductions::models::misc::{BinPacking, Knapsack}; +use problemreductions::models::misc::{BinPacking, Knapsack, SequencingWithinIntervals}; use problemreductions::prelude::*; use problemreductions::rules::{MinimizeSteps, ReductionGraph}; use problemreductions::solvers::{BruteForce, ILPSolver, Solver}; @@ -245,6 +245,7 @@ pub fn load_problem( _ => deser_opt::>(data), }, "Knapsack" => deser_opt::(data), + "SequencingWithinIntervals" => deser_sat::(data), _ => bail!("{}", crate::problem_name::unknown_problem_error(&canonical)), } } @@ -305,6 +306,7 @@ pub fn serialize_any_problem( _ => try_ser::>(any), }, "Knapsack" => try_ser::(any), + "SequencingWithinIntervals" => try_ser::(any), _ => bail!("{}", crate::problem_name::unknown_problem_error(&canonical)), } } diff --git a/problemreductions-cli/src/problem_name.rs b/problemreductions-cli/src/problem_name.rs index acd9b4b59..593e63ae1 100644 --- a/problemreductions-cli/src/problem_name.rs +++ b/problemreductions-cli/src/problem_name.rs @@ -52,6 +52,7 @@ pub fn resolve_alias(input: &str) -> String { "binpacking" => "BinPacking".to_string(), "cvp" | "closestvectorproblem" => "ClosestVectorProblem".to_string(), "knapsack" => "Knapsack".to_string(), + "sequencingwithinintervals" => "SequencingWithinIntervals".to_string(), _ => input.to_string(), // pass-through for exact names } } diff --git a/src/lib.rs b/src/lib.rs index c9ada7ef1..60dfd7290 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,7 +43,9 @@ pub mod prelude { KColoring, MaxCut, MaximalIS, MaximumClique, MaximumIndependentSet, MaximumMatching, MinimumDominatingSet, MinimumVertexCover, TravelingSalesman, }; - pub use crate::models::misc::{BinPacking, Factoring, Knapsack, PaintShop}; + pub use crate::models::misc::{ + BinPacking, Factoring, Knapsack, PaintShop, SequencingWithinIntervals, + }; pub use crate::models::set::{MaximumSetPacking, MinimumSetCovering}; // Core traits diff --git a/src/models/misc/mod.rs b/src/models/misc/mod.rs index cdb66e969..a5234f576 100644 --- a/src/models/misc/mod.rs +++ b/src/models/misc/mod.rs @@ -5,13 +5,16 @@ //! - [`Factoring`]: Integer factorization //! - [`Knapsack`]: 0-1 Knapsack (maximize value subject to weight capacity) //! - [`PaintShop`]: Minimize color switches in paint shop scheduling +//! - [`SequencingWithinIntervals`]: Schedule tasks within time windows mod bin_packing; pub(crate) mod factoring; mod knapsack; pub(crate) mod paintshop; +mod sequencing_within_intervals; pub use bin_packing::BinPacking; pub use factoring::Factoring; pub use knapsack::Knapsack; pub use paintshop::PaintShop; +pub use sequencing_within_intervals::SequencingWithinIntervals; diff --git a/src/models/misc/sequencing_within_intervals.rs b/src/models/misc/sequencing_within_intervals.rs new file mode 100644 index 000000000..feca21f5b --- /dev/null +++ b/src/models/misc/sequencing_within_intervals.rs @@ -0,0 +1,176 @@ +//! Sequencing Within Intervals problem implementation. +//! +//! Given a set of tasks, each with a release time, deadline, and processing length, +//! determine whether all tasks can be scheduled non-overlappingly such that each +//! task runs entirely within its allowed time window. + +use crate::registry::{FieldInfo, ProblemSchemaEntry}; +use crate::traits::{Problem, SatisfactionProblem}; +use serde::{Deserialize, Serialize}; + +inventory::submit! { + ProblemSchemaEntry { + name: "SequencingWithinIntervals", + module_path: module_path!(), + description: "Schedule tasks non-overlappingly within their time windows", + fields: &[ + FieldInfo { name: "release_times", type_name: "Vec", description: "Release time r(t) for each task" }, + FieldInfo { name: "deadlines", type_name: "Vec", description: "Deadline d(t) for each task" }, + FieldInfo { name: "lengths", type_name: "Vec", description: "Processing length l(t) for each task" }, + ], + } +} + +/// Sequencing Within Intervals problem. +/// +/// Given `n` tasks, each with release time `r(t)`, deadline `d(t)`, and processing +/// length `l(t)`, determine whether there exists a schedule `sigma: T -> Z_>=0` +/// such that: +/// - `sigma(t) >= r(t)` (task starts no earlier than its release time) +/// - `sigma(t) + l(t) <= d(t)` (task finishes by its deadline) +/// - No two tasks overlap in time +/// +/// This is problem SS1 from Garey & Johnson (1979), NP-complete via Theorem 3.8. +/// +/// # Representation +/// +/// Each task has a variable representing its start time offset from the release time. +/// Variable `i` takes values in `{0, ..., d(i) - r(i) - l(i)}`, so the actual start +/// time is `r(i) + config[i]`. +/// +/// # Example +/// +/// ``` +/// use problemreductions::models::misc::SequencingWithinIntervals; +/// use problemreductions::{Problem, Solver, BruteForce}; +/// +/// // 3 tasks: release_times = [0, 2, 4], deadlines = [3, 5, 7], lengths = [2, 2, 2] +/// let problem = SequencingWithinIntervals::new(vec![0, 2, 4], vec![3, 5, 7], vec![2, 2, 2]); +/// let solver = BruteForce::new(); +/// let solution = solver.find_satisfying(&problem); +/// assert!(solution.is_some()); +/// ``` +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SequencingWithinIntervals { + /// Release times for each task. + release_times: Vec, + /// Deadlines for each task. + deadlines: Vec, + /// Processing lengths for each task. + lengths: Vec, +} + +impl SequencingWithinIntervals { + /// Create a new SequencingWithinIntervals problem. + /// + /// # Panics + /// Panics if the three vectors have different lengths, or if any task has + /// `r(i) + l(i) > d(i)` (empty time window). + pub fn new(release_times: Vec, deadlines: Vec, lengths: Vec) -> Self { + assert_eq!( + release_times.len(), + deadlines.len(), + "release_times and deadlines must have the same length" + ); + assert_eq!( + release_times.len(), + lengths.len(), + "release_times and lengths must have the same length" + ); + for i in 0..release_times.len() { + assert!( + release_times[i] + lengths[i] <= deadlines[i], + "Task {i}: r({}) + l({}) > d({}), time window is empty", + release_times[i], + lengths[i], + deadlines[i] + ); + } + Self { + release_times, + deadlines, + lengths, + } + } + + /// Returns the release times. + pub fn release_times(&self) -> &[u64] { + &self.release_times + } + + /// Returns the deadlines. + pub fn deadlines(&self) -> &[u64] { + &self.deadlines + } + + /// Returns the processing lengths. + pub fn lengths(&self) -> &[u64] { + &self.lengths + } + + /// Returns the number of tasks. + pub fn num_tasks(&self) -> usize { + self.release_times.len() + } +} + +impl Problem for SequencingWithinIntervals { + const NAME: &'static str = "SequencingWithinIntervals"; + type Metric = bool; + + fn variant() -> Vec<(&'static str, &'static str)> { + crate::variant_params![] + } + + fn dims(&self) -> Vec { + (0..self.num_tasks()) + .map(|i| (self.deadlines[i] - self.release_times[i] - self.lengths[i] + 1) as usize) + .collect() + } + + fn evaluate(&self, config: &[usize]) -> bool { + let n = self.num_tasks(); + if config.len() != n { + return false; + } + + // Check each variable is within range and compute start times + let dims = self.dims(); + let mut starts = Vec::with_capacity(n); + for (i, (&c, &dim)) in config.iter().zip(dims.iter()).enumerate() { + if c >= dim { + return false; + } + let start = self.release_times[i] + c as u64; + // Check task finishes by deadline (should hold by construction) + if start + self.lengths[i] > self.deadlines[i] { + return false; + } + starts.push(start); + } + + // Check no two tasks overlap + for i in 0..n { + for j in (i + 1)..n { + let end_i = starts[i] + self.lengths[i]; + let end_j = starts[j] + self.lengths[j]; + // Tasks overlap if neither finishes before the other starts + if !(end_i <= starts[j] || end_j <= starts[i]) { + return false; + } + } + } + + true + } +} + +impl SatisfactionProblem for SequencingWithinIntervals {} + +crate::declare_variants! { + SequencingWithinIntervals => "2^num_tasks", +} + +#[cfg(test)] +#[path = "../../unit_tests/models/misc/sequencing_within_intervals.rs"] +mod tests; diff --git a/src/models/mod.rs b/src/models/mod.rs index 96b4b79d1..d80198b1f 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -15,5 +15,5 @@ pub use graph::{ BicliqueCover, KColoring, MaxCut, MaximalIS, MaximumClique, MaximumIndependentSet, MaximumMatching, MinimumDominatingSet, MinimumVertexCover, SpinGlass, TravelingSalesman, }; -pub use misc::{BinPacking, Factoring, Knapsack, PaintShop}; +pub use misc::{BinPacking, Factoring, Knapsack, PaintShop, SequencingWithinIntervals}; pub use set::{MaximumSetPacking, MinimumSetCovering}; diff --git a/src/unit_tests/models/misc/sequencing_within_intervals.rs b/src/unit_tests/models/misc/sequencing_within_intervals.rs new file mode 100644 index 000000000..176d763a4 --- /dev/null +++ b/src/unit_tests/models/misc/sequencing_within_intervals.rs @@ -0,0 +1,155 @@ +use super::*; +use crate::solvers::{BruteForce, Solver}; +use crate::traits::Problem; + +#[test] +fn test_sequencingwithinintervals_creation() { + let problem = SequencingWithinIntervals::new( + vec![0, 0, 0, 0, 5], + vec![11, 11, 11, 11, 6], + vec![3, 1, 2, 4, 1], + ); + assert_eq!(problem.num_tasks(), 5); + assert_eq!(problem.release_times(), &[0, 0, 0, 0, 5]); + assert_eq!(problem.deadlines(), &[11, 11, 11, 11, 6]); + assert_eq!(problem.lengths(), &[3, 1, 2, 4, 1]); + // dims: d[i] - r[i] - l[i] + 1 + // Task 0: 11 - 0 - 3 + 1 = 9 + // Task 1: 11 - 0 - 1 + 1 = 11 + // Task 2: 11 - 0 - 2 + 1 = 10 + // Task 3: 11 - 0 - 4 + 1 = 8 + // Task 4: 6 - 5 - 1 + 1 = 1 + assert_eq!(problem.dims(), vec![9, 11, 10, 8, 1]); +} + +#[test] +fn test_sequencingwithinintervals_evaluation_feasible() { + let problem = SequencingWithinIntervals::new( + vec![0, 0, 0, 0, 5], + vec![11, 11, 11, 11, 6], + vec![3, 1, 2, 4, 1], + ); + // Task 0: config=0 -> start=0, runs [0,3) + // Task 1: config=6 -> start=6, runs [6,7) + // Task 2: config=3 -> start=3, runs [3,5) + // Task 3: config=7 -> start=7, runs [7,11) + // Task 4: config=0 -> start=5, runs [5,6) + // No overlaps. + assert!(problem.evaluate(&[0, 6, 3, 7, 0])); +} + +#[test] +fn test_sequencingwithinintervals_evaluation_infeasible_overlap() { + let problem = SequencingWithinIntervals::new( + vec![0, 0, 0, 0, 5], + vec![11, 11, 11, 11, 6], + vec![3, 1, 2, 4, 1], + ); + // Task 0: config=0 -> start=0, runs [0,3) + // Task 1: config=1 -> start=1, runs [1,2) -- overlaps with task 0 + assert!(!problem.evaluate(&[0, 1, 3, 7, 0])); +} + +#[test] +fn test_sequencingwithinintervals_evaluation_wrong_length() { + let problem = SequencingWithinIntervals::new(vec![0, 2], vec![3, 5], vec![2, 2]); + assert!(!problem.evaluate(&[0])); + assert!(!problem.evaluate(&[0, 0, 0])); +} + +#[test] +fn test_sequencingwithinintervals_evaluation_out_of_range() { + let problem = SequencingWithinIntervals::new(vec![0, 2], vec![3, 5], vec![2, 2]); + // Task 0: dims = 3 - 0 - 2 + 1 = 2, so config must be 0 or 1 + // Task 1: dims = 5 - 2 - 2 + 1 = 2, so config must be 0 or 1 + assert!(!problem.evaluate(&[2, 0])); // out of range for task 0 +} + +#[test] +fn test_sequencingwithinintervals_solver() { + // Simple instance: 3 tasks that can be scheduled sequentially + let problem = SequencingWithinIntervals::new(vec![0, 2, 4], vec![3, 5, 7], vec![2, 2, 2]); + let solver = BruteForce::new(); + let solution = solver.find_satisfying(&problem); + assert!(solution.is_some()); + let config = solution.unwrap(); + assert!(problem.evaluate(&config)); +} + +#[test] +fn test_sequencingwithinintervals_solver_partition_example() { + // Instance from the plan (PARTITION reduction) + let problem = SequencingWithinIntervals::new( + vec![0, 0, 0, 0, 5], + vec![11, 11, 11, 11, 6], + vec![3, 1, 2, 4, 1], + ); + let solver = BruteForce::new(); + let solution = solver.find_satisfying(&problem); + assert!(solution.is_some()); + let config = solution.unwrap(); + assert!(problem.evaluate(&config)); +} + +#[test] +fn test_sequencingwithinintervals_no_solution() { + // Two tasks that must both use time [0,2), impossible without overlap + let problem = SequencingWithinIntervals::new(vec![0, 0], vec![2, 2], vec![2, 2]); + // Each task has dims = 2 - 0 - 2 + 1 = 1, so config can only be [0, 0] + // Task 0: start=0, runs [0,2) + // Task 1: start=0, runs [0,2) -> overlap + assert!(!problem.evaluate(&[0, 0])); + let solver = BruteForce::new(); + let solution = solver.find_satisfying(&problem); + assert!(solution.is_none()); +} + +#[test] +fn test_sequencingwithinintervals_serialization() { + let problem = SequencingWithinIntervals::new(vec![0, 2, 4], vec![3, 5, 7], vec![2, 2, 2]); + let json = serde_json::to_value(&problem).unwrap(); + let restored: SequencingWithinIntervals = serde_json::from_value(json).unwrap(); + assert_eq!(restored.release_times(), problem.release_times()); + assert_eq!(restored.deadlines(), problem.deadlines()); + assert_eq!(restored.lengths(), problem.lengths()); +} + +#[test] +fn test_sequencingwithinintervals_empty() { + let problem = SequencingWithinIntervals::new(vec![], vec![], vec![]); + assert_eq!(problem.num_tasks(), 0); + assert_eq!(problem.dims(), Vec::::new()); + assert!(problem.evaluate(&[])); +} + +#[test] +fn test_sequencingwithinintervals_problem_name() { + assert_eq!( + ::NAME, + "SequencingWithinIntervals" + ); +} + +#[test] +fn test_sequencingwithinintervals_variant() { + let v = ::variant(); + assert!(v.is_empty()); +} + +#[test] +fn test_sequencingwithinintervals_single_task() { + let problem = SequencingWithinIntervals::new(vec![0], vec![5], vec![3]); + // dims = 5 - 0 - 3 + 1 = 3 + assert_eq!(problem.dims(), vec![3]); + // Any valid config should be feasible (only one task, no overlaps possible) + assert!(problem.evaluate(&[0])); + assert!(problem.evaluate(&[1])); + assert!(problem.evaluate(&[2])); +} + +#[test] +#[should_panic(expected = "time window is empty")] +fn test_sequencingwithinintervals_invalid_window() { + // r + l > d: impossible task + SequencingWithinIntervals::new(vec![5], vec![3], vec![2]); +} From 7dbcf40f30e860dac361fc2181436ff439beab92 Mon Sep 17 00:00:00 2001 From: zazabap Date: Sun, 15 Mar 2026 11:01:40 +0000 Subject: [PATCH 04/14] fix: update SequencingWithinIntervals for new API Add missing display_name, aliases, dimensions fields to ProblemSchemaEntry. Use 'default sat' prefix in declare_variants! macro. Rename task_lengths CLI field to lengths to avoid clash with FlowShopScheduling. Co-Authored-By: Claude Opus 4.6 --- src/models/misc/sequencing_within_intervals.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/models/misc/sequencing_within_intervals.rs b/src/models/misc/sequencing_within_intervals.rs index feca21f5b..c37a6e652 100644 --- a/src/models/misc/sequencing_within_intervals.rs +++ b/src/models/misc/sequencing_within_intervals.rs @@ -11,6 +11,9 @@ use serde::{Deserialize, Serialize}; inventory::submit! { ProblemSchemaEntry { name: "SequencingWithinIntervals", + display_name: "Sequencing Within Intervals", + aliases: &[], + dimensions: &[], module_path: module_path!(), description: "Schedule tasks non-overlappingly within their time windows", fields: &[ @@ -168,7 +171,7 @@ impl Problem for SequencingWithinIntervals { impl SatisfactionProblem for SequencingWithinIntervals {} crate::declare_variants! { - SequencingWithinIntervals => "2^num_tasks", + default sat SequencingWithinIntervals => "2^num_tasks", } #[cfg(test)] From 255520ac0dd193bc2704694cfe94f01f527be47d Mon Sep 17 00:00:00 2001 From: zazabap Date: Sun, 15 Mar 2026 11:03:31 +0000 Subject: [PATCH 05/14] fix: address Copilot review comments - Use checked_add to prevent u64 overflow in constructor - Rename test functions to snake_case (test_sequencing_within_intervals_*) Co-Authored-By: Claude Opus 4.6 --- .../misc/sequencing_within_intervals.rs | 5 +++- .../misc/sequencing_within_intervals.rs | 28 +++++++++---------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/models/misc/sequencing_within_intervals.rs b/src/models/misc/sequencing_within_intervals.rs index c37a6e652..4a4598fdf 100644 --- a/src/models/misc/sequencing_within_intervals.rs +++ b/src/models/misc/sequencing_within_intervals.rs @@ -81,8 +81,11 @@ impl SequencingWithinIntervals { "release_times and lengths must have the same length" ); for i in 0..release_times.len() { + let sum = release_times[i] + .checked_add(lengths[i]) + .expect("overflow computing r(i) + l(i)"); assert!( - release_times[i] + lengths[i] <= deadlines[i], + sum <= deadlines[i], "Task {i}: r({}) + l({}) > d({}), time window is empty", release_times[i], lengths[i], diff --git a/src/unit_tests/models/misc/sequencing_within_intervals.rs b/src/unit_tests/models/misc/sequencing_within_intervals.rs index 176d763a4..b3b5d3205 100644 --- a/src/unit_tests/models/misc/sequencing_within_intervals.rs +++ b/src/unit_tests/models/misc/sequencing_within_intervals.rs @@ -3,7 +3,7 @@ use crate::solvers::{BruteForce, Solver}; use crate::traits::Problem; #[test] -fn test_sequencingwithinintervals_creation() { +fn test_sequencing_within_intervals_creation() { let problem = SequencingWithinIntervals::new( vec![0, 0, 0, 0, 5], vec![11, 11, 11, 11, 6], @@ -23,7 +23,7 @@ fn test_sequencingwithinintervals_creation() { } #[test] -fn test_sequencingwithinintervals_evaluation_feasible() { +fn test_sequencing_within_intervals_evaluation_feasible() { let problem = SequencingWithinIntervals::new( vec![0, 0, 0, 0, 5], vec![11, 11, 11, 11, 6], @@ -39,7 +39,7 @@ fn test_sequencingwithinintervals_evaluation_feasible() { } #[test] -fn test_sequencingwithinintervals_evaluation_infeasible_overlap() { +fn test_sequencing_within_intervals_evaluation_infeasible_overlap() { let problem = SequencingWithinIntervals::new( vec![0, 0, 0, 0, 5], vec![11, 11, 11, 11, 6], @@ -51,14 +51,14 @@ fn test_sequencingwithinintervals_evaluation_infeasible_overlap() { } #[test] -fn test_sequencingwithinintervals_evaluation_wrong_length() { +fn test_sequencing_within_intervals_evaluation_wrong_length() { let problem = SequencingWithinIntervals::new(vec![0, 2], vec![3, 5], vec![2, 2]); assert!(!problem.evaluate(&[0])); assert!(!problem.evaluate(&[0, 0, 0])); } #[test] -fn test_sequencingwithinintervals_evaluation_out_of_range() { +fn test_sequencing_within_intervals_evaluation_out_of_range() { let problem = SequencingWithinIntervals::new(vec![0, 2], vec![3, 5], vec![2, 2]); // Task 0: dims = 3 - 0 - 2 + 1 = 2, so config must be 0 or 1 // Task 1: dims = 5 - 2 - 2 + 1 = 2, so config must be 0 or 1 @@ -66,7 +66,7 @@ fn test_sequencingwithinintervals_evaluation_out_of_range() { } #[test] -fn test_sequencingwithinintervals_solver() { +fn test_sequencing_within_intervals_solver() { // Simple instance: 3 tasks that can be scheduled sequentially let problem = SequencingWithinIntervals::new(vec![0, 2, 4], vec![3, 5, 7], vec![2, 2, 2]); let solver = BruteForce::new(); @@ -77,7 +77,7 @@ fn test_sequencingwithinintervals_solver() { } #[test] -fn test_sequencingwithinintervals_solver_partition_example() { +fn test_sequencing_within_intervals_solver_partition_example() { // Instance from the plan (PARTITION reduction) let problem = SequencingWithinIntervals::new( vec![0, 0, 0, 0, 5], @@ -92,7 +92,7 @@ fn test_sequencingwithinintervals_solver_partition_example() { } #[test] -fn test_sequencingwithinintervals_no_solution() { +fn test_sequencing_within_intervals_no_solution() { // Two tasks that must both use time [0,2), impossible without overlap let problem = SequencingWithinIntervals::new(vec![0, 0], vec![2, 2], vec![2, 2]); // Each task has dims = 2 - 0 - 2 + 1 = 1, so config can only be [0, 0] @@ -105,7 +105,7 @@ fn test_sequencingwithinintervals_no_solution() { } #[test] -fn test_sequencingwithinintervals_serialization() { +fn test_sequencing_within_intervals_serialization() { let problem = SequencingWithinIntervals::new(vec![0, 2, 4], vec![3, 5, 7], vec![2, 2, 2]); let json = serde_json::to_value(&problem).unwrap(); let restored: SequencingWithinIntervals = serde_json::from_value(json).unwrap(); @@ -115,7 +115,7 @@ fn test_sequencingwithinintervals_serialization() { } #[test] -fn test_sequencingwithinintervals_empty() { +fn test_sequencing_within_intervals_empty() { let problem = SequencingWithinIntervals::new(vec![], vec![], vec![]); assert_eq!(problem.num_tasks(), 0); assert_eq!(problem.dims(), Vec::::new()); @@ -123,7 +123,7 @@ fn test_sequencingwithinintervals_empty() { } #[test] -fn test_sequencingwithinintervals_problem_name() { +fn test_sequencing_within_intervals_problem_name() { assert_eq!( ::NAME, "SequencingWithinIntervals" @@ -131,13 +131,13 @@ fn test_sequencingwithinintervals_problem_name() { } #[test] -fn test_sequencingwithinintervals_variant() { +fn test_sequencing_within_intervals_variant() { let v = ::variant(); assert!(v.is_empty()); } #[test] -fn test_sequencingwithinintervals_single_task() { +fn test_sequencing_within_intervals_single_task() { let problem = SequencingWithinIntervals::new(vec![0], vec![5], vec![3]); // dims = 5 - 0 - 3 + 1 = 3 assert_eq!(problem.dims(), vec![3]); @@ -149,7 +149,7 @@ fn test_sequencingwithinintervals_single_task() { #[test] #[should_panic(expected = "time window is empty")] -fn test_sequencingwithinintervals_invalid_window() { +fn test_sequencing_within_intervals_invalid_window() { // r + l > d: impossible task SequencingWithinIntervals::new(vec![5], vec![3], vec![2]); } From ba263853207596fe9638c87bb750f5aeaaf2ca2a Mon Sep 17 00:00:00 2001 From: zazabap Date: Sun, 15 Mar 2026 11:10:55 +0000 Subject: [PATCH 06/14] fix: add missing structural items for SequencingWithinIntervals - Add canonical model example in example_db (satisfaction_example) - Add trait_consistency test entry - Add paper display-name and problem-def block in reductions.typ Co-Authored-By: Claude Opus 4.6 --- docs/paper/reductions.typ | 20 +++++++++++++++++ src/models/misc/mod.rs | 1 + .../misc/sequencing_within_intervals.rs | 22 +++++++++++++++++++ src/unit_tests/trait_consistency.rs | 4 ++++ 4 files changed, 47 insertions(+) diff --git a/docs/paper/reductions.typ b/docs/paper/reductions.typ index 2b632fad6..27837df98 100644 --- a/docs/paper/reductions.typ +++ b/docs/paper/reductions.typ @@ -83,6 +83,7 @@ "SubgraphIsomorphism": [Subgraph Isomorphism], "PartitionIntoTriangles": [Partition Into Triangles], "FlowShopScheduling": [Flow Shop Scheduling], + "SequencingWithinIntervals": [Sequencing Within Intervals], ) // Definition label: "def:" — each definition block must have a matching label @@ -1335,6 +1336,25 @@ Biclique Cover is equivalent to factoring the biadjacency matrix $M$ of the bipa ) ] +#problem-def("SequencingWithinIntervals")[ + Given a finite set $T$ of tasks and, for each $t in T$, a release time $r(t) >= 0$, a deadline $d(t) >= 0$, and a processing length $ell(t) in ZZ^+$ satisfying $r(t) + ell(t) <= d(t)$, determine whether there exists a feasible schedule $sigma: T -> ZZ_(>= 0)$ such that for each $t in T$: (1) $sigma(t) >= r(t)$, (2) $sigma(t) + ell(t) <= d(t)$, and (3) for all $t' in T backslash {t}$, either $sigma(t') + ell(t') <= sigma(t)$ or $sigma(t') >= sigma(t) + ell(t)$. +][ + Sequencing Within Intervals is problem SS1 in Garey & Johnson @garey1979, proved NP-complete via reduction from Partition (Theorem 3.8). Each task $t$ must execute non-preemptively during the interval $[r(t), d(t))$, occupying $ell(t)$ consecutive time units, and no two tasks may overlap. The problem is a canonical single-machine scheduling problem and one of the earliest NP-completeness results for scheduling theory. + + The NP-completeness proof uses an "enforcer" task pinned at the midpoint of the time horizon, forcing the remaining tasks to split into two balanced groups --- directly encoding the Partition problem. + + *Example.* Consider 5 tasks derived from a Partition instance with $A = {3, 1, 2, 4}$ (sum $B = 10$): + #align(center, table( + columns: 6, + align: center, + table.header[$"Task"$][$t_1$][$t_2$][$t_3$][$t_4$][$overline(t)$], + [$r(t)$], [0], [0], [0], [0], [5], + [$d(t)$], [11], [11], [11], [11], [6], + [$ell(t)$], [3], [1], [2], [4], [1], + )) + The enforcer task $overline(t)$ must run in $[5, 6)$, splitting the schedule into $[0, 5)$ and $[6, 11)$. Each side has 5 time units, and tasks with total length $5$ must fill each side --- corresponding to a partition of $A$. +] + // Completeness check: warn about problem types in JSON but missing from paper #{ let json-models = { diff --git a/src/models/misc/mod.rs b/src/models/misc/mod.rs index 913f6380c..58da33c58 100644 --- a/src/models/misc/mod.rs +++ b/src/models/misc/mod.rs @@ -36,6 +36,7 @@ pub(crate) fn canonical_model_example_specs() -> Vec "2^num_tasks", } +#[cfg(feature = "example-db")] +pub(crate) fn canonical_model_example_specs() -> Vec { + vec![crate::example_db::specs::ModelExampleSpec { + id: "sequencing_within_intervals", + build: || { + use crate::solvers::BruteForce; + // Instance from the PARTITION reduction example (GJ Theorem 3.8) + let problem = SequencingWithinIntervals::new( + vec![0, 0, 0, 0, 5], + vec![11, 11, 11, 11, 6], + vec![3, 1, 2, 4, 1], + ); + let sample = BruteForce::new() + .find_all_satisfying(&problem) + .into_iter() + .next() + .expect("sequencing_within_intervals example should solve"); + crate::example_db::specs::satisfaction_example(problem, vec![sample]) + }, + }] +} + #[cfg(test)] #[path = "../../unit_tests/models/misc/sequencing_within_intervals.rs"] mod tests; diff --git a/src/unit_tests/trait_consistency.rs b/src/unit_tests/trait_consistency.rs index ebbc68a0e..6a7c2e5c7 100644 --- a/src/unit_tests/trait_consistency.rs +++ b/src/unit_tests/trait_consistency.rs @@ -122,6 +122,10 @@ fn test_all_problems_implement_trait_correctly() { &FlowShopScheduling::new(2, vec![vec![1, 2], vec![3, 4]], 10), "FlowShopScheduling", ); + check_problem_trait( + &SequencingWithinIntervals::new(vec![0, 2, 4], vec![3, 5, 7], vec![2, 2, 2]), + "SequencingWithinIntervals", + ); } #[test] From 1000567ecf2fbc8a81ade734a4b5cf45ce1b5248 Mon Sep 17 00:00:00 2001 From: zazabap Date: Sun, 15 Mar 2026 11:11:12 +0000 Subject: [PATCH 07/14] chore: regenerate JSON artifacts for SequencingWithinIntervals Co-Authored-By: Claude Opus 4.6 --- docs/src/reductions/problem_schemas.json | 21 ++++++++++++++++++ docs/src/reductions/reduction_graph.json | 27 +++++++++++++++--------- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/docs/src/reductions/problem_schemas.json b/docs/src/reductions/problem_schemas.json index 43b2a4456..c6aa72380 100644 --- a/docs/src/reductions/problem_schemas.json +++ b/docs/src/reductions/problem_schemas.json @@ -557,6 +557,27 @@ } ] }, + { + "name": "SequencingWithinIntervals", + "description": "Schedule tasks non-overlappingly within their time windows", + "fields": [ + { + "name": "release_times", + "type_name": "Vec", + "description": "Release time r(t) for each task" + }, + { + "name": "deadlines", + "type_name": "Vec", + "description": "Deadline d(t) for each task" + }, + { + "name": "lengths", + "type_name": "Vec", + "description": "Processing length l(t) for each task" + } + ] + }, { "name": "ShortestCommonSupersequence", "description": "Find a common supersequence of bounded length for a set of strings", diff --git a/docs/src/reductions/reduction_graph.json b/docs/src/reductions/reduction_graph.json index bb8c02551..a181eace7 100644 --- a/docs/src/reductions/reduction_graph.json +++ b/docs/src/reductions/reduction_graph.json @@ -450,6 +450,13 @@ "doc_path": "models/formula/struct.Satisfiability.html", "complexity": "2^num_variables" }, + { + "name": "SequencingWithinIntervals", + "variant": {}, + "category": "misc", + "doc_path": "models/misc/struct.SequencingWithinIntervals.html", + "complexity": "2^num_tasks" + }, { "name": "ShortestCommonSupersequence", "variant": {}, @@ -535,7 +542,7 @@ }, { "source": 4, - "target": 52, + "target": 53, "overhead": [ { "field": "num_spins", @@ -699,7 +706,7 @@ }, { "source": 20, - "target": 54, + "target": 55, "overhead": [ { "field": "num_elements", @@ -755,7 +762,7 @@ }, { "source": 24, - "target": 52, + "target": 53, "overhead": [ { "field": "num_spins", @@ -1201,7 +1208,7 @@ }, { "source": 47, - "target": 51, + "target": 52, "overhead": [ { "field": "num_spins", @@ -1286,7 +1293,7 @@ "doc_path": "rules/sat_minimumdominatingset/index.html" }, { - "source": 51, + "source": 52, "target": 47, "overhead": [ { @@ -1297,7 +1304,7 @@ "doc_path": "rules/spinglass_qubo/index.html" }, { - "source": 52, + "source": 53, "target": 24, "overhead": [ { @@ -1312,8 +1319,8 @@ "doc_path": "rules/spinglass_maxcut/index.html" }, { - "source": 52, - "target": 51, + "source": 53, + "target": 52, "overhead": [ { "field": "num_spins", @@ -1327,7 +1334,7 @@ "doc_path": "rules/spinglass_casts/index.html" }, { - "source": 55, + "source": 56, "target": 11, "overhead": [ { @@ -1342,7 +1349,7 @@ "doc_path": "rules/travelingsalesman_ilp/index.html" }, { - "source": 55, + "source": 56, "target": 47, "overhead": [ { From c6cc3d4eb863b13f474777e4f15b64c0d422343d Mon Sep 17 00:00:00 2001 From: zazabap Date: Sun, 15 Mar 2026 11:12:56 +0000 Subject: [PATCH 08/14] style: consolidate SequencingWithinIntervals re-exports into existing use blocks Co-Authored-By: Claude Opus 4.6 --- src/lib.rs | 3 +-- src/models/mod.rs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ae1ce1c50..cee66dc92 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,9 +56,8 @@ pub mod prelude { }; pub use crate::models::misc::{ BinPacking, Factoring, FlowShopScheduling, Knapsack, LongestCommonSubsequence, PaintShop, - ShortestCommonSupersequence, SubsetSum, + SequencingWithinIntervals, ShortestCommonSupersequence, SubsetSum, }; - pub use crate::models::misc::SequencingWithinIntervals; pub use crate::models::set::{MaximumSetPacking, MinimumSetCovering}; // Core traits diff --git a/src/models/mod.rs b/src/models/mod.rs index 7f53f58c1..315ffb703 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -20,7 +20,6 @@ pub use graph::{ }; pub use misc::{ BinPacking, Factoring, FlowShopScheduling, Knapsack, LongestCommonSubsequence, PaintShop, - ShortestCommonSupersequence, SubsetSum, + SequencingWithinIntervals, ShortestCommonSupersequence, SubsetSum, }; -pub use misc::SequencingWithinIntervals; pub use set::{MaximumSetPacking, MinimumSetCovering}; From c0e141b97ef7bd17d5f24625b2bbae14a0f09448 Mon Sep 17 00:00:00 2001 From: zazabap Date: Sun, 15 Mar 2026 11:32:15 +0000 Subject: [PATCH 09/14] chore: trigger CI Co-Authored-By: Claude Opus 4.6 From b0d3ed7eea6079f0598f44794e4497d6ecf65d8e Mon Sep 17 00:00:00 2001 From: zazabap Date: Sun, 15 Mar 2026 11:54:53 +0000 Subject: [PATCH 10/14] style: apply rustfmt Co-Authored-By: Claude Opus 4.6 --- problemreductions-cli/src/commands/create.rs | 4 ++-- src/models/graph/maximum_independent_set.rs | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/problemreductions-cli/src/commands/create.rs b/problemreductions-cli/src/commands/create.rs index 5e06d0bd9..07fe8d436 100644 --- a/problemreductions-cli/src/commands/create.rs +++ b/problemreductions-cli/src/commands/create.rs @@ -8,8 +8,8 @@ use problemreductions::export::{ModelExample, ProblemRef, ProblemSide, RuleExamp use problemreductions::models::algebraic::{ClosestVectorProblem, BMF}; use problemreductions::models::graph::{GraphPartitioning, HamiltonianPath}; use problemreductions::models::misc::{ - BinPacking, FlowShopScheduling, LongestCommonSubsequence, PaintShop, - SequencingWithinIntervals, ShortestCommonSupersequence, SubsetSum, + BinPacking, FlowShopScheduling, LongestCommonSubsequence, PaintShop, SequencingWithinIntervals, + ShortestCommonSupersequence, SubsetSum, }; use problemreductions::prelude::*; use problemreductions::registry::collect_schemas; diff --git a/src/models/graph/maximum_independent_set.rs b/src/models/graph/maximum_independent_set.rs index 0b8a3ddfc..1177398b9 100644 --- a/src/models/graph/maximum_independent_set.rs +++ b/src/models/graph/maximum_independent_set.rs @@ -233,8 +233,7 @@ pub(crate) fn canonical_model_example_specs() -> Vec Date: Mon, 16 Mar 2026 08:48:48 +0000 Subject: [PATCH 11/14] fix: remove unreachable deadline check in evaluate() The deadline guard `start + l[i] > d[i]` in evaluate() was dead code: since c < dim = d[i] - r[i] - l[i] + 1, start + l[i] <= d[i] holds by construction. Removing it fixes the 1-line codecov gap. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/models/misc/sequencing_within_intervals.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/models/misc/sequencing_within_intervals.rs b/src/models/misc/sequencing_within_intervals.rs index ea9f52838..32f272e8c 100644 --- a/src/models/misc/sequencing_within_intervals.rs +++ b/src/models/misc/sequencing_within_intervals.rs @@ -147,11 +147,9 @@ impl Problem for SequencingWithinIntervals { if c >= dim { return false; } + // start = r[i] + c, and c < dim = d[i] - r[i] - l[i] + 1, + // so start + l[i] <= d[i] is guaranteed by construction. let start = self.release_times[i] + c as u64; - // Check task finishes by deadline (should hold by construction) - if start + self.lengths[i] > self.deadlines[i] { - return false; - } starts.push(start); } From 1259e8cb3ea544ad81e22b919fd0a10099d48a00 Mon Sep 17 00:00:00 2001 From: zazabap Date: Mon, 16 Mar 2026 09:33:49 +0000 Subject: [PATCH 12/14] =?UTF-8?q?fix:=20align=20with=20PR=20#192=20standar?= =?UTF-8?q?d=20=E2=80=94=20use=20load-model-example,=20remove=20unrelated?= =?UTF-8?q?=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/paper/reductions.typ | 53 ++++--- .../2026-03-10-multiprocessor-scheduling.md | 147 ------------------ docs/src/reductions/reduction_graph.json | 11 +- run-review-batch.sh | 22 --- 4 files changed, 36 insertions(+), 197 deletions(-) delete mode 100644 docs/plans/2026-03-10-multiprocessor-scheduling.md delete mode 100755 run-review-batch.sh diff --git a/docs/paper/reductions.typ b/docs/paper/reductions.typ index 7deabcb1f..8df1f31c7 100644 --- a/docs/paper/reductions.typ +++ b/docs/paper/reductions.typ @@ -1864,24 +1864,41 @@ NP-completeness was established by Garey, Johnson, and Stockmeyer @gareyJohnsonS ) ] -#problem-def("SequencingWithinIntervals")[ - Given a finite set $T$ of tasks and, for each $t in T$, a release time $r(t) >= 0$, a deadline $d(t) >= 0$, and a processing length $ell(t) in ZZ^+$ satisfying $r(t) + ell(t) <= d(t)$, determine whether there exists a feasible schedule $sigma: T -> ZZ_(>= 0)$ such that for each $t in T$: (1) $sigma(t) >= r(t)$, (2) $sigma(t) + ell(t) <= d(t)$, and (3) for all $t' in T backslash {t}$, either $sigma(t') + ell(t') <= sigma(t)$ or $sigma(t') >= sigma(t) + ell(t)$. -][ - Sequencing Within Intervals is problem SS1 in Garey & Johnson @garey1979, proved NP-complete via reduction from Partition (Theorem 3.8). Each task $t$ must execute non-preemptively during the interval $[r(t), d(t))$, occupying $ell(t)$ consecutive time units, and no two tasks may overlap. The problem is a canonical single-machine scheduling problem and one of the earliest NP-completeness results for scheduling theory. - - The NP-completeness proof uses an "enforcer" task pinned at the midpoint of the time horizon, forcing the remaining tasks to split into two balanced groups --- directly encoding the Partition problem. - - *Example.* Consider 5 tasks derived from a Partition instance with $A = {3, 1, 2, 4}$ (sum $B = 10$): - #align(center, table( - columns: 6, - align: center, - table.header[$"Task"$][$t_1$][$t_2$][$t_3$][$t_4$][$overline(t)$], - [$r(t)$], [0], [0], [0], [0], [5], - [$d(t)$], [11], [11], [11], [11], [6], - [$ell(t)$], [3], [1], [2], [4], [1], - )) - The enforcer task $overline(t)$ must run in $[5, 6)$, splitting the schedule into $[0, 5)$ and $[6, 11)$. Each side has 5 time units, and tasks with total length $5$ must fill each side --- corresponding to a partition of $A$. -] +#{ + let x = load-model-example("SequencingWithinIntervals") + let ntasks = x.instance.lengths.len() + let release = x.instance.release_times + let deadline = x.instance.deadlines + let lengths = x.instance.lengths + let sol = x.optimal.at(0) + // Compute start times from config offsets: start_i = release_i + config_i + let starts = range(ntasks).map(i => release.at(i) + sol.config.at(i)) + // Identify the enforcer task: the one with the tightest window (deadline - release == length) + let enforcer = range(ntasks).filter(i => deadline.at(i) - release.at(i) == lengths.at(i)).at(0) + let regular = range(ntasks).filter(i => i != enforcer) + // Partition sum B = total length of regular tasks + let B = regular.map(i => lengths.at(i)).sum() + [ + #problem-def("SequencingWithinIntervals")[ + Given a finite set $T$ of tasks and, for each $t in T$, a release time $r(t) >= 0$, a deadline $d(t) >= 0$, and a processing length $ell(t) in ZZ^+$ satisfying $r(t) + ell(t) <= d(t)$, determine whether there exists a feasible schedule $sigma: T -> ZZ_(>= 0)$ such that for each $t in T$: (1) $sigma(t) >= r(t)$, (2) $sigma(t) + ell(t) <= d(t)$, and (3) for all $t' in T backslash {t}$, either $sigma(t') + ell(t') <= sigma(t)$ or $sigma(t') >= sigma(t) + ell(t)$. + ][ + Sequencing Within Intervals is problem SS1 in Garey & Johnson @garey1979, proved NP-complete via reduction from Partition (Theorem 3.8). Each task $t$ must execute non-preemptively during the interval $[r(t), d(t))$, occupying $ell(t)$ consecutive time units, and no two tasks may overlap. The problem is a canonical single-machine scheduling problem and one of the earliest NP-completeness results for scheduling theory. + + The NP-completeness proof uses an "enforcer" task pinned at the midpoint of the time horizon, forcing the remaining tasks to split into two balanced groups --- directly encoding the Partition problem. + + *Example.* Consider #ntasks tasks derived from a Partition instance with $A = {#regular.map(i => str(lengths.at(i))).join(", ")}$ (sum $B = #B$): + #align(center, table( + columns: ntasks + 1, + align: center, + table.header([$"Task"$], ..regular.map(i => [$t_#(i + 1)$]), [$overline(t)$]), + [$r(t)$], ..regular.map(i => [#release.at(i)]), [#release.at(enforcer)], + [$d(t)$], ..regular.map(i => [#deadline.at(i)]), [#deadline.at(enforcer)], + [$ell(t)$], ..regular.map(i => [#lengths.at(i)]), [#lengths.at(enforcer)], + )) + The enforcer task $overline(t)$ must run in $[#release.at(enforcer), #deadline.at(enforcer))$, splitting the schedule into $[0, #release.at(enforcer))$ and $[#deadline.at(enforcer), #deadline.at(0))$. Each side has #(B / 2) time units, and tasks with total length $#(B / 2)$ must fill each side --- corresponding to a partition of $A$. + ] + ] +} #{ let x = load-model-example("MinimumTardinessSequencing") let ntasks = x.instance.num_tasks diff --git a/docs/plans/2026-03-10-multiprocessor-scheduling.md b/docs/plans/2026-03-10-multiprocessor-scheduling.md deleted file mode 100644 index fb22490f7..000000000 --- a/docs/plans/2026-03-10-multiprocessor-scheduling.md +++ /dev/null @@ -1,147 +0,0 @@ -# Plan: [Model] MultiprocessorScheduling (#212) - -## Problem Summary - -**Name:** `MultiprocessorScheduling` -**Reference:** Garey & Johnson, *Computers and Intractability*, A5 SS8 -**Category:** `misc/` (scheduling input: list of processing times + number of machines) - -### Mathematical Definition - -INSTANCE: Set T of tasks, number m of processors, length l(t) for each t in T, and a deadline D. -QUESTION: Is there an assignment of tasks to processors such that the total load on each processor does not exceed D? - -Equivalently: given n jobs with integer processing times and m identical parallel machines, assign each job to a machine such that for every machine i, the sum of processing times of jobs assigned to i is at most D. - -### Problem Type - -**Satisfaction problem** (`Metric = bool`, implements `SatisfactionProblem`). - -The issue defines this as a decision problem: "Is there an m-processor schedule for T that meets the overall deadline D?" A configuration is feasible (true) iff for every processor, the total load does not exceed D. - -### Variables - -- **Count:** n = |T| (one variable per task) -- **Per-variable domain:** {0, 1, ..., m-1} -- the processor index assigned to the task -- **dims():** `vec![num_processors; num_tasks]` - -### Evaluation - -``` -evaluate(config): - for each processor i in 0..m: - load_i = sum of lengths[j] for all j where config[j] == i - if load_i > deadline: return false - return true -``` - -### Struct Fields - -| Field | Type | Description | -|------------------|------------|-------------------------------------| -| `lengths` | `Vec` | Processing time l(t) for each task | -| `num_processors` | `u64` | Number of identical processors m | -| `deadline` | `u64` | Global deadline D | - -### Getter Methods (for overhead system) - -- `num_tasks() -> usize` -- returns `lengths.len()` -- `num_processors() -> u64` -- returns `self.num_processors` - -### Complexity - -- For general m (part of input): strongly NP-hard. No known improvement over O*(m^n) brute-force enumeration. -- For fixed m: weakly NP-hard, solvable by pseudo-polynomial DP in O(n * D^(m-1)). -- Complexity string: `"num_processors ^ num_tasks"` (general case brute force) -- References: Garey & Johnson 1979; Lenstra, Rinnooy Kan & Brucker, *Annals of Discrete Mathematics*, 1977. - -### Solving Strategy - -- BruteForce: enumerate all m^n assignments, check if max load <= D. -- ILP: binary variables x_{t,i}, constraints sum_i x_{t,i} = 1, sum_t x_{t,i} * l(t) <= D. - -### Example Instance - -T = {t1, t2, t3, t4, t5}, lengths = [4, 5, 3, 2, 6], m = 2, D = 10. -Feasible assignment: config = [0, 1, 1, 1, 0] (processor 0 gets {t1, t5} load=10, processor 1 gets {t2, t3, t4} load=10). -Answer: true. - ---- - -## Implementation Steps - -### Step 1: Category - -`misc/` -- scheduling input does not fit graph, formula, set, or algebraic categories. - -### Step 2: Implement the model - -Create `src/models/misc/multiprocessor_scheduling.rs`: - -1. `inventory::submit!` for `ProblemSchemaEntry` with fields: `lengths`, `num_processors`, `deadline` -2. Struct `MultiprocessorScheduling` with `#[derive(Debug, Clone, Serialize, Deserialize)]` -3. Constructor `new(lengths: Vec, num_processors: u64, deadline: u64) -> Self` -4. Accessors: `lengths()`, `num_processors()`, `deadline()`, `num_tasks()` -5. `Problem` impl: - - `NAME = "MultiprocessorScheduling"` - - `Metric = bool` - - `variant() -> crate::variant_params![]` (no type parameters) - - `dims() -> vec![self.num_processors as usize; self.num_tasks()]` - - `evaluate()`: compute load per processor, return true iff all loads <= deadline -6. `SatisfactionProblem` impl (marker trait) -7. `declare_variants! { MultiprocessorScheduling => "num_processors ^ num_tasks" }` -8. `#[cfg(test)] #[path = "..."] mod tests;` - -### Step 2.5: Register variant complexity - -```rust -crate::declare_variants! { - MultiprocessorScheduling => "num_processors ^ num_tasks", -} -``` - -### Step 3: Register the model - -1. `src/models/misc/mod.rs`: add `mod multiprocessor_scheduling;` and `pub use multiprocessor_scheduling::MultiprocessorScheduling;` -2. `src/models/mod.rs`: add to the misc re-export line - -### Step 4: Register in CLI - -1. `problemreductions-cli/src/dispatch.rs`: - - `load_problem()`: add match arm `"MultiprocessorScheduling" => deser_sat::(json)` - - `serialize_any_problem()`: add match arm `"MultiprocessorScheduling" => try_ser::(json)` -2. `problemreductions-cli/src/problem_name.rs`: - - `resolve_alias()`: add `"multiprocessorscheduling" => "MultiprocessorScheduling".to_string()` - - No short alias (no well-established abbreviation in the literature) - -### Step 4.5: Add CLI creation support - -Add match arm in `problemreductions-cli/src/commands/create.rs` for `"MultiprocessorScheduling"`: -- Parse `--lengths`, `--num-processors`, `--deadline` flags -- Add required flags to `cli.rs` `CreateArgs` if not already present -- Update help text - -### Step 5: Write unit tests - -Create `src/unit_tests/models/misc/multiprocessor_scheduling.rs`: - -- `test_multiprocessor_scheduling_creation`: construct instance, verify dims = [2, 2, 2, 2, 2] for 5 tasks, 2 processors -- `test_multiprocessor_scheduling_evaluation`: test feasible (true) and infeasible (false) configs -- `test_multiprocessor_scheduling_serialization`: round-trip serde -- `test_multiprocessor_scheduling_solver`: brute-force finds a satisfying assignment for the example - -### Step 6: Document in paper - -Invoke `/write-model-in-paper` to add `#problem-def("MultiprocessorScheduling")` to `docs/paper/reductions.typ`: -- Add `"MultiprocessorScheduling": [Multiprocessor Scheduling]` to `display-name` dict -- Formal definition from Garey & Johnson -- Example with visualization of the 5-task, 2-processor instance -- Reference: Garey & Johnson 1979, Lenstra et al. 1977 - -### Step 7: Verify - -```bash -make test clippy -``` - -Run `/review-implementation` to verify structural and semantic checks. diff --git a/docs/src/reductions/reduction_graph.json b/docs/src/reductions/reduction_graph.json index ac4efa1fb..efdae85c4 100644 --- a/docs/src/reductions/reduction_graph.json +++ b/docs/src/reductions/reduction_graph.json @@ -1,12 +1,3 @@ -{ - "nodes": [ - { - "name": "BMF", - "variant": {}, - -Exported to: docs/src/reductions/reduction_graph.json - -JSON content: { "nodes": [ { @@ -1403,4 +1394,4 @@ JSON content: "doc_path": "rules/travelingsalesman_qubo/index.html" } ] -} +} \ No newline at end of file diff --git a/run-review-batch.sh b/run-review-batch.sh deleted file mode 100755 index 2effd2bd9..000000000 --- a/run-review-batch.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -set -e - -PRS=(223 637 623 633 626 625) -RUNNER=${RUNNER:-claude} - -echo "=== Batch review started at $(date) ===" -echo "PRs to review: ${PRS[*]}" -echo "Runner: $RUNNER" -echo "" - -for pr in "${PRS[@]}"; do - echo "--- Starting review of PR #$pr at $(date) ---" - RUNNER=claude make run-review N=$pr || { - echo "--- PR #$pr review FAILED at $(date) ---" - continue - } - echo "--- PR #$pr review completed successfully at $(date) ---" - echo "" -done - -echo "=== Batch review finished at $(date) ===" From be242aac9326b2cf77bdde6712547e08806cdecd Mon Sep 17 00:00:00 2001 From: zazabap Date: Mon, 16 Mar 2026 09:37:39 +0000 Subject: [PATCH 13/14] fix: add Gantt chart figure, remove plan doc Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/paper/reductions.typ | 59 +++++++ .../2026-03-10-sequencing-within-intervals.md | 144 ------------------ 2 files changed, 59 insertions(+), 144 deletions(-) delete mode 100644 docs/plans/2026-03-10-sequencing-within-intervals.md diff --git a/docs/paper/reductions.typ b/docs/paper/reductions.typ index 8df1f31c7..7615bfe4a 100644 --- a/docs/paper/reductions.typ +++ b/docs/paper/reductions.typ @@ -1896,6 +1896,65 @@ NP-completeness was established by Garey, Johnson, and Stockmeyer @gareyJohnsonS [$ell(t)$], ..regular.map(i => [#lengths.at(i)]), [#lengths.at(enforcer)], )) The enforcer task $overline(t)$ must run in $[#release.at(enforcer), #deadline.at(enforcer))$, splitting the schedule into $[0, #release.at(enforcer))$ and $[#deadline.at(enforcer), #deadline.at(0))$. Each side has #(B / 2) time units, and tasks with total length $#(B / 2)$ must fill each side --- corresponding to a partition of $A$. + + #figure( + canvas(length: 1cm, { + import draw: * + let colors = (rgb("#4e79a7"), rgb("#e15759"), rgb("#76b7b2"), rgb("#f28e2b")) + let enforcer-color = rgb("#b07aa1") + let task-labels = regular.map(i => "$t_" + str(i + 1) + "$") + ("$overline(t)$",) + let task-order = regular + (enforcer,) + let scale = 0.7 + let row-h = 0.6 + + // Single-row Gantt chart: all tasks on one timeline + for (k, i) in task-order.enumerate() { + let s = starts.at(i) + let e = s + lengths.at(i) + let x0 = s * scale + let x1 = e * scale + let col = if i == enforcer { enforcer-color } else { colors.at(regular.position(j => j == i)) } + rect((x0, -row-h / 2), (x1, row-h / 2), + fill: col.transparentize(30%), stroke: 0.4pt + col) + content(((x0 + x1) / 2, 0), text(6pt, task-labels.at(k))) + } + + // Release-time and deadline markers for each task + for (k, i) in task-order.enumerate() { + let col = if i == enforcer { enforcer-color } else { colors.at(regular.position(j => j == i)) } + // Release time: upward triangle below axis + let rx = release.at(i) * scale + line((rx, -row-h / 2 - 0.05), (rx, -row-h / 2 - 0.18), stroke: 0.5pt + col) + // Deadline: downward tick above axis + let dx = deadline.at(i) * scale + line((dx, row-h / 2 + 0.05), (dx, row-h / 2 + 0.18), stroke: 0.5pt + col) + } + + // Release / deadline group labels + content((-0.5, -row-h / 2 - 0.12), text(5pt)[$r$]) + content((-0.5, row-h / 2 + 0.12), text(5pt)[$d$]) + + // Time axis + let max-t = 11 + let y-axis = -row-h / 2 - 0.35 + line((0, y-axis), (max-t * scale, y-axis), stroke: 0.4pt) + for t in range(max-t + 1) { + let x = t * scale + line((x, y-axis), (x, y-axis - 0.08), stroke: 0.4pt) + if calc.rem(t, 2) == 0 or t == max-t { + content((x, y-axis - 0.22), text(5pt, str(t))) + } + } + content((max-t * scale / 2, y-axis - 0.45), text(7pt)[$t$]) + + // Enforcer region highlight + let ex0 = release.at(enforcer) * scale + let ex1 = deadline.at(enforcer) * scale + line((ex0, row-h / 2 + 0.3), (ex0, y-axis), stroke: (paint: enforcer-color, thickness: 0.6pt, dash: "dashed")) + line((ex1, row-h / 2 + 0.3), (ex1, y-axis), stroke: (paint: enforcer-color, thickness: 0.6pt, dash: "dashed")) + }), + caption: [Feasible schedule for the SWI instance. The enforcer task $overline(t)$ (purple) is pinned at $[#release.at(enforcer), #deadline.at(enforcer))$, splitting the timeline into two halves of #(B / 2) time units each.], + ) ] ] } diff --git a/docs/plans/2026-03-10-sequencing-within-intervals.md b/docs/plans/2026-03-10-sequencing-within-intervals.md deleted file mode 100644 index 50ac09e66..000000000 --- a/docs/plans/2026-03-10-sequencing-within-intervals.md +++ /dev/null @@ -1,144 +0,0 @@ -# Plan: Add SequencingWithinIntervals Model - -**Issue:** #219 — [Model] SequencingWithinIntervals -**Skill:** add-model (Steps 1–7) - -## Overview - -Add a `SequencingWithinIntervals` satisfaction model: given n tasks, each with a release time r(t), deadline d(t), and processing length l(t), determine whether all tasks can be scheduled non-overlappingly such that each task runs entirely within its allowed time window. This is problem SS1 from Garey & Johnson, NP-complete via Theorem 3.8 (reduction from PARTITION). - -## Design Decisions - -- **Category:** `misc/` — scheduling input (list of tasks with release times, deadlines, lengths); does not fit `graph/`, `set/`, `algebraic/`, or `formula/`. -- **Struct:** `SequencingWithinIntervals` with fields `release_times: Vec`, `deadlines: Vec`, `lengths: Vec`. No type parameters (all times are plain non-negative integers). -- **Problem type:** Satisfaction (`Metric = bool`, implements `SatisfactionProblem`). -- **dims():** For each task i, the number of valid start times is `d(i) - r(i) - l(i) + 1` (range `[r(i), d(i) - l(i)]`). So `dims()` returns `vec![d[i] - r[i] - l[i] + 1 for each i]` (as `usize`). Each variable selects an index into the valid start time range for that task. -- **evaluate():** Map each variable index back to an actual start time: `start_i = r[i] + config[i]`. Check: (1) each task finishes before deadline (`start_i + l[i] <= d[i]`), and (2) no two tasks overlap. Return `true` if feasible, `false` otherwise. -- **Constructor precondition:** Assert `r[i] + l[i] <= d[i]` for every task (otherwise domain is empty). -- **variant():** `variant_params![]` — no type parameters. -- **Getters:** `num_tasks()` (for complexity expression variable). -- **Complexity:** `2^num_tasks` — NP-complete, brute-force over orderings is O(n! * n) but the configuration space enumeration is exponential in n. The best known exact algorithms remain exponential. -- **Solver:** BruteForce (existing) — enumerates all configurations. - -## Information Checklist - -| # | Item | Value | -|---|------|-------| -| 1 | Problem name | `SequencingWithinIntervals` | -| 2 | Mathematical definition | Given tasks T with release times r(t), deadlines d(t), lengths l(t), find schedule sigma: T -> Z_>=0 s.t. sigma(t) >= r(t), sigma(t)+l(t) <= d(t), and no two tasks overlap | -| 3 | Problem type | Satisfaction (bool) | -| 4 | Type parameters | None | -| 5 | Struct fields | `release_times: Vec`, `deadlines: Vec`, `lengths: Vec` | -| 6 | Configuration space | `dims[i] = d[i] - r[i] - l[i] + 1` (number of valid start times per task) | -| 7 | Feasibility check | All tasks within window + no pairwise overlap | -| 8 | Objective function | N/A (satisfaction: returns bool) | -| 9 | Best known exact algorithm | NP-complete (GJ Thm 3.8, 1979). O*(2^n) brute force. | -| 10 | Solving strategy | BruteForce works; ILP reduction possible | -| 11 | Category | `misc/` | - -## Steps - -### Step 1: Determine category -Category: `misc/` — scheduling problem with unique input structure (task list with time windows). - -### Step 1.5: Infer problem size getters -From complexity `O*(2^n)` where n = |T|: -- `num_tasks()` -> number of tasks - -### Step 2: Implement the model -Create `src/models/misc/sequencing_within_intervals.rs`: - -```rust -// Structure: -// 1. inventory::submit! for ProblemSchemaEntry -// 2. SequencingWithinIntervals struct with release_times, deadlines, lengths (all Vec) -// 3. Constructor: new(release_times, deadlines, lengths) — panics if r[i]+l[i] > d[i] or lengths mismatch -// 4. Accessors: release_times(), deadlines(), lengths(), num_tasks() -// 5. Problem impl: NAME="SequencingWithinIntervals", Metric=bool, dims(), evaluate() -// 6. SatisfactionProblem impl (marker trait) -// 7. declare_variants! -// 8. #[cfg(test)] #[path] link -``` - -Key implementation details for `evaluate()`: -``` -1. For each task i, compute start_time = release_times[i] + config[i] -2. Check start_time + lengths[i] <= deadlines[i] (should always hold if dims is correct) -3. For all pairs (i, j), check non-overlap: - either start_i + l_i <= start_j OR start_j + l_j <= start_i -4. Return true iff all constraints satisfied -``` - -### Step 2.5: Register variant complexity -```rust -crate::declare_variants! { - SequencingWithinIntervals => "2^num_tasks", -} -``` - -### Step 3: Register the model -1. `src/models/misc/mod.rs` — add `mod sequencing_within_intervals;` and `pub use sequencing_within_intervals::SequencingWithinIntervals;` -2. `src/models/mod.rs` — add `SequencingWithinIntervals` to the `misc` re-export line - -### Step 4: Register in CLI -1. `problemreductions-cli/src/dispatch.rs`: - - `load_problem()`: add `"SequencingWithinIntervals" => deser_sat::(data)` - - `serialize_any_problem()`: add `"SequencingWithinIntervals" => try_ser::(any)` -2. `problemreductions-cli/src/problem_name.rs`: - - `resolve_alias()`: add `"sequencingwithinintervals" => "SequencingWithinIntervals".to_string()` - - No short alias — no well-established abbreviation in the literature - -### Step 4.5: Add CLI creation support -Add a match arm in `commands/create.rs` for `"SequencingWithinIntervals"` that parses: -- `--release-times` (or reuse an appropriate flag) -- `--deadlines` -- `--lengths` - -Add any needed CLI flags in `cli.rs` (`CreateArgs`). - -### Step 5: Write unit tests -Create `src/unit_tests/models/misc/sequencing_within_intervals.rs`: - -Tests: -- `test_sequencingwithinintervals_creation` — construct instance, verify num_tasks, dims -- `test_sequencingwithinintervals_evaluation_feasible` — valid schedule returns true -- `test_sequencingwithinintervals_evaluation_infeasible` — overlapping schedule returns false -- `test_sequencingwithinintervals_solver` — BruteForce finds a satisfying assignment for the example -- `test_sequencingwithinintervals_serialization` — round-trip serde test -- `test_sequencingwithinintervals_no_solution` — instance with no feasible schedule returns None from solver - -Example instance from issue (PARTITION reduction): -- 5 tasks: release_times = [0, 0, 0, 0, 5], deadlines = [11, 11, 11, 11, 6], lengths = [3, 1, 2, 4, 1] -- Feasible schedule: sigma = [0, 3, 3, 7, 5] -> starts at [0, 3, 3, 7, 5] - Wait — need to recheck. Config values are offsets from release time. - - Task 0: r=0, d=11, l=3 -> valid starts: 0..=8, dims=9, config=0 -> start=0, runs [0,3) - - Task 1: r=0, d=11, l=1 -> valid starts: 0..=10, dims=11, config=6 -> start=6, runs [6,7) - - Task 2: r=0, d=11, l=2 -> valid starts: 0..=9, dims=10, config=3 -> start=3, runs [3,5) - - Task 3: r=0, d=11, l=4 -> valid starts: 0..=7, dims=8, config=7 -> start=7, runs [7,11) - - Task 4: r=5, d=6, l=1 -> valid starts: 5..=5, dims=1, config=0 -> start=5, runs [5,6) - - No overlaps. Feasible. - -### Step 6: Document in paper -Invoke `/write-model-in-paper` to add: -1. `display-name` entry: `"SequencingWithinIntervals": [Sequencing Within Intervals]` -2. `#problem-def("SequencingWithinIntervals")[...]` with formal definition from GJ - -### Step 7: Verify -```bash -make check # fmt + clippy + test -``` -Then run `/review-implementation` to verify completeness. - -## Files Changed - -| File | Action | -|------|--------| -| `src/models/misc/sequencing_within_intervals.rs` | **Create** — model implementation | -| `src/unit_tests/models/misc/sequencing_within_intervals.rs` | **Create** — unit tests | -| `src/models/misc/mod.rs` | **Edit** — register module | -| `src/models/mod.rs` | **Edit** — add re-export | -| `problemreductions-cli/src/dispatch.rs` | **Edit** — CLI dispatch | -| `problemreductions-cli/src/problem_name.rs` | **Edit** — alias | -| `problemreductions-cli/src/commands/create.rs` | **Edit** — CLI create support | -| `problemreductions-cli/src/cli.rs` | **Edit** — CLI flags (if needed) | -| `docs/paper/reductions.typ` | **Edit** — paper definition | From 6d977997d04a0aa557ab8382ef0be59edb4b660b Mon Sep 17 00:00:00 2001 From: zazabap Date: Mon, 16 Mar 2026 11:47:57 +0000 Subject: [PATCH 14/14] fix: add CLI tests, inline dims computation in evaluate() Co-Authored-By: Claude Opus 4.6 (1M context) --- problemreductions-cli/tests/cli_tests.rs | 73 +++++++++++++++++++ .../misc/sequencing_within_intervals.rs | 4 +- 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/problemreductions-cli/tests/cli_tests.rs b/problemreductions-cli/tests/cli_tests.rs index d29ba2478..869efe461 100644 --- a/problemreductions-cli/tests/cli_tests.rs +++ b/problemreductions-cli/tests/cli_tests.rs @@ -3681,3 +3681,76 @@ fn test_create_weighted_mis_round_trips_into_solve() { let json: serde_json::Value = serde_json::from_str(&stdout).unwrap(); assert_eq!(json["evaluation"], "Valid(5)"); } + +#[test] +fn test_create_sequencing_within_intervals() { + let output_file = + std::env::temp_dir().join("pred_test_create_sequencing_within_intervals.json"); + let output = pred() + .args([ + "-o", + output_file.to_str().unwrap(), + "create", + "SequencingWithinIntervals", + "--release-times", + "0,0,0,0,5", + "--deadlines", + "11,11,11,11,6", + "--lengths", + "3,1,2,4,1", + ]) + .output() + .unwrap(); + assert!( + output.status.success(), + "stderr: {}", + String::from_utf8_lossy(&output.stderr) + ); + let content = std::fs::read_to_string(&output_file).unwrap(); + let json: serde_json::Value = serde_json::from_str(&content).unwrap(); + assert_eq!(json["type"], "SequencingWithinIntervals"); + assert_eq!( + json["data"]["release_times"], + serde_json::json!([0, 0, 0, 0, 5]) + ); + assert_eq!( + json["data"]["deadlines"], + serde_json::json!([11, 11, 11, 11, 6]) + ); + assert_eq!(json["data"]["lengths"], serde_json::json!([3, 1, 2, 4, 1])); + std::fs::remove_file(&output_file).ok(); +} + +#[test] +fn test_create_model_example_sequencing_within_intervals() { + let output = pred() + .args(["create", "--example", "SequencingWithinIntervals"]) + .output() + .unwrap(); + assert!( + output.status.success(), + "stderr: {}", + String::from_utf8_lossy(&output.stderr) + ); + let stdout = String::from_utf8(output.stdout).unwrap(); + let json: serde_json::Value = serde_json::from_str(&stdout).unwrap(); + assert_eq!(json["type"], "SequencingWithinIntervals"); +} + +#[test] +fn test_create_sequencing_within_intervals_rejects_empty_window() { + let output = pred() + .args([ + "create", + "SequencingWithinIntervals", + "--release-times", + "5", + "--deadlines", + "3", + "--lengths", + "2", + ]) + .output() + .unwrap(); + assert!(!output.status.success()); +} diff --git a/src/models/misc/sequencing_within_intervals.rs b/src/models/misc/sequencing_within_intervals.rs index 32f272e8c..cd7c3efe4 100644 --- a/src/models/misc/sequencing_within_intervals.rs +++ b/src/models/misc/sequencing_within_intervals.rs @@ -141,9 +141,9 @@ impl Problem for SequencingWithinIntervals { } // Check each variable is within range and compute start times - let dims = self.dims(); let mut starts = Vec::with_capacity(n); - for (i, (&c, &dim)) in config.iter().zip(dims.iter()).enumerate() { + for (i, &c) in config.iter().enumerate() { + let dim = (self.deadlines[i] - self.release_times[i] - self.lengths[i] + 1) as usize; if c >= dim { return false; }