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
20 changes: 20 additions & 0 deletions docs/paper/reductions.typ
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
"BoundedComponentSpanningForest": [Bounded Component Spanning Forest],
"BinPacking": [Bin Packing],
"ClosestVectorProblem": [Closest Vector Problem],
"ConsecutiveSets": [Consecutive Sets],
"MinimumMultiwayCut": [Minimum Multiway Cut],
"OptimalLinearArrangement": [Optimal Linear Arrangement],
"RuralPostman": [Rural Postman],
Expand Down Expand Up @@ -1395,6 +1396,25 @@ NP-completeness was established by Garey, Johnson, and Stockmeyer @gareyJohnsonS
]
}

#{
let x = load-model-example("ConsecutiveSets")
let m = x.instance.alphabet_size
let n = x.instance.subsets.len()
let K = x.instance.bound_k
let subs = x.instance.subsets
let sol = x.optimal.at(0).config
let fmt-set(s) = "${" + s.map(e => str(e)).join(", ") + "}$"
[
#problem-def("ConsecutiveSets")[
Given a finite alphabet $Sigma$ of size $m$, a collection $cal(C) = {Sigma_1, Sigma_2, dots, Sigma_n}$ of subsets of $Sigma$, and a positive integer $K$, determine whether there exists a string $w in Sigma^*$ with $|w| lt.eq K$ such that, for each $i$, the elements of $Sigma_i$ occur in a consecutive block of $|Sigma_i|$ symbols of $w$.
][
This problem arises in information retrieval and file organization (SR18 in Garey and Johnson @garey1979). It generalizes the _consecutive ones property_ from binary matrices to a string-based formulation: given subsets of an alphabet, construct the shortest string where each subset's elements appear contiguously. The problem is NP-complete, as shown by #cite(<kou1977>, form: "prose") via reduction from Hamiltonian Path. The circular variant, where blocks may wrap around from the end of $w$ back to its beginning (considering $w w$), is also NP-complete @boothlueker1976. When $K$ equals the number of distinct symbols appearing in the subsets, the problem reduces to testing a binary matrix for the consecutive ones property, which is solvable in linear time using PQ-tree algorithms @boothlueker1976.

*Example.* Let $Sigma = {0, 1, dots, #(m - 1)}$, $K = #K$, and $cal(C) = {#range(n).map(i => $Sigma_#(i + 1)$).join(", ")}$ with #range(n).map(i => $Sigma_#(i + 1) = #fmt-set(subs.at(i))$).join(", "). A valid string is $w = (#sol.map(e => str(e)).join(", "))$ with $|w| = #sol.len() = K$: $Sigma_1 = {0, 4}$ appears as the block $(0, 4)$ at positions 0--1, $Sigma_2 = {2, 4}$ appears as $(4, 2)$ at positions 1--2, $Sigma_3 = {2, 5}$ appears as $(2, 5)$ at positions 2--3, $Sigma_4 = {1, 5}$ appears as $(5, 1)$ at positions 3--4, and $Sigma_5 = {1, 3}$ appears as $(1, 3)$ at positions 4--5.
]
]
}

#{
let x3c = load-model-example("ExactCoverBy3Sets")
let n = x3c.instance.universe_size
Expand Down
22 changes: 22 additions & 0 deletions docs/paper/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -797,3 +797,25 @@ @article{papadimitriou1982
year = {1982},
doi = {10.1145/322307.322309}
}

@article{kou1977,
author = {Lawrence T. Kou},
title = {Polynomial Complete Consecutive Information Retrieval Problems},
journal = {SIAM Journal on Computing},
volume = {6},
number = {1},
pages = {67--75},
year = {1977},
doi = {10.1137/0206005}
}

