diff --git a/docs/paper/reductions.typ b/docs/paper/reductions.typ index 7a75c7c18..d47f69091 100644 --- a/docs/paper/reductions.typ +++ b/docs/paper/reductions.typ @@ -182,6 +182,7 @@ "QuadraticDiophantineEquations": [Quadratic Diophantine Equations], "QuantifiedBooleanFormulas": [Quantified Boolean Formulas (QBF)], "RectilinearPictureCompression": [Rectilinear Picture Compression], + "RegisterSufficiency": [Register Sufficiency], "ResourceConstrainedScheduling": [Resource Constrained Scheduling], "RootedTreeStorageAssignment": [Rooted Tree Storage Assignment], "SchedulingToMinimizeWeightedCompletionTime": [Scheduling to Minimize Weighted Completion Time], @@ -4170,6 +4171,33 @@ A classical NP-complete problem from Garey and Johnson @garey1979[Ch.~3, p.~76], ] } +#{ + let x = load-model-example("RegisterSufficiency") + let n = x.instance.num_vertices + let arcs = x.instance.arcs + let K = x.instance.bound + let sigma = x.optimal_config + // Build evaluation order (position -> vertex) + let order = range(n).map(pos => + range(n).find(v => sigma.at(v) == pos) + ) + [ + #problem-def("RegisterSufficiency")[ + Given a directed acyclic graph $G = (V, A)$ with $n = |V|$ vertices, where each arc $(v, u) in A$ means vertex $v$ depends on vertex $u$, and a positive integer $K$, determine whether there exists an evaluation ordering $v_(pi(0)), v_(pi(1)), dots, v_(pi(n-1))$ of all vertices such that the computation can be performed using at most $K$ registers. A value must reside in a register from the moment it is computed until all vertices that depend on it have been evaluated. + ][ + Register Sufficiency is problem SS19 (also A11 PO1) in Garey & Johnson @garey1979. NP-complete via reduction from 3-SAT @sethi1975. Remains NP-complete even when all vertices have out-degree at most 2. For expression trees (DAGs with tree structure), the Sethi--Ullman algorithm solves the problem in $O(n)$ time @sethiUllman1970. For general DAGs, Kessler's dynamic programming over register states runs in $O(n^2 dot 2^n)$ time @kessler1998. + + *Example.* Let $n = #n$ vertices with arcs (dependencies): #{arcs.map(a => $v_#(a.at(0)) arrow.r v_#(a.at(1))$).join(", ")}. Bound $K = #K$. The evaluation order $pi = (#order.map(v => $v_#v$).join(", "))$ achieves a maximum of $#K$ registers. + + #pred-commands( + "pred create --example RegisterSufficiency -o register-sufficiency.json", + "pred solve register-sufficiency.json --solver brute-force", + "pred evaluate register-sufficiency.json --config " + x.optimal_config.map(str).join(","), + ) + ] + ] +} + #{ let x = load-model-example("RuralPostman") let nv = x.instance.graph.num_vertices diff --git a/docs/paper/references.bib b/docs/paper/references.bib index 4e2ef3e54..8a21f9287 100644 --- a/docs/paper/references.bib +++ b/docs/paper/references.bib @@ -1,3 +1,32 @@ +@article{sethi1975, + author = {Ravi Sethi}, + title = {Complete Register Allocation Problems}, + journal = {SIAM Journal on Computing}, + volume = {4}, + number = {3}, + pages = {226--248}, + year = {1975}, + doi = {10.1137/0204020} +} + +@article{sethiUllman1970, + author = {Ravi Sethi and Jeffrey D. Ullman}, + title = {The Generation of Optimal Code for Arithmetic Expressions}, + journal = {Journal of the ACM}, + volume = {17}, + number = {4}, + pages = {715--728}, + year = {1970}, + doi = {10.1145/321607.321620} +} + +@phdthesis{kessler1998, + author = {Christoph W. Kessler}, + title = {Scheduling Expression {DAG}s for Minimal Register Need}, + school = {Universit{\"a}t des Saarlandes}, + year = {1998} +} + @inproceedings{wagner1975, author = {Robert A. Wagner}, title = {On the Complexity of the Extended String-to-String Correction Problem}, diff --git a/problemreductions-cli/src/cli.rs b/problemreductions-cli/src/cli.rs index 91efd0623..62015a45c 100644 --- a/problemreductions-cli/src/cli.rs +++ b/problemreductions-cli/src/cli.rs @@ -318,6 +318,7 @@ Flags by problem type: StringToStringCorrection --source-string, --target-string, --bound [--alphabet-size] D2CIF --arcs, --capacities, --source-1, --sink-1, --source-2, --sink-2, --requirement-1, --requirement-2 MinimumDummyActivitiesPert --arcs [--num-vertices] + RegisterSufficiency --arcs, --bound [--num-vertices] CBQ --domain-size, --relations, --conjuncts-spec IntegerExpressionMembership --expression (JSON), --target ILP, CircuitSAT (via reduction only) diff --git a/problemreductions-cli/src/commands/create.rs b/problemreductions-cli/src/commands/create.rs index 1e2a87a1c..129e8f137 100644 --- a/problemreductions-cli/src/commands/create.rs +++ b/problemreductions-cli/src/commands/create.rs @@ -27,7 +27,7 @@ use problemreductions::models::misc::{ IntegerExpressionMembership, JobShopScheduling, KnownValue, KthLargestMTuple, LongestCommonSubsequence, MinimumTardinessSequencing, MultiprocessorScheduling, PaintShop, PartiallyOrderedKnapsack, ProductionPlanning, QueryArg, RectilinearPictureCompression, - ResourceConstrainedScheduling, SchedulingToMinimizeWeightedCompletionTime, + RegisterSufficiency, ResourceConstrainedScheduling, SchedulingToMinimizeWeightedCompletionTime, SchedulingWithIndividualDeadlines, SequencingToMinimizeMaximumCumulativeCost, SequencingToMinimizeWeightedCompletionTime, SequencingToMinimizeWeightedTardiness, SequencingWithReleaseTimesAndDeadlines, SequencingWithinIntervals, ShortestCommonSupersequence, @@ -708,6 +708,9 @@ fn example_for(canonical: &str, graph_type: Option<&str>) -> &'static str { } "MinimumFeedbackArcSet" => "--arcs \"0>1,1>2,2>0\"", "MinimumDummyActivitiesPert" => "--arcs \"0>2,0>3,1>3,1>4,2>5\" --num-vertices 6", + "RegisterSufficiency" => { + "--arcs \"2>0,2>1,3>1,4>2,4>3,5>0,6>4,6>5\" --bound 3 --num-vertices 7" + } "StrongConnectivityAugmentation" => { "--arcs \"0>1,1>2\" --candidate-arcs \"2>0:1\" --bound 1" } @@ -4220,6 +4223,34 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> { ) } + // RegisterSufficiency + "RegisterSufficiency" => { + let usage = "Usage: pred create RegisterSufficiency --arcs \"2>0,2>1,3>1,4>2,4>3,5>0,6>4,6>5\" --bound 3 [--num-vertices N]"; + let arcs_str = args.arcs.as_deref().ok_or_else(|| { + anyhow::anyhow!( + "RegisterSufficiency requires --arcs and --bound\n\n\ + {usage}" + ) + })?; + let bound = args.bound.ok_or_else(|| { + anyhow::anyhow!( + "RegisterSufficiency requires --bound\n\n\ + {usage}" + ) + })?; + if bound < 0 { + bail!("RegisterSufficiency --bound must be non-negative\n\n{usage}"); + } + let bound = bound as usize; + let (graph, _) = parse_directed_graph(arcs_str, args.num_vertices)?; + let n = graph.num_vertices(); + let arcs = graph.arcs(); + ( + ser(RegisterSufficiency::new(n, arcs, bound))?, + resolved_variant.clone(), + ) + } + // MixedChinesePostman "MixedChinesePostman" => { let usage = "Usage: pred create MixedChinesePostman --graph 0-2,1-3,0-4,4-2 --arcs \"0>1,1>2,2>3,3>0\" --edge-weights 2,3,1,2 --arc-costs 2,3,1,4 [--num-vertices N]"; diff --git a/problemreductions-cli/tests/cli_tests.rs b/problemreductions-cli/tests/cli_tests.rs index 158e71584..448f86c7b 100644 --- a/problemreductions-cli/tests/cli_tests.rs +++ b/problemreductions-cli/tests/cli_tests.rs @@ -4088,6 +4088,38 @@ fn test_create_rectilinear_picture_compression_rejects_ragged_matrix() { ); } +#[test] +fn test_create_register_sufficiency() { + let output_file = std::env::temp_dir().join("pred_test_create_register_sufficiency.json"); + let output = pred() + .args([ + "-o", + output_file.to_str().unwrap(), + "create", + "RegisterSufficiency", + "--arcs", + "2>0,2>1,3>1,4>2,4>3,5>0,6>4,6>5", + "--bound", + "3", + "--num-vertices", + "7", + ]) + .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"], "RegisterSufficiency"); + assert_eq!(json["data"]["num_vertices"], 7); + assert_eq!(json["data"]["bound"], 3); + assert_eq!(json["data"]["arcs"].as_array().unwrap().len(), 8); + std::fs::remove_file(&output_file).ok(); +} + #[test] fn test_create_help_uses_generic_matrix_and_k_descriptions() { let output = pred().args(["create", "--help"]).output().unwrap(); diff --git a/src/models/misc/mod.rs b/src/models/misc/mod.rs index e77eba847..cfbaf213a 100644 --- a/src/models/misc/mod.rs +++ b/src/models/misc/mod.rs @@ -24,6 +24,7 @@ //! - [`PrecedenceConstrainedScheduling`]: Schedule unit tasks on processors by deadline //! - [`ProductionPlanning`]: Meet all period demands within capacity and total-cost bounds //! - [`RectilinearPictureCompression`]: Cover 1-entries with bounded rectangles +//! - [`RegisterSufficiency`]: Evaluate DAG computation with bounded registers //! - [`ResourceConstrainedScheduling`]: Schedule unit-length tasks on processors with resource constraints //! - [`SchedulingWithIndividualDeadlines`]: Meet per-task deadlines on parallel processors //! - [`StackerCrane`]: Minimize the total length of a closed walk through required arcs @@ -89,6 +90,7 @@ pub(crate) mod partition; mod precedence_constrained_scheduling; mod production_planning; mod rectilinear_picture_compression; +mod register_sufficiency; pub(crate) mod resource_constrained_scheduling; mod scheduling_to_minimize_weighted_completion_time; mod scheduling_with_individual_deadlines; @@ -134,6 +136,7 @@ pub use partition::Partition; pub use precedence_constrained_scheduling::PrecedenceConstrainedScheduling; pub use production_planning::ProductionPlanning; pub use rectilinear_picture_compression::RectilinearPictureCompression; +pub use register_sufficiency::RegisterSufficiency; pub use resource_constrained_scheduling::ResourceConstrainedScheduling; pub use scheduling_to_minimize_weighted_completion_time::SchedulingToMinimizeWeightedCompletionTime; pub use scheduling_with_individual_deadlines::SchedulingWithIndividualDeadlines; @@ -195,6 +198,7 @@ pub(crate) fn canonical_model_example_specs() -> Vec", description: "Directed arcs (v, u) meaning v depends on u" }, + FieldInfo { name: "bound", type_name: "usize", description: "Register bound K" }, + ], + } +} + +/// The Register Sufficiency problem. +/// +/// Given a directed acyclic graph G = (V, A) where arcs represent data +/// dependencies, and a positive integer K, determine whether there is an +/// evaluation ordering of all vertices such that at most K registers are +/// needed at any point during the computation. +/// +/// # Representation +/// +/// An arc `(v, u)` means vertex `v` depends on vertex `u` (i.e., `u` must be +/// in a register when `v` is evaluated). Each variable represents a vertex, +/// with domain `{0, ..., n-1}` giving its evaluation position (the config +/// must be a valid permutation). +/// +/// # Example +/// +/// ``` +/// use problemreductions::models::misc::RegisterSufficiency; +/// use problemreductions::{Problem, Solver, BruteForce}; +/// +/// // 4 vertices: v2 depends on v0, v3 depends on v0 and v1 +/// let problem = RegisterSufficiency::new( +/// 4, +/// vec![(2, 0), (3, 0), (3, 1)], +/// 2, +/// ); +/// let solver = BruteForce::new(); +/// let solution = solver.find_witness(&problem); +/// assert!(solution.is_some()); +/// ``` +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RegisterSufficiency { + /// Number of vertices. + num_vertices: usize, + /// Directed arcs (v, u) meaning v depends on u. + arcs: Vec<(usize, usize)>, + /// Register bound K. + bound: usize, +} + +impl RegisterSufficiency { + /// Create a new Register Sufficiency instance. + /// + /// # Panics + /// + /// Panics if any arc index is out of bounds (>= num_vertices), + /// or if any arc is a self-loop. + pub fn new(num_vertices: usize, arcs: Vec<(usize, usize)>, bound: usize) -> Self { + for &(v, u) in &arcs { + assert!( + v < num_vertices && u < num_vertices, + "Arc ({}, {}) out of bounds for {} vertices", + v, + u, + num_vertices + ); + assert!(v != u, "Self-loop ({}, {}) not allowed in a DAG", v, u); + } + Self { + num_vertices, + arcs, + bound, + } + } + + /// Get the number of vertices. + pub fn num_vertices(&self) -> usize { + self.num_vertices + } + + /// Get the number of arcs. + pub fn num_arcs(&self) -> usize { + self.arcs.len() + } + + /// Get the register bound K. + pub fn bound(&self) -> usize { + self.bound + } + + /// Get the arcs. + pub fn arcs(&self) -> &[(usize, usize)] { + &self.arcs + } + + /// Simulate register usage for a given evaluation ordering and return the + /// maximum number of registers used, or `None` if the ordering is invalid + /// (not a permutation or violates dependencies). + pub fn simulate_registers(&self, config: &[usize]) -> Option { + let n = self.num_vertices; + if config.len() != n { + return None; + } + + // Check valid permutation: each position 0..n-1 used exactly once + let mut order = vec![0usize; n]; // order[position] = vertex + let mut used = vec![false; n]; + for (vertex, &position) in config.iter().enumerate() { + if position >= n { + return None; + } + if used[position] { + return None; + } + used[position] = true; + order[position] = vertex; + } + + // Build dependency info: + // dependents[u] = list of vertices that depend on u (i.e., arcs (v, u)) + // dependencies[v] = list of vertices that v depends on (i.e., arcs (v, u)) + let mut dependencies: Vec> = vec![vec![]; n]; + let mut dependents: Vec> = vec![vec![]; n]; + for &(v, u) in &self.arcs { + dependencies[v].push(u); + dependents[u].push(v); + } + + // For each vertex u, compute the latest position among its dependents. + // A vertex u must stay in registers until all its dependents have been evaluated. + let mut last_use = vec![0usize; n]; + for u in 0..n { + if dependents[u].is_empty() { + // Vertex u has no dependents. It stays in registers from its + // evaluation step until the end (final outputs must be in S_n). + last_use[u] = n; // stays until the end + } else { + let mut latest = 0; + for &v in &dependents[u] { + latest = latest.max(config[v]); + } + last_use[u] = latest; + } + } + + let mut max_registers = 0; + + // Simulate: process vertices in evaluation order + for step in 0..n { + let vertex = order[step]; + + // Check dependencies: all dependencies of this vertex must have + // been evaluated before this step + for &dep in &dependencies[vertex] { + if config[dep] >= step { + // Dependency not yet evaluated + return None; + } + } + + // Count registers at this step: + // A vertex v is in registers if: + // - v has been evaluated (config[v] <= step) + // - v is still needed (last_use[v] > step, or v is the current vertex) + // Actually, more precisely: after evaluating vertex at position `step`, + // the register set contains all vertices evaluated so far whose last + // use is > step (they're still needed later), plus the current vertex. + let reg_count = order[..=step] + .iter() + .filter(|&&v| last_use[v] > step) + .count(); + + max_registers = max_registers.max(reg_count); + } + + Some(max_registers) + } +} + +impl Problem for RegisterSufficiency { + const NAME: &'static str = "RegisterSufficiency"; + type Value = crate::types::Or; + + fn variant() -> Vec<(&'static str, &'static str)> { + crate::variant_params![] + } + + fn dims(&self) -> Vec { + vec![self.num_vertices; self.num_vertices] + } + + fn evaluate(&self, config: &[usize]) -> crate::types::Or { + crate::types::Or( + self.simulate_registers(config) + .is_some_and(|max_reg| max_reg <= self.bound), + ) + } +} + +crate::declare_variants! { + default RegisterSufficiency => "num_vertices ^ 2 * 2 ^ num_vertices", +} + +#[cfg(feature = "example-db")] +pub(crate) fn canonical_model_example_specs() -> Vec { + vec![crate::example_db::specs::ModelExampleSpec { + id: "register_sufficiency", + // Issue #515 example: 7 vertices, 8 arcs, K=3 + // Arcs (0-indexed): (2,0), (2,1), (3,1), (4,2), (4,3), (5,0), (6,4), (6,5) + // Order: v0,v1,v2,v3,v5,v4,v6 -> positions [0,1,2,3,5,4,6] + instance: Box::new(RegisterSufficiency::new( + 7, + vec![ + (2, 0), + (2, 1), + (3, 1), + (4, 2), + (4, 3), + (5, 0), + (6, 4), + (6, 5), + ], + 3, + )), + // Order: v1,v2,v3,v4,v6,v5,v7 (1-indexed) = v0,v1,v2,v3,v5,v4,v6 (0-indexed) + // Positions: v0->0, v1->1, v2->2, v3->3, v4->5, v5->4, v6->6 + optimal_config: vec![0, 1, 2, 3, 5, 4, 6], + optimal_value: serde_json::json!(true), + }] +} + +#[cfg(test)] +#[path = "../../unit_tests/models/misc/register_sufficiency.rs"] +mod tests; diff --git a/src/models/mod.rs b/src/models/mod.rs index 29b5e05bf..7688a8896 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -43,7 +43,7 @@ pub use misc::{ IntExpr, IntegerExpressionMembership, JobShopScheduling, Knapsack, KthLargestMTuple, LongestCommonSubsequence, MinimumTardinessSequencing, MultiprocessorScheduling, PaintShop, Partition, PrecedenceConstrainedScheduling, ProductionPlanning, QueryArg, - RectilinearPictureCompression, ResourceConstrainedScheduling, + RectilinearPictureCompression, RegisterSufficiency, ResourceConstrainedScheduling, SchedulingToMinimizeWeightedCompletionTime, SchedulingWithIndividualDeadlines, SequencingToMinimizeMaximumCumulativeCost, SequencingToMinimizeWeightedCompletionTime, SequencingToMinimizeWeightedTardiness, SequencingWithReleaseTimesAndDeadlines, diff --git a/src/unit_tests/models/misc/register_sufficiency.rs b/src/unit_tests/models/misc/register_sufficiency.rs new file mode 100644 index 000000000..c4cd025fc --- /dev/null +++ b/src/unit_tests/models/misc/register_sufficiency.rs @@ -0,0 +1,230 @@ +use super::*; +use crate::solvers::BruteForce; +use crate::traits::Problem; + +#[test] +fn test_register_sufficiency_basic() { + let problem = RegisterSufficiency::new( + 7, + vec![ + (2, 0), + (2, 1), + (3, 1), + (4, 2), + (4, 3), + (5, 0), + (6, 4), + (6, 5), + ], + 3, + ); + assert_eq!(problem.num_vertices(), 7); + assert_eq!(problem.num_arcs(), 8); + assert_eq!(problem.bound(), 3); + assert_eq!( + problem.arcs(), + &[ + (2, 0), + (2, 1), + (3, 1), + (4, 2), + (4, 3), + (5, 0), + (6, 4), + (6, 5) + ] + ); + assert_eq!(problem.dims(), vec![7; 7]); + assert_eq!( + ::NAME, + "RegisterSufficiency" + ); + assert_eq!(::variant(), vec![]); +} + +#[test] +fn test_register_sufficiency_evaluate_valid() { + // Issue #515 example: 7 vertices, 8 arcs, K=3 + let problem = RegisterSufficiency::new( + 7, + vec![ + (2, 0), + (2, 1), + (3, 1), + (4, 2), + (4, 3), + (5, 0), + (6, 4), + (6, 5), + ], + 3, + ); + // Order: v0,v1,v2,v3,v5,v4,v6 (0-indexed) + // Positions: v0->0, v1->1, v2->2, v3->3, v4->5, v5->4, v6->6 + let config = vec![0, 1, 2, 3, 5, 4, 6]; + assert!(problem.evaluate(&config)); + + // Verify register count + let max_reg = problem.simulate_registers(&config).unwrap(); + assert_eq!(max_reg, 3); +} + +#[test] +fn test_register_sufficiency_evaluate_invalid_permutation() { + let problem = RegisterSufficiency::new(4, vec![(2, 0), (3, 0), (3, 1)], 2); + // Not a permutation: position 0 used twice + assert!(!problem.evaluate(&[0, 0, 1, 2])); + // Wrong length + assert!(!problem.evaluate(&[0, 1, 2])); + assert!(!problem.evaluate(&[0, 1, 2, 3, 4])); + // Position out of range + assert!(!problem.evaluate(&[0, 1, 2, 4])); +} + +#[test] +fn test_register_sufficiency_evaluate_invalid_dependency() { + // v2 depends on v0, v3 depends on v0 and v1 + let problem = RegisterSufficiency::new(4, vec![(2, 0), (3, 0), (3, 1)], 4); + // v2 at position 0, v0 at position 1 -> v2 evaluated before its dependency v0 + assert!(!problem.evaluate(&[1, 2, 0, 3])); +} + +#[test] +fn test_register_sufficiency_evaluate_exceeds_bound() { + // Issue example with K=2 (should fail - minimum is 3) + let problem = RegisterSufficiency::new( + 7, + vec![ + (2, 0), + (2, 1), + (3, 1), + (4, 2), + (4, 3), + (5, 0), + (6, 4), + (6, 5), + ], + 2, + ); + // Same valid ordering but K=2 is too small + let config = vec![0, 1, 2, 3, 5, 4, 6]; + assert!(!problem.evaluate(&config)); +} + +#[test] +fn test_register_sufficiency_brute_force() { + // Small instance: 4 vertices, v2 depends on v0, v3 depends on v1 + let problem = RegisterSufficiency::new(4, vec![(2, 0), (3, 1)], 2); + let solver = BruteForce::new(); + let solution = solver + .find_witness(&problem) + .expect("should find a solution"); + assert!(problem.evaluate(&solution)); +} + +#[test] +fn test_register_sufficiency_brute_force_all() { + let problem = RegisterSufficiency::new(4, vec![(2, 0), (3, 1)], 2); + let solver = BruteForce::new(); + let solutions = solver.find_all_witnesses(&problem); + assert!(!solutions.is_empty()); + for sol in &solutions { + assert!(problem.evaluate(sol)); + } +} + +#[test] +fn test_register_sufficiency_unsatisfiable() { + // Chain: v3 depends on v2, v2 depends on v1, v1 depends on v0 + // Plus: v3 also depends on v0 + // This requires 3 registers (v0 must stay alive until v3) + // With K=1, impossible + let problem = RegisterSufficiency::new(4, vec![(1, 0), (2, 1), (3, 2), (3, 0)], 1); + let solver = BruteForce::new(); + assert!(solver.find_witness(&problem).is_none()); +} + +#[test] +fn test_register_sufficiency_serialization() { + let problem = RegisterSufficiency::new( + 7, + vec![ + (2, 0), + (2, 1), + (3, 1), + (4, 2), + (4, 3), + (5, 0), + (6, 4), + (6, 5), + ], + 3, + ); + let json = serde_json::to_value(&problem).unwrap(); + let restored: RegisterSufficiency = serde_json::from_value(json).unwrap(); + assert_eq!(restored.num_vertices(), problem.num_vertices()); + assert_eq!(restored.num_arcs(), problem.num_arcs()); + assert_eq!(restored.bound(), problem.bound()); + assert_eq!(restored.arcs(), problem.arcs()); +} + +#[test] +fn test_register_sufficiency_empty() { + let problem = RegisterSufficiency::new(0, vec![], 0); + assert_eq!(problem.num_vertices(), 0); + assert_eq!(problem.dims(), Vec::::new()); + assert!(problem.evaluate(&[])); +} + +#[test] +fn test_register_sufficiency_single_vertex() { + let problem = RegisterSufficiency::new(1, vec![], 1); + assert!(problem.evaluate(&[0])); + // K=0 should fail (vertex needs one register) + let problem_k0 = RegisterSufficiency::new(1, vec![], 0); + assert!(!problem_k0.evaluate(&[0])); +} + +#[test] +fn test_register_sufficiency_paper_example() { + // The issue example: 7 vertices, 8 arcs, K=3 + let problem = RegisterSufficiency::new( + 7, + vec![ + (2, 0), + (2, 1), + (3, 1), + (4, 2), + (4, 3), + (5, 0), + (6, 4), + (6, 5), + ], + 3, + ); + + // The order from the issue: v1,v2,v3,v4,v6,v5,v7 (1-indexed) + // = v0,v1,v2,v3,v5,v4,v6 (0-indexed) + // Positions: v0->0, v1->1, v2->2, v3->3, v4->5, v5->4, v6->6 + let config = vec![0, 1, 2, 3, 5, 4, 6]; + assert!(problem.evaluate(&config)); + assert_eq!(problem.simulate_registers(&config).unwrap(), 3); + + // Verify K=2 is impossible using brute force + let problem_k2 = RegisterSufficiency::new( + 7, + vec![ + (2, 0), + (2, 1), + (3, 1), + (4, 2), + (4, 3), + (5, 0), + (6, 4), + (6, 5), + ], + 2, + ); + let solver = BruteForce::new(); + assert!(solver.find_witness(&problem_k2).is_none()); +}