From 814ab9f18aa58c28ec21b34b0bfe187ac89d4af2 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Mon, 23 Mar 2026 13:51:51 +0800 Subject: [PATCH 1/4] Add plan for #419: [Model] ConsecutiveOnesMatrixAugmentation --- ...23-consecutive-ones-matrix-augmentation.md | 278 ++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100644 docs/plans/2026-03-23-consecutive-ones-matrix-augmentation.md diff --git a/docs/plans/2026-03-23-consecutive-ones-matrix-augmentation.md b/docs/plans/2026-03-23-consecutive-ones-matrix-augmentation.md new file mode 100644 index 00000000..98d9e9ff --- /dev/null +++ b/docs/plans/2026-03-23-consecutive-ones-matrix-augmentation.md @@ -0,0 +1,278 @@ +# ConsecutiveOnesMatrixAugmentation Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Add the `ConsecutiveOnesMatrixAugmentation` satisfaction model for issue #419, wire it into the registry and CLI, register a canonical example, and document it in the paper using the corrected issue examples. + +**Architecture:** Represent a witness as a full column permutation. `evaluate()` should validate that permutation, compute the minimum number of `0 -> 1` augmentations needed to make each row consecutive under that order, and accept iff the total is at most `bound`. Keep the schema aligned with the issue comments: `matrix: Vec>`, `bound: i64`, and complexity `factorial(num_cols) * num_rows * num_cols`. + +**Tech Stack:** Rust workspace, serde + inventory registry metadata, `pred` CLI, example-db, Typst paper, `make` verification targets. + +--- + +## Issue-Locked Inputs + +- GitHub issue: `#419 [Model] ConsecutiveOnesMatrixAugmentation` +- Associated rule issue: `#434 [Rule] Optimal Linear Arrangement to Consecutive Ones Matrix Augmentation` +- Canonical YES example: the corrected `4 x 5` graph-incidence matrix with `bound = 2` and witness permutation `[0, 1, 4, 2, 3]` +- Canonical NO example: the corrected `4 x 4` matrix with `bound = 0` +- Required complexity string: `"factorial(num_cols) * num_rows * num_cols"` +- Do not revert to the older broken examples from the original issue body + +## Add-Model Step Mapping + +- Batch 1 covers add-model Steps `1`, `1.5`, `2`, `2.5`, `3`, `4`, `4.5`, `4.6`, and `5` +- Batch 2 covers add-model Step `6` (`write-model-in-paper` style work) +- Final verification covers add-model Step `7` + +## Batch 1: Model, Registry, CLI, and Tests + +### Task 1: Scaffold the Model and Lock Down Core Behavior + +**Files:** +- Create: `src/models/algebraic/consecutive_ones_matrix_augmentation.rs` +- Create: `src/unit_tests/models/algebraic/consecutive_ones_matrix_augmentation.rs` +- Modify: `src/models/algebraic/mod.rs` +- Modify: `src/models/mod.rs` +- Modify: `src/lib.rs` + +**Step 1: Write the failing unit tests** + +Add tests for: +- constructor/getters/`dims()`/`num_variables()` +- the corrected YES issue example using permutation `[0, 1, 4, 2, 3]` +- the corrected NO issue example with `bound = 0` +- invalid configs: wrong length, duplicate columns, out-of-range columns +- brute-force solver behavior on the YES and NO instances +- serde round-trip +- complexity metadata string + +Run: +```bash +cargo test consecutive_ones_matrix_augmentation --features "ilp-highs example-db" -- --nocapture +``` + +Expected: compile failure until the model and exports exist. + +**Step 2: Add the model skeleton and exports** + +Implement the basic file structure: +- `ProblemSchemaEntry` with `display_name`, empty aliases, and fields `matrix` / `bound` +- `ConsecutiveOnesMatrixAugmentation` struct with `matrix`, `bound`, `num_rows`, and `num_cols` +- `try_new` + `new`, plus `matrix()`, `bound()`, `num_rows()`, and `num_cols()` +- `#[cfg(test)]` link to the new unit test file +- module/export wiring in `src/models/algebraic/mod.rs`, `src/models/mod.rs`, and `src/lib.rs` + +Run: +```bash +cargo test consecutive_ones_matrix_augmentation --features "ilp-highs example-db" -- --nocapture +``` + +Expected: unresolved-symbol errors should be gone; remaining failures should be behavioral. + +**Step 3: Implement the evaluator** + +Implement the actual model logic: +- config is a full column permutation, so `dims()` is `vec![num_cols; num_cols]` +- validate permutations exactly once in a helper +- compute the minimum augmentation cost for one row under a fixed permutation by filling the holes between the first and last `1` +- sum row costs, short-circuit when the total exceeds `bound` +- make `evaluate()` return `false` for invalid permutations +- implement `SatisfactionProblem` +- add: + +```rust +crate::declare_variants! { + default sat ConsecutiveOnesMatrixAugmentation => "factorial(num_cols) * num_rows * num_cols", +} +``` + +Run: +```bash +cargo test consecutive_ones_matrix_augmentation --features "ilp-highs example-db" -- --nocapture +``` + +Expected: the targeted model tests pass. + +**Step 4: Commit** + +```bash +git add src/models/algebraic/consecutive_ones_matrix_augmentation.rs \ + src/unit_tests/models/algebraic/consecutive_ones_matrix_augmentation.rs \ + src/models/algebraic/mod.rs \ + src/models/mod.rs \ + src/lib.rs +git commit -m "Add ConsecutiveOnesMatrixAugmentation model" +``` + +### Task 2: Add CLI Create Support and Discovery + +**Files:** +- Modify: `problemreductions-cli/src/commands/create.rs` +- Modify: `problemreductions-cli/src/cli.rs` +- Inspect and modify only if needed: `problemreductions-cli/src/problem_name.rs` + +**Step 1: Add failing CLI tests** + +Add or extend CLI tests covering: +- successful `pred create ConsecutiveOnesMatrixAugmentation --matrix "..." --bound 2` +- missing `--bound` error text +- malformed permutation-independent matrix input stays consistent with the existing `parse_bool_matrix` path + +Run: +```bash +cargo test -p problemreductions-cli consecutive_ones_matrix_augmentation -- --nocapture +``` + +Expected: failures until the new create arm and help text exist. + +**Step 2: Implement CLI wiring** + +Update the CLI to: +- import `ConsecutiveOnesMatrixAugmentation` +- add a `create()` match arm beside the other algebraic matrix problems +- add the help-table entry in `problemreductions-cli/src/cli.rs` +- add any `help_flag_name()` / `help_flag_hint()` overrides needed so the schema/help shows `--bound` +- only touch `problem_name.rs` if canonical-name resolution does not already work through the registry; do **not** invent a short alias + +Run: +```bash +cargo test -p problemreductions-cli consecutive_ones_matrix_augmentation -- --nocapture +``` + +Expected: the targeted CLI tests pass. + +**Step 3: Commit** + +```bash +git add problemreductions-cli/src/commands/create.rs \ + problemreductions-cli/src/cli.rs \ + problemreductions-cli/src/problem_name.rs +git commit -m "Add CLI support for ConsecutiveOnesMatrixAugmentation" +``` + +### Task 3: Register the Canonical Example and Paper-Test Coverage + +**Files:** +- Modify: `src/models/algebraic/consecutive_ones_matrix_augmentation.rs` +- Modify: `src/models/algebraic/mod.rs` +- Inspect: `src/example_db/model_builders.rs` + +**Step 1: Add canonical example specs** + +Inside the model file, add `canonical_model_example_specs()` with: +- the corrected YES instance from issue #419 +- `bound = 2` +- `optimal_config = vec![0, 1, 4, 2, 3]` +- `optimal_value = serde_json::json!(true)` + +Update the algebraic category spec chain so the new model is included. + +**Step 2: Add paper-example-focused tests** + +Extend the new unit test file with a `test_consecutive_ones_matrix_augmentation_paper_example` that: +- constructs the same canonical YES instance +- verifies the issue witness permutation is satisfying +- uses `BruteForce` to confirm at least one satisfying permutation exists + +Run: +```bash +cargo test consecutive_ones_matrix_augmentation_paper_example --features "ilp-highs example-db" -- --nocapture +cargo test canonical_model_example --features "ilp-highs example-db" -- --nocapture +``` + +Expected: the example-db path and paper example test pass. + +**Step 3: Commit** + +```bash +git add src/models/algebraic/consecutive_ones_matrix_augmentation.rs \ + src/models/algebraic/mod.rs \ + src/unit_tests/models/algebraic/consecutive_ones_matrix_augmentation.rs +git commit -m "Add example-db coverage for ConsecutiveOnesMatrixAugmentation" +``` + +## Batch 2: Paper Entry + +### Task 4: Document the Model in `docs/paper/reductions.typ` + +**Files:** +- Modify: `docs/paper/reductions.typ` + +**Step 1: Add the display name and `problem-def`** + +Add: +- `"ConsecutiveOnesMatrixAugmentation": [Consecutive Ones Matrix Augmentation],` +- a `#problem-def("ConsecutiveOnesMatrixAugmentation")[ ... ][ ... ]` entry + +The paper entry should explicitly say: +- the matrix is binary and the goal is to flip at most `K` zeros to ones +- the witness displayed in the paper is a column permutation +- the augmentation set is derived by filling holes between the first and last `1` in each row under that permutation +- the historical notes and complexity match the corrected issue discussion + +**Step 2: Use the canonical example data** + +Follow the existing `load-model-example(...)` pattern and derive: +- the rendered matrix +- the example permutation +- the CLI `pred create --example`, `pred solve`, and `pred evaluate ... --config ...` commands + +Do not hand-write a bare alias or a mismatched witness. + +**Step 3: Build the paper** + +Run: +```bash +make paper +``` + +Expected: Typst compiles cleanly and the new problem entry renders without completeness regressions. + +**Step 4: Commit** + +```bash +git add docs/paper/reductions.typ +git commit -m "Document ConsecutiveOnesMatrixAugmentation in the paper" +``` + +## Final Verification + +### Task 5: Run Full Verification and Stage Expected Generated Outputs + +**Files:** +- Modify if regenerated: tracked schema/example/paper outputs only + +**Step 1: Run focused checks** + +```bash +cargo test consecutive_ones_matrix_augmentation --features "ilp-highs example-db" -- --nocapture +cargo test -p problemreductions-cli consecutive_ones_matrix_augmentation -- --nocapture +``` + +Expected: all new targeted tests pass. + +**Step 2: Run repo verification** + +```bash +make test +make clippy +make paper +``` + +If the new model requires regenerated tracked outputs, run the repo-standard regeneration command and stage only the expected files. + +**Step 3: Inspect the tree** + +```bash +git status --short +``` + +Expected: only intentional source/docs/generated changes remain; the plan file is still present until the implementation is complete. + +**Step 4: Final implementation commit** + +```bash +git add -A +git commit -m "Implement #419: ConsecutiveOnesMatrixAugmentation" +``` From 4d14d1afba07feaafe042d5606b26b70c55854e9 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Mon, 23 Mar 2026 14:03:56 +0800 Subject: [PATCH 2/4] Implement #419: [Model] ConsecutiveOnesMatrixAugmentation --- docs/paper/reductions.typ | 50 ++++++ problemreductions-cli/src/cli.rs | 1 + problemreductions-cli/src/commands/create.rs | 88 +++++++++- src/lib.rs | 5 +- .../consecutive_ones_matrix_augmentation.rs | 163 ++++++++++++++++++ src/models/algebraic/mod.rs | 3 + src/models/mod.rs | 4 +- .../consecutive_ones_matrix_augmentation.rs | 119 +++++++++++++ tests/main.rs | 2 + .../consecutive_ones_matrix_augmentation.rs | 17 ++ 10 files changed, 447 insertions(+), 5 deletions(-) create mode 100644 src/models/algebraic/consecutive_ones_matrix_augmentation.rs create mode 100644 src/unit_tests/models/algebraic/consecutive_ones_matrix_augmentation.rs create mode 100644 tests/suites/consecutive_ones_matrix_augmentation.rs diff --git a/docs/paper/reductions.typ b/docs/paper/reductions.typ index f500fe7d..50fb5845 100644 --- a/docs/paper/reductions.typ +++ b/docs/paper/reductions.typ @@ -136,6 +136,7 @@ "MinimumFeedbackVertexSet": [Minimum Feedback Vertex Set], "ConjunctiveBooleanQuery": [Conjunctive Boolean Query], "ConsecutiveBlockMinimization": [Consecutive Block Minimization], + "ConsecutiveOnesMatrixAugmentation": [Consecutive Ones Matrix Augmentation], "ConsecutiveOnesSubmatrix": [Consecutive Ones Submatrix], "SparseMatrixCompression": [Sparse Matrix Compression], "DirectedTwoCommodityIntegralFlow": [Directed Two-Commodity Integral Flow], @@ -6046,6 +6047,55 @@ A classical NP-complete problem from Garey and Johnson @garey1979[Ch.~3, p.~76], ] } +#{ + let x = load-model-example("ConsecutiveOnesMatrixAugmentation") + let A = x.instance.matrix + let m = A.len() + let n = if m > 0 { A.at(0).len() } else { 0 } + let K = x.instance.bound + let perm = x.optimal_config + let A-int = A.map(row => row.map(v => if v { 1 } else { 0 })) + let reordered = A.map(row => perm.map(c => if row.at(c) { 1 } else { 0 })) + let total-flips = 0 + for row in reordered { + let first = none + let last = none + let count = 0 + for (j, value) in row.enumerate() { + if value == 1 { + if first == none { + first = j + } + last = j + count += 1 + } + } + if first != none and last != none { + total-flips += last - first + 1 - count + } + } + [ + #problem-def("ConsecutiveOnesMatrixAugmentation")[ + Given an $m times n$ binary matrix $A$ and a nonnegative integer $K$, determine whether there exists a matrix $A'$, obtained from $A$ by changing at most $K$ zero entries to one, such that some permutation of the columns of $A'$ has the consecutive ones property. + ][ + Consecutive Ones Matrix Augmentation is problem SR16 in Garey & Johnson @garey1979. It asks whether a binary matrix can be repaired by a bounded number of augmenting flips so that every row's 1-entries become contiguous after reordering the columns. This setting appears in information retrieval and DNA physical mapping, where matrices close to the consecutive ones property can still encode useful interval structure. Booth and Lueker showed that testing whether a matrix already has the consecutive ones property is polynomial-time via PQ-trees @booth1976, but allowing bounded augmentation makes the decision problem NP-complete @booth1975. The direct exhaustive search tries all $n!$ column permutations and, for each one, computes the minimum augmentation cost by filling the holes between the first and last 1 in every row#footnote[No algorithm improving on brute-force permutation enumeration is known for the general problem in this repository's supported setting.]. + + *Example.* Consider the $#m times #n$ matrix $A = mat(#A-int.map(row => row.map(v => str(v)).join(", ")).join("; "))$ with $K = #K$. Under the permutation $pi = (#perm.map(p => str(p)).join(", "))$, the reordered rows are #reordered.enumerate().map(((i, row)) => [$r_#(i + 1) = (#row.map(v => str(v)).join(", "))$]).join(", "). The first row becomes $(1, 0, 1, 0, 1)$, so filling the two interior gaps yields $(1, 1, 1, 1, 1)$. The other three rows already have consecutive 1-entries under the same order, so the total augmentation cost is #total-flips and #total-flips $<= #K$, making the instance satisfiable. + + #pred-commands( + "pred create --example ConsecutiveOnesMatrixAugmentation -o consecutive-ones-matrix-augmentation.json", + "pred solve consecutive-ones-matrix-augmentation.json", + "pred evaluate consecutive-ones-matrix-augmentation.json --config " + x.optimal_config.map(str).join(","), + ) + + #figure( + align(center, math.equation([$A = #math.mat(..A-int.map(row => row.map(v => [#v])))$])), + caption: [The canonical $#m times #n$ example matrix for Consecutive Ones Matrix Augmentation. The permutation $pi = (#perm.map(p => str(p)).join(", "))$ makes only the first row need augmentation, and exactly two zero-to-one flips suffice.], + ) + ] + ] +} + #{ let x = load-model-example("ConsecutiveOnesSubmatrix") let A = x.instance.matrix diff --git a/problemreductions-cli/src/cli.rs b/problemreductions-cli/src/cli.rs index f17067df..e8aef438 100644 --- a/problemreductions-cli/src/cli.rs +++ b/problemreductions-cli/src/cli.rs @@ -267,6 +267,7 @@ Flags by problem type: BiconnectivityAugmentation --graph, --potential-edges, --budget [--num-vertices] BMF --matrix (0/1), --rank ConsecutiveBlockMinimization --matrix (JSON 2D bool), --bound-k + ConsecutiveOnesMatrixAugmentation --matrix (0/1), --bound ConsecutiveOnesSubmatrix --matrix (0/1), --k SparseMatrixCompression --matrix (0/1), --bound SteinerTree --graph, --edge-weights, --terminals diff --git a/problemreductions-cli/src/commands/create.rs b/problemreductions-cli/src/commands/create.rs index 4abe9ee3..33ab9e35 100644 --- a/problemreductions-cli/src/commands/create.rs +++ b/problemreductions-cli/src/commands/create.rs @@ -8,8 +8,8 @@ use crate::util; use anyhow::{bail, Context, Result}; use problemreductions::export::{ModelExample, ProblemRef, ProblemSide, RuleExample}; use problemreductions::models::algebraic::{ - ClosestVectorProblem, ConsecutiveBlockMinimization, ConsecutiveOnesSubmatrix, - SparseMatrixCompression, BMF, + ClosestVectorProblem, ConsecutiveBlockMinimization, ConsecutiveOnesMatrixAugmentation, + ConsecutiveOnesSubmatrix, SparseMatrixCompression, BMF, }; use problemreductions::models::formula::Quantifier; use problemreductions::models::graph::{ @@ -689,6 +689,9 @@ fn example_for(canonical: &str, graph_type: Option<&str>) -> &'static str { "ConsecutiveBlockMinimization" => { "--matrix '[[true,false,true],[false,true,true]]' --bound 2" } + "ConsecutiveOnesMatrixAugmentation" => { + "--matrix \"1,0,0,1,1;1,1,0,0,0;0,1,1,0,1;0,0,1,1,0\" --bound 2" + } "SparseMatrixCompression" => { "--matrix \"1,0,0,1;0,1,0,0;0,0,1,0;1,0,0,0\" --bound 2" } @@ -735,6 +738,7 @@ fn help_flag_name(canonical: &str, field_name: &str) -> String { ("PrimeAttributeName", "dependencies") => return "deps".to_string(), ("PrimeAttributeName", "query_attribute") => return "query".to_string(), ("MixedChinesePostman", "arc_weights") => return "arc-costs".to_string(), + ("ConsecutiveOnesMatrixAugmentation", "bound") => return "bound".to_string(), ("ConsecutiveOnesSubmatrix", "bound") => return "bound".to_string(), ("SparseMatrixCompression", "bound_k") => return "bound".to_string(), ("StackerCrane", "edges") => return "graph".to_string(), @@ -820,6 +824,9 @@ fn help_flag_hint( ("PathConstrainedNetworkFlow", "paths") => { "semicolon-separated arc-index paths: \"0,2,5,8;1,4,7,9\"" } + ("ConsecutiveOnesMatrixAugmentation", "matrix") => { + "semicolon-separated 0/1 rows: \"1,0;0,1\"" + } ("ConsecutiveOnesSubmatrix", "matrix") => "semicolon-separated 0/1 rows: \"1,0;0,1\"", ("SparseMatrixCompression", "matrix") => "semicolon-separated 0/1 rows: \"1,0;0,1\"", ("TimetableDesign", "craftsman_avail") | ("TimetableDesign", "task_avail") => { @@ -2788,6 +2795,21 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> { ) } + // ConsecutiveOnesMatrixAugmentation + "ConsecutiveOnesMatrixAugmentation" => { + let matrix = parse_bool_matrix(args)?; + let bound = args.bound.ok_or_else(|| { + anyhow::anyhow!( + "ConsecutiveOnesMatrixAugmentation requires --matrix and --bound\n\n\ + Usage: pred create ConsecutiveOnesMatrixAugmentation --matrix \"1,0,0,1,1;1,1,0,0,0;0,1,1,0,1;0,0,1,1,0\" --bound 2" + ) + })?; + ( + ser(ConsecutiveOnesMatrixAugmentation::new(matrix, bound))?, + resolved_variant.clone(), + ) + } + // SparseMatrixCompression "SparseMatrixCompression" => { let matrix = parse_bool_matrix(args)?; @@ -7902,4 +7924,66 @@ mod tests { let err = create(&args, &out).unwrap_err().to_string(); assert!(err.contains("bound >= 1")); } + + #[test] + fn test_create_consecutive_ones_matrix_augmentation_json() { + use crate::dispatch::ProblemJsonOutput; + + let mut args = empty_args(); + args.problem = Some("ConsecutiveOnesMatrixAugmentation".to_string()); + args.matrix = Some("1,0,0,1,1;1,1,0,0,0;0,1,1,0,1;0,0,1,1,0".to_string()); + args.bound = Some(2); + + let output_path = std::env::temp_dir().join(format!( + "coma-create-{}.json", + std::process::id() + )); + let out = OutputConfig { + output: Some(output_path.clone()), + quiet: true, + json: false, + auto_json: false, + }; + + create(&args, &out).unwrap(); + + let json = std::fs::read_to_string(&output_path).unwrap(); + let created: ProblemJsonOutput = serde_json::from_str(&json).unwrap(); + assert_eq!(created.problem_type, "ConsecutiveOnesMatrixAugmentation"); + assert!(created.variant.is_empty()); + assert_eq!( + created.data, + serde_json::json!({ + "matrix": [ + [true, false, false, true, true], + [true, true, false, false, false], + [false, true, true, false, true], + [false, false, true, true, false], + ], + "bound": 2, + }) + ); + + let _ = std::fs::remove_file(output_path); + } + + #[test] + fn test_create_consecutive_ones_matrix_augmentation_requires_bound() { + let mut args = empty_args(); + args.problem = Some("ConsecutiveOnesMatrixAugmentation".to_string()); + args.matrix = Some("1,0;0,1".to_string()); + + let out = OutputConfig { + output: None, + quiet: true, + json: false, + auto_json: false, + }; + + let err = create(&args, &out).unwrap_err().to_string(); + assert!(err.contains( + "ConsecutiveOnesMatrixAugmentation requires --matrix and --bound" + )); + assert!(err.contains("Usage: pred create ConsecutiveOnesMatrixAugmentation")); + } } diff --git a/src/lib.rs b/src/lib.rs index 217ff0a6..04803766 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,7 +42,10 @@ pub mod variant; /// Prelude module for convenient imports. pub mod prelude { // Problem types - pub use crate::models::algebraic::{QuadraticAssignment, SparseMatrixCompression, BMF, QUBO}; + pub use crate::models::algebraic::{ + ConsecutiveOnesMatrixAugmentation, QuadraticAssignment, SparseMatrixCompression, BMF, + QUBO, + }; pub use crate::models::formula::{ CNFClause, CircuitSAT, KSatisfiability, NAESatisfiability, QuantifiedBooleanFormulas, Satisfiability, diff --git a/src/models/algebraic/consecutive_ones_matrix_augmentation.rs b/src/models/algebraic/consecutive_ones_matrix_augmentation.rs new file mode 100644 index 00000000..aa5d187a --- /dev/null +++ b/src/models/algebraic/consecutive_ones_matrix_augmentation.rs @@ -0,0 +1,163 @@ +//! Consecutive Ones Matrix Augmentation problem implementation. +//! +//! Given an m x n binary matrix A and a nonnegative integer K, determine +//! whether there exists a permutation of the columns and at most K zero-to-one +//! augmentations such that every row has consecutive 1s. + +use crate::registry::{FieldInfo, ProblemSchemaEntry}; +use crate::traits::{Problem, SatisfactionProblem}; +use serde::{Deserialize, Serialize}; + +inventory::submit! { + ProblemSchemaEntry { + name: "ConsecutiveOnesMatrixAugmentation", + display_name: "Consecutive Ones Matrix Augmentation", + aliases: &[], + dimensions: &[], + module_path: module_path!(), + description: "Augment a binary matrix with at most K zero-to-one flips so some column permutation has the consecutive ones property", + fields: &[ + FieldInfo { name: "matrix", type_name: "Vec>", description: "m x n binary matrix A" }, + FieldInfo { name: "bound", type_name: "i64", description: "Upper bound K on zero-to-one augmentations" }, + ], + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ConsecutiveOnesMatrixAugmentation { + matrix: Vec>, + bound: i64, +} + +impl ConsecutiveOnesMatrixAugmentation { + pub fn new(matrix: Vec>, bound: i64) -> Self { + Self::try_new(matrix, bound).unwrap_or_else(|err| panic!("{err}")) + } + + pub fn try_new(matrix: Vec>, bound: i64) -> Result { + let num_cols = matrix.first().map_or(0, Vec::len); + if matrix.iter().any(|row| row.len() != num_cols) { + return Err("all matrix rows must have the same length".to_string()); + } + if bound < 0 { + return Err("bound must be nonnegative".to_string()); + } + Ok(Self { matrix, bound }) + } + + pub fn matrix(&self) -> &[Vec] { + &self.matrix + } + + pub fn bound(&self) -> i64 { + self.bound + } + + pub fn num_rows(&self) -> usize { + self.matrix.len() + } + + pub fn num_cols(&self) -> usize { + self.matrix.first().map_or(0, Vec::len) + } + + fn validate_permutation(&self, config: &[usize]) -> bool { + if config.len() != self.num_cols() { + return false; + } + + let mut seen = vec![false; self.num_cols()]; + for &col in config { + if col >= self.num_cols() || seen[col] { + return false; + } + seen[col] = true; + } + true + } + + fn row_augmentation_cost(row: &[bool], config: &[usize]) -> usize { + let mut first_one = None; + let mut last_one = None; + let mut one_count = 0usize; + + for (position, &col) in config.iter().enumerate() { + if row[col] { + first_one.get_or_insert(position); + last_one = Some(position); + one_count += 1; + } + } + + match (first_one, last_one) { + (Some(first), Some(last)) => last - first + 1 - one_count, + _ => 0, + } + } + + fn total_augmentation_cost(&self, config: &[usize]) -> Option { + if !self.validate_permutation(config) { + return None; + } + + let mut total = 0usize; + for row in &self.matrix { + total += Self::row_augmentation_cost(row, config); + if total > self.bound as usize { + return Some(total); + } + } + + Some(total) + } +} + +impl Problem for ConsecutiveOnesMatrixAugmentation { + const NAME: &'static str = "ConsecutiveOnesMatrixAugmentation"; + type Metric = bool; + + fn dims(&self) -> Vec { + vec![self.num_cols(); self.num_cols()] + } + + fn evaluate(&self, config: &[usize]) -> bool { + self.total_augmentation_cost(config) + .is_some_and(|cost| cost <= self.bound as usize) + } + + fn variant() -> Vec<(&'static str, &'static str)> { + crate::variant_params![] + } + + fn num_variables(&self) -> usize { + self.num_cols() + } +} + +impl SatisfactionProblem for ConsecutiveOnesMatrixAugmentation {} + +crate::declare_variants! { + default sat ConsecutiveOnesMatrixAugmentation => "factorial(num_cols) * num_rows * num_cols", +} + +#[cfg(feature = "example-db")] +pub(crate) fn canonical_model_example_specs() -> Vec { + vec![crate::example_db::specs::ModelExampleSpec { + id: "consecutive_ones_matrix_augmentation", + instance: Box::new(ConsecutiveOnesMatrixAugmentation::new( + vec![ + vec![true, false, false, true, true], + vec![true, true, false, false, false], + vec![false, true, true, false, true], + vec![false, false, true, true, false], + ], + 2, + )), + optimal_config: vec![0, 1, 4, 2, 3], + optimal_value: serde_json::json!(true), + }] +} + +#[cfg(test)] +#[path = "../../unit_tests/models/algebraic/consecutive_ones_matrix_augmentation.rs"] +mod tests; diff --git a/src/models/algebraic/mod.rs b/src/models/algebraic/mod.rs index f5aaff91..d341270c 100644 --- a/src/models/algebraic/mod.rs +++ b/src/models/algebraic/mod.rs @@ -13,6 +13,7 @@ pub(crate) mod bmf; pub(crate) mod closest_vector_problem; pub(crate) mod consecutive_block_minimization; +pub(crate) mod consecutive_ones_matrix_augmentation; pub(crate) mod consecutive_ones_submatrix; pub(crate) mod ilp; pub(crate) mod quadratic_assignment; @@ -22,6 +23,7 @@ pub(crate) mod sparse_matrix_compression; pub use bmf::BMF; pub use closest_vector_problem::{ClosestVectorProblem, VarBounds}; pub use consecutive_block_minimization::ConsecutiveBlockMinimization; +pub use consecutive_ones_matrix_augmentation::ConsecutiveOnesMatrixAugmentation; pub use consecutive_ones_submatrix::ConsecutiveOnesSubmatrix; pub use ilp::{Comparison, LinearConstraint, ObjectiveSense, VariableDomain, ILP}; pub use quadratic_assignment::QuadraticAssignment; @@ -36,6 +38,7 @@ pub(crate) fn canonical_model_example_specs() -> Vec Vec> { + vec![ + vec![true, false, false, true, true], + vec![true, true, false, false, false], + vec![false, true, true, false, true], + vec![false, false, true, true, false], + ] +} + +fn issue_no_matrix() -> Vec> { + vec![ + vec![true, false, true, false], + vec![false, true, false, true], + vec![true, true, false, true], + vec![false, true, true, false], + ] +} + +#[test] +fn test_consecutive_ones_matrix_augmentation_basic() { + let problem = ConsecutiveOnesMatrixAugmentation::new(issue_yes_matrix(), 2); + + assert_eq!(problem.num_rows(), 4); + assert_eq!(problem.num_cols(), 5); + assert_eq!(problem.bound(), 2); + assert_eq!(problem.num_variables(), 5); + assert_eq!(problem.dims(), vec![5; 5]); + assert_eq!( + ::NAME, + "ConsecutiveOnesMatrixAugmentation" + ); + assert_eq!( + ::variant(), + Vec::<(&'static str, &'static str)>::new() + ); +} + +#[test] +fn test_consecutive_ones_matrix_augmentation_yes_instance() { + let problem = ConsecutiveOnesMatrixAugmentation::new(issue_yes_matrix(), 2); + + assert!(problem.evaluate(&[0, 1, 4, 2, 3])); +} + +#[test] +fn test_consecutive_ones_matrix_augmentation_no_instance() { + let problem = ConsecutiveOnesMatrixAugmentation::new(issue_no_matrix(), 0); + + let solver = BruteForce::new(); + assert!(solver.find_satisfying(&problem).is_none()); +} + +#[test] +fn test_consecutive_ones_matrix_augmentation_invalid_permutations() { + let problem = ConsecutiveOnesMatrixAugmentation::new(issue_yes_matrix(), 2); + + assert!(!problem.evaluate(&[0, 1, 4, 2])); + assert!(!problem.evaluate(&[0, 1, 4, 2, 5])); + assert!(!problem.evaluate(&[0, 1, 4, 2, 2])); +} + +#[test] +fn test_consecutive_ones_matrix_augmentation_serialization() { + let problem = ConsecutiveOnesMatrixAugmentation::new(issue_yes_matrix(), 2); + let json = serde_json::to_value(&problem).unwrap(); + + assert_eq!( + json, + serde_json::json!({ + "matrix": [ + [true, false, false, true, true], + [true, true, false, false, false], + [false, true, true, false, true], + [false, false, true, true, false], + ], + "bound": 2, + }) + ); + + let restored: ConsecutiveOnesMatrixAugmentation = serde_json::from_value(json).unwrap(); + assert_eq!(restored.matrix(), problem.matrix()); + assert_eq!(restored.bound(), problem.bound()); +} + +#[test] +fn test_consecutive_ones_matrix_augmentation_complexity_metadata() { + use crate::registry::VariantEntry; + + let entry = inventory::iter::() + .find(|entry| entry.name == "ConsecutiveOnesMatrixAugmentation") + .expect("variant entry should exist"); + + assert_eq!(entry.complexity, "factorial(num_cols) * num_rows * num_cols"); +} + +#[cfg(feature = "example-db")] +#[test] +fn test_consecutive_ones_matrix_augmentation_has_canonical_example() { + let specs = canonical_model_example_specs(); + + assert_eq!(specs.len(), 1); + assert_eq!(specs[0].id, "consecutive_ones_matrix_augmentation"); +} + +#[test] +#[should_panic(expected = "same length")] +fn test_consecutive_ones_matrix_augmentation_rejects_ragged_matrix() { + ConsecutiveOnesMatrixAugmentation::new(vec![vec![true, false], vec![true]], 1); +} + +#[test] +#[should_panic(expected = "nonnegative")] +fn test_consecutive_ones_matrix_augmentation_rejects_negative_bound() { + ConsecutiveOnesMatrixAugmentation::new(issue_yes_matrix(), -1); +} diff --git a/tests/main.rs b/tests/main.rs index 4c93d3f9..bb527191 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -6,3 +6,5 @@ mod integration; mod jl_parity; #[path = "suites/reductions.rs"] mod reductions; +#[path = "suites/consecutive_ones_matrix_augmentation.rs"] +mod consecutive_ones_matrix_augmentation; diff --git a/tests/suites/consecutive_ones_matrix_augmentation.rs b/tests/suites/consecutive_ones_matrix_augmentation.rs new file mode 100644 index 00000000..f14c861e --- /dev/null +++ b/tests/suites/consecutive_ones_matrix_augmentation.rs @@ -0,0 +1,17 @@ +use problemreductions::models::algebraic::ConsecutiveOnesMatrixAugmentation; +use problemreductions::Problem; + +#[test] +fn test_consecutive_ones_matrix_augmentation_yes_instance() { + let problem = ConsecutiveOnesMatrixAugmentation::new( + vec![ + vec![true, false, false, true, true], + vec![true, true, false, false, false], + vec![false, true, true, false, true], + vec![false, false, true, true, false], + ], + 2, + ); + + assert!(problem.evaluate(&[0, 1, 4, 2, 3])); +} From 2b4d6c48955a6d11d20d088364b6b2c5d2e6ebbf Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Mon, 23 Mar 2026 14:04:02 +0800 Subject: [PATCH 3/4] chore: remove plan file after implementation --- ...23-consecutive-ones-matrix-augmentation.md | 278 ------------------ 1 file changed, 278 deletions(-) delete mode 100644 docs/plans/2026-03-23-consecutive-ones-matrix-augmentation.md diff --git a/docs/plans/2026-03-23-consecutive-ones-matrix-augmentation.md b/docs/plans/2026-03-23-consecutive-ones-matrix-augmentation.md deleted file mode 100644 index 98d9e9ff..00000000 --- a/docs/plans/2026-03-23-consecutive-ones-matrix-augmentation.md +++ /dev/null @@ -1,278 +0,0 @@ -# ConsecutiveOnesMatrixAugmentation Implementation Plan - -> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. - -**Goal:** Add the `ConsecutiveOnesMatrixAugmentation` satisfaction model for issue #419, wire it into the registry and CLI, register a canonical example, and document it in the paper using the corrected issue examples. - -**Architecture:** Represent a witness as a full column permutation. `evaluate()` should validate that permutation, compute the minimum number of `0 -> 1` augmentations needed to make each row consecutive under that order, and accept iff the total is at most `bound`. Keep the schema aligned with the issue comments: `matrix: Vec>`, `bound: i64`, and complexity `factorial(num_cols) * num_rows * num_cols`. - -**Tech Stack:** Rust workspace, serde + inventory registry metadata, `pred` CLI, example-db, Typst paper, `make` verification targets. - ---- - -## Issue-Locked Inputs - -- GitHub issue: `#419 [Model] ConsecutiveOnesMatrixAugmentation` -- Associated rule issue: `#434 [Rule] Optimal Linear Arrangement to Consecutive Ones Matrix Augmentation` -- Canonical YES example: the corrected `4 x 5` graph-incidence matrix with `bound = 2` and witness permutation `[0, 1, 4, 2, 3]` -- Canonical NO example: the corrected `4 x 4` matrix with `bound = 0` -- Required complexity string: `"factorial(num_cols) * num_rows * num_cols"` -- Do not revert to the older broken examples from the original issue body - -## Add-Model Step Mapping - -- Batch 1 covers add-model Steps `1`, `1.5`, `2`, `2.5`, `3`, `4`, `4.5`, `4.6`, and `5` -- Batch 2 covers add-model Step `6` (`write-model-in-paper` style work) -- Final verification covers add-model Step `7` - -## Batch 1: Model, Registry, CLI, and Tests - -### Task 1: Scaffold the Model and Lock Down Core Behavior - -**Files:** -- Create: `src/models/algebraic/consecutive_ones_matrix_augmentation.rs` -- Create: `src/unit_tests/models/algebraic/consecutive_ones_matrix_augmentation.rs` -- Modify: `src/models/algebraic/mod.rs` -- Modify: `src/models/mod.rs` -- Modify: `src/lib.rs` - -**Step 1: Write the failing unit tests** - -Add tests for: -- constructor/getters/`dims()`/`num_variables()` -- the corrected YES issue example using permutation `[0, 1, 4, 2, 3]` -- the corrected NO issue example with `bound = 0` -- invalid configs: wrong length, duplicate columns, out-of-range columns -- brute-force solver behavior on the YES and NO instances -- serde round-trip -- complexity metadata string - -Run: -```bash -cargo test consecutive_ones_matrix_augmentation --features "ilp-highs example-db" -- --nocapture -``` - -Expected: compile failure until the model and exports exist. - -**Step 2: Add the model skeleton and exports** - -Implement the basic file structure: -- `ProblemSchemaEntry` with `display_name`, empty aliases, and fields `matrix` / `bound` -- `ConsecutiveOnesMatrixAugmentation` struct with `matrix`, `bound`, `num_rows`, and `num_cols` -- `try_new` + `new`, plus `matrix()`, `bound()`, `num_rows()`, and `num_cols()` -- `#[cfg(test)]` link to the new unit test file -- module/export wiring in `src/models/algebraic/mod.rs`, `src/models/mod.rs`, and `src/lib.rs` - -Run: -```bash -cargo test consecutive_ones_matrix_augmentation --features "ilp-highs example-db" -- --nocapture -``` - -Expected: unresolved-symbol errors should be gone; remaining failures should be behavioral. - -**Step 3: Implement the evaluator** - -Implement the actual model logic: -- config is a full column permutation, so `dims()` is `vec![num_cols; num_cols]` -- validate permutations exactly once in a helper -- compute the minimum augmentation cost for one row under a fixed permutation by filling the holes between the first and last `1` -- sum row costs, short-circuit when the total exceeds `bound` -- make `evaluate()` return `false` for invalid permutations -- implement `SatisfactionProblem` -- add: - -```rust -crate::declare_variants! { - default sat ConsecutiveOnesMatrixAugmentation => "factorial(num_cols) * num_rows * num_cols", -} -``` - -Run: -```bash -cargo test consecutive_ones_matrix_augmentation --features "ilp-highs example-db" -- --nocapture -``` - -Expected: the targeted model tests pass. - -**Step 4: Commit** - -```bash -git add src/models/algebraic/consecutive_ones_matrix_augmentation.rs \ - src/unit_tests/models/algebraic/consecutive_ones_matrix_augmentation.rs \ - src/models/algebraic/mod.rs \ - src/models/mod.rs \ - src/lib.rs -git commit -m "Add ConsecutiveOnesMatrixAugmentation model" -``` - -### Task 2: Add CLI Create Support and Discovery - -**Files:** -- Modify: `problemreductions-cli/src/commands/create.rs` -- Modify: `problemreductions-cli/src/cli.rs` -- Inspect and modify only if needed: `problemreductions-cli/src/problem_name.rs` - -**Step 1: Add failing CLI tests** - -Add or extend CLI tests covering: -- successful `pred create ConsecutiveOnesMatrixAugmentation --matrix "..." --bound 2` -- missing `--bound` error text -- malformed permutation-independent matrix input stays consistent with the existing `parse_bool_matrix` path - -Run: -```bash -cargo test -p problemreductions-cli consecutive_ones_matrix_augmentation -- --nocapture -``` - -Expected: failures until the new create arm and help text exist. - -**Step 2: Implement CLI wiring** - -Update the CLI to: -- import `ConsecutiveOnesMatrixAugmentation` -- add a `create()` match arm beside the other algebraic matrix problems -- add the help-table entry in `problemreductions-cli/src/cli.rs` -- add any `help_flag_name()` / `help_flag_hint()` overrides needed so the schema/help shows `--bound` -- only touch `problem_name.rs` if canonical-name resolution does not already work through the registry; do **not** invent a short alias - -Run: -```bash -cargo test -p problemreductions-cli consecutive_ones_matrix_augmentation -- --nocapture -``` - -Expected: the targeted CLI tests pass. - -**Step 3: Commit** - -```bash -git add problemreductions-cli/src/commands/create.rs \ - problemreductions-cli/src/cli.rs \ - problemreductions-cli/src/problem_name.rs -git commit -m "Add CLI support for ConsecutiveOnesMatrixAugmentation" -``` - -### Task 3: Register the Canonical Example and Paper-Test Coverage - -**Files:** -- Modify: `src/models/algebraic/consecutive_ones_matrix_augmentation.rs` -- Modify: `src/models/algebraic/mod.rs` -- Inspect: `src/example_db/model_builders.rs` - -**Step 1: Add canonical example specs** - -Inside the model file, add `canonical_model_example_specs()` with: -- the corrected YES instance from issue #419 -- `bound = 2` -- `optimal_config = vec![0, 1, 4, 2, 3]` -- `optimal_value = serde_json::json!(true)` - -Update the algebraic category spec chain so the new model is included. - -**Step 2: Add paper-example-focused tests** - -Extend the new unit test file with a `test_consecutive_ones_matrix_augmentation_paper_example` that: -- constructs the same canonical YES instance -- verifies the issue witness permutation is satisfying -- uses `BruteForce` to confirm at least one satisfying permutation exists - -Run: -```bash -cargo test consecutive_ones_matrix_augmentation_paper_example --features "ilp-highs example-db" -- --nocapture -cargo test canonical_model_example --features "ilp-highs example-db" -- --nocapture -``` - -Expected: the example-db path and paper example test pass. - -**Step 3: Commit** - -```bash -git add src/models/algebraic/consecutive_ones_matrix_augmentation.rs \ - src/models/algebraic/mod.rs \ - src/unit_tests/models/algebraic/consecutive_ones_matrix_augmentation.rs -git commit -m "Add example-db coverage for ConsecutiveOnesMatrixAugmentation" -``` - -## Batch 2: Paper Entry - -### Task 4: Document the Model in `docs/paper/reductions.typ` - -**Files:** -- Modify: `docs/paper/reductions.typ` - -**Step 1: Add the display name and `problem-def`** - -Add: -- `"ConsecutiveOnesMatrixAugmentation": [Consecutive Ones Matrix Augmentation],` -- a `#problem-def("ConsecutiveOnesMatrixAugmentation")[ ... ][ ... ]` entry - -The paper entry should explicitly say: -- the matrix is binary and the goal is to flip at most `K` zeros to ones -- the witness displayed in the paper is a column permutation -- the augmentation set is derived by filling holes between the first and last `1` in each row under that permutation -- the historical notes and complexity match the corrected issue discussion - -**Step 2: Use the canonical example data** - -Follow the existing `load-model-example(...)` pattern and derive: -- the rendered matrix -- the example permutation -- the CLI `pred create --example`, `pred solve`, and `pred evaluate ... --config ...` commands - -Do not hand-write a bare alias or a mismatched witness. - -**Step 3: Build the paper** - -Run: -```bash -make paper -``` - -Expected: Typst compiles cleanly and the new problem entry renders without completeness regressions. - -**Step 4: Commit** - -```bash -git add docs/paper/reductions.typ -git commit -m "Document ConsecutiveOnesMatrixAugmentation in the paper" -``` - -## Final Verification - -### Task 5: Run Full Verification and Stage Expected Generated Outputs - -**Files:** -- Modify if regenerated: tracked schema/example/paper outputs only - -**Step 1: Run focused checks** - -```bash -cargo test consecutive_ones_matrix_augmentation --features "ilp-highs example-db" -- --nocapture -cargo test -p problemreductions-cli consecutive_ones_matrix_augmentation -- --nocapture -``` - -Expected: all new targeted tests pass. - -**Step 2: Run repo verification** - -```bash -make test -make clippy -make paper -``` - -If the new model requires regenerated tracked outputs, run the repo-standard regeneration command and stage only the expected files. - -**Step 3: Inspect the tree** - -```bash -git status --short -``` - -Expected: only intentional source/docs/generated changes remain; the plan file is still present until the implementation is complete. - -**Step 4: Final implementation commit** - -```bash -git add -A -git commit -m "Implement #419: ConsecutiveOnesMatrixAugmentation" -``` From 22247cb23b792d854974556a31e657b8a6c28a38 Mon Sep 17 00:00:00 2001 From: Xiwei Pan Date: Mon, 23 Mar 2026 15:12:48 +0800 Subject: [PATCH 4/4] Fix CLI panic on negative bound, paper solve command, and test coverage - Use try_new() instead of new() in CLI create path to avoid panic on negative --bound input - Add --solver brute-force to paper pred solve command (no ILP path) - Add CLI regression test for negative bound - Add unit test for all-zero row branch - Apply cargo fmt fixes Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/paper/reductions.typ | 2 +- problemreductions-cli/src/commands/create.rs | 31 ++++++++++++++----- src/lib.rs | 3 +- .../consecutive_ones_matrix_augmentation.rs | 22 ++++++++++++- tests/main.rs | 4 +-- 5 files changed, 48 insertions(+), 14 deletions(-) diff --git a/docs/paper/reductions.typ b/docs/paper/reductions.typ index 4e6f0686..57c56040 100644 --- a/docs/paper/reductions.typ +++ b/docs/paper/reductions.typ @@ -6144,7 +6144,7 @@ A classical NP-complete problem from Garey and Johnson @garey1979[Ch.~3, p.~76], #pred-commands( "pred create --example ConsecutiveOnesMatrixAugmentation -o consecutive-ones-matrix-augmentation.json", - "pred solve consecutive-ones-matrix-augmentation.json", + "pred solve consecutive-ones-matrix-augmentation.json --solver brute-force", "pred evaluate consecutive-ones-matrix-augmentation.json --config " + x.optimal_config.map(str).join(","), ) diff --git a/problemreductions-cli/src/commands/create.rs b/problemreductions-cli/src/commands/create.rs index a58f40d6..0dbdec6e 100644 --- a/problemreductions-cli/src/commands/create.rs +++ b/problemreductions-cli/src/commands/create.rs @@ -2834,7 +2834,8 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> { ) })?; ( - ser(ConsecutiveOnesMatrixAugmentation::new(matrix, bound))?, + ser(ConsecutiveOnesMatrixAugmentation::try_new(matrix, bound) + .map_err(|e| anyhow::anyhow!(e))?)?, resolved_variant.clone(), ) } @@ -8026,10 +8027,8 @@ mod tests { args.matrix = Some("1,0,0,1,1;1,1,0,0,0;0,1,1,0,1;0,0,1,1,0".to_string()); args.bound = Some(2); - let output_path = std::env::temp_dir().join(format!( - "coma-create-{}.json", - std::process::id() - )); + let output_path = + std::env::temp_dir().join(format!("coma-create-{}.json", std::process::id())); let out = OutputConfig { output: Some(output_path.clone()), quiet: true, @@ -8073,9 +8072,25 @@ mod tests { }; let err = create(&args, &out).unwrap_err().to_string(); - assert!(err.contains( - "ConsecutiveOnesMatrixAugmentation requires --matrix and --bound" - )); + assert!(err.contains("ConsecutiveOnesMatrixAugmentation requires --matrix and --bound")); assert!(err.contains("Usage: pred create ConsecutiveOnesMatrixAugmentation")); } + + #[test] + fn test_create_consecutive_ones_matrix_augmentation_negative_bound() { + let mut args = empty_args(); + args.problem = Some("ConsecutiveOnesMatrixAugmentation".to_string()); + args.matrix = Some("1,0;0,1".to_string()); + args.bound = Some(-1); + + let out = OutputConfig { + output: None, + quiet: true, + json: false, + auto_json: false, + }; + + let err = create(&args, &out).unwrap_err().to_string(); + assert!(err.contains("nonnegative")); + } } diff --git a/src/lib.rs b/src/lib.rs index c17ed2a7..2933fc2c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,8 +43,7 @@ pub mod variant; pub mod prelude { // Problem types pub use crate::models::algebraic::{ - ConsecutiveOnesMatrixAugmentation, QuadraticAssignment, SparseMatrixCompression, BMF, - QUBO, + ConsecutiveOnesMatrixAugmentation, QuadraticAssignment, SparseMatrixCompression, BMF, QUBO, }; pub use crate::models::formula::{ CNFClause, CircuitSAT, KSatisfiability, NAESatisfiability, QuantifiedBooleanFormulas, diff --git a/src/unit_tests/models/algebraic/consecutive_ones_matrix_augmentation.rs b/src/unit_tests/models/algebraic/consecutive_ones_matrix_augmentation.rs index 2bd645fa..4b59c2dc 100644 --- a/src/unit_tests/models/algebraic/consecutive_ones_matrix_augmentation.rs +++ b/src/unit_tests/models/algebraic/consecutive_ones_matrix_augmentation.rs @@ -94,7 +94,10 @@ fn test_consecutive_ones_matrix_augmentation_complexity_metadata() { .find(|entry| entry.name == "ConsecutiveOnesMatrixAugmentation") .expect("variant entry should exist"); - assert_eq!(entry.complexity, "factorial(num_cols) * num_rows * num_cols"); + assert_eq!( + entry.complexity, + "factorial(num_cols) * num_rows * num_cols" + ); } #[cfg(feature = "example-db")] @@ -106,6 +109,23 @@ fn test_consecutive_ones_matrix_augmentation_has_canonical_example() { assert_eq!(specs[0].id, "consecutive_ones_matrix_augmentation"); } +#[test] +fn test_consecutive_ones_matrix_augmentation_all_zero_row() { + let problem = ConsecutiveOnesMatrixAugmentation::new( + vec![ + vec![true, false, true], + vec![false, false, false], + vec![false, true, false], + ], + 0, + ); + + // Permutation [0, 1, 2] — row 0 has gap, row 1 has no 1s (0 cost), row 2 is fine + assert!(!problem.evaluate(&[0, 1, 2])); + // Permutation [0, 2, 1] — row 0: [1,1,0] consecutive, row 1: all zeros (0 cost), row 2: [0,0,1] consecutive + assert!(problem.evaluate(&[0, 2, 1])); +} + #[test] #[should_panic(expected = "same length")] fn test_consecutive_ones_matrix_augmentation_rejects_ragged_matrix() { diff --git a/tests/main.rs b/tests/main.rs index bb527191..6a7cc0c0 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -1,3 +1,5 @@ +#[path = "suites/consecutive_ones_matrix_augmentation.rs"] +mod consecutive_ones_matrix_augmentation; #[path = "suites/examples.rs"] mod examples; #[path = "suites/integration.rs"] @@ -6,5 +8,3 @@ mod integration; mod jl_parity; #[path = "suites/reductions.rs"] mod reductions; -#[path = "suites/consecutive_ones_matrix_augmentation.rs"] -mod consecutive_ones_matrix_augmentation;