@article{boothlueker1976,
author = {Kellogg S. Booth and George S. Lueker},
title = {Testing for the Consecutive Ones Property, Interval Graphs, and Graph Planarity Using {PQ}-Tree Algorithms},
journal = {Journal of Computer and System Sciences},
volume = {13},
number = {3},
pages = {335--379},
year = {1976},
doi = {10.1016/S0022-0000(76)80045-1}
}
1 change: 1 addition & 0 deletions src/example_db/fixtures/examples.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
{"problem":"CircuitSAT","variant":{},"instance":{"circuit":{"assignments":[{"expr":{"op":{"And":[{"op":{"Var":"x1"}},{"op":{"Var":"x2"}}]}},"outputs":["a"]},{"expr":{"op":{"Or":[{"op":{"Var":"x1"}},{"op":{"Var":"x2"}}]}},"outputs":["b"]},{"expr":{"op":{"Xor":[{"op":{"Var":"a"}},{"op":{"Var":"b"}}]}},"outputs":["c"]}]},"variables":["a","b","c","x1","x2"]},"samples":[{"config":[0,1,1,0,1],"metric":true},{"config":[0,1,1,1,0],"metric":true}],"optimal":[{"config":[0,0,0,0,0],"metric":true},{"config":[0,1,1,0,1],"metric":true},{"config":[0,1,1,1,0],"metric":true},{"config":[1,1,0,1,1],"metric":true}]},
{"problem":"ClosestVectorProblem","variant":{"weight":"i32"},"instance":{"basis":[[2,0],[1,2]],"bounds":[{"lower":-2,"upper":4},{"lower":-2,"upper":4}],"target":[2.8,1.5]},"samples":[{"config":[3,3],"metric":{"Valid":0.5385164807134505}}],"optimal":[{"config":[3,3],"metric":{"Valid":0.5385164807134505}}]},
{"problem":"ComparativeContainment","variant":{"weight":"i32"},"instance":{"r_sets":[[0,1,2,3],[0,1]],"r_weights":[2,5],"s_sets":[[0,1,2,3],[2,3]],"s_weights":[3,6],"universe_size":4},"samples":[{"config":[1,0,0,0],"metric":true}],"optimal":[{"config":[0,1,0,0],"metric":true},{"config":[1,0,0,0],"metric":true},{"config":[1,1,0,0],"metric":true}]},
{"problem":"ConsecutiveSets","variant":{},"instance":{"alphabet_size":6,"bound_k":6,"subsets":[[0,4],[2,4],[2,5],[1,5],[1,3]]},"samples":[{"config":[0,4,2,5,1,3],"metric":true}],"optimal":[{"config":[0,4,2,5,1,3],"metric":true},{"config":[3,1,5,2,4,0],"metric":true}]},
{"problem":"DirectedTwoCommodityIntegralFlow","variant":{},"instance":{"capacities":[1,1,1,1,1,1,1,1],"graph":{"inner":{"edge_property":"directed","edges":[[0,2,null],[0,3,null],[1,2,null],[1,3,null],[2,4,null],[2,5,null],[3,4,null],[3,5,null]],"node_holes":[],"nodes":[null,null,null,null,null,null]}},"requirement_1":1,"requirement_2":1,"sink_1":4,"sink_2":5,"source_1":0,"source_2":1},"samples":[{"config":[1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,1],"metric":true}],"optimal":[{"config":[0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0],"metric":true},{"config":[0,0,0,1,0,0,1,0,0,1,0,0,0,0,0,1],"metric":true},{"config":[0,0,0,1,0,0,1,0,0,1,1,0,0,1,0,1],"metric":true},{"config":[0,0,0,1,0,0,1,0,0,1,1,0,1,0,0,1],"metric":true},{"config":[0,0,0,1,0,0,1,0,1,0,0,0,0,1,0,0],"metric":true},{"config":[0,0,0,1,0,0,1,0,1,0,1,0,1,1,0,0],"metric":true},{"config":[0,0,0,1,0,0,1,0,1,1,0,0,0,1,0,1],"metric":true},{"config":[0,0,0,1,0,0,1,0,1,1,0,0,1,0,0,1],"metric":true},{"config":[0,0,0,1,0,0,1,0,1,1,1,0,1,1,0,1],"metric":true},{"config":[0,0,1,0,1,0,0,0,0,0,0,1,0,0,0,1],"metric":true},{"config":[0,0,1,0,1,0,0,0,0,1,0,0,0,0,0,1],"metric":true},{"config":[0,0,1,0,1,0,0,0,0,1,0,1,0,0,1,1],"metric":true},{"config":[0,0,1,0,1,0,0,0,1,0,0,0,0,1,0,0],"metric":true},{"config":[0,0,1,0,1,0,0,0,1,0,0,1,0,1,0,1],"metric":true},{"config":[0,0,1,0,1,0,0,0,1,0,0,1,0,1,1,0],"metric":true},{"config":[0,0,1,0,1,0,0,0,1,1,0,0,0,1,0,1],"metric":true},{"config":[0,0,1,0,1,0,0,0,1,1,0,0,0,1,1,0],"metric":true},{"config":[0,0,1,0,1,0,0,0,1,1,0,1,0,1,1,1],"metric":true},{"config":[0,0,1,1,0,1,1,0,0,1,0,0,0,0,0,1],"metric":true},{"config":[0,0,1,1,0,1,1,0,1,1,0,0,1,0,0,1],"metric":true},{"config":[0,0,1,1,1,0,0,1,1,0,0,0,0,1,0,0],"metric":true},{"config":[0,0,1,1,1,0,0,1,1,1,0,0,0,1,1,0],"metric":true},{"config":[0,0,1,1,1,0,1,0,0,1,0,0,0,0,0,1],"metric":true},{"config":[0,0,1,1,1,0,1,0,1,0,0,0,0,1,0,0],"metric":true},{"config":[0,0,1,1,1,0,1,0,1,1,0,0,0,1,0,1],"metric":true},{"config":[0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,1],"metric":true},{"config":[0,1,0,0,0,0,1,0,0,0,1,0,0,1,0,0],"metric":true},{"config":[0,1,0,0,0,0,1,0,0,0,1,1,0,1,0,1],"metric":true},{"config":[0,1,0,0,0,0,1,0,0,0,1,1,1,0,0,1],"metric":true},{"config":[0,1,0,0,0,0,1,0,1,0,0,0,0,1,0,0],"metric":true},{"config":[0,1,0,0,0,0,1,0,1,0,0,1,0,1,0,1],"metric":true},{"config":[0,1,0,0,0,0,1,0,1,0,0,1,1,0,0,1],"metric":true},{"config":[0,1,0,0,0,0,1,0,1,0,1,0,1,1,0,0],"metric":true},{"config":[0,1,0,0,0,0,1,0,1,0,1,1,1,1,0,1],"metric":true},{"config":[0,1,0,1,0,0,1,1,0,0,1,0,0,1,0,0],"metric":true},{"config":[0,1,0,1,0,0,1,1,1,0,0,0,0,1,0,0],"metric":true},{"config":[0,1,0,1,0,0,1,1,1,0,1,0,1,1,0,0],"metric":true},{"config":[0,1,1,0,0,1,1,0,0,0,0,1,0,0,0,1],"metric":true},{"config":[0,1,1,0,0,1,1,0,1,0,0,1,1,0,0,1],"metric":true},{"config":[0,1,1,0,1,0,0,1,1,0,0,0,0,1,0,0],"metric":true},{"config":[0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0],"metric":true},{"config":[0,1,1,0,1,0,1,0,0,0,0,1,0,0,0,1],"metric":true},{"config":[0,1,1,0,1,0,1,0,1,0,0,0,0,1,0,0],"metric":true},{"config":[0,1,1,0,1,0,1,0,1,0,0,1,0,1,0,1],"metric":true},{"config":[0,1,1,1,1,0,1,1,1,0,0,0,0,1,0,0],"metric":true},{"config":[1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,1],"metric":true},{"config":[1,0,0,0,1,0,0,0,0,0,1,0,0,1,0,0],"metric":true},{"config":[1,0,0,0,1,0,0,0,0,0,1,1,0,1,0,1],"metric":true},{"config":[1,0,0,0,1,0,0,0,0,0,1,1,0,1,1,0],"metric":true},{"config":[1,0,0,0,1,0,0,0,0,1,0,0,0,0,0,1],"metric":true},{"config":[1,0,0,0,1,0,0,0,0,1,0,1,0,0,1,1],"metric":true},{"config":[1,0,0,0,1,0,0,0,0,1,1,0,0,1,0,1],"metric":true},{"config":[1,0,0,0,1,0,0,0,0,1,1,0,0,1,1,0],"metric":true},{"config":[1,0,0,0,1,0,0,0,0,1,1,1,0,1,1,1],"metric":true},{"config":[1,0,0,1,0,1,1,0,0,1,0,0,0,0,0,1],"metric":true},{"config":[1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1],"metric":true},{"config":[1,0,0,1,1,0,0,1,0,0,1,0,0,1,0,0],"metric":true},{"config":[1,0,0,1,1,0,0,1,0,1,1,0,0,1,1,0],"metric":true},{"config":[1,0,0,1,1,0,1,0,0,0,1,0,0,1,0,0],"metric":true},{"config":[1,0,0,1,1,0,1,0,0,1,0,0,0,0,0,1],"metric":true},{"config":[1,0,0,1,1,0,1,0,0,1,1,0,0,1,0,1],"metric":true},{"config":[1,0,1,0,1,1,0,0,0,0,0,1,0,0,0,1],"metric":true},{"config":[1,0,1,0,1,1,0,0,0,1,0,0,0,0,0,1],"metric":true},{"config":[1,0,1,0,1,1,0,0,0,1,0,1,0,0,1,1],"metric":true},{"config":[1,0,1,1,1,1,1,0,0,1,0,0,0,0,0,1],"metric":true},{"config":[1,1,0,0,0,1,1,0,0,0,0,1,0,0,0,1],"metric":true},{"config":[1,1,0,0,0,1,1,0,0,0,1,1,1,0,0,1],"metric":true},{"config":[1,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0],"metric":true},{"config":[1,1,0,0,1,0,0,1,0,0,1,1,0,1,1,0],"metric":true},{"config":[1,1,0,0,1,0,1,0,0,0,0,1,0,0,0,1],"metric":true},{"config":[1,1,0,0,1,0,1,0,0,0,1,0,0,1,0,0],"metric":true},{"config":[1,1,0,0,1,0,1,0,0,0,1,1,0,1,0,1],"metric":true},{"config":[1,1,0,1,1,0,1,1,0,0,1,0,0,1,0,0],"metric":true},{"config":[1,1,1,0,1,1,1,0,0,0,0,1,0,0,0,1],"metric":true}]},
{"problem":"ExactCoverBy3Sets","variant":{},"instance":{"subsets":[[0,1,2],[0,2,4],[3,4,5],[3,5,7],[6,7,8],[1,4,6],[2,5,8]],"universe_size":9},"samples":[{"config":[1,0,1,0,1,0,0],"metric":true}],"optimal":[{"config":[1,0,1,0,1,0,0],"metric":true}]},
{"problem":"Factoring","variant":{},"instance":{"m":2,"n":3,"target":15},"samples":[{"config":[1,1,1,0,1],"metric":{"Valid":0}}],"optimal":[{"config":[1,1,1,0,1],"metric":{"Valid":0}}]},
Expand Down
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ pub mod prelude {
ShortestCommonSupersequence, StaffScheduling, StringToStringCorrection, SubsetSum,
};
pub use crate::models::set::{
ComparativeContainment, ExactCoverBy3Sets, MaximumSetPacking, MinimumSetCovering, SetBasis,
ComparativeContainment, ConsecutiveSets, ExactCoverBy3Sets, MaximumSetPacking,
MinimumCardinalityKey, MinimumSetCovering, SetBasis,
};

