Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions docs/paper/reductions.typ
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
"PartitionIntoTriangles": [Partition Into Triangles],
"FlowShopScheduling": [Flow Shop Scheduling],
"MinimumTardinessSequencing": [Minimum Tardiness Sequencing],
"TwoDimensionalConsecutiveSets": [2-Dimensional Consecutive Sets],
"SequencingWithinIntervals": [Sequencing Within Intervals],
"DirectedTwoCommodityIntegralFlow": [Directed Two-Commodity Integral Flow],
)
Expand Down Expand Up @@ -1313,6 +1314,52 @@ NP-completeness was established by Garey, Johnson, and Stockmeyer @gareyJohnsonS
]
}

#{
let x = load-model-example("TwoDimensionalConsecutiveSets")
let n = x.instance.alphabet_size
let subs = x.instance.subsets
let m = subs.len()
let sol = x.optimal.at(0)
let config = sol.config
// Build groups from config: groups.at(g) = list of symbols in group g
let groups = range(n).map(g => range(n).filter(s => config.at(s) == g))
// Only non-empty groups
let nonempty = groups.enumerate().filter(((_, g)) => g.len() > 0)
let k = nonempty.len()
let fmt-set(s) = "${" + s.map(e => str(e)).join(", ") + "}$"
[
#problem-def("TwoDimensionalConsecutiveSets")[
Given finite alphabet $Sigma = {0, 1, dots, n - 1}$ and collection $cal(C) = {Sigma_1, dots, Sigma_m}$ of subsets of $Sigma$, determine whether $Sigma$ can be partitioned into disjoint sets $X_1, X_2, dots, X_k$ such that each $X_i$ has at most one element in common with each $Sigma_j$, and for each $Sigma_j in cal(C)$ there is an index $l(j)$ with $Sigma_j subset.eq X_(l(j)) union X_(l(j)+1) union dots.c union X_(l(j)+|Sigma_j|-1)$.
][
This problem generalizes the Consecutive Sets problem (SR18) by requiring not just that each subset's elements appear consecutively in an ordering, but that they be spread across consecutive groups of a partition where each group contributes at most one element per subset. Shown NP-complete by Lipski @lipski1977fct via transformation from Graph 3-Colorability. The problem arises in information storage and retrieval where records must be organized in contiguous blocks. It remains NP-complete if all subsets have at most 5 elements, but is solvable in polynomial time if all subsets have at most 2 elements. The brute-force algorithm assigns each of $n$ symbols to one of up to $n$ groups, giving $O^*(n^n)$ time#footnote[No algorithm improving on brute-force enumeration is known for this problem.].

*Example.* Let $Sigma = {0, 1, dots, #(n - 1)}$ and $cal(C) = {#range(m).map(i => $Sigma_#(i + 1)$).join(", ")}$ with #subs.enumerate().map(((i, s)) => $Sigma_#(i + 1) = #fmt-set(s)$).join(", "). A valid partition uses $k = #k$ groups: #nonempty.map(((g, elems)) => $X_#(g + 1) = #fmt-set(elems)$).join(", "). Each group intersects every subset in at most one element, and each subset's elements span exactly $|Sigma_j|$ consecutive groups. For instance, $Sigma_1 = {0, 1, 2}$ maps to groups $X_1, X_2, X_3$ (consecutive), and $Sigma_5 = {0, 5}$ maps to groups $X_1, X_2$ (consecutive). The example database records many satisfying assignments for this instance, including encodings that differ only by unused or shifted group labels.

#figure(
canvas(length: 1cm, {
import draw: *
// Draw groups as labeled columns
let gw = 1.4
let gh = 0.45
for (col, (g, elems)) in nonempty.enumerate() {
let x0 = col * (gw + 0.3)
// Group header
content((x0 + gw / 2, 0.5), $X_#(g + 1)$, anchor: "south")
// Draw box for the group
rect((x0, -elems.len() * gh), (x0 + gw, 0),
stroke: 0.5pt + black, fill: rgb("#e8f0fe"))
// Elements inside
for (row, elem) in elems.enumerate() {
content((x0 + gw / 2, -row * gh - gh / 2), text(size: 9pt, str(elem)))
}
}
}),
caption: [2-Dimensional Consecutive Sets: partition of $Sigma = {0, dots, 5}$ into #k groups satisfying intersection and consecutiveness constraints for all #m subsets.],
) <fig:two-dim-consecutive-sets>
]
]
}