// Core traits
Expand Down
4 changes: 2 additions & 2 deletions src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ pub use misc::{
ShortestCommonSupersequence, StaffScheduling, StringToStringCorrection, SubsetSum,
};
pub use set::{
ComparativeContainment, ExactCoverBy3Sets, MaximumSetPacking, MinimumCardinalityKey,
MinimumSetCovering, SetBasis,
ComparativeContainment, ConsecutiveSets, ExactCoverBy3Sets, MaximumSetPacking,
MinimumCardinalityKey, MinimumSetCovering, SetBasis,
};
246 changes: 246 additions & 0 deletions src/models/set/consecutive_sets.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
//! Consecutive Sets problem implementation.
//!
//! Given an alphabet of size n, a collection of subsets of the alphabet, and a
//! bound K, determine if there exists a string of length at most K over the
//! alphabet such that the elements of each subset appear consecutively (as a
//! contiguous block in some order) within the string.

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

inventory::submit! {
ProblemSchemaEntry {
name: "ConsecutiveSets",
display_name: "Consecutive Sets",
aliases: &[],
dimensions: &[],
module_path: module_path!(),
description: "Determine if a string exists where each subset's elements appear consecutively",
fields: &[
FieldInfo { name: "alphabet_size", type_name: "usize", description: "Size of the alphabet (elements are 0..alphabet_size-1)" },
FieldInfo { name: "subsets", type_name: "Vec<Vec<usize>>", description: "Collection of subsets of the alphabet" },
FieldInfo { name: "bound_k", type_name: "usize", description: "Maximum string length K" },
],
}
}

/// Consecutive Sets problem.
///
/// Given an alphabet {0, 1, ..., n-1}, a collection of subsets, and a bound K,
/// determine if there exists a string w of length at most K over the alphabet
/// such that the elements of each subset appear as a contiguous block (in any
/// order) within w.
///
/// Configurations use `bound_k` positions. Values `0..alphabet_size-1`
/// represent alphabet symbols, and the extra value `alphabet_size` marks
/// unused positions beyond the end of a shorter string. Only trailing unused
/// positions are valid.
///
/// This problem is NP-complete and arises in physical mapping of DNA and in
/// consecutive arrangements of hypergraph vertices.
///
/// # Example
///
/// ```
/// use problemreductions::models::set::ConsecutiveSets;
/// use problemreductions::{Problem, Solver, BruteForce};
///
/// // Alphabet: {0, 1, 2, 3, 4, 5}, subsets that must appear consecutively
/// let problem = ConsecutiveSets::new(
/// 6,
/// vec![vec![0, 4], vec![2, 4], vec![2, 5], vec![1, 5], vec![1, 3]],
/// 6,
/// );
///
/// let solver = BruteForce::new();
/// let solution = solver.find_satisfying(&problem);
///
/// // w = [0, 4, 2, 5, 1, 3] is a valid solution
/// assert!(solution.is_some());
/// assert!(problem.evaluate(&solution.unwrap()));
///
/// // Shorter strings are encoded with trailing `unused = alphabet_size`.
/// let shorter = ConsecutiveSets::new(3, vec![vec![0, 1]], 4);
/// let unused = shorter.alphabet_size();
/// assert!(shorter.evaluate(&[0, 1, unused, unused]));
/// assert!(!shorter.evaluate(&[0, unused, 1, unused]));
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConsecutiveSets {
/// Size of the alphabet (elements are 0..alphabet_size-1).
alphabet_size: usize,
/// Collection of subsets of the alphabet, each sorted in canonical form.
subsets: Vec<Vec<usize>>,
/// Maximum string length K.
bound_k: usize,
}