== Optimization Problems

#{
Expand Down
12 changes: 12 additions & 0 deletions docs/paper/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -703,3 +703,15 @@ @article{papadimitriou1982
year = {1982},
doi = {10.1145/322307.322309}
}

@inproceedings{lipski1977fct,
author = {Witold Lipski Jr.},
title = {Two {NP}-Complete Problems Related to Information Retrieval},
booktitle = {Fundamentals of Computation Theory (FCT 1977)},
series = {Lecture Notes in Computer Science},
volume = {56},
pages = {452--458},
publisher = {Springer},
year = {1977},
doi = {10.1007/3-540-08442-8_115}
}
1 change: 1 addition & 0 deletions docs/src/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ Stdin is supported with `-`:
```bash
pred create MIS --graph 0-1,1-2,2-3 | pred solve -
pred create MIS --graph 0-1,1-2,2-3 | pred solve - --solver brute-force
pred create TwoDimensionalConsecutiveSets --alphabet-size 6 --sets "0,1,2;3,4,5;1,3;2,4;0,5" | pred solve - --solver brute-force
```

When the problem is not ILP, the solver automatically reduces it to ILP, solves, and maps the solution back. The auto-reduction is shown in the output:
Expand Down
7 changes: 5 additions & 2 deletions problemreductions-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ Flags by problem type:
MinimumSetCovering --universe, --sets [--weights]
X3C (ExactCoverBy3Sets) --universe, --sets (3 elements each)
SetBasis --universe, --sets, --k
TwoDimensionalConsecutiveSets --alphabet-size, --sets
BicliqueCover --left, --right, --biedges, --k
BMF --matrix (0/1), --rank
SteinerTree --graph, --edge-weights, --terminals
Expand Down Expand Up @@ -274,7 +275,8 @@ Examples:
pred create FVS --arcs \"0>1,1>2,2>0\" --weights 1,1,1
pred create UndirectedTwoCommodityIntegralFlow --graph 0-2,1-2,2-3 --capacities 1,1,2 --source-1 0 --sink-1 3 --source-2 1 --sink-2 3 --requirement-1 1 --requirement-2 1
pred create X3C --universe 9 --sets \"0,1,2;0,2,4;3,4,5;3,5,7;6,7,8;1,4,6;2,5,8\"
pred create SetBasis --universe 4 --sets \"0,1;1,2;0,2;0,1,2\" --k 3")]
pred create SetBasis --universe 4 --sets \"0,1;1,2;0,2;0,1,2\" --k 3
pred create TwoDimensionalConsecutiveSets --alphabet-size 6 --sets \"0,1,2;3,4,5;1,3;2,4;0,5\"")]
pub struct CreateArgs {
/// Problem type (e.g., MIS, QUBO, SAT). Omit when using --example.
#[arg(value_parser = crate::problem_name::ProblemNameParser)]
Expand Down Expand Up @@ -453,7 +455,7 @@ pub struct CreateArgs {
/// Number of processors/machines for FlowShopScheduling
#[arg(long)]
pub num_processors: Option<usize>,
/// Alphabet size for SCS (optional; inferred from max symbol + 1 if omitted)
/// Alphabet size for SCS (optional) or TwoDimensionalConsecutiveSets
#[arg(long)]
pub alphabet_size: Option<usize>,
}
Expand All @@ -466,6 +468,7 @@ Examples:
pred solve reduced.json # solve a reduction bundle
pred solve reduced.json -o solution.json # save result to file
pred create MIS --graph 0-1,1-2 | pred solve - # read from stdin
pred create TwoDimensionalConsecutiveSets --alphabet-size 6 --sets \"0,1,2;3,4,5;1,3;2,4;0,5\" | pred solve - --solver brute-force
pred solve problem.json --timeout 10 # abort after 10 seconds

Typical workflow:
Expand Down
24 changes: 24 additions & 0 deletions problemreductions-cli/src/commands/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,9 @@ fn example_for(canonical: &str, graph_type: Option<&str>) -> &'static str {
"SubgraphIsomorphism" => "--graph 0-1,1-2,2-0 --pattern 0-1",
"SubsetSum" => "--sizes 3,7,1,8,2,4 --target 11",
"SetBasis" => "--universe 4 --sets \"0,1;1,2;0,2;0,1,2\" --k 3",
"TwoDimensionalConsecutiveSets" => {
"--alphabet-size 6 --sets \"0,1,2;3,4,5;1,3;2,4;0,5\""
}
"ShortestCommonSupersequence" => "--strings \"0,1,2;1,2,0\" --bound 4",
_ => "",
}
Expand Down Expand Up @@ -1073,6 +1076,27 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> {
)
}