impl ConsecutiveSets {
/// Create a new Consecutive Sets problem.
///
/// # Panics
///
/// Panics if `bound_k` is zero, if any subset contains duplicate elements,
/// or if any element is outside the alphabet.
pub fn new(alphabet_size: usize, subsets: Vec<Vec<usize>>, bound_k: usize) -> Self {
assert!(bound_k > 0, "bound_k must be positive, got 0");
let mut subsets = subsets;
for (i, subset) in subsets.iter_mut().enumerate() {
let mut seen = HashSet::with_capacity(subset.len());
for &elem in subset.iter() {
assert!(
elem < alphabet_size,
"Subset {} contains element {} which is outside alphabet of size {}",
i,
elem,
alphabet_size
);
assert!(
seen.insert(elem),
"Subset {} contains duplicate elements",
i
);
}
subset.sort();
}
Self {
alphabet_size,
subsets,
bound_k,
}
}

/// Get the alphabet size.
pub fn alphabet_size(&self) -> usize {
self.alphabet_size
}

/// Get the number of subsets in the collection.
pub fn num_subsets(&self) -> usize {
self.subsets.len()
}

/// Get the bound K.
pub fn bound_k(&self) -> usize {
self.bound_k
}

/// Get the subsets.
pub fn subsets(&self) -> &[Vec<usize>] {
&self.subsets
}
}

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

fn dims(&self) -> Vec<usize> {
// Each position can be any symbol (0..alphabet_size-1) or "unused" (alphabet_size)
vec![self.alphabet_size + 1; self.bound_k]
}

fn evaluate(&self, config: &[usize]) -> bool {
// 1. Validate config
if config.len() != self.bound_k || config.iter().any(|&v| v > self.alphabet_size) {
return false;
}

// 2. Build string: find the actual string length (strip trailing "unused")
let unused = self.alphabet_size;
let str_len = config
.iter()
.rposition(|&v| v != unused)
.map_or(0, |p| p + 1);

// 3. Check no internal "unused" symbols
let w = &config[..str_len];
if w.contains(&unused) {
return false;
}

let mut subset_membership = vec![0usize; self.alphabet_size];
let mut seen_in_window = vec![0usize; self.alphabet_size];
let mut subset_stamp = 1usize;
let mut window_stamp = 1usize;

// 4. Check each subset has a consecutive block
for subset in &self.subsets {
let subset_len = subset.len();
if subset_len == 0 {
continue; // empty subset trivially satisfied
}
if subset_len > str_len {
return false; // can't fit
}

for &elem in subset {
subset_membership[elem] = subset_stamp;
}

let mut found = false;
for start in 0..=(str_len - subset_len) {
let window = &w[start..start + subset_len];
let current_window_stamp = window_stamp;
window_stamp += 1;

// Because subsets are validated to contain unique elements,
// a window matches iff every symbol belongs to the subset and
// appears at most once.
if window.iter().all(|&elem| {
let is_member = subset_membership[elem] == subset_stamp;
let is_new = seen_in_window[elem] != current_window_stamp;
if is_member && is_new {
seen_in_window[elem] = current_window_stamp;
true
} else {
false
}
}) {
// subset is already sorted
Comment on lines +183 to +202
found = true;
break;
}
}
if !found {
return false;
}

subset_stamp += 1;
}

true
}

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

impl SatisfactionProblem for ConsecutiveSets {}

crate::declare_variants! {
default sat ConsecutiveSets => "alphabet_size^bound_k * num_subsets",
}

#[cfg(feature = "example-db")]
pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
vec![crate::example_db::specs::ModelExampleSpec {
id: "consecutive_sets",
build: || {
// YES instance from issue: w = [0, 4, 2, 5, 1, 3]
let problem = ConsecutiveSets::new(
6,
vec![vec![0, 4], vec![2, 4], vec![2, 5], vec![1, 5], vec![1, 3]],
6,
);
crate::example_db::specs::satisfaction_example(problem, vec![vec![0, 4, 2, 5, 1, 3]])
},
}]
}

#[cfg(test)]
#[path = "../../unit_tests/models/set/consecutive_sets.rs"]
mod tests;
Loading
Loading