// TwoDimensionalConsecutiveSets
"TwoDimensionalConsecutiveSets" => {
let alphabet_size = args.alphabet_size.or(args.universe).ok_or_else(|| {
anyhow::anyhow!(
"TwoDimensionalConsecutiveSets requires --alphabet-size (or --universe) and --sets\n\n\
Usage: pred create TwoDimensionalConsecutiveSets --alphabet-size 6 --sets \"0,1,2;3,4,5;1,3;2,4;0,5\""
)
})?;
let sets = parse_sets(args)?;
(
ser(
problemreductions::models::set::TwoDimensionalConsecutiveSets::try_new(
alphabet_size,
sets,
)
.map_err(anyhow::Error::msg)?,
)?,
resolved_variant.clone(),
)
}

// BicliqueCover
"BicliqueCover" => {
let left = args.left.ok_or_else(|| {
Expand Down
73 changes: 73 additions & 0 deletions problemreductions-cli/tests/cli_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1045,6 +1045,79 @@ fn test_create_set_basis_rejects_out_of_range_elements() {
assert!(!stderr.contains("panicked at"), "stderr: {stderr}");
}

#[test]
fn test_create_two_dimensional_consecutive_sets_accepts_alphabet_size_flag() {
let output_file =
std::env::temp_dir().join("pred_test_create_two_dimensional_consecutive_sets.json");
let output = pred()
.args([
"-o",
output_file.to_str().unwrap(),
"create",
"TwoDimensionalConsecutiveSets",
"--alphabet-size",
"6",
"--sets",
"0,1,2;3,4,5;1,3;2,4;0,5",
])
.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"], "TwoDimensionalConsecutiveSets");
assert_eq!(json["data"]["alphabet_size"], 6);
assert_eq!(json["data"]["subsets"][0], serde_json::json!([0, 1, 2]));

std::fs::remove_file(&output_file).ok();
}

#[test]
fn test_create_two_dimensional_consecutive_sets_rejects_zero_alphabet_size_without_panic() {
let output = pred()
.args([
"create",
"TwoDimensionalConsecutiveSets",
"--alphabet-size",
"0",
"--sets",
"0",
])
.output()
.unwrap();
assert!(!output.status.success());
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("Alphabet size must be positive"),
"stderr: {stderr}"
);
assert!(!stderr.contains("panicked at"), "stderr: {stderr}");
}

#[test]
fn test_create_two_dimensional_consecutive_sets_rejects_duplicate_elements_without_panic() {
let output = pred()
.args([
"create",
"TwoDimensionalConsecutiveSets",
"--alphabet-size",
"3",
"--sets",
"0,0",
])
.output()
.unwrap();
assert!(!output.status.success());
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stderr.contains("duplicate element"), "stderr: {stderr}");
assert!(!stderr.contains("panicked at"), "stderr: {stderr}");
}

#[test]
fn test_create_then_evaluate() {
// Create a problem
Expand Down
Loading
Loading