diff --git a/.github/ISSUE_TEMPLATE/problem.md b/.github/ISSUE_TEMPLATE/problem.md index 47763dbc1..2c5fff517 100644 --- a/.github/ISSUE_TEMPLATE/problem.md +++ b/.github/ISSUE_TEMPLATE/problem.md @@ -7,6 +7,10 @@ assignees: '' --- +## Motivation + + + ## Definition **Name:** diff --git a/.github/ISSUE_TEMPLATE/rule.md b/.github/ISSUE_TEMPLATE/rule.md index 6395d33a5..e52253941 100644 --- a/.github/ISSUE_TEMPLATE/rule.md +++ b/.github/ISSUE_TEMPLATE/rule.md @@ -9,6 +9,7 @@ assignees: '' **Source:** **Target:** +**Motivation:** **Reference:** ## Reduction Algorithm diff --git a/benches/solver_benchmarks.rs b/benches/solver_benchmarks.rs index 78ccf75dd..b36ed0901 100644 --- a/benches/solver_benchmarks.rs +++ b/benches/solver_benchmarks.rs @@ -18,7 +18,7 @@ fn bench_independent_set(c: &mut Criterion) { for n in [4, 6, 8, 10].iter() { // Create a path graph with n vertices let edges: Vec<(usize, usize)> = (0..*n - 1).map(|i| (i, i + 1)).collect(); - let problem = MaximumIndependentSet::::new(*n, edges); + let problem = MaximumIndependentSet::new(SimpleGraph::new(*n, edges), vec![1i32; *n]); let solver = BruteForce::new(); group.bench_with_input(BenchmarkId::new("path", n), n, |b, _| { @@ -35,7 +35,7 @@ fn bench_vertex_covering(c: &mut Criterion) { for n in [4, 6, 8, 10].iter() { let edges: Vec<(usize, usize)> = (0..*n - 1).map(|i| (i, i + 1)).collect(); - let problem = MinimumVertexCover::::new(*n, edges); + let problem = MinimumVertexCover::new(SimpleGraph::new(*n, edges), vec![1i32; *n]); let solver = BruteForce::new(); group.bench_with_input(BenchmarkId::new("path", n), n, |b, _| { @@ -51,8 +51,9 @@ fn bench_max_cut(c: &mut Criterion) { let mut group = c.benchmark_group("MaxCut"); for n in [4, 6, 8, 10].iter() { - let edges: Vec<(usize, usize, i32)> = (0..*n - 1).map(|i| (i, i + 1, 1)).collect(); - let problem = MaxCut::new(*n, edges); + let edges: Vec<(usize, usize)> = (0..*n - 1).map(|i| (i, i + 1)).collect(); + let weights = vec![1i32; edges.len()]; + let problem = MaxCut::new(SimpleGraph::new(*n, edges), weights); let solver = BruteForce::new(); group.bench_with_input(BenchmarkId::new("path", n), n, |b, _| { @@ -139,7 +140,7 @@ fn bench_coloring(c: &mut Criterion) { for n in [3, 4, 5, 6].iter() { let edges: Vec<(usize, usize)> = (0..*n - 1).map(|i| (i, i + 1)).collect(); - let problem = KColoring::::new(*n, edges); + let problem = KColoring::::new(SimpleGraph::new(*n, edges)); let solver = BruteForce::new(); group.bench_with_input(BenchmarkId::new("path_3colors", n), n, |b, _| { @@ -155,8 +156,9 @@ fn bench_matching(c: &mut Criterion) { let mut group = c.benchmark_group("Matching"); for n in [4, 6, 8, 10].iter() { - let edges: Vec<(usize, usize, i32)> = (0..*n - 1).map(|i| (i, i + 1, 1)).collect(); - let problem = MaximumMatching::new(*n, edges); + let edges: Vec<(usize, usize)> = (0..*n - 1).map(|i| (i, i + 1)).collect(); + let weights = vec![1i32; edges.len()]; + let problem = MaximumMatching::new(SimpleGraph::new(*n, edges), weights); let solver = BruteForce::new(); group.bench_with_input(BenchmarkId::new("path", n), n, |b, _| { @@ -196,7 +198,7 @@ fn bench_comparison(c: &mut Criterion) { // MaximumIndependentSet with 8 vertices let is_problem = - MaximumIndependentSet::::new(8, vec![(0, 1), (2, 3), (4, 5), (6, 7)]); + MaximumIndependentSet::new(SimpleGraph::new(8, vec![(0, 1), (2, 3), (4, 5), (6, 7)]), vec![1i32; 8]); group.bench_function("MaximumIndependentSet", |b| { b.iter(|| solver.find_best(black_box(&is_problem))) }); @@ -226,7 +228,7 @@ fn bench_comparison(c: &mut Criterion) { }); // MaxCut with 8 vertices - let mc_problem = MaxCut::new(8, vec![(0, 1, 1), (2, 3, 1), (4, 5, 1), (6, 7, 1)]); + let mc_problem = MaxCut::new(SimpleGraph::new(8, vec![(0, 1), (2, 3), (4, 5), (6, 7)]), vec![1, 1, 1, 1]); group.bench_function("MaxCut", |b| { b.iter(|| solver.find_best(black_box(&mc_problem))) }); diff --git a/docs/src/getting-started.md b/docs/src/getting-started.md index f5bf8b2d8..b824ef9b6 100644 --- a/docs/src/getting-started.md +++ b/docs/src/getting-started.md @@ -35,7 +35,7 @@ use problemreductions::prelude::*; use problemreductions::topology::SimpleGraph; // 1. Create: Independent Set on a path graph (4 vertices) -let problem = MaximumIndependentSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); +let problem = MaximumIndependentSet::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1i32; 4]); // 2. Reduce: Transform to Minimum Vertex Cover let reduction = ReduceTo::>::reduce_to(&problem); diff --git a/examples/reduction_kcoloring_to_ilp.rs b/examples/reduction_kcoloring_to_ilp.rs index 36376c580..7a243abbf 100644 --- a/examples/reduction_kcoloring_to_ilp.rs +++ b/examples/reduction_kcoloring_to_ilp.rs @@ -24,7 +24,7 @@ use problemreductions::topology::{Graph, SimpleGraph}; pub fn run() { // 1. Create KColoring instance: Petersen graph (10 vertices, 15 edges) with 3 colors, χ=3 let (num_vertices, edges) = petersen(); - let coloring = KColoring::::new(num_vertices, edges.clone()); + let coloring = KColoring::::new(SimpleGraph::new(num_vertices, edges.clone())); // 2. Reduce to ILP let reduction = ReduceTo::::reduce_to(&coloring); diff --git a/examples/reduction_kcoloring_to_qubo.rs b/examples/reduction_kcoloring_to_qubo.rs index ef379cb08..6bdfc1dbd 100644 --- a/examples/reduction_kcoloring_to_qubo.rs +++ b/examples/reduction_kcoloring_to_qubo.rs @@ -38,7 +38,7 @@ pub fn run() { // House graph: 5 vertices, 6 edges (square base + triangle roof), χ=3 let (num_vertices, edges) = house(); - let kc = KColoring::::new(num_vertices, edges.clone()); + let kc = KColoring::::new(SimpleGraph::new(num_vertices, edges.clone())); // Reduce to QUBO let reduction = ReduceTo::::reduce_to(&kc); diff --git a/examples/reduction_maxcut_to_spinglass.rs b/examples/reduction_maxcut_to_spinglass.rs index 924400a00..3c9e8538b 100644 --- a/examples/reduction_maxcut_to_spinglass.rs +++ b/examples/reduction_maxcut_to_spinglass.rs @@ -22,7 +22,7 @@ use problemreductions::topology::{Graph, SimpleGraph}; pub fn run() { let (num_vertices, edges) = petersen(); - let maxcut = MaxCut::::unweighted(num_vertices, edges.clone()); + let maxcut = MaxCut::<_, i32>::unweighted(SimpleGraph::new(num_vertices, edges.clone())); let reduction = ReduceTo::>::reduce_to(&maxcut); let sg = reduction.target_problem(); diff --git a/examples/reduction_maximumclique_to_ilp.rs b/examples/reduction_maximumclique_to_ilp.rs index b1bef1dcb..5738bf4eb 100644 --- a/examples/reduction_maximumclique_to_ilp.rs +++ b/examples/reduction_maximumclique_to_ilp.rs @@ -22,7 +22,7 @@ use problemreductions::topology::{Graph, SimpleGraph}; pub fn run() { // 1. Create MaximumClique instance: Octahedron (K_{2,2,2}), 6 vertices, 12 edges, clique number 3 let (num_vertices, edges) = octahedral(); - let clique = MaximumClique::::new(num_vertices, edges.clone()); + let clique = MaximumClique::new(SimpleGraph::new(num_vertices, edges.clone()), vec![1i32; num_vertices]); // 2. Reduce to ILP let reduction = ReduceTo::::reduce_to(&clique); diff --git a/examples/reduction_maximumindependentset_to_ilp.rs b/examples/reduction_maximumindependentset_to_ilp.rs index 3b1d54b2a..027ed15d7 100644 --- a/examples/reduction_maximumindependentset_to_ilp.rs +++ b/examples/reduction_maximumindependentset_to_ilp.rs @@ -21,7 +21,7 @@ use problemreductions::topology::{Graph, SimpleGraph}; pub fn run() { // 1. Create IS instance: Petersen graph let (num_vertices, edges) = petersen(); - let is = MaximumIndependentSet::::new(num_vertices, edges.clone()); + let is = MaximumIndependentSet::new(SimpleGraph::new(num_vertices, edges.clone()), vec![1i32; num_vertices]); // 2. Reduce to ILP let reduction = ReduceTo::::reduce_to(&is); diff --git a/examples/reduction_maximumindependentset_to_maximumsetpacking.rs b/examples/reduction_maximumindependentset_to_maximumsetpacking.rs index 0ba36348a..7f9054651 100644 --- a/examples/reduction_maximumindependentset_to_maximumsetpacking.rs +++ b/examples/reduction_maximumindependentset_to_maximumsetpacking.rs @@ -25,7 +25,7 @@ pub fn run() { // Petersen graph: 10 vertices, 15 edges, 3-regular let (num_vertices, edges) = petersen(); - let source = MaximumIndependentSet::::new(num_vertices, edges.clone()); + let source = MaximumIndependentSet::new(SimpleGraph::new(num_vertices, edges.clone()), vec![1i32; num_vertices]); println!("Source: MaximumIndependentSet on Petersen graph"); println!(" Vertices: {}", num_vertices); diff --git a/examples/reduction_maximumindependentset_to_minimumvertexcover.rs b/examples/reduction_maximumindependentset_to_minimumvertexcover.rs index 85d8d0976..95576de19 100644 --- a/examples/reduction_maximumindependentset_to_minimumvertexcover.rs +++ b/examples/reduction_maximumindependentset_to_minimumvertexcover.rs @@ -22,7 +22,7 @@ use problemreductions::topology::{Graph, SimpleGraph}; pub fn run() { // 1. Create IS instance: Petersen graph let (num_vertices, edges) = petersen(); - let is = MaximumIndependentSet::::new(num_vertices, edges.clone()); + let is = MaximumIndependentSet::new(SimpleGraph::new(num_vertices, edges.clone()), vec![1i32; num_vertices]); // 2. Reduce to VC let reduction = ReduceTo::>::reduce_to(&is); diff --git a/examples/reduction_maximumindependentset_to_qubo.rs b/examples/reduction_maximumindependentset_to_qubo.rs index 30b4bc078..279d8e17e 100644 --- a/examples/reduction_maximumindependentset_to_qubo.rs +++ b/examples/reduction_maximumindependentset_to_qubo.rs @@ -34,7 +34,7 @@ pub fn run() { // Petersen graph: 10 vertices, 15 edges, 3-regular let (num_vertices, edges) = petersen(); - let is = MaximumIndependentSet::::new(num_vertices, edges.clone()); + let is = MaximumIndependentSet::new(SimpleGraph::new(num_vertices, edges.clone()), vec![1i32; num_vertices]); // Reduce to QUBO let reduction = ReduceTo::::reduce_to(&is); diff --git a/examples/reduction_maximummatching_to_ilp.rs b/examples/reduction_maximummatching_to_ilp.rs index 66d63c123..e3a6b9e98 100644 --- a/examples/reduction_maximummatching_to_ilp.rs +++ b/examples/reduction_maximummatching_to_ilp.rs @@ -21,7 +21,7 @@ use problemreductions::topology::{Graph, SimpleGraph}; pub fn run() { // 1. Create MaximumMatching instance: Petersen graph with unit weights let (num_vertices, edges) = petersen(); - let matching = MaximumMatching::::unweighted(num_vertices, edges.clone()); + let matching = MaximumMatching::<_, i32>::unit_weights(SimpleGraph::new(num_vertices, edges.clone())); // 2. Reduce to ILP let reduction = ReduceTo::::reduce_to(&matching); diff --git a/examples/reduction_maximummatching_to_maximumsetpacking.rs b/examples/reduction_maximummatching_to_maximumsetpacking.rs index 675aa932d..26e352d33 100644 --- a/examples/reduction_maximummatching_to_maximumsetpacking.rs +++ b/examples/reduction_maximummatching_to_maximumsetpacking.rs @@ -25,7 +25,7 @@ pub fn run() { // Petersen graph with unit weights let (num_vertices, edges) = petersen(); - let source = MaximumMatching::::unweighted(num_vertices, edges.clone()); + let source = MaximumMatching::<_, i32>::unit_weights(SimpleGraph::new(num_vertices, edges.clone())); println!("Source: MaximumMatching on Petersen graph"); println!(" Vertices: {}", num_vertices); diff --git a/examples/reduction_minimumdominatingset_to_ilp.rs b/examples/reduction_minimumdominatingset_to_ilp.rs index c41c6ecd5..de87c07a6 100644 --- a/examples/reduction_minimumdominatingset_to_ilp.rs +++ b/examples/reduction_minimumdominatingset_to_ilp.rs @@ -21,7 +21,7 @@ use problemreductions::topology::{Graph, SimpleGraph}; pub fn run() { // 1. Create MinimumDominatingSet instance: Petersen graph let (num_vertices, edges) = petersen(); - let ds = MinimumDominatingSet::::new(num_vertices, edges.clone()); + let ds = MinimumDominatingSet::new(SimpleGraph::new(num_vertices, edges.clone()), vec![1i32; num_vertices]); // 2. Reduce to ILP let reduction = ReduceTo::::reduce_to(&ds); diff --git a/examples/reduction_minimumvertexcover_to_ilp.rs b/examples/reduction_minimumvertexcover_to_ilp.rs index 7c765193c..2592466f6 100644 --- a/examples/reduction_minimumvertexcover_to_ilp.rs +++ b/examples/reduction_minimumvertexcover_to_ilp.rs @@ -21,7 +21,7 @@ use problemreductions::topology::{Graph, SimpleGraph}; pub fn run() { // 1. Create VC instance: Petersen graph (10 vertices, 15 edges), VC=6 let (num_vertices, edges) = petersen(); - let vc = MinimumVertexCover::::new(num_vertices, edges.clone()); + let vc = MinimumVertexCover::new(SimpleGraph::new(num_vertices, edges.clone()), vec![1i32; num_vertices]); // 2. Reduce to ILP let reduction = ReduceTo::::reduce_to(&vc); diff --git a/examples/reduction_minimumvertexcover_to_maximumindependentset.rs b/examples/reduction_minimumvertexcover_to_maximumindependentset.rs index bbca9f4e2..718d95d5c 100644 --- a/examples/reduction_minimumvertexcover_to_maximumindependentset.rs +++ b/examples/reduction_minimumvertexcover_to_maximumindependentset.rs @@ -23,7 +23,7 @@ use problemreductions::topology::{Graph, SimpleGraph}; pub fn run() { // Petersen graph: 10 vertices, 15 edges, VC=6 let (num_vertices, edges) = petersen(); - let vc = MinimumVertexCover::::new(num_vertices, edges.clone()); + let vc = MinimumVertexCover::new(SimpleGraph::new(num_vertices, edges.clone()), vec![1i32; num_vertices]); let reduction = ReduceTo::>::reduce_to(&vc); let is = reduction.target_problem(); diff --git a/examples/reduction_minimumvertexcover_to_minimumsetcovering.rs b/examples/reduction_minimumvertexcover_to_minimumsetcovering.rs index 07f042a61..98d625cc9 100644 --- a/examples/reduction_minimumvertexcover_to_minimumsetcovering.rs +++ b/examples/reduction_minimumvertexcover_to_minimumsetcovering.rs @@ -25,7 +25,7 @@ pub fn run() { // Petersen graph: 10 vertices, 15 edges, VC=6 let (num_vertices, edges) = petersen(); - let source = MinimumVertexCover::::new(num_vertices, edges.clone()); + let source = MinimumVertexCover::new(SimpleGraph::new(num_vertices, edges.clone()), vec![1i32; num_vertices]); println!("Source: MinimumVertexCover on Petersen graph"); println!(" Vertices: {}", num_vertices); diff --git a/examples/reduction_minimumvertexcover_to_qubo.rs b/examples/reduction_minimumvertexcover_to_qubo.rs index 3ee7293dc..4b23236b1 100644 --- a/examples/reduction_minimumvertexcover_to_qubo.rs +++ b/examples/reduction_minimumvertexcover_to_qubo.rs @@ -34,7 +34,7 @@ pub fn run() { // Petersen graph: 10 vertices, 15 edges, VC=6 let (num_vertices, edges) = petersen(); - let vc = MinimumVertexCover::::new(num_vertices, edges.clone()); + let vc = MinimumVertexCover::new(SimpleGraph::new(num_vertices, edges.clone()), vec![1i32; num_vertices]); // Reduce to QUBO let reduction = ReduceTo::::reduce_to(&vc); diff --git a/examples/reduction_travelingsalesman_to_ilp.rs b/examples/reduction_travelingsalesman_to_ilp.rs index 07cfb8080..681cb07d4 100644 --- a/examples/reduction_travelingsalesman_to_ilp.rs +++ b/examples/reduction_travelingsalesman_to_ilp.rs @@ -21,16 +21,9 @@ use problemreductions::topology::{Graph, SimpleGraph}; pub fn run() { // 1. Create TSP instance: K4 with weights - let problem = TravelingSalesman::::new( - 4, - vec![ - (0, 1, 10), - (0, 2, 15), - (0, 3, 20), - (1, 2, 35), - (1, 3, 25), - (2, 3, 30), - ], + let problem = TravelingSalesman::new( + SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]), + vec![10, 15, 20, 35, 25, 30], ); // 2. Reduce to ILP diff --git a/src/io.rs b/src/io.rs index 78104cbea..4814e5cd5 100644 --- a/src/io.rs +++ b/src/io.rs @@ -44,7 +44,7 @@ impl FileFormat { /// use problemreductions::models::graph::MaximumIndependentSet; /// use problemreductions::topology::SimpleGraph; /// -/// let problem = MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2)]); +/// let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); /// write_problem(&problem, "problem.json", FileFormat::Json).unwrap(); /// ``` pub fn write_problem>( diff --git a/src/lib.rs b/src/lib.rs index e73c0c5c5..1d931fa46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,7 @@ //! use problemreductions::topology::SimpleGraph; //! //! // Create an Independent Set problem on a triangle graph -//! let problem = MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2), (0, 2)]); +//! let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), vec![1i32; 3]); //! //! // Solve with brute force //! let solver = BruteForce::new(); diff --git a/src/models/graph/kcoloring.rs b/src/models/graph/kcoloring.rs index facbdd08d..074ffac4b 100644 --- a/src/models/graph/kcoloring.rs +++ b/src/models/graph/kcoloring.rs @@ -4,7 +4,7 @@ //! such that no two adjacent vertices have the same color. use crate::registry::{FieldInfo, ProblemSchemaEntry}; -use crate::topology::{Graph, SimpleGraph}; +use crate::topology::Graph; use crate::traits::{Problem, SatisfactionProblem}; use crate::variant::{KValue, VariantParam, KN}; use serde::{Deserialize, Serialize}; @@ -39,7 +39,8 @@ inventory::submit! { /// use problemreductions::{Problem, Solver, BruteForce}; /// /// // Triangle graph needs at least 3 colors -/// let problem = KColoring::::new(3, vec![(0, 1), (1, 2), (0, 2)]); +/// let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]); +/// let problem = KColoring::::new(graph); /// /// let solver = BruteForce::new(); /// let solutions = solver.find_all_satisfying(&problem); @@ -65,34 +66,15 @@ fn default_num_colors() -> usize { K::K.unwrap_or(0) } -impl KColoring { - /// Create a new K-Coloring problem. - /// - /// # Arguments - /// * `num_vertices` - Number of vertices - /// * `edges` - List of edges as (u, v) pairs - /// - /// # Panics - /// Panics if `K` is `KN` (use [`from_graph_with_k`](Self::from_graph_with_k) instead). - pub fn new(num_vertices: usize, edges: Vec<(usize, usize)>) -> Self { - let graph = SimpleGraph::new(num_vertices, edges); - Self { - graph, - num_colors: K::K.expect("KN requires from_graph_with_k"), - _phantom: std::marker::PhantomData, - } - } -} - impl KColoring { - /// Create a K-Coloring problem from an existing graph. + /// Create a new K-Coloring problem from a graph. /// /// # Panics - /// Panics if `K` is `KN` (use [`KColoring::::from_graph_with_k`] instead). - pub fn from_graph(graph: G) -> Self { + /// Panics if `K` is `KN` (use [`KColoring::::with_k`] instead). + pub fn new(graph: G) -> Self { Self { graph, - num_colors: K::K.expect("KN requires from_graph_with_k"), + num_colors: K::K.expect("KN requires with_k"), _phantom: std::marker::PhantomData, } } @@ -124,9 +106,9 @@ impl KColoring { /// Create a K-Coloring problem with an explicit number of colors. /// /// Only available for `KN` (runtime K). For compile-time K types like - /// `K3`, use [`from_graph`](KColoring::from_graph) which derives K - /// from the type parameter. - pub fn from_graph_with_k(graph: G, num_colors: usize) -> Self { + /// `K3`, use [`new`](KColoring::new) which derives K from the type + /// parameter. + pub fn with_k(graph: G, num_colors: usize) -> Self { Self { graph, num_colors, @@ -158,22 +140,22 @@ where impl SatisfactionProblem for KColoring {} /// Check if a coloring is valid for a graph. -pub fn is_valid_coloring( - num_vertices: usize, - edges: &[(usize, usize)], - coloring: &[usize], - num_colors: usize, -) -> bool { - if coloring.len() != num_vertices { - return false; - } +/// +/// # Panics +/// Panics if `coloring.len() != graph.num_vertices()`. +pub fn is_valid_coloring(graph: &G, coloring: &[usize], num_colors: usize) -> bool { + assert_eq!( + coloring.len(), + graph.num_vertices(), + "coloring length must match num_vertices" + ); // Check all colors are valid if coloring.iter().any(|&c| c >= num_colors) { return false; } // Check no adjacent vertices have same color - for &(u, v) in edges { - if u < coloring.len() && v < coloring.len() && coloring[u] == coloring[v] { + for (u, v) in graph.edges() { + if coloring[u] == coloring[v] { return false; } } diff --git a/src/models/graph/max_cut.rs b/src/models/graph/max_cut.rs index cfd5abe56..d4d3fb603 100644 --- a/src/models/graph/max_cut.rs +++ b/src/models/graph/max_cut.rs @@ -4,7 +4,7 @@ //! that maximizes the total weight of edges crossing the partition. use crate::registry::{FieldInfo, ProblemSchemaEntry}; -use crate::topology::{Graph, SimpleGraph}; +use crate::topology::Graph; use crate::traits::{OptimizationProblem, Problem}; use crate::types::{Direction, SolutionSize, WeightElement}; use num_traits::Zero; @@ -50,7 +50,8 @@ inventory::submit! { /// use problemreductions::{Problem, Solver, BruteForce}; /// /// // Create a triangle with unit weights -/// let problem = MaxCut::::new(3, vec![(0, 1, 1), (1, 2, 1), (0, 2, 1)]); +/// let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]); +/// let problem = MaxCut::new(graph, vec![1, 1, 1]); /// /// // Solve with brute force /// let solver = BruteForce::new(); @@ -70,57 +71,13 @@ pub struct MaxCut { edge_weights: Vec, } -impl MaxCut { - /// Create a new MaxCut problem. - /// - /// # Arguments - /// * `num_vertices` - Number of vertices - /// * `edges` - List of weighted edges as (u, v, weight) triples - pub fn new(num_vertices: usize, edges: Vec<(usize, usize, W)>) -> Self { - let edge_list: Vec<(usize, usize)> = edges.iter().map(|(u, v, _)| (*u, *v)).collect(); - let edge_weights: Vec = edges.into_iter().map(|(_, _, w)| w).collect(); - let graph = SimpleGraph::new(num_vertices, edge_list); - Self { - graph, - edge_weights, - } - } - - /// Create a MaxCut problem with unit weights. - pub fn unweighted(num_vertices: usize, edges: Vec<(usize, usize)>) -> Self - where - W: From, - { - let edge_weights = vec![W::from(1); edges.len()]; - let graph = SimpleGraph::new(num_vertices, edges); - Self { - graph, - edge_weights, - } - } - - /// Create a MaxCut problem from edges without weights in tuple form. - pub fn with_weights(num_vertices: usize, edges: Vec<(usize, usize)>, weights: Vec) -> Self { - assert_eq!( - edges.len(), - weights.len(), - "edges and weights must have same length" - ); - let graph = SimpleGraph::new(num_vertices, edges); - Self { - graph, - edge_weights: weights, - } - } -} - impl MaxCut { /// Create a MaxCut problem from a graph with specified edge weights. /// /// # Arguments /// * `graph` - The underlying graph /// * `edge_weights` - Weights for each edge (must match graph.num_edges()) - pub fn from_graph(graph: G, edge_weights: Vec) -> Self { + pub fn new(graph: G, edge_weights: Vec) -> Self { assert_eq!( edge_weights.len(), graph.num_edges(), @@ -132,8 +89,8 @@ impl MaxCut { } } - /// Create a MaxCut problem from a graph with unit weights. - pub fn from_graph_unweighted(graph: G) -> Self + /// Create a MaxCut problem with unit weights. + pub fn unweighted(graph: G) -> Self where W: From, { diff --git a/src/models/graph/maximal_is.rs b/src/models/graph/maximal_is.rs index 85a14ee58..67d37f891 100644 --- a/src/models/graph/maximal_is.rs +++ b/src/models/graph/maximal_is.rs @@ -4,7 +4,7 @@ //! cannot be extended by adding any other vertex. use crate::registry::{FieldInfo, ProblemSchemaEntry}; -use crate::topology::{Graph, SimpleGraph}; +use crate::topology::Graph; use crate::traits::{OptimizationProblem, Problem}; use crate::types::{Direction, SolutionSize, WeightElement}; use num_traits::Zero; @@ -38,7 +38,8 @@ inventory::submit! { /// use problemreductions::{Problem, Solver, BruteForce}; /// /// // Path graph 0-1-2 -/// let problem = MaximalIS::::new(3, vec![(0, 1), (1, 2)]); +/// let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); +/// let problem = MaximalIS::new(graph, vec![1; 3]); /// /// let solver = BruteForce::new(); /// let solutions = solver.find_all_best(&problem); @@ -56,29 +57,14 @@ pub struct MaximalIS { weights: Vec, } -impl MaximalIS { - /// Create a new Maximal Independent Set problem with unit weights. - pub fn new(num_vertices: usize, edges: Vec<(usize, usize)>) -> Self - where - W: From, - { - let graph = SimpleGraph::new(num_vertices, edges); - let weights = vec![W::from(1); num_vertices]; - Self { graph, weights } - } - - /// Create a new Maximal Independent Set problem with custom weights. - pub fn with_weights(num_vertices: usize, edges: Vec<(usize, usize)>, weights: Vec) -> Self { - assert_eq!(weights.len(), num_vertices); - let graph = SimpleGraph::new(num_vertices, edges); - Self { graph, weights } - } -} - impl MaximalIS { - /// Create a new Maximal Independent Set problem from a graph with custom weights. - pub fn from_graph(graph: G, weights: Vec) -> Self { - assert_eq!(weights.len(), graph.num_vertices()); + /// Create a Maximal Independent Set problem from a graph with given weights. + pub fn new(graph: G, weights: Vec) -> Self { + assert_eq!( + weights.len(), + graph.num_vertices(), + "weights length must match graph num_vertices" + ); Self { graph, weights } } @@ -92,16 +78,12 @@ impl MaximalIS { &self.weights } - /// Check if the problem has non-uniform weights. + /// Check if the problem uses a non-unit weight type. pub fn is_weighted(&self) -> bool where - W: PartialEq, + W: WeightElement, { - if self.weights.is_empty() { - return false; - } - let first = &self.weights[0]; - !self.weights.iter().all(|w| w == first) + !W::IS_UNIT } /// Check if a configuration is an independent set. @@ -185,38 +167,29 @@ where } /// Check if a set is a maximal independent set. -pub fn is_maximal_independent_set( - num_vertices: usize, - edges: &[(usize, usize)], - selected: &[bool], -) -> bool { - if selected.len() != num_vertices { - return false; - } - - // Build adjacency - let mut adj: Vec> = vec![vec![]; num_vertices]; - for &(u, v) in edges { - if u < num_vertices && v < num_vertices { - adj[u].push(v); - adj[v].push(u); - } - } +/// +/// # Panics +/// Panics if `selected.len() != graph.num_vertices()`. +pub fn is_maximal_independent_set(graph: &G, selected: &[bool]) -> bool { + assert_eq!( + selected.len(), + graph.num_vertices(), + "selected length must match num_vertices" + ); // Check independence - for &(u, v) in edges { - if u < selected.len() && v < selected.len() && selected[u] && selected[v] { + for (u, v) in graph.edges() { + if selected[u] && selected[v] { return false; } } - // Check maximality - for v in 0..num_vertices { + // Check maximality: no unselected vertex can be added + for v in 0..graph.num_vertices() { if selected[v] { continue; } - let can_add = adj[v].iter().all(|&u| !selected[u]); - if can_add { + if graph.neighbors(v).iter().all(|&u| !selected[u]) { return false; } } diff --git a/src/models/graph/maximum_clique.rs b/src/models/graph/maximum_clique.rs index d1d10f4ae..33bc98a27 100644 --- a/src/models/graph/maximum_clique.rs +++ b/src/models/graph/maximum_clique.rs @@ -4,7 +4,7 @@ //! such that all vertices in the subset are pairwise adjacent. use crate::registry::{FieldInfo, ProblemSchemaEntry}; -use crate::topology::{Graph, SimpleGraph}; +use crate::topology::Graph; use crate::traits::{OptimizationProblem, Problem}; use crate::types::{Direction, SolutionSize, WeightElement}; use num_traits::Zero; @@ -42,7 +42,8 @@ inventory::submit! { /// use problemreductions::{Problem, Solver, BruteForce}; /// /// // Create a triangle graph (3 vertices, 3 edges - complete graph) -/// let problem = MaximumClique::::new(3, vec![(0, 1), (1, 2), (0, 2)]); +/// let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]); +/// let problem = MaximumClique::new(graph, vec![1; 3]); /// /// // Solve with brute force /// let solver = BruteForce::new(); @@ -59,36 +60,9 @@ pub struct MaximumClique { weights: Vec, } -impl MaximumClique { - /// Create a new MaximumClique problem with unit weights. - /// - /// # Arguments - /// * `num_vertices` - Number of vertices in the graph - /// * `edges` - List of edges as (u, v) pairs - pub fn new(num_vertices: usize, edges: Vec<(usize, usize)>) -> Self - where - W: From, - { - let graph = SimpleGraph::new(num_vertices, edges); - let weights = vec![W::from(1); num_vertices]; - Self { graph, weights } - } - - /// Create a new MaximumClique problem with custom weights. - pub fn with_weights(num_vertices: usize, edges: Vec<(usize, usize)>, weights: Vec) -> Self { - assert_eq!( - weights.len(), - num_vertices, - "weights length must match num_vertices" - ); - let graph = SimpleGraph::new(num_vertices, edges); - Self { graph, weights } - } -} - impl MaximumClique { - /// Create a MaximumClique problem from an existing graph with custom weights. - pub fn from_graph(graph: G, weights: Vec) -> Self { + /// Create a MaximumClique problem from a graph with given weights. + pub fn new(graph: G, weights: Vec) -> Self { assert_eq!( weights.len(), graph.num_vertices(), @@ -107,16 +81,12 @@ impl MaximumClique { &self.weights } - /// Check if the problem has non-uniform weights. + /// Check if the problem uses a non-unit weight type. pub fn is_weighted(&self) -> bool where - W: PartialEq, + W: WeightElement, { - if self.weights.is_empty() { - return false; - } - let first = &self.weights[0]; - !self.weights.iter().all(|w| w == first) + !W::IS_UNIT } } @@ -186,13 +156,17 @@ fn is_clique_config(graph: &G, config: &[usize]) -> bool { /// Check if a set of vertices forms a clique. /// /// # Arguments -/// * `num_vertices` - Total number of vertices -/// * `edges` - List of edges as (u, v) pairs +/// * `graph` - The graph /// * `selected` - Boolean slice indicating which vertices are selected -pub fn is_clique(num_vertices: usize, edges: &[(usize, usize)], selected: &[bool]) -> bool { - if selected.len() != num_vertices { - return false; - } +/// +/// # Panics +/// Panics if `selected.len() != graph.num_vertices()`. +pub fn is_clique(graph: &G, selected: &[bool]) -> bool { + assert_eq!( + selected.len(), + graph.num_vertices(), + "selected length must match num_vertices" + ); // Collect selected vertices let selected_vertices: Vec = selected @@ -202,19 +176,10 @@ pub fn is_clique(num_vertices: usize, edges: &[(usize, usize)], selected: &[bool .map(|(i, _)| i) .collect(); - // Build adjacency set for O(1) edge lookup - use std::collections::HashSet; - let edge_set: HashSet<(usize, usize)> = edges - .iter() - .flat_map(|&(u, v)| vec![(u, v), (v, u)]) - .collect(); - // Check all pairs of selected vertices are adjacent for i in 0..selected_vertices.len() { for j in (i + 1)..selected_vertices.len() { - let u = selected_vertices[i]; - let v = selected_vertices[j]; - if !edge_set.contains(&(u, v)) { + if !graph.has_edge(selected_vertices[i], selected_vertices[j]) { return false; } } diff --git a/src/models/graph/maximum_independent_set.rs b/src/models/graph/maximum_independent_set.rs index 64c3742c4..548a5dcda 100644 --- a/src/models/graph/maximum_independent_set.rs +++ b/src/models/graph/maximum_independent_set.rs @@ -4,7 +4,7 @@ //! such that no two vertices in the subset are adjacent. use crate::registry::{FieldInfo, ProblemSchemaEntry}; -use crate::topology::{Graph, SimpleGraph}; +use crate::topology::Graph; use crate::traits::{OptimizationProblem, Problem}; use crate::types::{Direction, SolutionSize, WeightElement}; use num_traits::Zero; @@ -42,7 +42,8 @@ inventory::submit! { /// use problemreductions::{Problem, Solver, BruteForce}; /// /// // Create a triangle graph (3 vertices, 3 edges) -/// let problem = MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2), (0, 2)]); +/// let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]); +/// let problem = MaximumIndependentSet::new(graph, vec![1; 3]); /// /// // Solve with brute force /// let solver = BruteForce::new(); @@ -59,36 +60,9 @@ pub struct MaximumIndependentSet { weights: Vec, } -impl MaximumIndependentSet { - /// Create a new Independent Set problem with unit weights. - /// - /// # Arguments - /// * `num_vertices` - Number of vertices in the graph - /// * `edges` - List of edges as (u, v) pairs - pub fn new(num_vertices: usize, edges: Vec<(usize, usize)>) -> Self - where - W: From, - { - let graph = SimpleGraph::new(num_vertices, edges); - let weights = vec![W::from(1); num_vertices]; - Self { graph, weights } - } - - /// Create a new Independent Set problem with custom weights. - pub fn with_weights(num_vertices: usize, edges: Vec<(usize, usize)>, weights: Vec) -> Self { - assert_eq!( - weights.len(), - num_vertices, - "weights length must match num_vertices" - ); - let graph = SimpleGraph::new(num_vertices, edges); - Self { graph, weights } - } -} - impl MaximumIndependentSet { - /// Create an Independent Set problem from an existing graph with custom weights. - pub fn from_graph(graph: G, weights: Vec) -> Self { + /// Create an Independent Set problem from a graph with given weights. + pub fn new(graph: G, weights: Vec) -> Self { assert_eq!( weights.len(), graph.num_vertices(), @@ -107,16 +81,12 @@ impl MaximumIndependentSet { &self.weights } - /// Check if the problem has non-uniform weights. + /// Check if the problem uses a non-unit weight type. pub fn is_weighted(&self) -> bool where - W: PartialEq, + W: WeightElement, { - if self.weights.is_empty() { - return false; - } - let first = &self.weights[0]; - !self.weights.iter().all(|w| w == first) + !W::IS_UNIT } } @@ -175,19 +145,19 @@ fn is_independent_set_config(graph: &G, config: &[usize]) -> bool { /// Check if a set of vertices forms an independent set. /// /// # Arguments -/// * `num_vertices` - Total number of vertices -/// * `edges` - List of edges as (u, v) pairs +/// * `graph` - The graph /// * `selected` - Boolean slice indicating which vertices are selected -pub fn is_independent_set( - num_vertices: usize, - edges: &[(usize, usize)], - selected: &[bool], -) -> bool { - if selected.len() != num_vertices { - return false; - } - for &(u, v) in edges { - if u < selected.len() && v < selected.len() && selected[u] && selected[v] { +/// +/// # Panics +/// Panics if `selected.len() != graph.num_vertices()`. +pub fn is_independent_set(graph: &G, selected: &[bool]) -> bool { + assert_eq!( + selected.len(), + graph.num_vertices(), + "selected length must match num_vertices" + ); + for (u, v) in graph.edges() { + if selected[u] && selected[v] { return false; } } diff --git a/src/models/graph/maximum_matching.rs b/src/models/graph/maximum_matching.rs index b5c8fc9ed..727dabe6b 100644 --- a/src/models/graph/maximum_matching.rs +++ b/src/models/graph/maximum_matching.rs @@ -4,7 +4,7 @@ //! such that no two edges share a vertex. use crate::registry::{FieldInfo, ProblemSchemaEntry}; -use crate::topology::{Graph, SimpleGraph}; +use crate::topology::Graph; use crate::traits::{OptimizationProblem, Problem}; use crate::types::{Direction, SolutionSize, WeightElement}; use num_traits::Zero; @@ -41,7 +41,8 @@ inventory::submit! { /// use problemreductions::{Problem, Solver, BruteForce}; /// /// // Path graph 0-1-2 -/// let problem = MaximumMatching::::new(3, vec![(0, 1, 1), (1, 2, 1)]); +/// let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); +/// let problem = MaximumMatching::<_, i32>::unit_weights(graph); /// /// let solver = BruteForce::new(); /// let solutions = solver.find_all_best(&problem); @@ -59,45 +60,13 @@ pub struct MaximumMatching { edge_weights: Vec, } -impl MaximumMatching { - /// Create a new MaximumMatching problem. - /// - /// # Arguments - /// * `num_vertices` - Number of vertices - /// * `edges` - List of weighted edges as (u, v, weight) triples - pub fn new(num_vertices: usize, edges: Vec<(usize, usize, W)>) -> Self { - let mut edge_list = Vec::new(); - let mut edge_weights = Vec::new(); - for (u, v, w) in edges { - edge_list.push((u, v)); - edge_weights.push(w); - } - let graph = SimpleGraph::new(num_vertices, edge_list); - Self { - graph, - edge_weights, - } - } - - /// Create a MaximumMatching problem with unit weights. - pub fn unweighted(num_vertices: usize, edges: Vec<(usize, usize)>) -> Self - where - W: From, - { - Self::new( - num_vertices, - edges.into_iter().map(|(u, v)| (u, v, W::from(1))).collect(), - ) - } -} - impl MaximumMatching { /// Create a MaximumMatching problem from a graph with given edge weights. /// /// # Arguments /// * `graph` - The graph /// * `edge_weights` - Weight for each edge (in graph.edges() order) - pub fn from_graph(graph: G, edge_weights: Vec) -> Self { + pub fn new(graph: G, edge_weights: Vec) -> Self { assert_eq!( edge_weights.len(), graph.num_edges(), @@ -109,8 +78,8 @@ impl MaximumMatching { } } - /// Create a MaximumMatching problem from a graph with unit weights. - pub fn from_graph_unit_weights(graph: G) -> Self + /// Create a MaximumMatching problem with unit weights. + pub fn unit_weights(graph: G) -> Self where W: From, { @@ -180,16 +149,12 @@ impl MaximumMatching { self.edge_weights.clone() } - /// Check if the problem has non-uniform weights. + /// Check if the problem uses a non-unit weight type. pub fn is_weighted(&self) -> bool where - W: PartialEq, + W: WeightElement, { - if self.edge_weights.is_empty() { - return false; - } - let first = &self.edge_weights[0]; - !self.edge_weights.iter().all(|w| w == first) + !W::IS_UNIT } } @@ -238,18 +203,21 @@ where } /// Check if a selection of edges forms a valid matching. -pub fn is_matching(num_vertices: usize, edges: &[(usize, usize)], selected: &[bool]) -> bool { - if selected.len() != edges.len() { - return false; - } - - let mut vertex_used = vec![false; num_vertices]; +/// +/// # Panics +/// Panics if `selected.len() != graph.num_edges()`. +pub fn is_matching(graph: &G, selected: &[bool]) -> bool { + assert_eq!( + selected.len(), + graph.num_edges(), + "selected length must match num_edges" + ); + + let edges = graph.edges(); + let mut vertex_used = vec![false; graph.num_vertices()]; for (idx, &sel) in selected.iter().enumerate() { if sel { let (u, v) = edges[idx]; - if u >= num_vertices || v >= num_vertices { - return false; - } if vertex_used[u] || vertex_used[v] { return false; } diff --git a/src/models/graph/minimum_dominating_set.rs b/src/models/graph/minimum_dominating_set.rs index 540db57f8..3a1c1568a 100644 --- a/src/models/graph/minimum_dominating_set.rs +++ b/src/models/graph/minimum_dominating_set.rs @@ -4,7 +4,7 @@ //! such that every vertex is either in the set or adjacent to a vertex in the set. use crate::registry::{FieldInfo, ProblemSchemaEntry}; -use crate::topology::{Graph, SimpleGraph}; +use crate::topology::Graph; use crate::traits::{OptimizationProblem, Problem}; use crate::types::{Direction, SolutionSize, WeightElement}; use num_traits::Zero; @@ -38,7 +38,8 @@ inventory::submit! { /// use problemreductions::{Problem, Solver, BruteForce}; /// /// // Star graph: center dominates all -/// let problem = MinimumDominatingSet::::new(4, vec![(0, 1), (0, 2), (0, 3)]); +/// let graph = SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3)]); +/// let problem = MinimumDominatingSet::new(graph, vec![1; 4]); /// /// let solver = BruteForce::new(); /// let solutions = solver.find_all_best(&problem); @@ -54,29 +55,14 @@ pub struct MinimumDominatingSet { weights: Vec, } -impl MinimumDominatingSet { - /// Create a new Dominating Set problem with unit weights. - pub fn new(num_vertices: usize, edges: Vec<(usize, usize)>) -> Self - where - W: From, - { - let graph = SimpleGraph::new(num_vertices, edges); - let weights = vec![W::from(1); num_vertices]; - Self { graph, weights } - } - - /// Create a new Dominating Set problem with custom weights. - pub fn with_weights(num_vertices: usize, edges: Vec<(usize, usize)>, weights: Vec) -> Self { - assert_eq!(weights.len(), num_vertices); - let graph = SimpleGraph::new(num_vertices, edges); - Self { graph, weights } - } -} - impl MinimumDominatingSet { - /// Create a Dominating Set problem from a graph with custom weights. - pub fn from_graph(graph: G, weights: Vec) -> Self { - assert_eq!(weights.len(), graph.num_vertices()); + /// Create a Dominating Set problem from a graph with given weights. + pub fn new(graph: G, weights: Vec) -> Self { + assert_eq!( + weights.len(), + graph.num_vertices(), + "weights length must match graph num_vertices" + ); Self { graph, weights } } @@ -167,28 +153,23 @@ where } /// Check if a set of vertices is a dominating set. -pub fn is_dominating_set(num_vertices: usize, edges: &[(usize, usize)], selected: &[bool]) -> bool { - if selected.len() != num_vertices { - return false; - } - - // Build adjacency list - let mut adj: Vec> = vec![HashSet::new(); num_vertices]; - for &(u, v) in edges { - if u < num_vertices && v < num_vertices { - adj[u].insert(v); - adj[v].insert(u); - } - } +/// +/// # Panics +/// Panics if `selected.len() != graph.num_vertices()`. +pub fn is_dominating_set(graph: &G, selected: &[bool]) -> bool { + assert_eq!( + selected.len(), + graph.num_vertices(), + "selected length must match num_vertices" + ); // Check each vertex is dominated - for v in 0..num_vertices { + for v in 0..graph.num_vertices() { if selected[v] { continue; // v dominates itself } // Check if any neighbor of v is selected - let dominated = adj[v].iter().any(|&u| selected[u]); - if !dominated { + if !graph.neighbors(v).iter().any(|&u| selected[u]) { return false; } } diff --git a/src/models/graph/minimum_vertex_cover.rs b/src/models/graph/minimum_vertex_cover.rs index 6ca4b5078..29468d1e9 100644 --- a/src/models/graph/minimum_vertex_cover.rs +++ b/src/models/graph/minimum_vertex_cover.rs @@ -4,7 +4,7 @@ //! such that every edge has at least one endpoint in the subset. use crate::registry::{FieldInfo, ProblemSchemaEntry}; -use crate::topology::{Graph, SimpleGraph}; +use crate::topology::Graph; use crate::traits::{OptimizationProblem, Problem}; use crate::types::{Direction, SolutionSize, WeightElement}; use num_traits::Zero; @@ -37,7 +37,8 @@ inventory::submit! { /// use problemreductions::{Problem, Solver, BruteForce}; /// /// // Create a path graph 0-1-2 -/// let problem = MinimumVertexCover::::new(3, vec![(0, 1), (1, 2)]); +/// let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); +/// let problem = MinimumVertexCover::new(graph, vec![1; 3]); /// /// // Solve with brute force /// let solver = BruteForce::new(); @@ -54,29 +55,14 @@ pub struct MinimumVertexCover { weights: Vec, } -impl MinimumVertexCover { - /// Create a new Vertex Covering problem with unit weights. - pub fn new(num_vertices: usize, edges: Vec<(usize, usize)>) -> Self - where - W: From, - { - let graph = SimpleGraph::new(num_vertices, edges); - let weights = vec![W::from(1); num_vertices]; - Self { graph, weights } - } - - /// Create a new Vertex Covering problem with custom weights. - pub fn with_weights(num_vertices: usize, edges: Vec<(usize, usize)>, weights: Vec) -> Self { - assert_eq!(weights.len(), num_vertices); - let graph = SimpleGraph::new(num_vertices, edges); - Self { graph, weights } - } -} - impl MinimumVertexCover { - /// Create a Vertex Covering problem from a graph with custom weights. - pub fn from_graph(graph: G, weights: Vec) -> Self { - assert_eq!(weights.len(), graph.num_vertices()); + /// Create a Vertex Covering problem from a graph with given weights. + pub fn new(graph: G, weights: Vec) -> Self { + assert_eq!( + weights.len(), + graph.num_vertices(), + "weights length must match graph num_vertices" + ); Self { graph, weights } } @@ -90,16 +76,12 @@ impl MinimumVertexCover { &self.weights } - /// Check if the problem has non-uniform weights. + /// Check if the problem uses a non-unit weight type. pub fn is_weighted(&self) -> bool where - W: PartialEq, + W: WeightElement, { - if self.weights.is_empty() { - return false; - } - let first = &self.weights[0]; - !self.weights.iter().all(|w| w == first) + !W::IS_UNIT } } @@ -160,15 +142,19 @@ fn is_vertex_cover_config(graph: &G, config: &[usize]) -> bool { /// Check if a set of vertices forms a vertex cover. /// /// # Arguments -/// * `num_vertices` - Total number of vertices -/// * `edges` - List of edges as (u, v) pairs +/// * `graph` - The graph /// * `selected` - Boolean slice indicating which vertices are selected -pub fn is_vertex_cover(num_vertices: usize, edges: &[(usize, usize)], selected: &[bool]) -> bool { - if selected.len() != num_vertices { - return false; - } - for &(u, v) in edges { - if u < selected.len() && v < selected.len() && !selected[u] && !selected[v] { +/// +/// # Panics +/// Panics if `selected.len() != graph.num_vertices()`. +pub fn is_vertex_cover(graph: &G, selected: &[bool]) -> bool { + assert_eq!( + selected.len(), + graph.num_vertices(), + "selected length must match num_vertices" + ); + for (u, v) in graph.edges() { + if !selected[u] && !selected[v] { return false; } } diff --git a/src/models/graph/traveling_salesman.rs b/src/models/graph/traveling_salesman.rs index 19e10b82b..75e3b4d54 100644 --- a/src/models/graph/traveling_salesman.rs +++ b/src/models/graph/traveling_salesman.rs @@ -4,7 +4,7 @@ //! that visits every vertex exactly once. use crate::registry::{FieldInfo, ProblemSchemaEntry}; -use crate::topology::{Graph, SimpleGraph}; +use crate::topology::Graph; use crate::traits::{OptimizationProblem, Problem}; use crate::types::{Direction, SolutionSize, WeightElement}; use num_traits::Zero; @@ -51,39 +51,9 @@ pub struct TravelingSalesman { edge_weights: Vec, } -impl TravelingSalesman { - /// Create a new TravelingSalesman problem. - pub fn new(num_vertices: usize, edges: Vec<(usize, usize, W)>) -> Self { - let mut edge_list = Vec::new(); - let mut edge_weights = Vec::new(); - for (u, v, w) in edges { - edge_list.push((u, v)); - edge_weights.push(w); - } - let graph = SimpleGraph::new(num_vertices, edge_list); - Self { - graph, - edge_weights, - } - } - - /// Create a TravelingSalesman problem with unit weights. - pub fn unweighted(num_vertices: usize, edges: Vec<(usize, usize)>) -> Self - where - W: From, - { - let edge_weights = vec![W::from(1); edges.len()]; - let graph = SimpleGraph::new(num_vertices, edges); - Self { - graph, - edge_weights, - } - } -} - impl TravelingSalesman { /// Create a TravelingSalesman problem from a graph with given edge weights. - pub fn from_graph(graph: G, edge_weights: Vec) -> Self { + pub fn new(graph: G, edge_weights: Vec) -> Self { assert_eq!( edge_weights.len(), graph.num_edges(), @@ -95,8 +65,8 @@ impl TravelingSalesman { } } - /// Create a TravelingSalesman problem from a graph with unit weights. - pub fn from_graph_unit_weights(graph: G) -> Self + /// Create a TravelingSalesman problem with unit weights. + pub fn unit_weights(graph: G) -> Self where W: From, { @@ -133,23 +103,21 @@ impl TravelingSalesman { self.edge_weights.clone() } - /// Check if the problem has non-uniform weights. + /// Check if the problem uses a non-unit weight type. pub fn is_weighted(&self) -> bool where - W: PartialEq, + W: WeightElement, { - if self.edge_weights.is_empty() { - return false; - } - let first = &self.edge_weights[0]; - !self.edge_weights.iter().all(|w| w == first) + !W::IS_UNIT } /// Check if a configuration forms a valid Hamiltonian cycle. fn is_valid_hamiltonian_cycle(&self, config: &[usize]) -> bool { - let edges = self.graph.edges(); + if config.len() != self.graph.num_edges() { + return false; + } let selected: Vec = config.iter().map(|&s| s == 1).collect(); - is_hamiltonian_cycle(self.graph.num_vertices(), &edges, &selected) + is_hamiltonian_cycle(&self.graph, &selected) } } @@ -198,16 +166,18 @@ where } /// Check if a selection of edges forms a valid Hamiltonian cycle. -pub fn is_hamiltonian_cycle( - num_vertices: usize, - edges: &[(usize, usize)], - selected: &[bool], -) -> bool { - if selected.len() != edges.len() { - return false; - } - - let n = num_vertices; +/// +/// # Panics +/// Panics if `selected.len() != graph.num_edges()`. +pub fn is_hamiltonian_cycle(graph: &G, selected: &[bool]) -> bool { + assert_eq!( + selected.len(), + graph.num_edges(), + "selected length must match num_edges" + ); + + let n = graph.num_vertices(); + let edges = graph.edges(); let mut degree = vec![0usize; n]; let mut selected_count = 0; let mut first_vertex = None; @@ -215,9 +185,6 @@ pub fn is_hamiltonian_cycle( for (idx, &sel) in selected.iter().enumerate() { if sel { let (u, v) = edges[idx]; - if u >= n || v >= n { - return false; - } degree[u] += 1; degree[v] += 1; selected_count += 1; diff --git a/src/rules/kcoloring_casts.rs b/src/rules/kcoloring_casts.rs index 9e964be14..b70252c9e 100644 --- a/src/rules/kcoloring_casts.rs +++ b/src/rules/kcoloring_casts.rs @@ -9,5 +9,5 @@ impl_variant_reduction!( KColoring, => , fields: [num_vertices, num_colors], - |src| KColoring::from_graph_with_k(src.graph().clone(), src.num_colors()) + |src| KColoring::with_k(src.graph().clone(), src.num_colors()) ); diff --git a/src/rules/maximumindependentset_casts.rs b/src/rules/maximumindependentset_casts.rs index 38257c2b7..9027cd0de 100644 --- a/src/rules/maximumindependentset_casts.rs +++ b/src/rules/maximumindependentset_casts.rs @@ -12,7 +12,7 @@ impl_variant_reduction!( MaximumIndependentSet, => , fields: [num_vertices, num_edges], - |src| MaximumIndependentSet::from_graph( + |src| MaximumIndependentSet::new( src.graph().cast_to_parent(), src.weights().to_vec()) ); @@ -20,7 +20,7 @@ impl_variant_reduction!( MaximumIndependentSet, => , fields: [num_vertices, num_edges], - |src| MaximumIndependentSet::from_graph( + |src| MaximumIndependentSet::new( src.graph().cast_to_parent(), src.weights().to_vec()) ); @@ -28,6 +28,6 @@ impl_variant_reduction!( MaximumIndependentSet, => , fields: [num_vertices, num_edges], - |src| MaximumIndependentSet::from_graph( + |src| MaximumIndependentSet::new( src.graph().cast_to_parent(), src.weights().to_vec()) ); diff --git a/src/rules/maximumindependentset_gridgraph.rs b/src/rules/maximumindependentset_gridgraph.rs index e96ecda41..8d0ee475c 100644 --- a/src/rules/maximumindependentset_gridgraph.rs +++ b/src/rules/maximumindependentset_gridgraph.rs @@ -50,7 +50,7 @@ impl ReduceTo> let result = ksg::map_unweighted(n, &edges); let weights = result.node_weights.clone(); let grid = result.to_kings_subgraph(); - let target = MaximumIndependentSet::from_graph(grid, weights); + let target = MaximumIndependentSet::new(grid, weights); ReductionISSimpleToGrid { target, mapping_result: result, @@ -97,7 +97,7 @@ impl ReduceTo> let result = ksg::map_unweighted(n, &edges); let weights = result.node_weights.clone(); let grid = result.to_kings_subgraph(); - let target = MaximumIndependentSet::from_graph(grid, weights); + let target = MaximumIndependentSet::new(grid, weights); ReductionISUnitDiskToGrid { target, mapping_result: result, diff --git a/src/rules/maximumindependentset_maximumsetpacking.rs b/src/rules/maximumindependentset_maximumsetpacking.rs index 0d3ab17a4..a6c88f526 100644 --- a/src/rules/maximumindependentset_maximumsetpacking.rs +++ b/src/rules/maximumindependentset_maximumsetpacking.rs @@ -114,7 +114,7 @@ impl ReduceTo> for MaximumSetPacking> let result = triangular::map_weighted(n, &edges); let weights = result.node_weights.clone(); let grid = result.to_triangular_subgraph(); - let target = MaximumIndependentSet::from_graph(grid, weights); + let target = MaximumIndependentSet::new(grid, weights); ReductionISSimpleToTriangular { target, mapping_result: result, diff --git a/src/rules/minimumvertexcover_maximumindependentset.rs b/src/rules/minimumvertexcover_maximumindependentset.rs index 2a3e9f2bf..0715c4742 100644 --- a/src/rules/minimumvertexcover_maximumindependentset.rs +++ b/src/rules/minimumvertexcover_maximumindependentset.rs @@ -46,9 +46,8 @@ impl ReduceTo> for MaximumIndependentSet; fn reduce_to(&self) -> Self::Result { - let target = MinimumVertexCover::with_weights( - self.graph().num_vertices(), - self.graph().edges(), + let target = MinimumVertexCover::new( + SimpleGraph::new(self.graph().num_vertices(), self.graph().edges()), self.weights().to_vec(), ); ReductionISToVC { target } @@ -90,9 +89,8 @@ impl ReduceTo> for MinimumVertexCover; fn reduce_to(&self) -> Self::Result { - let target = MaximumIndependentSet::with_weights( - self.graph().num_vertices(), - self.graph().edges(), + let target = MaximumIndependentSet::new( + SimpleGraph::new(self.graph().num_vertices(), self.graph().edges()), self.weights().to_vec(), ); ReductionVCToIS { target } diff --git a/src/rules/mod.rs b/src/rules/mod.rs index 6c0ce0b03..e2ce2a7cd 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -104,7 +104,7 @@ pub use traits::{ReduceTo, ReductionAutoCast, ReductionResult}; /// MaximumIndependentSet, /// => , /// fields: [num_vertices, num_edges], -/// |src| MaximumIndependentSet::from_graph( +/// |src| MaximumIndependentSet::new( /// src.graph().cast_to_parent(), src.weights()) /// ); /// ``` diff --git a/src/rules/sat_coloring.rs b/src/rules/sat_coloring.rs index e2bfaff50..5e6f1ee83 100644 --- a/src/rules/sat_coloring.rs +++ b/src/rules/sat_coloring.rs @@ -201,7 +201,7 @@ impl SATColoringConstructor { /// Build the final KColoring problem. fn build_coloring(&self) -> KColoring { - KColoring::::new(self.num_vertices, self.edges.clone()) + KColoring::::new(SimpleGraph::new(self.num_vertices, self.edges.clone())) } } diff --git a/src/rules/sat_maximumindependentset.rs b/src/rules/sat_maximumindependentset.rs index ae5d265c3..cdaca0fd8 100644 --- a/src/rules/sat_maximumindependentset.rs +++ b/src/rules/sat_maximumindependentset.rs @@ -155,7 +155,7 @@ impl ReduceTo> for Satisfiability { } } - let target = MaximumIndependentSet::new(vertex_count, edges); + let target = MaximumIndependentSet::new(SimpleGraph::new(vertex_count, edges), vec![1i32; vertex_count]); ReductionSATToIS { target, diff --git a/src/rules/sat_minimumdominatingset.rs b/src/rules/sat_minimumdominatingset.rs index 60034a90c..03e34f9c7 100644 --- a/src/rules/sat_minimumdominatingset.rs +++ b/src/rules/sat_minimumdominatingset.rs @@ -166,7 +166,7 @@ impl ReduceTo> for Satisfiability { } } - let target = MinimumDominatingSet::new(num_vertices, edges); + let target = MinimumDominatingSet::new(SimpleGraph::new(num_vertices, edges), vec![1i32; num_vertices]); ReductionSATToDS { target, diff --git a/src/rules/spinglass_maxcut.rs b/src/rules/spinglass_maxcut.rs index 2858f0327..ef6ae91b0 100644 --- a/src/rules/spinglass_maxcut.rs +++ b/src/rules/spinglass_maxcut.rs @@ -176,7 +176,7 @@ impl ReduceTo> for SpinGlass { } } - let target = MaxCut::with_weights(total_vertices, edges, weights); + let target = MaxCut::new(SimpleGraph::new(total_vertices, edges), weights); ReductionSGToMaxCut { target, diff --git a/src/solvers/ilp/solver.rs b/src/solvers/ilp/solver.rs index 1b388960b..a6525bb96 100644 --- a/src/solvers/ilp/solver.rs +++ b/src/solvers/ilp/solver.rs @@ -148,7 +148,7 @@ impl ILPSolver { /// use problemreductions::topology::SimpleGraph; /// /// // Create a problem that reduces to ILP (e.g., Independent Set) - /// let problem = MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2)]); + /// let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); /// /// // Solve using ILP solver /// let solver = ILPSolver::new(); diff --git a/src/topology/mod.rs b/src/topology/mod.rs index 195458f27..01c081e4f 100644 --- a/src/topology/mod.rs +++ b/src/topology/mod.rs @@ -15,7 +15,8 @@ //! use problemreductions::models::graph::MaximumIndependentSet; //! //! // Problems work with any graph type - SimpleGraph by default -//! let simple_graph_problem: MaximumIndependentSet = MaximumIndependentSet::new(3, vec![(0, 1)]); +//! let graph = SimpleGraph::new(3, vec![(0, 1)]); +//! let simple_graph_problem = MaximumIndependentSet::new(graph, vec![1i32; 3]); //! assert_eq!(simple_graph_problem.graph().num_vertices(), 3); //! //! // Different graph topologies enable different reduction algorithms diff --git a/src/types.rs b/src/types.rs index 99236ca3a..1daca23c1 100644 --- a/src/types.rs +++ b/src/types.rs @@ -36,12 +36,15 @@ impl NumericSize for T where pub trait WeightElement: Clone + Default + 'static { /// The numeric type used for sums and comparisons. type Sum: NumericSize; + /// Whether this is the unit weight type (`One`). + const IS_UNIT: bool; /// Convert this weight element to the sum type. fn to_sum(&self) -> Self::Sum; } impl WeightElement for i32 { type Sum = i32; + const IS_UNIT: bool = false; fn to_sum(&self) -> i32 { *self } @@ -49,6 +52,7 @@ impl WeightElement for i32 { impl WeightElement for f64 { type Sum = f64; + const IS_UNIT: bool = false; fn to_sum(&self) -> f64 { *self } @@ -63,6 +67,7 @@ pub struct One; impl WeightElement for One { type Sum = i32; + const IS_UNIT: bool = true; fn to_sum(&self) -> i32 { 1 } diff --git a/src/unit_tests/graph_models.rs b/src/unit_tests/graph_models.rs index fa5b3d3cf..b80fbed23 100644 --- a/src/unit_tests/graph_models.rs +++ b/src/unit_tests/graph_models.rs @@ -23,7 +23,7 @@ mod maximum_independent_set { #[test] fn test_creation() { let problem = - MaximumIndependentSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + MaximumIndependentSet::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1i32; 4]); assert_eq!(problem.graph().num_vertices(), 4); assert_eq!(problem.graph().num_edges(), 3); assert_eq!(problem.num_variables(), 4); @@ -31,20 +31,21 @@ mod maximum_independent_set { #[test] fn test_with_weights() { - let problem = MaximumIndependentSet::with_weights(3, vec![(0, 1)], vec![1, 2, 3]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1, 2, 3]); assert_eq!(problem.weights().to_vec(), vec![1, 2, 3]); assert!(problem.is_weighted()); } #[test] fn test_unweighted() { - let problem = MaximumIndependentSet::::new(3, vec![(0, 1)]); - assert!(!problem.is_weighted()); + // i32 type is always considered weighted, even with uniform values + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1i32; 3]); + assert!(problem.is_weighted()); } #[test] fn test_has_edge() { - let problem = MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2)]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); assert!(problem.graph().has_edge(0, 1)); assert!(problem.graph().has_edge(1, 0)); // Undirected assert!(problem.graph().has_edge(1, 2)); @@ -53,7 +54,7 @@ mod maximum_independent_set { #[test] fn test_evaluate_valid() { - let problem = MaximumIndependentSet::::new(4, vec![(0, 1), (2, 3)]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(4, vec![(0, 1), (2, 3)]), vec![1i32; 4]); // Valid: select 0 and 2 (not adjacent) assert_eq!(problem.evaluate(&[1, 0, 1, 0]), SolutionSize::Valid(2)); @@ -64,7 +65,7 @@ mod maximum_independent_set { #[test] fn test_evaluate_invalid() { - let problem = MaximumIndependentSet::::new(4, vec![(0, 1), (2, 3)]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(4, vec![(0, 1), (2, 3)]), vec![1i32; 4]); // Invalid: 0 and 1 are adjacent - returns Invalid assert_eq!(problem.evaluate(&[1, 1, 0, 0]), SolutionSize::Invalid); @@ -75,14 +76,14 @@ mod maximum_independent_set { #[test] fn test_evaluate_empty() { - let problem = MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2)]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); // Empty selection is valid with size 0 assert_eq!(problem.evaluate(&[0, 0, 0]), SolutionSize::Valid(0)); } #[test] fn test_evaluate_weighted() { - let problem = MaximumIndependentSet::with_weights(3, vec![(0, 1)], vec![10, 20, 30]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1)]), vec![10, 20, 30]); // Select vertex 2 (weight 30) assert_eq!(problem.evaluate(&[0, 0, 1]), SolutionSize::Valid(30)); @@ -95,7 +96,7 @@ mod maximum_independent_set { fn test_brute_force_triangle() { // Triangle graph: maximum IS has size 1 let problem = - MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2), (0, 2)]); + MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), vec![1i32; 3]); let solver = BruteForce::new(); let solutions = solver.find_all_best(&problem); @@ -110,7 +111,7 @@ mod maximum_independent_set { fn test_brute_force_path() { // Path graph 0-1-2-3: maximum IS = {0,2} or {1,3} or {0,3} let problem = - MaximumIndependentSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + MaximumIndependentSet::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1i32; 4]); let solver = BruteForce::new(); let solutions = solver.find_all_best(&problem); @@ -126,7 +127,7 @@ mod maximum_independent_set { #[test] fn test_brute_force_weighted() { // Graph with weights: vertex 1 has high weight but is connected to both 0 and 2 - let problem = MaximumIndependentSet::with_weights(3, vec![(0, 1), (1, 2)], vec![1, 100, 1]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1, 100, 1]); let solver = BruteForce::new(); let solutions = solver.find_all_best(&problem); @@ -137,30 +138,37 @@ mod maximum_independent_set { #[test] fn test_is_independent_set_function() { - assert!(is_independent_set(3, &[(0, 1)], &[true, false, true])); - assert!(is_independent_set(3, &[(0, 1)], &[false, true, true])); - assert!(!is_independent_set(3, &[(0, 1)], &[true, true, false])); assert!(is_independent_set( - 3, - &[(0, 1), (1, 2)], + &SimpleGraph::new(3, vec![(0, 1)]), + &[true, false, true] + )); + assert!(is_independent_set( + &SimpleGraph::new(3, vec![(0, 1)]), + &[false, true, true] + )); + assert!(!is_independent_set( + &SimpleGraph::new(3, vec![(0, 1)]), + &[true, true, false] + )); + assert!(is_independent_set( + &SimpleGraph::new(3, vec![(0, 1), (1, 2)]), &[true, false, true] )); assert!(!is_independent_set( - 3, - &[(0, 1), (1, 2)], + &SimpleGraph::new(3, vec![(0, 1), (1, 2)]), &[false, true, true] )); } #[test] fn test_direction() { - let problem = MaximumIndependentSet::::new(3, vec![(0, 1)]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1i32; 3]); assert_eq!(problem.direction(), Direction::Maximize); } #[test] fn test_edges() { - let problem = MaximumIndependentSet::::new(4, vec![(0, 1), (2, 3)]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(4, vec![(0, 1), (2, 3)]), vec![1i32; 4]); let edges = problem.graph().edges(); assert_eq!(edges.len(), 2); assert!(edges.contains(&(0, 1)) || edges.contains(&(1, 0))); @@ -169,9 +177,8 @@ mod maximum_independent_set { #[test] fn test_with_custom_weights() { - let problem = MaximumIndependentSet::::with_weights( - 3, - vec![(0, 1)], + let problem = MaximumIndependentSet::new( + SimpleGraph::new(3, vec![(0, 1)]), vec![5, 10, 15], ); assert_eq!(problem.weights().to_vec(), vec![5, 10, 15]); @@ -179,7 +186,7 @@ mod maximum_independent_set { #[test] fn test_empty_graph() { - let problem = MaximumIndependentSet::::new(3, vec![]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![]), vec![1i32; 3]); let solver = BruteForce::new(); let solutions = solver.find_all_best(&problem); @@ -190,7 +197,7 @@ mod maximum_independent_set { #[test] fn test_validity_via_evaluate() { - let problem = MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2)]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); // Valid IS configurations return is_valid() == true assert!(problem.evaluate(&[1, 0, 1]).is_valid()); @@ -210,7 +217,7 @@ mod minimum_vertex_cover { #[test] fn test_creation() { - let problem = MinimumVertexCover::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = MinimumVertexCover::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1i32; 4]); assert_eq!(problem.graph().num_vertices(), 4); assert_eq!(problem.graph().num_edges(), 3); assert_eq!(problem.num_variables(), 4); @@ -218,14 +225,14 @@ mod minimum_vertex_cover { #[test] fn test_with_weights() { - let problem = MinimumVertexCover::with_weights(3, vec![(0, 1)], vec![1, 2, 3]); + let problem = MinimumVertexCover::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1, 2, 3]); assert_eq!(problem.weights().to_vec(), vec![1, 2, 3]); assert!(problem.is_weighted()); } #[test] fn test_evaluate_valid() { - let problem = MinimumVertexCover::::new(3, vec![(0, 1), (1, 2)]); + let problem = MinimumVertexCover::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); // Valid: select vertex 1 (covers both edges) assert_eq!(problem.evaluate(&[0, 1, 0]), SolutionSize::Valid(1)); @@ -236,7 +243,7 @@ mod minimum_vertex_cover { #[test] fn test_evaluate_invalid() { - let problem = MinimumVertexCover::::new(3, vec![(0, 1), (1, 2)]); + let problem = MinimumVertexCover::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); // Invalid: no vertex selected - returns Invalid for minimization assert_eq!(problem.evaluate(&[0, 0, 0]), SolutionSize::Invalid); @@ -248,7 +255,7 @@ mod minimum_vertex_cover { #[test] fn test_brute_force_path() { // Path graph 0-1-2: minimum vertex cover is {1} - let problem = MinimumVertexCover::::new(3, vec![(0, 1), (1, 2)]); + let problem = MinimumVertexCover::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); let solver = BruteForce::new(); let solutions = solver.find_all_best(&problem); @@ -259,7 +266,7 @@ mod minimum_vertex_cover { #[test] fn test_brute_force_triangle() { // Triangle: minimum vertex cover has size 2 - let problem = MinimumVertexCover::::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let problem = MinimumVertexCover::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), vec![1i32; 3]); let solver = BruteForce::new(); let solutions = solver.find_all_best(&problem); @@ -275,7 +282,7 @@ mod minimum_vertex_cover { #[test] fn test_brute_force_weighted() { // Weighted: prefer selecting low-weight vertices - let problem = MinimumVertexCover::with_weights(3, vec![(0, 1), (1, 2)], vec![100, 1, 100]); + let problem = MinimumVertexCover::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![100, 1, 100]); let solver = BruteForce::new(); let solutions = solver.find_all_best(&problem); @@ -286,29 +293,33 @@ mod minimum_vertex_cover { #[test] fn test_is_vertex_cover_function() { - assert!(is_vertex_cover(3, &[(0, 1), (1, 2)], &[false, true, false])); - assert!(is_vertex_cover(3, &[(0, 1), (1, 2)], &[true, false, true])); + assert!(is_vertex_cover( + &SimpleGraph::new(3, vec![(0, 1), (1, 2)]), + &[false, true, false] + )); + assert!(is_vertex_cover( + &SimpleGraph::new(3, vec![(0, 1), (1, 2)]), + &[true, false, true] + )); assert!(!is_vertex_cover( - 3, - &[(0, 1), (1, 2)], + &SimpleGraph::new(3, vec![(0, 1), (1, 2)]), &[true, false, false] )); assert!(!is_vertex_cover( - 3, - &[(0, 1), (1, 2)], + &SimpleGraph::new(3, vec![(0, 1), (1, 2)]), &[false, false, false] )); } #[test] fn test_direction() { - let problem = MinimumVertexCover::::new(3, vec![(0, 1)]); + let problem = MinimumVertexCover::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1i32; 3]); assert_eq!(problem.direction(), Direction::Minimize); } #[test] fn test_empty_graph() { - let problem = MinimumVertexCover::::new(3, vec![]); + let problem = MinimumVertexCover::new(SimpleGraph::new(3, vec![]), vec![1i32; 3]); let solver = BruteForce::new(); let solutions = solver.find_all_best(&problem); @@ -319,7 +330,7 @@ mod minimum_vertex_cover { #[test] fn test_single_edge() { - let problem = MinimumVertexCover::::new(2, vec![(0, 1)]); + let problem = MinimumVertexCover::new(SimpleGraph::new(2, vec![(0, 1)]), vec![1i32; 2]); let solver = BruteForce::new(); let solutions = solver.find_all_best(&problem); @@ -329,7 +340,7 @@ mod minimum_vertex_cover { #[test] fn test_validity_via_evaluate() { - let problem = MinimumVertexCover::::new(3, vec![(0, 1), (1, 2)]); + let problem = MinimumVertexCover::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); // Valid cover configurations return is_valid() == true assert!(problem.evaluate(&[0, 1, 0]).is_valid()); @@ -343,8 +354,8 @@ mod minimum_vertex_cover { fn test_complement_relationship() { // For a graph, if S is an independent set, then V\S is a vertex cover let edges = vec![(0, 1), (1, 2), (2, 3)]; - let is_problem = MaximumIndependentSet::::new(4, edges.clone()); - let vc_problem = MinimumVertexCover::::new(4, edges); + let is_problem = MaximumIndependentSet::new(SimpleGraph::new(4, edges.clone()), vec![1i32; 4]); + let vc_problem = MinimumVertexCover::new(SimpleGraph::new(4, edges), vec![1i32; 4]); let solver = BruteForce::new(); @@ -359,21 +370,23 @@ mod minimum_vertex_cover { #[test] fn test_with_custom_weights() { - let problem = MinimumVertexCover::with_weights(3, vec![(0, 1)], vec![1, 2, 3]); + let problem = MinimumVertexCover::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1, 2, 3]); assert!(problem.is_weighted()); assert_eq!(problem.weights().to_vec(), vec![1, 2, 3]); } #[test] fn test_is_weighted_empty() { - let problem = MinimumVertexCover::::new(0, vec![]); - assert!(!problem.is_weighted()); + // i32 type is always considered weighted, even with empty weights + let problem = MinimumVertexCover::new(SimpleGraph::new(0, vec![]), vec![0i32; 0]); + assert!(problem.is_weighted()); } #[test] + #[should_panic(expected = "selected length must match num_vertices")] fn test_is_vertex_cover_wrong_len() { - // Wrong length should return false - assert!(!is_vertex_cover(3, &[(0, 1)], &[true, false])); + // Wrong length should panic + is_vertex_cover(&SimpleGraph::new(3, vec![(0, 1)]), &[true, false]); } } @@ -386,7 +399,7 @@ mod kcoloring { #[test] fn test_creation() { - let problem = KColoring::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = KColoring::::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)])); assert_eq!(problem.graph().num_vertices(), 4); assert_eq!(problem.graph().num_edges(), 3); assert_eq!(problem.num_colors(), 3); @@ -395,7 +408,7 @@ mod kcoloring { #[test] fn test_evaluate_valid() { - let problem = KColoring::::new(3, vec![(0, 1), (1, 2)]); + let problem = KColoring::::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)])); // Valid: different colors on adjacent vertices - returns true assert!(problem.evaluate(&[0, 1, 0])); @@ -404,7 +417,7 @@ mod kcoloring { #[test] fn test_evaluate_invalid() { - let problem = KColoring::::new(3, vec![(0, 1), (1, 2)]); + let problem = KColoring::::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)])); // Invalid: adjacent vertices have same color assert!(!problem.evaluate(&[0, 0, 1])); // 0-1 conflict @@ -414,7 +427,7 @@ mod kcoloring { #[test] fn test_brute_force_path() { // Path graph can be 2-colored - let problem = KColoring::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = KColoring::::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)])); let solver = BruteForce::new(); let solutions = solver.find_all_satisfying(&problem); @@ -427,7 +440,7 @@ mod kcoloring { #[test] fn test_brute_force_triangle() { // Triangle needs 3 colors - let problem = KColoring::::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let problem = KColoring::::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)])); let solver = BruteForce::new(); let solutions = solver.find_all_satisfying(&problem); @@ -443,7 +456,7 @@ mod kcoloring { #[test] fn test_triangle_2_colors_unsat() { // Triangle cannot be 2-colored - let problem = KColoring::::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let problem = KColoring::::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)])); let solver = BruteForce::new(); // No satisfying assignments @@ -453,19 +466,25 @@ mod kcoloring { #[test] fn test_is_valid_coloring_function() { - let edges = vec![(0, 1), (1, 2)]; + let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); - assert!(is_valid_coloring(3, &edges, &[0, 1, 0], 2)); - assert!(is_valid_coloring(3, &edges, &[0, 1, 2], 3)); - assert!(!is_valid_coloring(3, &edges, &[0, 0, 1], 2)); // 0-1 conflict - assert!(!is_valid_coloring(3, &edges, &[0, 1, 1], 2)); // 1-2 conflict - assert!(!is_valid_coloring(3, &edges, &[0, 1], 2)); // Wrong length - assert!(!is_valid_coloring(3, &edges, &[0, 2, 0], 2)); // Color out of range + assert!(is_valid_coloring(&graph, &[0, 1, 0], 2)); + assert!(is_valid_coloring(&graph, &[0, 1, 2], 3)); + assert!(!is_valid_coloring(&graph, &[0, 0, 1], 2)); // 0-1 conflict + assert!(!is_valid_coloring(&graph, &[0, 1, 1], 2)); // 1-2 conflict + assert!(!is_valid_coloring(&graph, &[0, 2, 0], 2)); // Color out of range + } + + #[test] + #[should_panic(expected = "coloring length must match num_vertices")] + fn test_is_valid_coloring_wrong_len() { + let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); + is_valid_coloring(&graph, &[0, 1], 2); // Wrong length } #[test] fn test_empty_graph() { - let problem = KColoring::::new(3, vec![]); + let problem = KColoring::::new(SimpleGraph::new(3, vec![])); let solver = BruteForce::new(); let solutions = solver.find_all_satisfying(&problem); @@ -476,10 +495,10 @@ mod kcoloring { #[test] fn test_complete_graph_k4() { // K4 needs 4 colors - let problem = KColoring::::new( + let problem = KColoring::::new(SimpleGraph::new( 4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], - ); + )); let solver = BruteForce::new(); let solutions = solver.find_all_satisfying(&problem); diff --git a/src/unit_tests/io.rs b/src/unit_tests/io.rs index 3ae403796..11f2743e9 100644 --- a/src/unit_tests/io.rs +++ b/src/unit_tests/io.rs @@ -6,7 +6,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; #[test] fn test_to_json() { - let problem = MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2)]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); let json = to_json(&problem); assert!(json.is_ok()); let json = json.unwrap(); @@ -15,7 +15,7 @@ fn test_to_json() { #[test] fn test_from_json() { - let problem = MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2)]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); let json = to_json(&problem).unwrap(); let restored: MaximumIndependentSet = from_json(&json).unwrap(); assert_eq!(restored.graph().num_vertices(), 3); @@ -24,7 +24,7 @@ fn test_from_json() { #[test] fn test_json_compact() { - let problem = MaximumIndependentSet::::new(3, vec![(0, 1)]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1i32; 3]); let compact = to_json_compact(&problem).unwrap(); let pretty = to_json(&problem).unwrap(); // Compact should be shorter @@ -33,7 +33,7 @@ fn test_json_compact() { #[test] fn test_file_roundtrip() { - let problem = MaximumIndependentSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1i32; 4]); let ts = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() diff --git a/src/unit_tests/models/graph/kcoloring.rs b/src/unit_tests/models/graph/kcoloring.rs index c991e42be..c3d695661 100644 --- a/src/unit_tests/models/graph/kcoloring.rs +++ b/src/unit_tests/models/graph/kcoloring.rs @@ -1,5 +1,6 @@ use super::*; use crate::solvers::BruteForce; +use crate::topology::SimpleGraph; use crate::variant::{K1, K2, K3, K4}; include!("../../jl_helpers.rs"); @@ -7,7 +8,7 @@ include!("../../jl_helpers.rs"); fn test_kcoloring_creation() { use crate::traits::Problem; - let problem = KColoring::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = KColoring::::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)])); assert_eq!(problem.graph().num_vertices(), 4); assert_eq!(problem.graph().num_edges(), 3); assert_eq!(problem.num_colors(), 3); @@ -18,7 +19,7 @@ fn test_kcoloring_creation() { fn test_evaluate_valid() { use crate::traits::Problem; - let problem = KColoring::::new(3, vec![(0, 1), (1, 2)]); + let problem = KColoring::::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)])); // Valid: different colors on adjacent vertices assert!(problem.evaluate(&[0, 1, 0])); @@ -29,7 +30,7 @@ fn test_evaluate_valid() { fn test_evaluate_invalid() { use crate::traits::Problem; - let problem = KColoring::::new(3, vec![(0, 1), (1, 2)]); + let problem = KColoring::::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)])); // Invalid: adjacent vertices have same color assert!(!problem.evaluate(&[0, 0, 1])); @@ -41,7 +42,7 @@ fn test_brute_force_path() { use crate::traits::Problem; // Path graph can be 2-colored - let problem = KColoring::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = KColoring::::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)])); let solver = BruteForce::new(); let solutions = solver.find_all_satisfying(&problem); @@ -56,7 +57,7 @@ fn test_brute_force_triangle() { use crate::traits::Problem; // Triangle needs 3 colors - let problem = KColoring::::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let problem = KColoring::::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)])); let solver = BruteForce::new(); let solutions = solver.find_all_satisfying(&problem); @@ -72,7 +73,7 @@ fn test_brute_force_triangle() { #[test] fn test_triangle_2_colors() { // Triangle cannot be 2-colored - let problem = KColoring::::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let problem = KColoring::::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)])); let solver = BruteForce::new(); let solutions = solver.find_all_satisfying(&problem); @@ -82,21 +83,27 @@ fn test_triangle_2_colors() { #[test] fn test_is_valid_coloring_function() { - let edges = vec![(0, 1), (1, 2)]; - - assert!(is_valid_coloring(3, &edges, &[0, 1, 0], 2)); - assert!(is_valid_coloring(3, &edges, &[0, 1, 2], 3)); - assert!(!is_valid_coloring(3, &edges, &[0, 0, 1], 2)); // 0-1 conflict - assert!(!is_valid_coloring(3, &edges, &[0, 1, 1], 2)); // 1-2 conflict - assert!(!is_valid_coloring(3, &edges, &[0, 1], 2)); // Wrong length - assert!(!is_valid_coloring(3, &edges, &[0, 2, 0], 2)); // Color out of range + let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); + + assert!(is_valid_coloring(&graph, &[0, 1, 0], 2)); + assert!(is_valid_coloring(&graph, &[0, 1, 2], 3)); + assert!(!is_valid_coloring(&graph, &[0, 0, 1], 2)); // 0-1 conflict + assert!(!is_valid_coloring(&graph, &[0, 1, 1], 2)); // 1-2 conflict + assert!(!is_valid_coloring(&graph, &[0, 2, 0], 2)); // Color out of range +} + +#[test] +#[should_panic(expected = "coloring length must match num_vertices")] +fn test_is_valid_coloring_wrong_len() { + let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); + is_valid_coloring(&graph, &[0, 1], 2); // Wrong length } #[test] fn test_empty_graph() { use crate::traits::Problem; - let problem = KColoring::::new(3, vec![]); + let problem = KColoring::::new(SimpleGraph::new(3, vec![])); let solver = BruteForce::new(); let solutions = solver.find_all_satisfying(&problem); @@ -113,7 +120,7 @@ fn test_complete_graph_k4() { // K4 needs 4 colors let problem = - KColoring::::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]); + KColoring::::new(SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)])); let solver = BruteForce::new(); let solutions = solver.find_all_satisfying(&problem); @@ -123,9 +130,9 @@ fn test_complete_graph_k4() { } #[test] -fn test_from_graph() { +fn test_new_from_graph() { let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); - let problem = KColoring::::from_graph(graph); + let problem = KColoring::::new(graph); assert_eq!(problem.graph().num_vertices(), 3); assert_eq!(problem.graph().num_edges(), 2); } @@ -135,7 +142,7 @@ fn test_kcoloring_problem() { use crate::traits::Problem; // Triangle graph with 3 colors - let p = KColoring::::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let p = KColoring::::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)])); assert_eq!(p.dims(), vec![3, 3, 3]); // Valid: each vertex different color assert!(p.evaluate(&[0, 1, 2])); @@ -151,7 +158,7 @@ fn test_jl_parity_evaluation() { let nv = instance["instance"]["num_vertices"].as_u64().unwrap() as usize; let edges = jl_parse_edges(&instance["instance"]); let num_edges = edges.len(); - let problem = KColoring::::new(nv, edges); + let problem = KColoring::::new(SimpleGraph::new(nv, edges)); for eval in instance["evaluations"].as_array().unwrap() { let config = jl_parse_config(&eval["config"]); let result: bool = problem.evaluate(&config); diff --git a/src/unit_tests/models/graph/max_cut.rs b/src/unit_tests/models/graph/max_cut.rs index b379ff9d9..cfe75eae5 100644 --- a/src/unit_tests/models/graph/max_cut.rs +++ b/src/unit_tests/models/graph/max_cut.rs @@ -1,12 +1,13 @@ use super::*; use crate::solvers::BruteForce; +use crate::topology::SimpleGraph; include!("../../jl_helpers.rs"); #[test] fn test_maxcut_creation() { use crate::traits::Problem; - let problem = MaxCut::::new(4, vec![(0, 1, 1), (1, 2, 2), (2, 3, 3)]); + let problem = MaxCut::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1, 2, 3]); assert_eq!(problem.graph().num_vertices(), 4); assert_eq!(problem.graph().num_edges(), 3); assert_eq!(problem.dims(), vec![2, 2, 2, 2]); @@ -14,7 +15,7 @@ fn test_maxcut_creation() { #[test] fn test_maxcut_unweighted() { - let problem = MaxCut::::unweighted(3, vec![(0, 1), (1, 2)]); + let problem = MaxCut::<_, i32>::unweighted(SimpleGraph::new(3, vec![(0, 1), (1, 2)])); assert_eq!(problem.graph().num_edges(), 2); } @@ -36,7 +37,7 @@ fn test_cut_size_function() { #[test] fn test_edge_weight() { - let problem = MaxCut::::new(3, vec![(0, 1, 5), (1, 2, 10)]); + let problem = MaxCut::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![5, 10]); assert_eq!(problem.edge_weight(0, 1), Some(&5)); assert_eq!(problem.edge_weight(1, 2), Some(&10)); assert_eq!(problem.edge_weight(0, 2), None); @@ -44,7 +45,7 @@ fn test_edge_weight() { #[test] fn test_edges() { - let problem = MaxCut::::new(3, vec![(0, 1, 1), (1, 2, 2)]); + let problem = MaxCut::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1, 2]); let edges = problem.edges(); assert_eq!(edges.len(), 2); } @@ -54,23 +55,21 @@ fn test_direction() { use crate::traits::OptimizationProblem; use crate::types::Direction; - let problem = MaxCut::::unweighted(2, vec![(0, 1)]); + let problem = MaxCut::<_, i32>::unweighted(SimpleGraph::new(2, vec![(0, 1)])); assert_eq!(problem.direction(), Direction::Maximize); } #[test] -fn test_from_graph() { - let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); - let problem = MaxCut::::from_graph(graph, vec![5, 10]); +fn test_new() { + let problem = MaxCut::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![5, 10]); assert_eq!(problem.graph().num_vertices(), 3); assert_eq!(problem.graph().num_edges(), 2); assert_eq!(problem.edge_weights(), vec![5, 10]); } #[test] -fn test_from_graph_unweighted() { - let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); - let problem = MaxCut::::from_graph_unweighted(graph); +fn test_unweighted() { + let problem = MaxCut::<_, i32>::unweighted(SimpleGraph::new(3, vec![(0, 1), (1, 2)])); assert_eq!(problem.graph().num_vertices(), 3); assert_eq!(problem.graph().num_edges(), 2); assert_eq!(problem.edge_weights(), vec![1, 1]); @@ -78,21 +77,21 @@ fn test_from_graph_unweighted() { #[test] fn test_graph_accessor() { - let problem = MaxCut::::unweighted(3, vec![(0, 1), (1, 2)]); + let problem = MaxCut::<_, i32>::unweighted(SimpleGraph::new(3, vec![(0, 1), (1, 2)])); let graph = problem.graph(); assert_eq!(graph.num_vertices(), 3); assert_eq!(graph.num_edges(), 2); } #[test] -fn test_with_weights() { - let problem = MaxCut::::with_weights(3, vec![(0, 1), (1, 2)], vec![7, 3]); +fn test_new_with_separate_weights() { + let problem = MaxCut::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![7, 3]); assert_eq!(problem.edge_weights(), vec![7, 3]); } #[test] fn test_edge_weight_by_index() { - let problem = MaxCut::::new(3, vec![(0, 1, 5), (1, 2, 10)]); + let problem = MaxCut::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![5, 10]); assert_eq!(problem.edge_weight_by_index(0), Some(&5)); assert_eq!(problem.edge_weight_by_index(1), Some(&10)); assert_eq!(problem.edge_weight_by_index(2), None); @@ -105,7 +104,9 @@ fn test_jl_parity_evaluation() { for instance in data["instances"].as_array().unwrap() { let nv = instance["instance"]["num_vertices"].as_u64().unwrap() as usize; let weighted_edges = jl_parse_weighted_edges(&instance["instance"]); - let problem = MaxCut::::new(nv, weighted_edges); + let edges: Vec<(usize, usize)> = weighted_edges.iter().map(|&(u, v, _)| (u, v)).collect(); + let weights: Vec = weighted_edges.into_iter().map(|(_, _, w)| w).collect(); + let problem = MaxCut::new(SimpleGraph::new(nv, edges), weights); for eval in instance["evaluations"].as_array().unwrap() { let config = jl_parse_config(&eval["config"]); let result = problem.evaluate(&config); diff --git a/src/unit_tests/models/graph/maximal_is.rs b/src/unit_tests/models/graph/maximal_is.rs index 2f52c330f..e6bf88b4f 100644 --- a/src/unit_tests/models/graph/maximal_is.rs +++ b/src/unit_tests/models/graph/maximal_is.rs @@ -1,17 +1,18 @@ use super::*; use crate::solvers::BruteForce; +use crate::topology::SimpleGraph; include!("../../jl_helpers.rs"); #[test] fn test_maximal_is_creation() { - let problem = MaximalIS::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = MaximalIS::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1i32; 4]); assert_eq!(problem.graph().num_vertices(), 4); assert_eq!(problem.graph().num_edges(), 3); } #[test] fn test_maximal_is_with_weights() { - let problem = MaximalIS::::with_weights(3, vec![(0, 1)], vec![1, 2, 3]); + let problem = MaximalIS::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1, 2, 3]); assert_eq!(problem.weights().to_vec(), vec![1, 2, 3]); assert!(problem.is_weighted()); } @@ -19,14 +20,14 @@ fn test_maximal_is_with_weights() { #[test] fn test_maximal_is_from_graph() { let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); - let problem = MaximalIS::::from_graph(graph, vec![1, 2, 3]); + let problem = MaximalIS::new(graph, vec![1, 2, 3]); assert_eq!(problem.graph().num_vertices(), 3); assert_eq!(problem.weights().to_vec(), vec![1, 2, 3]); } #[test] fn test_is_independent() { - let problem = MaximalIS::::new(3, vec![(0, 1), (1, 2)]); + let problem = MaximalIS::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); assert!(problem.is_independent(&[1, 0, 1])); assert!(problem.is_independent(&[0, 1, 0])); @@ -35,7 +36,7 @@ fn test_is_independent() { #[test] fn test_is_maximal() { - let problem = MaximalIS::::new(3, vec![(0, 1), (1, 2)]); + let problem = MaximalIS::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); // {0, 2} is maximal (cannot add 1) assert!(problem.is_maximal(&[1, 0, 1])); @@ -52,16 +53,15 @@ fn test_is_maximal() { #[test] fn test_is_maximal_independent_set_function() { - let edges = vec![(0, 1), (1, 2)]; + let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); - assert!(is_maximal_independent_set(3, &edges, &[true, false, true])); - assert!(is_maximal_independent_set(3, &edges, &[false, true, false])); + assert!(is_maximal_independent_set(&graph, &[true, false, true])); + assert!(is_maximal_independent_set(&graph, &[false, true, false])); assert!(!is_maximal_independent_set( - 3, - &edges, + &graph, &[true, false, false] )); // Can add 2 - assert!(!is_maximal_independent_set(3, &edges, &[true, true, false])); // Not independent + assert!(!is_maximal_independent_set(&graph, &[true, true, false])); // Not independent } #[test] @@ -69,36 +69,39 @@ fn test_direction() { use crate::traits::OptimizationProblem; use crate::types::Direction; - let problem = MaximalIS::::new(2, vec![(0, 1)]); + let problem = MaximalIS::new(SimpleGraph::new(2, vec![(0, 1)]), vec![1i32; 2]); assert_eq!(problem.direction(), Direction::Maximize); } #[test] fn test_weights() { - let problem = MaximalIS::::new(3, vec![(0, 1)]); + let problem = MaximalIS::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1i32; 3]); assert_eq!(problem.weights().to_vec(), vec![1, 1, 1]); // Unit weights } #[test] fn test_is_weighted() { - let problem = MaximalIS::::new(3, vec![(0, 1)]); - assert!(!problem.is_weighted()); // Initially uniform + // i32 type is always considered weighted + let problem = MaximalIS::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1i32; 3]); + assert!(problem.is_weighted()); } #[test] fn test_is_weighted_empty() { - let problem = MaximalIS::::with_weights(0, vec![], vec![]); - assert!(!problem.is_weighted()); + // i32 type is always considered weighted, even with empty weights + let problem = MaximalIS::new(SimpleGraph::new(0, vec![]), vec![0i32; 0]); + assert!(problem.is_weighted()); } #[test] +#[should_panic(expected = "selected length must match num_vertices")] fn test_is_maximal_independent_set_wrong_len() { - assert!(!is_maximal_independent_set(3, &[(0, 1)], &[true, false])); + is_maximal_independent_set(&SimpleGraph::new(3, vec![(0, 1)]), &[true, false]); } #[test] fn test_graph_ref() { - let problem = MaximalIS::::new(3, vec![(0, 1), (1, 2)]); + let problem = MaximalIS::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); let graph = problem.graph(); assert_eq!(graph.num_vertices(), 3); assert_eq!(graph.num_edges(), 2); @@ -106,14 +109,14 @@ fn test_graph_ref() { #[test] fn test_edges() { - let problem = MaximalIS::::new(3, vec![(0, 1), (1, 2)]); + let problem = MaximalIS::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); let edges = problem.graph().edges(); assert_eq!(edges.len(), 2); } #[test] fn test_has_edge() { - let problem = MaximalIS::::new(3, vec![(0, 1), (1, 2)]); + let problem = MaximalIS::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); assert!(problem.graph().has_edge(0, 1)); assert!(problem.graph().has_edge(1, 0)); // Undirected assert!(problem.graph().has_edge(1, 2)); @@ -122,7 +125,7 @@ fn test_has_edge() { #[test] fn test_weights_ref() { - let problem = MaximalIS::::new(3, vec![(0, 1)]); + let problem = MaximalIS::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1i32; 3]); assert_eq!(problem.weights(), &[1, 1, 1]); } @@ -133,7 +136,7 @@ fn test_jl_parity_evaluation() { for instance in data["instances"].as_array().unwrap() { let nv = instance["instance"]["num_vertices"].as_u64().unwrap() as usize; let edges = jl_parse_edges(&instance["instance"]); - let problem = MaximalIS::::new(nv, edges); + let problem = MaximalIS::new(SimpleGraph::new(nv, edges), vec![1i32; nv]); for eval in instance["evaluations"].as_array().unwrap() { let config = jl_parse_config(&eval["config"]); let result = problem.evaluate(&config); diff --git a/src/unit_tests/models/graph/maximum_clique.rs b/src/unit_tests/models/graph/maximum_clique.rs index 5e5d45a8a..bf0e4b062 100644 --- a/src/unit_tests/models/graph/maximum_clique.rs +++ b/src/unit_tests/models/graph/maximum_clique.rs @@ -1,12 +1,13 @@ use super::*; use crate::solvers::BruteForce; +use crate::topology::SimpleGraph; use crate::types::SolutionSize; #[test] fn test_clique_creation() { use crate::traits::Problem; - let problem = MaximumClique::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = MaximumClique::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1i32; 4]); assert_eq!(problem.graph().num_vertices(), 4); assert_eq!(problem.graph().num_edges(), 3); assert_eq!(problem.dims(), vec![2, 2, 2, 2]); @@ -14,20 +15,21 @@ fn test_clique_creation() { #[test] fn test_clique_with_weights() { - let problem = MaximumClique::::with_weights(3, vec![(0, 1)], vec![1, 2, 3]); + let problem = MaximumClique::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1, 2, 3]); assert_eq!(problem.weights().to_vec(), vec![1, 2, 3]); assert!(problem.is_weighted()); } #[test] fn test_clique_unweighted() { - let problem = MaximumClique::::new(3, vec![(0, 1)]); - assert!(!problem.is_weighted()); + // i32 type is always considered weighted, even with uniform values + let problem = MaximumClique::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1i32; 3]); + assert!(problem.is_weighted()); } #[test] fn test_has_edge() { - let problem = MaximumClique::::new(3, vec![(0, 1), (1, 2)]); + let problem = MaximumClique::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); assert!(problem.graph().has_edge(0, 1)); assert!(problem.graph().has_edge(1, 0)); // Undirected assert!(problem.graph().has_edge(1, 2)); @@ -39,7 +41,7 @@ fn test_evaluate_valid() { use crate::traits::Problem; // Complete graph K3 (triangle) - let problem = MaximumClique::::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let problem = MaximumClique::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), vec![1i32; 3]); // Valid: all three form a clique assert_eq!(problem.evaluate(&[1, 1, 1]), SolutionSize::Valid(3)); @@ -53,7 +55,7 @@ fn test_evaluate_invalid() { use crate::traits::Problem; // Path graph: 0-1-2 (no edge between 0 and 2) - let problem = MaximumClique::::new(3, vec![(0, 1), (1, 2)]); + let problem = MaximumClique::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); // Invalid: 0 and 2 are not adjacent - returns Invalid assert_eq!(problem.evaluate(&[1, 0, 1]), SolutionSize::Invalid); @@ -66,7 +68,7 @@ fn test_evaluate_invalid() { fn test_evaluate_empty() { use crate::traits::Problem; - let problem = MaximumClique::::new(3, vec![(0, 1), (1, 2)]); + let problem = MaximumClique::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); // Empty set is a valid clique with size 0 assert_eq!(problem.evaluate(&[0, 0, 0]), SolutionSize::Valid(0)); } @@ -75,9 +77,8 @@ fn test_evaluate_empty() { fn test_weighted_solution() { use crate::traits::Problem; - let problem = MaximumClique::::with_weights( - 3, - vec![(0, 1), (1, 2), (0, 2)], + let problem = MaximumClique::new( + SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), vec![10, 20, 30], ); @@ -91,7 +92,7 @@ fn test_weighted_solution() { #[test] fn test_brute_force_triangle() { // Triangle graph (K3): max clique is all 3 vertices - let problem = MaximumClique::::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let problem = MaximumClique::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), vec![1i32; 3]); let solver = BruteForce::new(); let solutions = solver.find_all_best(&problem); @@ -104,7 +105,7 @@ fn test_brute_force_path() { use crate::traits::Problem; // Path graph 0-1-2: max clique is any adjacent pair - let problem = MaximumClique::::new(3, vec![(0, 1), (1, 2)]); + let problem = MaximumClique::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); let solver = BruteForce::new(); let solutions = solver.find_all_best(&problem); @@ -123,7 +124,7 @@ fn test_brute_force_weighted() { // Path with weights: vertex 1 has high weight let problem = - MaximumClique::::with_weights(3, vec![(0, 1), (1, 2)], vec![1, 100, 1]); + MaximumClique::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1, 100, 1]); let solver = BruteForce::new(); let solutions = solver.find_all_best(&problem); @@ -137,16 +138,24 @@ fn test_brute_force_weighted() { #[test] fn test_is_clique_function() { // Triangle - assert!(is_clique(3, &[(0, 1), (1, 2), (0, 2)], &[true, true, true])); assert!(is_clique( - 3, - &[(0, 1), (1, 2), (0, 2)], + &SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), + &[true, true, true] + )); + assert!(is_clique( + &SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), &[true, true, false] )); // Path - not all pairs adjacent - assert!(!is_clique(3, &[(0, 1), (1, 2)], &[true, false, true])); - assert!(is_clique(3, &[(0, 1), (1, 2)], &[true, true, false])); // Adjacent pair + assert!(!is_clique( + &SimpleGraph::new(3, vec![(0, 1), (1, 2)]), + &[true, false, true] + )); + assert!(is_clique( + &SimpleGraph::new(3, vec![(0, 1), (1, 2)]), + &[true, true, false] + )); // Adjacent pair } #[test] @@ -154,13 +163,13 @@ fn test_direction() { use crate::traits::OptimizationProblem; use crate::types::Direction; - let problem = MaximumClique::::new(3, vec![(0, 1)]); + let problem = MaximumClique::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1i32; 3]); assert_eq!(problem.direction(), Direction::Maximize); } #[test] fn test_edges() { - let problem = MaximumClique::::new(4, vec![(0, 1), (2, 3)]); + let problem = MaximumClique::new(SimpleGraph::new(4, vec![(0, 1), (2, 3)]), vec![1i32; 4]); let edges = problem.graph().edges(); assert_eq!(edges.len(), 2); } @@ -168,7 +177,7 @@ fn test_edges() { #[test] fn test_empty_graph() { // No edges means any single vertex is a max clique - let problem = MaximumClique::::new(3, vec![]); + let problem = MaximumClique::new(SimpleGraph::new(3, vec![]), vec![1i32; 3]); let solver = BruteForce::new(); let solutions = solver.find_all_best(&problem); @@ -183,7 +192,7 @@ fn test_empty_graph() { fn test_is_clique_method() { use crate::traits::Problem; - let problem = MaximumClique::::new(3, vec![(0, 1), (1, 2)]); + let problem = MaximumClique::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); // Valid clique - returns Valid assert!(problem.evaluate(&[1, 1, 0]).is_valid()); @@ -195,14 +204,14 @@ fn test_is_clique_method() { #[test] fn test_from_graph() { let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); - let problem = MaximumClique::::from_graph(graph.clone(), vec![1, 2, 3]); + let problem = MaximumClique::new(graph, vec![1, 2, 3]); assert_eq!(problem.graph().num_vertices(), 3); assert_eq!(problem.weights().to_vec(), vec![1, 2, 3]); } #[test] fn test_graph_accessor() { - let problem = MaximumClique::::new(3, vec![(0, 1)]); + let problem = MaximumClique::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1i32; 3]); let graph = problem.graph(); assert_eq!(graph.num_vertices(), 3); assert_eq!(graph.num_edges(), 1); @@ -210,22 +219,23 @@ fn test_graph_accessor() { #[test] fn test_weights_ref() { - let problem = MaximumClique::::with_weights(3, vec![(0, 1)], vec![5, 10, 15]); + let problem = MaximumClique::new(SimpleGraph::new(3, vec![(0, 1)]), vec![5, 10, 15]); assert_eq!(problem.weights(), &[5, 10, 15]); } #[test] +#[should_panic(expected = "selected length must match num_vertices")] fn test_is_clique_wrong_len() { - // Wrong length should return false - assert!(!is_clique(3, &[(0, 1)], &[true, false])); + // Wrong length should panic + is_clique(&SimpleGraph::new(3, vec![(0, 1)]), &[true, false]); } #[test] fn test_complete_graph() { // K4 - complete graph with 4 vertices - let problem = MaximumClique::::new( - 4, - vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], + let problem = MaximumClique::new( + SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]), + vec![1i32; 4], ); let solver = BruteForce::new(); @@ -240,7 +250,7 @@ fn test_clique_problem() { use crate::types::Direction; // Triangle graph: all pairs connected - let p = MaximumClique::::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let p = MaximumClique::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), vec![1i32; 3]); assert_eq!(p.dims(), vec![2, 2, 2]); // Valid clique: select all 3 vertices (triangle is a clique) assert_eq!(p.evaluate(&[1, 1, 1]), SolutionSize::Valid(3)); diff --git a/src/unit_tests/models/graph/maximum_independent_set.rs b/src/unit_tests/models/graph/maximum_independent_set.rs index 2e15b501f..ab670bbe7 100644 --- a/src/unit_tests/models/graph/maximum_independent_set.rs +++ b/src/unit_tests/models/graph/maximum_independent_set.rs @@ -1,12 +1,13 @@ use super::*; use crate::solvers::BruteForce; +use crate::topology::SimpleGraph; use crate::traits::{OptimizationProblem, Problem}; use crate::types::Direction; include!("../../jl_helpers.rs"); #[test] fn test_independent_set_creation() { - let problem = MaximumIndependentSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1i32; 4]); assert_eq!(problem.graph().num_vertices(), 4); assert_eq!(problem.graph().num_edges(), 3); assert_eq!(problem.dims().len(), 4); @@ -15,20 +16,21 @@ fn test_independent_set_creation() { #[test] fn test_independent_set_with_weights() { let problem = - MaximumIndependentSet::::with_weights(3, vec![(0, 1)], vec![1, 2, 3]); + MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1, 2, 3]); assert_eq!(problem.weights().to_vec(), vec![1, 2, 3]); assert!(problem.is_weighted()); } #[test] fn test_independent_set_unweighted() { - let problem = MaximumIndependentSet::::new(3, vec![(0, 1)]); - assert!(!problem.is_weighted()); + // i32 type is always considered weighted, even with uniform values + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1i32; 3]); + assert!(problem.is_weighted()); } #[test] fn test_has_edge() { - let problem = MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2)]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); assert!(problem.graph().has_edge(0, 1)); assert!(problem.graph().has_edge(1, 0)); // Undirected assert!(problem.graph().has_edge(1, 2)); @@ -37,30 +39,37 @@ fn test_has_edge() { #[test] fn test_is_independent_set_function() { - assert!(is_independent_set(3, &[(0, 1)], &[true, false, true])); - assert!(is_independent_set(3, &[(0, 1)], &[false, true, true])); - assert!(!is_independent_set(3, &[(0, 1)], &[true, true, false])); assert!(is_independent_set( - 3, - &[(0, 1), (1, 2)], + &SimpleGraph::new(3, vec![(0, 1)]), + &[true, false, true] + )); + assert!(is_independent_set( + &SimpleGraph::new(3, vec![(0, 1)]), + &[false, true, true] + )); + assert!(!is_independent_set( + &SimpleGraph::new(3, vec![(0, 1)]), + &[true, true, false] + )); + assert!(is_independent_set( + &SimpleGraph::new(3, vec![(0, 1), (1, 2)]), &[true, false, true] )); assert!(!is_independent_set( - 3, - &[(0, 1), (1, 2)], + &SimpleGraph::new(3, vec![(0, 1), (1, 2)]), &[false, true, true] )); } #[test] fn test_direction() { - let problem = MaximumIndependentSet::::new(3, vec![(0, 1)]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1i32; 3]); assert_eq!(problem.direction(), Direction::Maximize); } #[test] fn test_edges() { - let problem = MaximumIndependentSet::::new(4, vec![(0, 1), (2, 3)]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(4, vec![(0, 1), (2, 3)]), vec![1i32; 4]); let edges = problem.graph().edges(); assert_eq!(edges.len(), 2); assert!(edges.contains(&(0, 1)) || edges.contains(&(1, 0))); @@ -70,7 +79,7 @@ fn test_edges() { #[test] fn test_with_custom_weights() { let problem = - MaximumIndependentSet::::with_weights(3, vec![(0, 1)], vec![5, 10, 15]); + MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1)]), vec![5, 10, 15]); assert_eq!(problem.weights().to_vec(), vec![5, 10, 15]); } @@ -78,7 +87,7 @@ fn test_with_custom_weights() { fn test_from_graph() { let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); let problem = - MaximumIndependentSet::::from_graph(graph.clone(), vec![1, 2, 3]); + MaximumIndependentSet::new(graph.clone(), vec![1, 2, 3]); assert_eq!(problem.graph().num_vertices(), 3); assert_eq!(problem.weights().to_vec(), vec![1, 2, 3]); } @@ -86,14 +95,14 @@ fn test_from_graph() { #[test] fn test_from_graph_with_unit_weights() { let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); - let problem = MaximumIndependentSet::::from_graph(graph, vec![1, 1, 1]); + let problem = MaximumIndependentSet::new(graph, vec![1i32; 3]); assert_eq!(problem.graph().num_vertices(), 3); assert_eq!(problem.weights().to_vec(), vec![1, 1, 1]); } #[test] fn test_graph_accessor() { - let problem = MaximumIndependentSet::::new(3, vec![(0, 1)]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1i32; 3]); let graph = problem.graph(); assert_eq!(graph.num_vertices(), 3); assert_eq!(graph.num_edges(), 1); @@ -102,7 +111,7 @@ fn test_graph_accessor() { #[test] fn test_weights() { let problem = - MaximumIndependentSet::::with_weights(3, vec![(0, 1)], vec![5, 10, 15]); + MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1)]), vec![5, 10, 15]); assert_eq!(problem.weights(), &[5, 10, 15]); } @@ -124,11 +133,7 @@ fn test_jl_parity_evaluation() { let nv = instance["instance"]["num_vertices"].as_u64().unwrap() as usize; let edges = jl_parse_edges(&instance["instance"]); let weights = jl_parse_i32_vec(&instance["instance"]["weights"]); - let problem = if weights.iter().all(|&w| w == 1) { - MaximumIndependentSet::::new(nv, edges) - } else { - MaximumIndependentSet::with_weights(nv, edges, weights) - }; + let problem = MaximumIndependentSet::new(SimpleGraph::new(nv, edges), weights); for eval in instance["evaluations"].as_array().unwrap() { let config = jl_parse_config(&eval["config"]); let result = problem.evaluate(&config); diff --git a/src/unit_tests/models/graph/maximum_matching.rs b/src/unit_tests/models/graph/maximum_matching.rs index 8906d2483..ba1447350 100644 --- a/src/unit_tests/models/graph/maximum_matching.rs +++ b/src/unit_tests/models/graph/maximum_matching.rs @@ -1,5 +1,6 @@ use super::*; use crate::solvers::BruteForce; +use crate::topology::SimpleGraph; use crate::traits::{OptimizationProblem, Problem}; use crate::types::{Direction, SolutionSize}; include!("../../jl_helpers.rs"); @@ -7,21 +8,21 @@ include!("../../jl_helpers.rs"); #[test] fn test_matching_creation() { let problem = - MaximumMatching::::new(4, vec![(0, 1, 1), (1, 2, 2), (2, 3, 3)]); + MaximumMatching::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1, 2, 3]); assert_eq!(problem.graph().num_vertices(), 4); assert_eq!(problem.graph().num_edges(), 3); assert_eq!(problem.num_variables(), 3); } #[test] -fn test_matching_unweighted() { - let problem = MaximumMatching::::unweighted(3, vec![(0, 1), (1, 2)]); +fn test_matching_unit_weights() { + let problem = MaximumMatching::<_, i32>::unit_weights(SimpleGraph::new(3, vec![(0, 1), (1, 2)])); assert_eq!(problem.graph().num_edges(), 2); } #[test] fn test_edge_endpoints() { - let problem = MaximumMatching::::new(3, vec![(0, 1, 1), (1, 2, 2)]); + let problem = MaximumMatching::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1, 2]); assert_eq!(problem.edge_endpoints(0), Some((0, 1))); assert_eq!(problem.edge_endpoints(1), Some((1, 2))); assert_eq!(problem.edge_endpoints(2), None); @@ -30,7 +31,7 @@ fn test_edge_endpoints() { #[test] fn test_is_valid_matching() { let problem = - MaximumMatching::::new(4, vec![(0, 1, 1), (1, 2, 1), (2, 3, 1)]); + MaximumMatching::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1, 1, 1]); // Valid: select edge 0 only assert!(problem.is_valid_matching(&[1, 0, 0])); @@ -44,66 +45,59 @@ fn test_is_valid_matching() { #[test] fn test_is_matching_function() { - let edges = vec![(0, 1), (1, 2), (2, 3)]; + let graph = SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]); - assert!(is_matching(4, &edges, &[true, false, true])); // Disjoint - assert!(is_matching(4, &edges, &[false, true, false])); // Single edge - assert!(!is_matching(4, &edges, &[true, true, false])); // Share vertex 1 - assert!(is_matching(4, &edges, &[false, false, false])); // Empty is valid + assert!(is_matching(&graph, &[true, false, true])); // Disjoint + assert!(is_matching(&graph, &[false, true, false])); // Single edge + assert!(!is_matching(&graph, &[true, true, false])); // Share vertex 1 + assert!(is_matching(&graph, &[false, false, false])); // Empty is valid } #[test] fn test_direction() { - let problem = MaximumMatching::::unweighted(2, vec![(0, 1)]); + let problem = MaximumMatching::<_, i32>::unit_weights(SimpleGraph::new(2, vec![(0, 1)])); assert_eq!(problem.direction(), Direction::Maximize); } #[test] fn test_empty_graph() { - let problem = MaximumMatching::::unweighted(3, vec![]); + let problem = MaximumMatching::<_, i32>::unit_weights(SimpleGraph::new(3, vec![])); // Empty matching is valid with size 0 assert_eq!(Problem::evaluate(&problem, &[]), SolutionSize::Valid(0)); } #[test] fn test_edges() { - let problem = MaximumMatching::::new(3, vec![(0, 1, 5), (1, 2, 10)]); + let problem = MaximumMatching::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![5, 10]); let edges = problem.edges(); assert_eq!(edges.len(), 2); } #[test] fn test_empty_sets() { - let problem = MaximumMatching::::unweighted(2, vec![]); + let problem = MaximumMatching::<_, i32>::unit_weights(SimpleGraph::new(2, vec![])); // Empty matching assert_eq!(Problem::evaluate(&problem, &[]), SolutionSize::Valid(0)); } #[test] +#[should_panic(expected = "selected length must match num_edges")] fn test_is_matching_wrong_len() { - let edges = vec![(0, 1), (1, 2)]; - assert!(!is_matching(3, &edges, &[true])); // Wrong length -} - -#[test] -fn test_is_matching_out_of_bounds() { - let edges = vec![(0, 5)]; // Vertex 5 doesn't exist - assert!(!is_matching(3, &edges, &[true])); + let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); + is_matching(&graph, &[true]); // Wrong length } #[test] -fn test_from_graph() { - let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); - let problem = MaximumMatching::::from_graph(graph, vec![5, 10]); +fn test_new() { + let problem = MaximumMatching::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![5, 10]); assert_eq!(problem.graph().num_vertices(), 3); assert_eq!(problem.graph().num_edges(), 2); assert_eq!(problem.weights(), vec![5, 10]); } #[test] -fn test_from_graph_unit_weights() { - let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); - let problem = MaximumMatching::::from_graph_unit_weights(graph); +fn test_unit_weights() { + let problem = MaximumMatching::<_, i32>::unit_weights(SimpleGraph::new(3, vec![(0, 1), (1, 2)])); assert_eq!(problem.graph().num_vertices(), 3); assert_eq!(problem.graph().num_edges(), 2); assert_eq!(problem.weights(), vec![1, 1]); @@ -111,8 +105,7 @@ fn test_from_graph_unit_weights() { #[test] fn test_graph_accessor() { - let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); - let problem = MaximumMatching::::from_graph_unit_weights(graph); + let problem = MaximumMatching::<_, i32>::unit_weights(SimpleGraph::new(3, vec![(0, 1), (1, 2)])); assert_eq!(problem.graph().num_vertices(), 3); assert_eq!(problem.graph().num_edges(), 2); } @@ -124,7 +117,9 @@ fn test_jl_parity_evaluation() { for instance in data["instances"].as_array().unwrap() { let nv = instance["instance"]["num_vertices"].as_u64().unwrap() as usize; let weighted_edges = jl_parse_weighted_edges(&instance["instance"]); - let problem = MaximumMatching::::new(nv, weighted_edges); + let edges: Vec<(usize, usize)> = weighted_edges.iter().map(|&(u, v, _)| (u, v)).collect(); + let weights: Vec = weighted_edges.into_iter().map(|(_, _, w)| w).collect(); + let problem = MaximumMatching::new(SimpleGraph::new(nv, edges), weights); for eval in instance["evaluations"].as_array().unwrap() { let config = jl_parse_config(&eval["config"]); let result = problem.evaluate(&config); diff --git a/src/unit_tests/models/graph/minimum_dominating_set.rs b/src/unit_tests/models/graph/minimum_dominating_set.rs index da5c204e0..f9b49f356 100644 --- a/src/unit_tests/models/graph/minimum_dominating_set.rs +++ b/src/unit_tests/models/graph/minimum_dominating_set.rs @@ -1,12 +1,13 @@ use super::*; use crate::solvers::BruteForce; +use crate::topology::SimpleGraph; use crate::traits::{OptimizationProblem, Problem}; use crate::types::Direction; include!("../../jl_helpers.rs"); #[test] fn test_dominating_set_creation() { - let problem = MinimumDominatingSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = MinimumDominatingSet::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1i32; 4]); assert_eq!(problem.graph().num_vertices(), 4); assert_eq!(problem.graph().num_edges(), 3); } @@ -14,13 +15,13 @@ fn test_dominating_set_creation() { #[test] fn test_dominating_set_with_weights() { let problem = - MinimumDominatingSet::::with_weights(3, vec![(0, 1)], vec![1, 2, 3]); + MinimumDominatingSet::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1, 2, 3]); assert_eq!(problem.weights(), &[1, 2, 3]); } #[test] fn test_neighbors() { - let problem = MinimumDominatingSet::::new(4, vec![(0, 1), (0, 2), (1, 2)]); + let problem = MinimumDominatingSet::new(SimpleGraph::new(4, vec![(0, 1), (0, 2), (1, 2)]), vec![1i32; 4]); let nbrs = problem.neighbors(0); assert!(nbrs.contains(&1)); assert!(nbrs.contains(&2)); @@ -29,7 +30,7 @@ fn test_neighbors() { #[test] fn test_closed_neighborhood() { - let problem = MinimumDominatingSet::::new(4, vec![(0, 1), (0, 2)]); + let problem = MinimumDominatingSet::new(SimpleGraph::new(4, vec![(0, 1), (0, 2)]), vec![1i32; 4]); let cn = problem.closed_neighborhood(0); assert!(cn.contains(&0)); assert!(cn.contains(&1)); @@ -39,28 +40,28 @@ fn test_closed_neighborhood() { #[test] fn test_is_dominating_set_function() { - let edges = vec![(0, 1), (0, 2), (0, 3)]; + let graph = SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3)]); // Center dominates all - assert!(is_dominating_set(4, &edges, &[true, false, false, false])); + assert!(is_dominating_set(&graph, &[true, false, false, false])); // All leaves dominate (leaf dominates center which dominates others) - assert!(is_dominating_set(4, &edges, &[false, true, true, true])); + assert!(is_dominating_set(&graph, &[false, true, true, true])); // Single leaf doesn't dominate other leaves - assert!(!is_dominating_set(4, &edges, &[false, true, false, false])); + assert!(!is_dominating_set(&graph, &[false, true, false, false])); // Empty doesn't dominate - assert!(!is_dominating_set(4, &edges, &[false, false, false, false])); + assert!(!is_dominating_set(&graph, &[false, false, false, false])); } #[test] fn test_direction() { - let problem = MinimumDominatingSet::::new(2, vec![(0, 1)]); + let problem = MinimumDominatingSet::new(SimpleGraph::new(2, vec![(0, 1)]), vec![1i32; 2]); assert_eq!(problem.direction(), Direction::Minimize); } #[test] fn test_isolated_vertex() { // Isolated vertex must be in dominating set - let problem = MinimumDominatingSet::::new(3, vec![(0, 1)]); + let problem = MinimumDominatingSet::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1i32; 3]); let solver = BruteForce::new(); let solutions = solver.find_all_best(&problem); @@ -73,21 +74,22 @@ fn test_isolated_vertex() { } #[test] +#[should_panic(expected = "selected length must match num_vertices")] fn test_is_dominating_set_wrong_len() { - assert!(!is_dominating_set(3, &[(0, 1)], &[true, false])); + is_dominating_set(&SimpleGraph::new(3, vec![(0, 1)]), &[true, false]); } #[test] fn test_from_graph() { let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); - let problem = MinimumDominatingSet::::from_graph(graph, vec![1, 2, 3]); + let problem = MinimumDominatingSet::new(graph, vec![1, 2, 3]); assert_eq!(problem.graph().num_vertices(), 3); assert_eq!(problem.weights(), &[1, 2, 3]); } #[test] fn test_graph_accessor() { - let problem = MinimumDominatingSet::::new(3, vec![(0, 1)]); + let problem = MinimumDominatingSet::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1i32; 3]); assert_eq!(problem.graph().num_vertices(), 3); assert_eq!(problem.graph().num_edges(), 1); } @@ -95,20 +97,20 @@ fn test_graph_accessor() { #[test] fn test_weights() { let problem = - MinimumDominatingSet::::with_weights(3, vec![(0, 1)], vec![5, 10, 15]); + MinimumDominatingSet::new(SimpleGraph::new(3, vec![(0, 1)]), vec![5, 10, 15]); assert_eq!(problem.weights(), &[5, 10, 15]); } #[test] fn test_edges() { - let problem = MinimumDominatingSet::::new(3, vec![(0, 1), (1, 2)]); + let problem = MinimumDominatingSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); let edges = problem.graph().edges(); assert_eq!(edges.len(), 2); } #[test] fn test_has_edge() { - let problem = MinimumDominatingSet::::new(3, vec![(0, 1), (1, 2)]); + let problem = MinimumDominatingSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); assert!(problem.graph().has_edge(0, 1)); assert!(problem.graph().has_edge(1, 0)); // Undirected assert!(problem.graph().has_edge(1, 2)); @@ -122,7 +124,7 @@ fn test_jl_parity_evaluation() { for instance in data["instances"].as_array().unwrap() { let nv = instance["instance"]["num_vertices"].as_u64().unwrap() as usize; let edges = jl_parse_edges(&instance["instance"]); - let problem = MinimumDominatingSet::::new(nv, edges); + let problem = MinimumDominatingSet::new(SimpleGraph::new(nv, edges), vec![1i32; nv]); for eval in instance["evaluations"].as_array().unwrap() { let config = jl_parse_config(&eval["config"]); let result = problem.evaluate(&config); diff --git a/src/unit_tests/models/graph/minimum_vertex_cover.rs b/src/unit_tests/models/graph/minimum_vertex_cover.rs index 64bd117d4..842ffcb14 100644 --- a/src/unit_tests/models/graph/minimum_vertex_cover.rs +++ b/src/unit_tests/models/graph/minimum_vertex_cover.rs @@ -1,12 +1,13 @@ use super::*; use crate::solvers::BruteForce; +use crate::topology::SimpleGraph; use crate::traits::{OptimizationProblem, Problem}; use crate::types::Direction; include!("../../jl_helpers.rs"); #[test] fn test_vertex_cover_creation() { - let problem = MinimumVertexCover::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = MinimumVertexCover::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1i32; 4]); assert_eq!(problem.graph().num_vertices(), 4); assert_eq!(problem.graph().num_edges(), 3); assert_eq!(problem.num_variables(), 4); @@ -15,29 +16,33 @@ fn test_vertex_cover_creation() { #[test] fn test_vertex_cover_with_weights() { let problem = - MinimumVertexCover::::with_weights(3, vec![(0, 1)], vec![1, 2, 3]); + MinimumVertexCover::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1, 2, 3]); assert_eq!(problem.weights().to_vec(), vec![1, 2, 3]); } #[test] fn test_is_vertex_cover_function() { - assert!(is_vertex_cover(3, &[(0, 1), (1, 2)], &[false, true, false])); - assert!(is_vertex_cover(3, &[(0, 1), (1, 2)], &[true, false, true])); + assert!(is_vertex_cover( + &SimpleGraph::new(3, vec![(0, 1), (1, 2)]), + &[false, true, false] + )); + assert!(is_vertex_cover( + &SimpleGraph::new(3, vec![(0, 1), (1, 2)]), + &[true, false, true] + )); assert!(!is_vertex_cover( - 3, - &[(0, 1), (1, 2)], + &SimpleGraph::new(3, vec![(0, 1), (1, 2)]), &[true, false, false] )); assert!(!is_vertex_cover( - 3, - &[(0, 1), (1, 2)], + &SimpleGraph::new(3, vec![(0, 1), (1, 2)]), &[false, false, false] )); } #[test] fn test_direction() { - let problem = MinimumVertexCover::::new(3, vec![(0, 1)]); + let problem = MinimumVertexCover::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1i32; 3]); assert_eq!(problem.direction(), Direction::Minimize); } @@ -47,8 +52,8 @@ fn test_complement_relationship() { use crate::models::graph::MaximumIndependentSet; let edges = vec![(0, 1), (1, 2), (2, 3)]; - let is_problem = MaximumIndependentSet::::new(4, edges.clone()); - let vc_problem = MinimumVertexCover::::new(4, edges); + let is_problem = MaximumIndependentSet::new(SimpleGraph::new(4, edges.clone()), vec![1i32; 4]); + let vc_problem = MinimumVertexCover::new(SimpleGraph::new(4, edges), vec![1i32; 4]); let solver = BruteForce::new(); @@ -62,15 +67,16 @@ fn test_complement_relationship() { } #[test] +#[should_panic(expected = "selected length must match num_vertices")] fn test_is_vertex_cover_wrong_len() { - // Wrong length should return false - assert!(!is_vertex_cover(3, &[(0, 1)], &[true, false])); + // Wrong length should panic + is_vertex_cover(&SimpleGraph::new(3, vec![(0, 1)]), &[true, false]); } #[test] fn test_from_graph() { let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); - let problem = MinimumVertexCover::::from_graph(graph, vec![1, 1, 1]); + let problem = MinimumVertexCover::new(graph, vec![1i32, 1, 1]); assert_eq!(problem.graph().num_vertices(), 3); assert_eq!(problem.graph().num_edges(), 2); } @@ -78,13 +84,13 @@ fn test_from_graph() { #[test] fn test_from_graph_with_weights() { let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); - let problem = MinimumVertexCover::::from_graph(graph, vec![1, 2, 3]); + let problem = MinimumVertexCover::new(graph, vec![1, 2, 3]); assert_eq!(problem.weights().to_vec(), vec![1, 2, 3]); } #[test] fn test_graph_accessor() { - let problem = MinimumVertexCover::::new(3, vec![(0, 1), (1, 2)]); + let problem = MinimumVertexCover::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); let graph = problem.graph(); assert_eq!(graph.num_vertices(), 3); assert_eq!(graph.num_edges(), 2); @@ -92,7 +98,7 @@ fn test_graph_accessor() { #[test] fn test_has_edge() { - let problem = MinimumVertexCover::::new(3, vec![(0, 1), (1, 2)]); + let problem = MinimumVertexCover::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); assert!(problem.graph().has_edge(0, 1)); assert!(problem.graph().has_edge(1, 0)); // Undirected assert!(problem.graph().has_edge(1, 2)); @@ -109,11 +115,7 @@ fn test_jl_parity_evaluation() { let nv = instance["instance"]["num_vertices"].as_u64().unwrap() as usize; let edges = jl_parse_edges(&instance["instance"]); let weights = jl_parse_i32_vec(&instance["instance"]["weights"]); - let problem = if weights.iter().all(|&w| w == 1) { - MinimumVertexCover::::new(nv, edges) - } else { - MinimumVertexCover::with_weights(nv, edges, weights) - }; + let problem = MinimumVertexCover::new(SimpleGraph::new(nv, edges), weights); for eval in instance["evaluations"].as_array().unwrap() { let config = jl_parse_config(&eval["config"]); let result = problem.evaluate(&config); diff --git a/src/unit_tests/models/graph/traveling_salesman.rs b/src/unit_tests/models/graph/traveling_salesman.rs index 0a3ac2c75..c7027baf5 100644 --- a/src/unit_tests/models/graph/traveling_salesman.rs +++ b/src/unit_tests/models/graph/traveling_salesman.rs @@ -1,60 +1,47 @@ use super::*; use crate::solvers::BruteForce; +use crate::topology::SimpleGraph; use crate::traits::{OptimizationProblem, Problem}; use crate::types::{Direction, SolutionSize}; +fn k4_tsp() -> TravelingSalesman { + TravelingSalesman::new( + SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]), + vec![10, 15, 20, 35, 25, 30], + ) +} + #[test] fn test_traveling_salesman_creation() { // K4 complete graph - let problem = TravelingSalesman::::new( - 4, - vec![ - (0, 1, 10), - (0, 2, 15), - (0, 3, 20), - (1, 2, 35), - (1, 3, 25), - (2, 3, 30), - ], - ); + let problem = k4_tsp(); assert_eq!(problem.graph().num_vertices(), 4); assert_eq!(problem.graph().num_edges(), 6); assert_eq!(problem.dims().len(), 6); } #[test] -fn test_traveling_salesman_unweighted() { - let problem = TravelingSalesman::::unweighted( - 5, - vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], +fn test_traveling_salesman_unit_weights() { + // i32 type is always considered weighted, even with uniform values + let problem = TravelingSalesman::<_, i32>::unit_weights( + SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)]), ); - assert!(!problem.is_weighted()); + assert!(problem.is_weighted()); assert_eq!(problem.graph().num_vertices(), 5); assert_eq!(problem.graph().num_edges(), 5); } #[test] fn test_traveling_salesman_weighted() { - let problem = TravelingSalesman::::new( - 4, - vec![ - (0, 1, 10), - (0, 2, 15), - (0, 3, 20), - (1, 2, 35), - (1, 3, 25), - (2, 3, 30), - ], - ); + let problem = k4_tsp(); assert!(problem.is_weighted()); } #[test] fn test_evaluate_valid_cycle() { // C5 cycle graph with unit weights: all 5 edges form the only Hamiltonian cycle - let problem = TravelingSalesman::::unweighted( - 5, - vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], + let problem = TravelingSalesman::<_, i32>::unit_weights( + SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)]), ); // Select all edges -> valid Hamiltonian cycle, cost = 5 assert_eq!(problem.evaluate(&[1, 1, 1, 1, 1]), SolutionSize::Valid(5)); @@ -63,17 +50,7 @@ fn test_evaluate_valid_cycle() { #[test] fn test_evaluate_invalid_degree() { // K4: select 3 edges incident to vertex 0 -> degree > 2 at vertex 0 - let problem = TravelingSalesman::::new( - 4, - vec![ - (0, 1, 10), - (0, 2, 15), - (0, 3, 20), - (1, 2, 35), - (1, 3, 25), - (2, 3, 30), - ], - ); + let problem = k4_tsp(); // edges: 0-1, 0-2, 0-3, 1-2, 1-3, 2-3 // Select first 3 edges (all incident to 0): degree(0)=3 -> Invalid assert_eq!(problem.evaluate(&[1, 1, 1, 0, 0, 0]), SolutionSize::Invalid); @@ -82,9 +59,8 @@ fn test_evaluate_invalid_degree() { #[test] fn test_evaluate_invalid_not_connected() { // 6 vertices, two disjoint triangles: 0-1-2-0 and 3-4-5-3 - let problem = TravelingSalesman::::unweighted( - 6, - vec![(0, 1), (1, 2), (0, 2), (3, 4), (4, 5), (3, 5)], + let problem = TravelingSalesman::<_, i32>::unit_weights( + SimpleGraph::new(6, vec![(0, 1), (1, 2), (0, 2), (3, 4), (4, 5), (3, 5)]), ); // Select all 6 edges: two disjoint cycles, not a single Hamiltonian cycle assert_eq!(problem.evaluate(&[1, 1, 1, 1, 1, 1]), SolutionSize::Invalid); @@ -93,18 +69,16 @@ fn test_evaluate_invalid_not_connected() { #[test] fn test_evaluate_invalid_wrong_edge_count() { // C5 with only 4 edges selected -> not enough edges - let problem = TravelingSalesman::::unweighted( - 5, - vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], + let problem = TravelingSalesman::<_, i32>::unit_weights( + SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)]), ); assert_eq!(problem.evaluate(&[1, 1, 1, 1, 0]), SolutionSize::Invalid); } #[test] fn test_evaluate_no_edges_selected() { - let problem = TravelingSalesman::::unweighted( - 5, - vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], + let problem = TravelingSalesman::<_, i32>::unit_weights( + SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)]), ); assert_eq!(problem.evaluate(&[0, 0, 0, 0, 0]), SolutionSize::Invalid); } @@ -112,17 +86,7 @@ fn test_evaluate_no_edges_selected() { #[test] fn test_brute_force_k4() { // Instance 1 from issue: K4 with weights - let problem = TravelingSalesman::::new( - 4, - vec![ - (0, 1, 10), - (0, 2, 15), - (0, 3, 20), - (1, 2, 35), - (1, 3, 25), - (2, 3, 30), - ], - ); + let problem = k4_tsp(); let solver = BruteForce::new(); let solutions = solver.find_all_best(&problem); assert!(!solutions.is_empty()); @@ -136,7 +100,7 @@ fn test_brute_force_k4() { fn test_brute_force_path_graph_no_solution() { // Instance 2 from issue: path graph, no Hamiltonian cycle exists let problem = - TravelingSalesman::::unweighted(4, vec![(0, 1), (1, 2), (2, 3)]); + TravelingSalesman::<_, i32>::unit_weights(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)])); let solver = BruteForce::new(); let solutions = solver.find_all_best(&problem); assert!(solutions.is_empty()); @@ -145,9 +109,8 @@ fn test_brute_force_path_graph_no_solution() { #[test] fn test_brute_force_c5_unique_solution() { // Instance 3 from issue: C5 cycle graph, unique Hamiltonian cycle - let problem = TravelingSalesman::::unweighted( - 5, - vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], + let problem = TravelingSalesman::<_, i32>::unit_weights( + SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)]), ); let solver = BruteForce::new(); let solutions = solver.find_all_best(&problem); @@ -159,9 +122,8 @@ fn test_brute_force_c5_unique_solution() { #[test] fn test_brute_force_bipartite_no_solution() { // Instance 4 from issue: K_{2,3} bipartite, no Hamiltonian cycle - let problem = TravelingSalesman::::unweighted( - 5, - vec![(0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4)], + let problem = TravelingSalesman::<_, i32>::unit_weights( + SimpleGraph::new(5, vec![(0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4)]), ); let solver = BruteForce::new(); let solutions = solver.find_all_best(&problem); @@ -171,7 +133,7 @@ fn test_brute_force_bipartite_no_solution() { #[test] fn test_direction() { let problem = - TravelingSalesman::::unweighted(3, vec![(0, 1), (1, 2), (0, 2)]); + TravelingSalesman::<_, i32>::unit_weights(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)])); assert_eq!(problem.direction(), Direction::Minimize); } @@ -187,18 +149,20 @@ fn test_problem_name() { fn test_is_hamiltonian_cycle_function() { // Triangle: selecting all 3 edges is a valid Hamiltonian cycle assert!(is_hamiltonian_cycle( - 3, - &[(0, 1), (1, 2), (0, 2)], + &SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), &[true, true, true] )); // Path: not a cycle - assert!(!is_hamiltonian_cycle(3, &[(0, 1), (1, 2)], &[true, true])); + assert!(!is_hamiltonian_cycle( + &SimpleGraph::new(3, vec![(0, 1), (1, 2)]), + &[true, true] + )); } #[test] fn test_set_weights() { let mut problem = - TravelingSalesman::::unweighted(3, vec![(0, 1), (1, 2), (0, 2)]); + TravelingSalesman::<_, i32>::unit_weights(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)])); problem.set_weights(vec![5, 10, 15]); assert_eq!(problem.weights(), vec![5, 10, 15]); } @@ -206,23 +170,21 @@ fn test_set_weights() { #[test] fn test_edges() { let problem = - TravelingSalesman::::new(3, vec![(0, 1, 10), (1, 2, 20), (0, 2, 30)]); + TravelingSalesman::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), vec![10, 20, 30]); let edges = problem.edges(); assert_eq!(edges.len(), 3); } #[test] -fn test_from_graph() { - let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]); - let problem = TravelingSalesman::::from_graph(graph, vec![10, 20, 30]); +fn test_new() { + let problem = TravelingSalesman::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), vec![10, 20, 30]); assert_eq!(problem.graph().num_vertices(), 3); assert_eq!(problem.weights(), vec![10, 20, 30]); } #[test] -fn test_from_graph_unit_weights() { - let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]); - let problem = TravelingSalesman::::from_graph_unit_weights(graph); +fn test_unit_weights() { + let problem = TravelingSalesman::<_, i32>::unit_weights(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)])); assert_eq!(problem.weights(), vec![1, 1, 1]); } @@ -230,7 +192,7 @@ fn test_from_graph_unit_weights() { fn test_brute_force_triangle_weighted() { // Triangle with weights: unique Hamiltonian cycle using all edges let problem = - TravelingSalesman::::new(3, vec![(0, 1, 5), (1, 2, 10), (0, 2, 15)]); + TravelingSalesman::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), vec![5, 10, 15]); let solver = BruteForce::new(); let solutions = solver.find_all_best(&problem); assert_eq!(solutions.len(), 1); diff --git a/src/unit_tests/models/set/maximum_set_packing.rs b/src/unit_tests/models/set/maximum_set_packing.rs index 86625a601..6f65c4d6b 100644 --- a/src/unit_tests/models/set/maximum_set_packing.rs +++ b/src/unit_tests/models/set/maximum_set_packing.rs @@ -78,7 +78,8 @@ fn test_relationship_to_independent_set() { // Build intersection graph let edges = sp_problem.overlapping_pairs(); - let is_problem = MaximumIndependentSet::::new(sets.len(), edges); + let n = sets.len(); + let is_problem = MaximumIndependentSet::new(SimpleGraph::new(n, edges), vec![1i32; n]); let solver = BruteForce::new(); diff --git a/src/unit_tests/property.rs b/src/unit_tests/property.rs index ad475b377..537106083 100644 --- a/src/unit_tests/property.rs +++ b/src/unit_tests/property.rs @@ -40,8 +40,8 @@ proptest! { /// is a minimum vertex cover, and their sizes sum to n. #[test] fn independent_set_complement_is_vertex_cover((n, edges) in graph_strategy(8)) { - let is_problem = MaximumIndependentSet::::new(n, edges.clone()); - let vc_problem = MinimumVertexCover::::new(n, edges); + let is_problem = MaximumIndependentSet::new(SimpleGraph::new(n, edges.clone()), vec![1i32; n]); + let vc_problem = MinimumVertexCover::new(SimpleGraph::new(n, edges), vec![1i32; n]); let solver = BruteForce::new(); let is_solutions = solver.find_all_best(&is_problem); @@ -57,7 +57,7 @@ proptest! { /// Property: Any subset of a valid independent set is also a valid independent set. #[test] fn valid_solution_stays_valid_under_subset((n, edges) in graph_strategy(6)) { - let problem = MaximumIndependentSet::::new(n, edges); + let problem = MaximumIndependentSet::new(SimpleGraph::new(n, edges), vec![1i32; n]); let solver = BruteForce::new(); for sol in solver.find_all_best(&problem) { @@ -74,7 +74,7 @@ proptest! { /// Property: A vertex cover with additional vertices is still a valid cover. #[test] fn vertex_cover_superset_is_valid((n, edges) in graph_strategy(6)) { - let problem = MinimumVertexCover::::new(n, edges); + let problem = MinimumVertexCover::new(SimpleGraph::new(n, edges), vec![1i32; n]); let solver = BruteForce::new(); for sol in solver.find_all_best(&problem) { @@ -91,8 +91,8 @@ proptest! { /// Property: The complement of any valid independent set is a valid vertex cover. #[test] fn is_complement_is_vc((n, edges) in graph_strategy(7)) { - let is_problem = MaximumIndependentSet::::new(n, edges.clone()); - let vc_problem = MinimumVertexCover::::new(n, edges); + let is_problem = MaximumIndependentSet::new(SimpleGraph::new(n, edges.clone()), vec![1i32; n]); + let vc_problem = MinimumVertexCover::new(SimpleGraph::new(n, edges), vec![1i32; n]); let solver = BruteForce::new(); // Get all valid independent sets (not just optimal) @@ -107,7 +107,7 @@ proptest! { /// Property: Empty selection is always a valid (but possibly non-optimal) independent set. #[test] fn empty_is_always_valid_is((n, edges) in graph_strategy(10)) { - let problem = MaximumIndependentSet::::new(n, edges); + let problem = MaximumIndependentSet::new(SimpleGraph::new(n, edges), vec![1i32; n]); let empty = vec![0; n]; // Valid configuration returns is_valid() == true (0 for empty set) prop_assert!(problem.evaluate(&empty).is_valid()); @@ -117,7 +117,7 @@ proptest! { /// (when there is at least one vertex). #[test] fn full_is_always_valid_vc((n, edges) in graph_strategy(10)) { - let problem = MinimumVertexCover::::new(n, edges); + let problem = MinimumVertexCover::new(SimpleGraph::new(n, edges), vec![1i32; n]); let full = vec![1; n]; // Valid configuration returns is_valid() == true prop_assert!(problem.evaluate(&full).is_valid()); @@ -126,7 +126,7 @@ proptest! { /// Property: Solution size is non-negative for independent sets. #[test] fn is_size_non_negative((n, edges) in graph_strategy(8)) { - let problem = MaximumIndependentSet::::new(n, edges); + let problem = MaximumIndependentSet::new(SimpleGraph::new(n, edges), vec![1i32; n]); let solver = BruteForce::new(); for sol in solver.find_all_best(&problem) { diff --git a/src/unit_tests/rules/coloring_ilp.rs b/src/unit_tests/rules/coloring_ilp.rs index f296dc440..34889659c 100644 --- a/src/unit_tests/rules/coloring_ilp.rs +++ b/src/unit_tests/rules/coloring_ilp.rs @@ -6,7 +6,7 @@ use crate::variant::{K1, K2, K3, K4}; #[test] fn test_reduction_creates_valid_ilp() { // Triangle graph with 3 colors - let problem = KColoring::::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let problem = KColoring::::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)])); let reduction = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -37,7 +37,7 @@ fn test_reduction_creates_valid_ilp() { #[test] fn test_reduction_path_graph() { // Path graph 0-1-2 with 2 colors (2-colorable) - let problem = KColoring::::new(3, vec![(0, 1), (1, 2)]); + let problem = KColoring::::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)])); let reduction = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -51,7 +51,7 @@ fn test_reduction_path_graph() { #[test] fn test_ilp_solution_equals_brute_force_triangle() { // Triangle needs 3 colors - let problem = KColoring::::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let problem = KColoring::::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)])); let reduction = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -84,7 +84,7 @@ fn test_ilp_solution_equals_brute_force_triangle() { #[test] fn test_ilp_solution_equals_brute_force_path() { // Path graph 0-1-2-3 with 2 colors - let problem = KColoring::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = KColoring::::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)])); let reduction = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -109,7 +109,7 @@ fn test_ilp_solution_equals_brute_force_path() { #[test] fn test_ilp_infeasible_triangle_2_colors() { // Triangle cannot be 2-colored - let problem = KColoring::::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let problem = KColoring::::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)])); let reduction = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -125,7 +125,7 @@ fn test_ilp_infeasible_triangle_2_colors() { #[test] fn test_solution_extraction() { - let problem = KColoring::::new(3, vec![(0, 1)]); + let problem = KColoring::::new(SimpleGraph::new(3, vec![(0, 1)])); let reduction = ReduceTo::::reduce_to(&problem); // ILP solution where: @@ -144,7 +144,7 @@ fn test_solution_extraction() { #[test] fn test_ilp_structure() { - let problem = KColoring::::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]); + let problem = KColoring::::new(SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)])); let reduction = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -157,7 +157,7 @@ fn test_ilp_structure() { #[test] fn test_empty_graph() { // Graph with no edges: any coloring is valid - let problem = KColoring::::new(3, vec![]); + let problem = KColoring::::new(SimpleGraph::new(3, vec![])); let reduction = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -175,7 +175,7 @@ fn test_empty_graph() { fn test_complete_graph_k4() { // K4 needs 4 colors let problem = - KColoring::::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]); + KColoring::::new(SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)])); let reduction = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -196,7 +196,7 @@ fn test_complete_graph_k4() { fn test_complete_graph_k4_with_3_colors_infeasible() { // K4 cannot be 3-colored let problem = - KColoring::::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]); + KColoring::::new(SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)])); let reduction = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -209,7 +209,7 @@ fn test_complete_graph_k4_with_3_colors_infeasible() { fn test_bipartite_graph() { // Complete bipartite K_{2,2}: 0-2, 0-3, 1-2, 1-3 // This is 2-colorable - let problem = KColoring::::new(4, vec![(0, 2), (0, 3), (1, 2), (1, 3)]); + let problem = KColoring::::new(SimpleGraph::new(4, vec![(0, 2), (0, 3), (1, 2), (1, 3)])); let reduction = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -229,7 +229,7 @@ fn test_bipartite_graph() { #[test] fn test_solve_reduced() { // Test the ILPSolver::solve_reduced method - let problem = KColoring::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = KColoring::::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)])); let ilp_solver = ILPSolver::new(); let solution = ilp_solver @@ -242,7 +242,7 @@ fn test_solve_reduced() { #[test] fn test_single_vertex() { // Single vertex graph: always 1-colorable - let problem = KColoring::::new(1, vec![]); + let problem = KColoring::::new(SimpleGraph::new(1, vec![])); let reduction = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -259,7 +259,7 @@ fn test_single_vertex() { #[test] fn test_single_edge() { // Single edge: needs 2 colors - let problem = KColoring::::new(2, vec![(0, 1)]); + let problem = KColoring::::new(SimpleGraph::new(2, vec![(0, 1)])); let reduction = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); diff --git a/src/unit_tests/rules/coloring_qubo.rs b/src/unit_tests/rules/coloring_qubo.rs index b4a48a607..54ba3177c 100644 --- a/src/unit_tests/rules/coloring_qubo.rs +++ b/src/unit_tests/rules/coloring_qubo.rs @@ -6,7 +6,7 @@ use crate::variant::{K2, K3}; #[test] fn test_kcoloring_to_qubo_closed_loop() { // Triangle K3, 3 colors → exactly 6 valid colorings (3! permutations) - let kc = KColoring::::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let kc = KColoring::::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)])); let reduction = ReduceTo::>::reduce_to(&kc); let qubo = reduction.target_problem(); @@ -26,7 +26,7 @@ fn test_kcoloring_to_qubo_closed_loop() { #[test] fn test_kcoloring_to_qubo_path() { // Path graph: 0-1-2, 2 colors - let kc = KColoring::::new(3, vec![(0, 1), (1, 2)]); + let kc = KColoring::::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)])); let reduction = ReduceTo::>::reduce_to(&kc); let qubo = reduction.target_problem(); @@ -46,7 +46,7 @@ fn test_kcoloring_to_qubo_path() { fn test_kcoloring_to_qubo_reversed_edges() { // Edge (2, 0) triggers the idx_v < idx_u swap branch (line 104). // Path: 2-0-1 with reversed edge ordering - let kc = KColoring::::new(3, vec![(2, 0), (0, 1)]); + let kc = KColoring::::new(SimpleGraph::new(3, vec![(2, 0), (0, 1)])); let reduction = ReduceTo::>::reduce_to(&kc); let qubo = reduction.target_problem(); @@ -64,7 +64,7 @@ fn test_kcoloring_to_qubo_reversed_edges() { #[test] fn test_kcoloring_to_qubo_sizes() { - let kc = KColoring::::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let kc = KColoring::::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)])); let reduction = ReduceTo::>::reduce_to(&kc); // QUBO should have n*K = 3*3 = 9 variables diff --git a/src/unit_tests/rules/graph.rs b/src/unit_tests/rules/graph.rs index 5d523c7af..56d0a6259 100644 --- a/src/unit_tests/rules/graph.rs +++ b/src/unit_tests/rules/graph.rs @@ -734,7 +734,7 @@ fn test_chained_reduction_direct() { >(&rpath) .unwrap(); - let problem = MaximumIndependentSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1i32; 4]); let reduction = path.reduce(&problem); let target = reduction.target_problem(); @@ -764,7 +764,7 @@ fn test_chained_reduction_multi_step() { >(&rpath) .unwrap(); - let problem = MaximumIndependentSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1i32; 4]); let reduction = path.reduce(&problem); let target = reduction.target_problem(); @@ -815,7 +815,7 @@ fn test_chained_reduction_with_variant_casts() { // Create a small UnitDiskGraph MIS problem (triangle of close nodes) let udg = UnitDiskGraph::new(vec![(0.0, 0.0), (0.5, 0.0), (0.25, 0.4)], 1.0); - let mis = MaximumIndependentSet::::from_graph(udg, vec![1, 1, 1]); + let mis = MaximumIndependentSet::new(udg, vec![1i32, 1, 1]); let reduction = path.reduce(&mis); let target = reduction.target_problem(); diff --git a/src/unit_tests/rules/maximumclique_ilp.rs b/src/unit_tests/rules/maximumclique_ilp.rs index 110909a72..25f7da1e6 100644 --- a/src/unit_tests/rules/maximumclique_ilp.rs +++ b/src/unit_tests/rules/maximumclique_ilp.rs @@ -53,7 +53,7 @@ fn test_reduction_creates_valid_ilp() { // Triangle graph: 3 vertices, 3 edges (complete graph K3) // All pairs are adjacent, so no constraints should be added let problem: MaximumClique = - MaximumClique::new(3, vec![(0, 1), (1, 2), (0, 2)]); + MaximumClique::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), vec![1; 3]); let reduction: ReductionCliqueToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -75,7 +75,7 @@ fn test_reduction_creates_valid_ilp() { #[test] fn test_reduction_with_non_edges() { // Path graph 0-1-2: edges (0,1) and (1,2), non-edge (0,2) - let problem: MaximumClique = MaximumClique::new(3, vec![(0, 1), (1, 2)]); + let problem: MaximumClique = MaximumClique::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1; 3]); let reduction: ReductionCliqueToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -91,7 +91,7 @@ fn test_reduction_with_non_edges() { #[test] fn test_reduction_weighted() { let problem: MaximumClique = - MaximumClique::with_weights(3, vec![(0, 1)], vec![5, 10, 15]); + MaximumClique::new(SimpleGraph::new(3, vec![(0, 1)]), vec![5, 10, 15]); let reduction: ReductionCliqueToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -109,7 +109,7 @@ fn test_reduction_weighted() { fn test_ilp_solution_equals_brute_force_triangle() { // Triangle graph (K3): max clique = 3 vertices let problem: MaximumClique = - MaximumClique::new(3, vec![(0, 1), (1, 2), (0, 2)]); + MaximumClique::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), vec![1; 3]); let reduction: ReductionCliqueToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -138,7 +138,7 @@ fn test_ilp_solution_equals_brute_force_triangle() { fn test_ilp_solution_equals_brute_force_path() { // Path graph 0-1-2-3: max clique = 2 (any adjacent pair) let problem: MaximumClique = - MaximumClique::new(4, vec![(0, 1), (1, 2), (2, 3)]); + MaximumClique::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1; 4]); let reduction: ReductionCliqueToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -166,7 +166,7 @@ fn test_ilp_solution_equals_brute_force_weighted() { // Max clique by weight: {0, 1} (weight 101) or {1, 2} (weight 101), or just {1} (weight 100) // Since 0-1 and 1-2 are edges, both {0,1} and {1,2} are valid cliques let problem: MaximumClique = - MaximumClique::with_weights(3, vec![(0, 1), (1, 2)], vec![1, 100, 1]); + MaximumClique::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1, 100, 1]); let reduction: ReductionCliqueToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -187,7 +187,7 @@ fn test_ilp_solution_equals_brute_force_weighted() { #[test] fn test_solution_extraction() { - let problem: MaximumClique = MaximumClique::new(4, vec![(0, 1), (2, 3)]); + let problem: MaximumClique = MaximumClique::new(SimpleGraph::new(4, vec![(0, 1), (2, 3)]), vec![1; 4]); let reduction: ReductionCliqueToILP = ReduceTo::::reduce_to(&problem); // Test that extraction works correctly (1:1 mapping) @@ -202,7 +202,7 @@ fn test_solution_extraction() { #[test] fn test_ilp_structure() { let problem: MaximumClique = - MaximumClique::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]); + MaximumClique::new(SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]), vec![1; 5]); let reduction: ReductionCliqueToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -214,7 +214,7 @@ fn test_ilp_structure() { #[test] fn test_empty_graph() { // Graph with no edges: max clique = 1 (any single vertex) - let problem: MaximumClique = MaximumClique::new(3, vec![]); + let problem: MaximumClique = MaximumClique::new(SimpleGraph::new(3, vec![]), vec![1; 3]); let reduction: ReductionCliqueToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -236,7 +236,7 @@ fn test_empty_graph() { fn test_complete_graph() { // Complete graph K4: max clique = 4 (all vertices) let problem: MaximumClique = - MaximumClique::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]); + MaximumClique::new(SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]), vec![1; 4]); let reduction: ReductionCliqueToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -259,7 +259,7 @@ fn test_bipartite_graph() { // Bipartite graph: 0-2, 0-3, 1-2, 1-3 (two independent sets: {0,1} and {2,3}) // Max clique = 2 (any edge, e.g., {0, 2}) let problem: MaximumClique = - MaximumClique::new(4, vec![(0, 2), (0, 3), (1, 2), (1, 3)]); + MaximumClique::new(SimpleGraph::new(4, vec![(0, 2), (0, 3), (1, 2), (1, 3)]), vec![1; 4]); let reduction: ReductionCliqueToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -280,7 +280,7 @@ fn test_star_graph() { // Star graph: center 0 connected to 1, 2, 3 // Max clique = 2 (center + any leaf) let problem: MaximumClique = - MaximumClique::new(4, vec![(0, 1), (0, 2), (0, 3)]); + MaximumClique::new(SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3)]), vec![1; 4]); let reduction: ReductionCliqueToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); diff --git a/src/unit_tests/rules/maximumindependentset_gridgraph.rs b/src/unit_tests/rules/maximumindependentset_gridgraph.rs index f2d681b38..d149b211d 100644 --- a/src/unit_tests/rules/maximumindependentset_gridgraph.rs +++ b/src/unit_tests/rules/maximumindependentset_gridgraph.rs @@ -6,7 +6,7 @@ use crate::topology::{Graph, KingsSubgraph, SimpleGraph, UnitDiskGraph}; #[test] fn test_mis_simple_to_grid_closed_loop() { // Triangle graph: 3 vertices, 3 edges - let problem = MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), vec![1i32; 3]); let result = ReduceTo::>::reduce_to(&problem); let target = result.target_problem(); @@ -30,7 +30,7 @@ fn test_mis_simple_to_grid_closed_loop() { #[test] fn test_mis_simple_to_grid_path_graph() { // Path graph: 0-1-2 - let problem = MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2)]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); let result = ReduceTo::>::reduce_to(&problem); let target = result.target_problem(); @@ -52,7 +52,7 @@ fn test_mis_unitdisk_to_grid_closed_loop() { // Only edge is 0-1 (distance 0.5 <= 1.0), vertex 2 is isolated assert_eq!(udg.num_edges(), 1); - let problem = MaximumIndependentSet::::from_graph(udg, vec![1, 1, 1]); + let problem = MaximumIndependentSet::new(udg, vec![1i32, 1, 1]); let result = ReduceTo::>::reduce_to(&problem); let target = result.target_problem(); diff --git a/src/unit_tests/rules/maximumindependentset_ilp.rs b/src/unit_tests/rules/maximumindependentset_ilp.rs index 84e9d255a..36dbc6794 100644 --- a/src/unit_tests/rules/maximumindependentset_ilp.rs +++ b/src/unit_tests/rules/maximumindependentset_ilp.rs @@ -6,7 +6,7 @@ use crate::types::SolutionSize; #[test] fn test_reduction_creates_valid_ilp() { // Triangle graph: 3 vertices, 3 edges - let problem = MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), vec![1i32; 3]); let reduction: ReductionISToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -33,7 +33,7 @@ fn test_reduction_creates_valid_ilp() { #[test] fn test_reduction_weighted() { - let problem = MaximumIndependentSet::with_weights(3, vec![(0, 1)], vec![5, 10, 15]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1)]), vec![5, 10, 15]); let reduction: ReductionISToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -50,7 +50,7 @@ fn test_reduction_weighted() { #[test] fn test_ilp_solution_equals_brute_force_triangle() { // Triangle graph: max IS = 1 vertex - let problem = MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), vec![1i32; 3]); let reduction: ReductionISToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -80,7 +80,7 @@ fn test_ilp_solution_equals_brute_force_triangle() { #[test] fn test_ilp_solution_equals_brute_force_path() { // Path graph 0-1-2-3: max IS = 2 (e.g., {0, 2} or {1, 3} or {0, 3}) - let problem = MaximumIndependentSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1i32; 4]); let reduction: ReductionISToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -109,7 +109,7 @@ fn test_ilp_solution_equals_brute_force_weighted() { // 0 -- 1 -- 2 // Weights: [1, 100, 1] // Max IS by weight: just vertex 1 (weight 100) beats 0+2 (weight 2) - let problem = MaximumIndependentSet::with_weights(3, vec![(0, 1), (1, 2)], vec![1, 100, 1]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1, 100, 1]); let reduction: ReductionISToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -132,7 +132,7 @@ fn test_ilp_solution_equals_brute_force_weighted() { #[test] fn test_solution_extraction() { - let problem = MaximumIndependentSet::::new(4, vec![(0, 1), (2, 3)]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(4, vec![(0, 1), (2, 3)]), vec![1i32; 4]); let reduction: ReductionISToILP = ReduceTo::::reduce_to(&problem); // Test that extraction works correctly (1:1 mapping) @@ -147,7 +147,7 @@ fn test_solution_extraction() { #[test] fn test_ilp_structure() { let problem = - MaximumIndependentSet::::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]); + MaximumIndependentSet::new(SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]), vec![1i32; 5]); let reduction: ReductionISToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -158,7 +158,7 @@ fn test_ilp_structure() { #[test] fn test_empty_graph() { // Graph with no edges: all vertices can be selected - let problem = MaximumIndependentSet::::new(3, vec![]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![]), vec![1i32; 3]); let reduction: ReductionISToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -178,9 +178,9 @@ fn test_empty_graph() { #[test] fn test_complete_graph() { // Complete graph K4: max IS = 1 - let problem = MaximumIndependentSet::::new( - 4, - vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], + let problem = MaximumIndependentSet::new( + SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]), + vec![1i32; 4], ); let reduction: ReductionISToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -198,7 +198,7 @@ fn test_complete_graph() { #[test] fn test_solve_reduced() { // Test the ILPSolver::solve_reduced method - let problem = MaximumIndependentSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1i32; 4]); let ilp_solver = ILPSolver::new(); let solution = ilp_solver @@ -214,7 +214,7 @@ fn test_bipartite_graph() { // Bipartite graph: 0-2, 0-3, 1-2, 1-3 (two independent sets: {0,1} and {2,3}) // With equal weights, max IS = 2 let problem = - MaximumIndependentSet::::new(4, vec![(0, 2), (0, 3), (1, 2), (1, 3)]); + MaximumIndependentSet::new(SimpleGraph::new(4, vec![(0, 2), (0, 3), (1, 2), (1, 3)]), vec![1i32; 4]); let reduction: ReductionISToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); diff --git a/src/unit_tests/rules/maximumindependentset_maximumsetpacking.rs b/src/unit_tests/rules/maximumindependentset_maximumsetpacking.rs index 7743a5f55..d930c44f0 100644 --- a/src/unit_tests/rules/maximumindependentset_maximumsetpacking.rs +++ b/src/unit_tests/rules/maximumindependentset_maximumsetpacking.rs @@ -4,7 +4,7 @@ include!("../jl_helpers.rs"); #[test] fn test_weighted_reduction() { - let is_problem = MaximumIndependentSet::with_weights(3, vec![(0, 1), (1, 2)], vec![10, 20, 30]); + let is_problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![10, 20, 30]); let reduction = ReduceTo::>::reduce_to(&is_problem); let sp_problem = reduction.target_problem(); @@ -15,7 +15,7 @@ fn test_weighted_reduction() { #[test] fn test_empty_graph() { // No edges means all sets are empty (or we need to handle it) - let is_problem = MaximumIndependentSet::::new(3, vec![]); + let is_problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![]), vec![1i32; 3]); let reduction = ReduceTo::>::reduce_to(&is_problem); let sp_problem = reduction.target_problem(); @@ -45,7 +45,7 @@ fn test_disjoint_sets() { #[test] fn test_reduction_structure() { // Test IS to SP structure - let is_problem = MaximumIndependentSet::::new(4, vec![(0, 1), (1, 2)]); + let is_problem = MaximumIndependentSet::new(SimpleGraph::new(4, vec![(0, 1), (1, 2)]), vec![1i32; 4]); let reduction = ReduceTo::>::reduce_to(&is_problem); let sp = reduction.target_problem(); @@ -72,9 +72,10 @@ fn test_jl_parity_is_to_setpacking() { let is_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/independentset.json")).unwrap(); let inst = &is_data["instances"][0]["instance"]; - let source = MaximumIndependentSet::::new( - inst["num_vertices"].as_u64().unwrap() as usize, - jl_parse_edges(inst), + let nv = inst["num_vertices"].as_u64().unwrap() as usize; + let source = MaximumIndependentSet::new( + SimpleGraph::new(nv, jl_parse_edges(inst)), + vec![1i32; nv], ); let result = ReduceTo::>::reduce_to(&source); let solver = BruteForce::new(); @@ -123,9 +124,10 @@ fn test_jl_parity_rule_is_to_setpacking() { let is_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/independentset.json")).unwrap(); let inst = &jl_find_instance_by_label(&is_data, "doc_4vertex")["instance"]; - let source = MaximumIndependentSet::::new( - inst["num_vertices"].as_u64().unwrap() as usize, - jl_parse_edges(inst), + let nv = inst["num_vertices"].as_u64().unwrap() as usize; + let source = MaximumIndependentSet::new( + SimpleGraph::new(nv, jl_parse_edges(inst)), + vec![1i32; nv], ); let result = ReduceTo::>::reduce_to(&source); let solver = BruteForce::new(); @@ -151,9 +153,10 @@ fn test_jl_parity_doc_is_to_setpacking() { serde_json::from_str(include_str!("../../../tests/data/jl/independentset.json")).unwrap(); let is_instance = jl_find_instance_by_label(&is_data, "doc_4vertex"); let inst = &is_instance["instance"]; - let source = MaximumIndependentSet::::new( - inst["num_vertices"].as_u64().unwrap() as usize, - jl_parse_edges(inst), + let nv = inst["num_vertices"].as_u64().unwrap() as usize; + let source = MaximumIndependentSet::new( + SimpleGraph::new(nv, jl_parse_edges(inst)), + vec![1i32; nv], ); let result = ReduceTo::>::reduce_to(&source); let solver = BruteForce::new(); diff --git a/src/unit_tests/rules/maximumindependentset_qubo.rs b/src/unit_tests/rules/maximumindependentset_qubo.rs index 2065d9b9d..8554fb7d0 100644 --- a/src/unit_tests/rules/maximumindependentset_qubo.rs +++ b/src/unit_tests/rules/maximumindependentset_qubo.rs @@ -6,7 +6,7 @@ use crate::traits::Problem; fn test_independentset_to_qubo_closed_loop() { // Path graph: 0-1-2-3 (4 vertices, 3 edges) // Maximum IS = {0, 2} or {1, 3} (size 2) - let is = MaximumIndependentSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let is = MaximumIndependentSet::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1i32; 4]); let reduction = ReduceTo::>::reduce_to(&is); let qubo = reduction.target_problem(); @@ -24,7 +24,7 @@ fn test_independentset_to_qubo_closed_loop() { fn test_independentset_to_qubo_triangle() { // Triangle: 0-1-2 (complete graph K3) // Maximum IS = any single vertex (size 1) - let is = MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let is = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), vec![1i32; 3]); let reduction = ReduceTo::>::reduce_to(&is); let qubo = reduction.target_problem(); @@ -41,7 +41,7 @@ fn test_independentset_to_qubo_triangle() { #[test] fn test_independentset_to_qubo_empty_graph() { // No edges: all vertices form the IS - let is = MaximumIndependentSet::::new(3, vec![]); + let is = MaximumIndependentSet::new(SimpleGraph::new(3, vec![]), vec![1i32; 3]); let reduction = ReduceTo::>::reduce_to(&is); let qubo = reduction.target_problem(); @@ -57,7 +57,7 @@ fn test_independentset_to_qubo_empty_graph() { #[test] fn test_independentset_to_qubo_structure() { - let is = MaximumIndependentSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let is = MaximumIndependentSet::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1i32; 4]); let reduction = ReduceTo::>::reduce_to(&is); let qubo = reduction.target_problem(); diff --git a/src/unit_tests/rules/maximumindependentset_triangular.rs b/src/unit_tests/rules/maximumindependentset_triangular.rs index 1b11f1c2c..70feee2a7 100644 --- a/src/unit_tests/rules/maximumindependentset_triangular.rs +++ b/src/unit_tests/rules/maximumindependentset_triangular.rs @@ -5,7 +5,7 @@ use crate::topology::{Graph, SimpleGraph, TriangularSubgraph}; #[test] fn test_mis_simple_to_triangular_closed_loop() { // Path graph: 0-1-2 - let problem = MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2)]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); let result = ReduceTo::>::reduce_to(&problem); let target = result.target_problem(); @@ -21,7 +21,7 @@ fn test_mis_simple_to_triangular_closed_loop() { #[test] fn test_mis_simple_to_triangular_graph_methods() { // Single edge graph: 0-1 - let problem = MaximumIndependentSet::::new(2, vec![(0, 1)]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(2, vec![(0, 1)]), vec![1i32; 2]); let result = ReduceTo::>::reduce_to(&problem); let target = result.target_problem(); let graph = target.graph(); diff --git a/src/unit_tests/rules/maximummatching_ilp.rs b/src/unit_tests/rules/maximummatching_ilp.rs index c886fbea0..f7b4dba67 100644 --- a/src/unit_tests/rules/maximummatching_ilp.rs +++ b/src/unit_tests/rules/maximummatching_ilp.rs @@ -1,12 +1,13 @@ use super::*; use crate::solvers::{BruteForce, ILPSolver}; +use crate::topology::SimpleGraph; use crate::traits::Problem; use crate::types::SolutionSize; #[test] fn test_reduction_creates_valid_ilp() { // Triangle graph: 3 vertices, 3 edges - let problem = MaximumMatching::::unweighted(3, vec![(0, 1), (1, 2), (0, 2)]); + let problem = MaximumMatching::<_, i32>::unit_weights(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)])); let reduction: ReductionMatchingToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -33,7 +34,7 @@ fn test_reduction_creates_valid_ilp() { #[test] fn test_reduction_weighted() { - let problem = MaximumMatching::new(3, vec![(0, 1, 5), (1, 2, 10)]); + let problem = MaximumMatching::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![5, 10]); let reduction: ReductionMatchingToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -49,7 +50,7 @@ fn test_reduction_weighted() { #[test] fn test_ilp_solution_equals_brute_force_triangle() { // Triangle graph: max matching = 1 edge - let problem = MaximumMatching::::unweighted(3, vec![(0, 1), (1, 2), (0, 2)]); + let problem = MaximumMatching::<_, i32>::unit_weights(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)])); let reduction: ReductionMatchingToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -79,7 +80,7 @@ fn test_ilp_solution_equals_brute_force_triangle() { #[test] fn test_ilp_solution_equals_brute_force_path() { // Path graph 0-1-2-3: max matching = 2 (edges {0-1, 2-3}) - let problem = MaximumMatching::::unweighted(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = MaximumMatching::<_, i32>::unit_weights(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)])); let reduction: ReductionMatchingToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -108,7 +109,7 @@ fn test_ilp_solution_equals_brute_force_weighted() { // 0 -- 1 -- 2 // Weights: [100, 1] // Max matching by weight: just edge 0-1 (weight 100) beats edge 1-2 (weight 1) - let problem = MaximumMatching::new(3, vec![(0, 1, 100), (1, 2, 1)]); + let problem = MaximumMatching::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![100, 1]); let reduction: ReductionMatchingToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -131,7 +132,7 @@ fn test_ilp_solution_equals_brute_force_weighted() { #[test] fn test_solution_extraction() { - let problem = MaximumMatching::::unweighted(4, vec![(0, 1), (2, 3)]); + let problem = MaximumMatching::<_, i32>::unit_weights(SimpleGraph::new(4, vec![(0, 1), (2, 3)])); let reduction: ReductionMatchingToILP = ReduceTo::::reduce_to(&problem); // Test that extraction works correctly (1:1 mapping) @@ -146,7 +147,7 @@ fn test_solution_extraction() { #[test] fn test_ilp_structure() { let problem = - MaximumMatching::::unweighted(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]); + MaximumMatching::<_, i32>::unit_weights(SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)])); let reduction: ReductionMatchingToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -159,7 +160,7 @@ fn test_ilp_structure() { #[test] fn test_empty_graph() { // Graph with no edges: empty matching - let problem = MaximumMatching::::unweighted(3, vec![]); + let problem = MaximumMatching::<_, i32>::unit_weights(SimpleGraph::new(3, vec![])); let reduction: ReductionMatchingToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -173,9 +174,8 @@ fn test_empty_graph() { #[test] fn test_k4_perfect_matching() { // Complete graph K4: can have perfect matching (2 edges covering all 4 vertices) - let problem = MaximumMatching::::unweighted( - 4, - vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], + let problem = MaximumMatching::<_, i32>::unit_weights( + SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]), ); let reduction: ReductionMatchingToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -200,7 +200,7 @@ fn test_k4_perfect_matching() { fn test_star_graph() { // Star graph with center vertex 0 connected to 1, 2, 3 // Max matching = 1 (only one edge can be selected) - let problem = MaximumMatching::::unweighted(4, vec![(0, 1), (0, 2), (0, 3)]); + let problem = MaximumMatching::<_, i32>::unit_weights(SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3)])); let reduction: ReductionMatchingToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -217,7 +217,7 @@ fn test_bipartite_graph() { // Bipartite graph: {0,1} and {2,3} with all cross edges // Max matching = 2 (one perfect matching) let problem = - MaximumMatching::::unweighted(4, vec![(0, 2), (0, 3), (1, 2), (1, 3)]); + MaximumMatching::<_, i32>::unit_weights(SimpleGraph::new(4, vec![(0, 2), (0, 3), (1, 2), (1, 3)])); let reduction: ReductionMatchingToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -232,7 +232,7 @@ fn test_bipartite_graph() { #[test] fn test_solve_reduced() { // Test the ILPSolver::solve_reduced method - let problem = MaximumMatching::::unweighted(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = MaximumMatching::<_, i32>::unit_weights(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)])); let ilp_solver = ILPSolver::new(); let solution = ilp_solver diff --git a/src/unit_tests/rules/maximummatching_maximumsetpacking.rs b/src/unit_tests/rules/maximummatching_maximumsetpacking.rs index 468cb9d7a..efb56c785 100644 --- a/src/unit_tests/rules/maximummatching_maximumsetpacking.rs +++ b/src/unit_tests/rules/maximummatching_maximumsetpacking.rs @@ -8,7 +8,7 @@ include!("../jl_helpers.rs"); #[test] fn test_matching_to_setpacking_structure() { // Path graph 0-1-2 - let matching = MaximumMatching::::unweighted(3, vec![(0, 1), (1, 2)]); + let matching = MaximumMatching::<_, i32>::unit_weights(SimpleGraph::new(3, vec![(0, 1), (1, 2)])); let reduction = ReduceTo::>::reduce_to(&matching); let sp = reduction.target_problem(); @@ -25,7 +25,7 @@ fn test_matching_to_setpacking_structure() { fn test_matching_to_setpacking_weighted() { // Weighted edges: heavy edge should win over multiple light edges let matching = - MaximumMatching::::new(4, vec![(0, 1, 100), (0, 2, 1), (1, 3, 1)]); + MaximumMatching::new(SimpleGraph::new(4, vec![(0, 1), (0, 2), (1, 3)]), vec![100, 1, 1]); let reduction = ReduceTo::>::reduce_to(&matching); let sp = reduction.target_problem(); @@ -52,7 +52,7 @@ fn test_matching_to_setpacking_weighted() { #[test] fn test_matching_to_setpacking_solution_extraction() { - let matching = MaximumMatching::::unweighted(4, vec![(0, 1), (1, 2), (2, 3)]); + let matching = MaximumMatching::<_, i32>::unit_weights(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)])); let reduction = ReduceTo::>::reduce_to(&matching); // Test solution extraction is 1:1 @@ -67,7 +67,7 @@ fn test_matching_to_setpacking_solution_extraction() { #[test] fn test_matching_to_setpacking_empty() { // Graph with no edges - let matching = MaximumMatching::::unweighted(3, vec![]); + let matching = MaximumMatching::<_, i32>::unit_weights(SimpleGraph::new(3, vec![])); let reduction = ReduceTo::>::reduce_to(&matching); let sp = reduction.target_problem(); @@ -76,7 +76,7 @@ fn test_matching_to_setpacking_empty() { #[test] fn test_matching_to_setpacking_single_edge() { - let matching = MaximumMatching::::unweighted(2, vec![(0, 1)]); + let matching = MaximumMatching::<_, i32>::unit_weights(SimpleGraph::new(2, vec![(0, 1)])); let reduction = ReduceTo::>::reduce_to(&matching); let sp = reduction.target_problem(); @@ -93,7 +93,7 @@ fn test_matching_to_setpacking_single_edge() { #[test] fn test_matching_to_setpacking_disjoint_edges() { // Two disjoint edges: 0-1 and 2-3 - let matching = MaximumMatching::::unweighted(4, vec![(0, 1), (2, 3)]); + let matching = MaximumMatching::<_, i32>::unit_weights(SimpleGraph::new(4, vec![(0, 1), (2, 3)])); let reduction = ReduceTo::>::reduce_to(&matching); let sp = reduction.target_problem(); @@ -106,7 +106,7 @@ fn test_matching_to_setpacking_disjoint_edges() { #[test] fn test_reduction_structure() { - let matching = MaximumMatching::::unweighted(5, vec![(0, 1), (1, 2), (2, 3)]); + let matching = MaximumMatching::<_, i32>::unit_weights(SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3)])); let reduction = ReduceTo::>::reduce_to(&matching); let sp = reduction.target_problem(); @@ -117,7 +117,7 @@ fn test_reduction_structure() { #[test] fn test_matching_to_setpacking_star() { // Star graph: center vertex 0 connected to 1, 2, 3 - let matching = MaximumMatching::::unweighted(4, vec![(0, 1), (0, 2), (0, 3)]); + let matching = MaximumMatching::<_, i32>::unit_weights(SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3)])); let reduction = ReduceTo::>::reduce_to(&matching); let sp = reduction.target_problem(); @@ -153,9 +153,12 @@ fn test_jl_parity_matching_to_setpacking() { for (fixture_str, label) in fixtures { let data: serde_json::Value = serde_json::from_str(fixture_str).unwrap(); let inst = &jl_find_instance_by_label(&match_data, label)["instance"]; - let source = MaximumMatching::::new( - inst["num_vertices"].as_u64().unwrap() as usize, - jl_parse_weighted_edges(inst), + let weighted_edges = jl_parse_weighted_edges(inst); + let edges: Vec<(usize, usize)> = weighted_edges.iter().map(|&(u, v, _)| (u, v)).collect(); + let weights: Vec = weighted_edges.into_iter().map(|(_, _, w)| w).collect(); + let source = MaximumMatching::new( + SimpleGraph::new(inst["num_vertices"].as_u64().unwrap() as usize, edges), + weights, ); let result = ReduceTo::>::reduce_to(&source); let solver = BruteForce::new(); diff --git a/src/unit_tests/rules/minimumdominatingset_ilp.rs b/src/unit_tests/rules/minimumdominatingset_ilp.rs index 40a348254..1429b564b 100644 --- a/src/unit_tests/rules/minimumdominatingset_ilp.rs +++ b/src/unit_tests/rules/minimumdominatingset_ilp.rs @@ -6,7 +6,7 @@ use crate::types::SolutionSize; #[test] fn test_reduction_creates_valid_ilp() { // Triangle graph: 3 vertices, 3 edges - let problem = MinimumDominatingSet::::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let problem = MinimumDominatingSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), vec![1i32; 3]); let reduction: ReductionDSToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -33,7 +33,7 @@ fn test_reduction_creates_valid_ilp() { #[test] fn test_reduction_weighted() { - let problem = MinimumDominatingSet::with_weights(3, vec![(0, 1)], vec![5, 10, 15]); + let problem = MinimumDominatingSet::new(SimpleGraph::new(3, vec![(0, 1)]), vec![5, 10, 15]); let reduction: ReductionDSToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -51,7 +51,7 @@ fn test_reduction_weighted() { fn test_ilp_solution_equals_brute_force_star() { // Star graph: center vertex 0 connected to all others // Minimum dominating set is just the center (weight 1) - let problem = MinimumDominatingSet::::new(4, vec![(0, 1), (0, 2), (0, 3)]); + let problem = MinimumDominatingSet::new(SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3)]), vec![1i32; 4]); let reduction: ReductionDSToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -82,7 +82,7 @@ fn test_ilp_solution_equals_brute_force_star() { fn test_ilp_solution_equals_brute_force_path() { // Path graph 0-1-2-3-4: min DS = 2 (e.g., vertices 1 and 3) let problem = - MinimumDominatingSet::::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]); + MinimumDominatingSet::new(SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]), vec![1i32; 5]); let reduction: ReductionDSToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -110,7 +110,7 @@ fn test_ilp_solution_equals_brute_force_weighted() { // Star with heavy center: prefer selecting all leaves (total weight 3) // over center (weight 100) let problem = - MinimumDominatingSet::with_weights(4, vec![(0, 1), (0, 2), (0, 3)], vec![100, 1, 1, 1]); + MinimumDominatingSet::new(SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3)]), vec![100, 1, 1, 1]); let reduction: ReductionDSToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -133,7 +133,7 @@ fn test_ilp_solution_equals_brute_force_weighted() { #[test] fn test_solution_extraction() { - let problem = MinimumDominatingSet::::new(4, vec![(0, 1), (2, 3)]); + let problem = MinimumDominatingSet::new(SimpleGraph::new(4, vec![(0, 1), (2, 3)]), vec![1i32; 4]); let reduction: ReductionDSToILP = ReduceTo::::reduce_to(&problem); // Test that extraction works correctly (1:1 mapping) @@ -148,7 +148,7 @@ fn test_solution_extraction() { #[test] fn test_ilp_structure() { let problem = - MinimumDominatingSet::::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]); + MinimumDominatingSet::new(SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]), vec![1i32; 5]); let reduction: ReductionDSToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -159,7 +159,7 @@ fn test_ilp_structure() { #[test] fn test_isolated_vertices() { // Graph with isolated vertex 2: it must be in the dominating set - let problem = MinimumDominatingSet::::new(3, vec![(0, 1)]); + let problem = MinimumDominatingSet::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1i32; 3]); let reduction: ReductionDSToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -176,9 +176,9 @@ fn test_isolated_vertices() { #[test] fn test_complete_graph() { // Complete graph K4: min DS = 1 (any vertex dominates all) - let problem = MinimumDominatingSet::::new( - 4, - vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], + let problem = MinimumDominatingSet::new( + SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]), + vec![1i32; 4], ); let reduction: ReductionDSToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -194,7 +194,7 @@ fn test_complete_graph() { #[test] fn test_single_vertex() { // Single vertex with no edges: must be in dominating set - let problem = MinimumDominatingSet::::new(1, vec![]); + let problem = MinimumDominatingSet::new(SimpleGraph::new(1, vec![]), vec![1i32; 1]); let reduction: ReductionDSToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -212,9 +212,9 @@ fn test_single_vertex() { fn test_cycle_graph() { // Cycle C5: 0-1-2-3-4-0 // Minimum dominating set size = 2 - let problem = MinimumDominatingSet::::new( - 5, - vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], + let problem = MinimumDominatingSet::new( + SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)]), + vec![1i32; 5], ); let reduction: ReductionDSToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); diff --git a/src/unit_tests/rules/minimumvertexcover_ilp.rs b/src/unit_tests/rules/minimumvertexcover_ilp.rs index 0523abb90..383b19631 100644 --- a/src/unit_tests/rules/minimumvertexcover_ilp.rs +++ b/src/unit_tests/rules/minimumvertexcover_ilp.rs @@ -6,7 +6,7 @@ use crate::types::SolutionSize; #[test] fn test_reduction_creates_valid_ilp() { // Triangle graph: 3 vertices, 3 edges - let problem = MinimumVertexCover::::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let problem = MinimumVertexCover::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), vec![1i32; 3]); let reduction: ReductionVCToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -33,7 +33,7 @@ fn test_reduction_creates_valid_ilp() { #[test] fn test_reduction_weighted() { - let problem = MinimumVertexCover::with_weights(3, vec![(0, 1)], vec![5, 10, 15]); + let problem = MinimumVertexCover::new(SimpleGraph::new(3, vec![(0, 1)]), vec![5, 10, 15]); let reduction: ReductionVCToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -50,7 +50,7 @@ fn test_reduction_weighted() { #[test] fn test_ilp_solution_equals_brute_force_triangle() { // Triangle graph: min VC = 2 vertices - let problem = MinimumVertexCover::::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let problem = MinimumVertexCover::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), vec![1i32; 3]); let reduction: ReductionVCToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -80,7 +80,7 @@ fn test_ilp_solution_equals_brute_force_triangle() { #[test] fn test_ilp_solution_equals_brute_force_path() { // Path graph 0-1-2-3: min VC = 2 (e.g., {1, 2} or {0, 2} or {1, 3}) - let problem = MinimumVertexCover::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = MinimumVertexCover::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1i32; 4]); let reduction: ReductionVCToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -109,7 +109,7 @@ fn test_ilp_solution_equals_brute_force_weighted() { // 0 -- 1 -- 2 // Weights: [100, 1, 100] // Min VC by weight: just vertex 1 (weight 1) beats 0+2 (weight 200) - let problem = MinimumVertexCover::with_weights(3, vec![(0, 1), (1, 2)], vec![100, 1, 100]); + let problem = MinimumVertexCover::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![100, 1, 100]); let reduction: ReductionVCToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -132,7 +132,7 @@ fn test_ilp_solution_equals_brute_force_weighted() { #[test] fn test_solution_extraction() { - let problem = MinimumVertexCover::::new(4, vec![(0, 1), (2, 3)]); + let problem = MinimumVertexCover::new(SimpleGraph::new(4, vec![(0, 1), (2, 3)]), vec![1i32; 4]); let reduction: ReductionVCToILP = ReduceTo::::reduce_to(&problem); // Test that extraction works correctly (1:1 mapping) @@ -147,7 +147,7 @@ fn test_solution_extraction() { #[test] fn test_ilp_structure() { let problem = - MinimumVertexCover::::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]); + MinimumVertexCover::new(SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]), vec![1i32; 5]); let reduction: ReductionVCToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -158,7 +158,7 @@ fn test_ilp_structure() { #[test] fn test_empty_graph() { // Graph with no edges: empty cover is valid - let problem = MinimumVertexCover::::new(3, vec![]); + let problem = MinimumVertexCover::new(SimpleGraph::new(3, vec![]), vec![1i32; 3]); let reduction: ReductionVCToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -178,9 +178,9 @@ fn test_empty_graph() { #[test] fn test_complete_graph() { // Complete graph K4: min VC = 3 (all but one vertex) - let problem = MinimumVertexCover::::new( - 4, - vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], + let problem = MinimumVertexCover::new( + SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]), + vec![1i32; 4], ); let reduction: ReductionVCToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -198,7 +198,7 @@ fn test_complete_graph() { #[test] fn test_solve_reduced() { // Test the ILPSolver::solve_reduced method - let problem = MinimumVertexCover::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = MinimumVertexCover::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1i32; 4]); let ilp_solver = ILPSolver::new(); let solution = ilp_solver @@ -214,7 +214,7 @@ fn test_bipartite_graph() { // Bipartite graph: 0-2, 0-3, 1-2, 1-3 (complete bipartite K_{2,2}) // Min VC = 2 (either side of the bipartition) let problem = - MinimumVertexCover::::new(4, vec![(0, 2), (0, 3), (1, 2), (1, 3)]); + MinimumVertexCover::new(SimpleGraph::new(4, vec![(0, 2), (0, 3), (1, 2), (1, 3)]), vec![1i32; 4]); let reduction: ReductionVCToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -233,7 +233,7 @@ fn test_bipartite_graph() { #[test] fn test_single_edge() { // Single edge: min VC = 1 - let problem = MinimumVertexCover::::new(2, vec![(0, 1)]); + let problem = MinimumVertexCover::new(SimpleGraph::new(2, vec![(0, 1)]), vec![1i32; 2]); let reduction: ReductionVCToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -256,7 +256,7 @@ fn test_star_graph() { // Star graph: center vertex 0 connected to all others // Min VC = 1 (just the center) let problem = - MinimumVertexCover::::new(5, vec![(0, 1), (0, 2), (0, 3), (0, 4)]); + MinimumVertexCover::new(SimpleGraph::new(5, vec![(0, 1), (0, 2), (0, 3), (0, 4)]), vec![1i32; 5]); let reduction: ReductionVCToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); diff --git a/src/unit_tests/rules/minimumvertexcover_maximumindependentset.rs b/src/unit_tests/rules/minimumvertexcover_maximumindependentset.rs index fcdac7764..d5f5b0c49 100644 --- a/src/unit_tests/rules/minimumvertexcover_maximumindependentset.rs +++ b/src/unit_tests/rules/minimumvertexcover_maximumindependentset.rs @@ -5,7 +5,7 @@ include!("../jl_helpers.rs"); #[test] fn test_weighted_reduction() { // Test with weighted problems - let is_problem = MaximumIndependentSet::with_weights(3, vec![(0, 1), (1, 2)], vec![10, 20, 30]); + let is_problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![10, 20, 30]); let reduction = ReduceTo::>::reduce_to(&is_problem); let vc_problem = reduction.target_problem(); @@ -16,7 +16,7 @@ fn test_weighted_reduction() { #[test] fn test_reduction_structure() { let is_problem = - MaximumIndependentSet::::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]); + MaximumIndependentSet::new(SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]), vec![1i32; 5]); let reduction = ReduceTo::>::reduce_to(&is_problem); let vc = reduction.target_problem(); @@ -33,9 +33,10 @@ fn test_jl_parity_is_to_vertexcovering() { let is_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/independentset.json")).unwrap(); let inst = &is_data["instances"][0]["instance"]; - let source = MaximumIndependentSet::::new( - inst["num_vertices"].as_u64().unwrap() as usize, - jl_parse_edges(inst), + let nv = inst["num_vertices"].as_u64().unwrap() as usize; + let source = MaximumIndependentSet::new( + SimpleGraph::new(nv, jl_parse_edges(inst)), + vec![1i32; nv], ); let result = ReduceTo::>::reduce_to(&source); let solver = BruteForce::new(); @@ -60,9 +61,10 @@ fn test_jl_parity_rule_is_to_vertexcovering() { let is_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/independentset.json")).unwrap(); let inst = &jl_find_instance_by_label(&is_data, "doc_4vertex")["instance"]; - let source = MaximumIndependentSet::::new( - inst["num_vertices"].as_u64().unwrap() as usize, - jl_parse_edges(inst), + let nv = inst["num_vertices"].as_u64().unwrap() as usize; + let source = MaximumIndependentSet::new( + SimpleGraph::new(nv, jl_parse_edges(inst)), + vec![1i32; nv], ); let result = ReduceTo::>::reduce_to(&source); let solver = BruteForce::new(); diff --git a/src/unit_tests/rules/minimumvertexcover_minimumsetcovering.rs b/src/unit_tests/rules/minimumvertexcover_minimumsetcovering.rs index 96879e289..0ebabcf77 100644 --- a/src/unit_tests/rules/minimumvertexcover_minimumsetcovering.rs +++ b/src/unit_tests/rules/minimumvertexcover_minimumsetcovering.rs @@ -8,7 +8,7 @@ fn test_vc_to_sc_basic() { // Vertex 0 covers edge 0 // Vertex 1 covers edges 0 and 1 // Vertex 2 covers edge 1 - let vc_problem = MinimumVertexCover::::new(3, vec![(0, 1), (1, 2)]); + let vc_problem = MinimumVertexCover::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); let reduction = ReduceTo::>::reduce_to(&vc_problem); let sc_problem = reduction.target_problem(); @@ -28,7 +28,7 @@ fn test_vc_to_sc_basic() { fn test_vc_to_sc_triangle() { // Triangle graph: 3 vertices, 3 edges // Edge indices: (0,1)->0, (1,2)->1, (0,2)->2 - let vc_problem = MinimumVertexCover::::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let vc_problem = MinimumVertexCover::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), vec![1i32; 3]); let reduction = ReduceTo::>::reduce_to(&vc_problem); let sc_problem = reduction.target_problem(); @@ -45,7 +45,7 @@ fn test_vc_to_sc_triangle() { #[test] fn test_vc_to_sc_weighted() { // Weighted problem: weights should be preserved - let vc_problem = MinimumVertexCover::with_weights(3, vec![(0, 1), (1, 2)], vec![10, 1, 10]); + let vc_problem = MinimumVertexCover::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![10, 1, 10]); let reduction = ReduceTo::>::reduce_to(&vc_problem); let sc_problem = reduction.target_problem(); @@ -65,7 +65,7 @@ fn test_vc_to_sc_weighted() { #[test] fn test_vc_to_sc_empty_graph() { // Graph with no edges - let vc_problem = MinimumVertexCover::::new(3, vec![]); + let vc_problem = MinimumVertexCover::new(SimpleGraph::new(3, vec![]), vec![1i32; 3]); let reduction = ReduceTo::>::reduce_to(&vc_problem); let sc_problem = reduction.target_problem(); @@ -82,7 +82,7 @@ fn test_vc_to_sc_empty_graph() { fn test_vc_to_sc_star_graph() { // Star graph: center vertex 0 connected to all others // Edges: (0,1), (0,2), (0,3) - let vc_problem = MinimumVertexCover::::new(4, vec![(0, 1), (0, 2), (0, 3)]); + let vc_problem = MinimumVertexCover::new(SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3)]), vec![1i32; 4]); let reduction = ReduceTo::>::reduce_to(&vc_problem); let sc_problem = reduction.target_problem(); @@ -108,9 +108,9 @@ fn test_jl_parity_vc_to_setcovering() { let vc_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/vertexcovering.json")).unwrap(); let inst = &vc_data["instances"][0]["instance"]; - let source = MinimumVertexCover::with_weights( - inst["num_vertices"].as_u64().unwrap() as usize, - jl_parse_edges(inst), + let nv = inst["num_vertices"].as_u64().unwrap() as usize; + let source = MinimumVertexCover::new( + SimpleGraph::new(nv, jl_parse_edges(inst)), jl_parse_i32_vec(&inst["weights"]), ); let result = ReduceTo::>::reduce_to(&source); @@ -136,9 +136,9 @@ fn test_jl_parity_rule_vc_to_setcovering() { let vc_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/vertexcovering.json")).unwrap(); let inst = &jl_find_instance_by_label(&vc_data, "rule_4vertex")["instance"]; - let source = MinimumVertexCover::with_weights( - inst["num_vertices"].as_u64().unwrap() as usize, - jl_parse_edges(inst), + let nv = inst["num_vertices"].as_u64().unwrap() as usize; + let source = MinimumVertexCover::new( + SimpleGraph::new(nv, jl_parse_edges(inst)), jl_parse_i32_vec(&inst["weights"]), ); let result = ReduceTo::>::reduce_to(&source); diff --git a/src/unit_tests/rules/minimumvertexcover_qubo.rs b/src/unit_tests/rules/minimumvertexcover_qubo.rs index a742d4f73..99992af84 100644 --- a/src/unit_tests/rules/minimumvertexcover_qubo.rs +++ b/src/unit_tests/rules/minimumvertexcover_qubo.rs @@ -6,7 +6,7 @@ use crate::traits::Problem; fn test_vertexcovering_to_qubo_closed_loop() { // Cycle C4: 0-1-2-3-0 (4 vertices, 4 edges) // Minimum VC = 2 vertices (e.g., {0, 2} or {1, 3}) - let vc = MinimumVertexCover::::new(4, vec![(0, 1), (1, 2), (2, 3), (0, 3)]); + let vc = MinimumVertexCover::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3), (0, 3)]), vec![1i32; 4]); let reduction = ReduceTo::>::reduce_to(&vc); let qubo = reduction.target_problem(); @@ -23,7 +23,7 @@ fn test_vertexcovering_to_qubo_closed_loop() { #[test] fn test_vertexcovering_to_qubo_triangle() { // Triangle K3: minimum VC = 2 (any two vertices) - let vc = MinimumVertexCover::::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let vc = MinimumVertexCover::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), vec![1i32; 3]); let reduction = ReduceTo::>::reduce_to(&vc); let qubo = reduction.target_problem(); @@ -41,7 +41,7 @@ fn test_vertexcovering_to_qubo_triangle() { fn test_vertexcovering_to_qubo_star() { // Star graph: center vertex 0 connected to 1, 2, 3 // Minimum VC = {0} (just the center) - let vc = MinimumVertexCover::::new(4, vec![(0, 1), (0, 2), (0, 3)]); + let vc = MinimumVertexCover::new(SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3)]), vec![1i32; 4]); let reduction = ReduceTo::>::reduce_to(&vc); let qubo = reduction.target_problem(); @@ -57,7 +57,7 @@ fn test_vertexcovering_to_qubo_star() { #[test] fn test_vertexcovering_to_qubo_structure() { - let vc = MinimumVertexCover::::new(4, vec![(0, 1), (1, 2), (2, 3), (0, 3)]); + let vc = MinimumVertexCover::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3), (0, 3)]), vec![1i32; 4]); let reduction = ReduceTo::>::reduce_to(&vc); let qubo = reduction.target_problem(); diff --git a/src/unit_tests/rules/spinglass_maxcut.rs b/src/unit_tests/rules/spinglass_maxcut.rs index 0d46d8d76..c0d9a38b6 100644 --- a/src/unit_tests/rules/spinglass_maxcut.rs +++ b/src/unit_tests/rules/spinglass_maxcut.rs @@ -52,7 +52,7 @@ fn test_solution_extraction_with_ancilla() { #[test] fn test_weighted_maxcut() { - let mc = MaxCut::::new(3, vec![(0, 1, 10), (1, 2, 20)]); + let mc = MaxCut::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![10, 20]); let reduction = ReduceTo::>::reduce_to(&mc); let sg = reduction.target_problem(); @@ -64,7 +64,7 @@ fn test_weighted_maxcut() { #[test] fn test_reduction_structure() { // Test MaxCut to SpinGlass structure - let mc = MaxCut::::unweighted(3, vec![(0, 1), (1, 2)]); + let mc = MaxCut::<_, i32>::unweighted(SimpleGraph::new(3, vec![(0, 1), (1, 2)])); let reduction = ReduceTo::>::reduce_to(&mc); let sg = reduction.target_problem(); @@ -119,7 +119,9 @@ fn test_jl_parity_maxcut_to_spinglass() { let inst = &mc_data["instances"][0]["instance"]; let nv = inst["num_vertices"].as_u64().unwrap() as usize; let weighted_edges = jl_parse_weighted_edges(inst); - let source = MaxCut::::new(nv, weighted_edges); + let edges: Vec<(usize, usize)> = weighted_edges.iter().map(|&(u, v, _)| (u, v)).collect(); + let weights: Vec = weighted_edges.into_iter().map(|(_, _, w)| w).collect(); + let source = MaxCut::new(SimpleGraph::new(nv, edges), weights); let result = ReduceTo::>::reduce_to(&source); let solver = BruteForce::new(); let best_target = solver.find_all_best(result.target_problem()); @@ -143,9 +145,12 @@ fn test_jl_parity_rule_maxcut_to_spinglass() { let mc_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/maxcut.json")).unwrap(); let inst = &jl_find_instance_by_label(&mc_data, "rule_4vertex")["instance"]; - let source = MaxCut::::new( - inst["num_vertices"].as_u64().unwrap() as usize, - jl_parse_weighted_edges(inst), + let weighted_edges = jl_parse_weighted_edges(inst); + let edges: Vec<(usize, usize)> = weighted_edges.iter().map(|&(u, v, _)| (u, v)).collect(); + let weights: Vec = weighted_edges.into_iter().map(|(_, _, w)| w).collect(); + let source = MaxCut::new( + SimpleGraph::new(inst["num_vertices"].as_u64().unwrap() as usize, edges), + weights, ); let result = ReduceTo::>::reduce_to(&source); let solver = BruteForce::new(); diff --git a/src/unit_tests/rules/travelingsalesman_ilp.rs b/src/unit_tests/rules/travelingsalesman_ilp.rs index c7fb97736..c3e260a73 100644 --- a/src/unit_tests/rules/travelingsalesman_ilp.rs +++ b/src/unit_tests/rules/travelingsalesman_ilp.rs @@ -1,13 +1,21 @@ use super::*; use crate::solvers::{BruteForce, ILPSolver}; +use crate::topology::SimpleGraph; use crate::traits::Problem; use crate::types::SolutionSize; +fn k4_tsp() -> TravelingSalesman { + TravelingSalesman::new( + SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]), + vec![10, 15, 20, 35, 25, 30], + ) +} + #[test] fn test_reduction_creates_valid_ilp_c4() { // C4 cycle: 4 vertices, 4 edges. Unique Hamiltonian cycle (the cycle itself). let problem = - TravelingSalesman::::unweighted(4, vec![(0, 1), (1, 2), (2, 3), (3, 0)]); + TravelingSalesman::<_, i32>::unit_weights(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3), (3, 0)])); let reduction: ReductionTSPToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -25,7 +33,7 @@ fn test_reduction_creates_valid_ilp_c4() { fn test_reduction_c4_closed_loop() { // C4 cycle with unit weights: optimal tour cost = 4 let problem = - TravelingSalesman::::unweighted(4, vec![(0, 1), (1, 2), (2, 3), (3, 0)]); + TravelingSalesman::<_, i32>::unit_weights(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3), (3, 0)])); let reduction: ReductionTSPToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -42,17 +50,7 @@ fn test_reduction_c4_closed_loop() { #[test] fn test_reduction_k4_weighted_closed_loop() { // K4 weighted: find minimum weight Hamiltonian cycle - let problem = TravelingSalesman::::new( - 4, - vec![ - (0, 1, 10), - (0, 2, 15), - (0, 3, 20), - (1, 2, 35), - (1, 3, 25), - (2, 3, 30), - ], - ); + let problem = k4_tsp(); // Solve via ILP reduction let reduction: ReductionTSPToILP = ReduceTo::::reduce_to(&problem); @@ -77,9 +75,8 @@ fn test_reduction_k4_weighted_closed_loop() { #[test] fn test_reduction_c5_unweighted_closed_loop() { // C5 cycle with unit weights - let problem = TravelingSalesman::::unweighted( - 5, - vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], + let problem = TravelingSalesman::<_, i32>::unit_weights( + SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)]), ); let reduction: ReductionTSPToILP = ReduceTo::::reduce_to(&problem); @@ -97,7 +94,7 @@ fn test_reduction_c5_unweighted_closed_loop() { fn test_no_hamiltonian_cycle_infeasible() { // Path graph 0-1-2-3: no Hamiltonian cycle exists let problem = - TravelingSalesman::::unweighted(4, vec![(0, 1), (1, 2), (2, 3)]); + TravelingSalesman::<_, i32>::unit_weights(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)])); let reduction: ReductionTSPToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -114,7 +111,7 @@ fn test_no_hamiltonian_cycle_infeasible() { fn test_solution_extraction_structure() { // C4 cycle: verify extraction produces correct edge selection format let problem = - TravelingSalesman::::unweighted(4, vec![(0, 1), (1, 2), (2, 3), (3, 0)]); + TravelingSalesman::<_, i32>::unit_weights(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3), (3, 0)])); let reduction: ReductionTSPToILP = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -131,17 +128,7 @@ fn test_solution_extraction_structure() { #[test] fn test_solve_reduced() { // Test via ILPSolver::solve_reduced - let problem = TravelingSalesman::::new( - 4, - vec![ - (0, 1, 10), - (0, 2, 15), - (0, 3, 20), - (1, 2, 35), - (1, 3, 25), - (2, 3, 30), - ], - ); + let problem = k4_tsp(); let ilp_solver = ILPSolver::new(); let solution = ilp_solver diff --git a/src/unit_tests/solvers/brute_force.rs b/src/unit_tests/solvers/brute_force.rs index 7ce9f718d..87044ca7c 100644 --- a/src/unit_tests/solvers/brute_force.rs +++ b/src/unit_tests/solvers/brute_force.rs @@ -246,7 +246,7 @@ fn test_solver_with_real_mis() { use crate::traits::Problem; // Triangle graph: MIS = 1 - let problem = MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), vec![1i32; 3]); let solver = BruteForce::new(); let best = solver.find_all_best(&problem); diff --git a/src/unit_tests/testing/macros.rs b/src/unit_tests/testing/macros.rs index f97619802..8e2f7cc88 100644 --- a/src/unit_tests/testing/macros.rs +++ b/src/unit_tests/testing/macros.rs @@ -6,31 +6,55 @@ use crate::types::SolutionSize; #[test] fn test_quick_problem_test_macro() { // Test a valid solution - quick_problem_test!( - MaximumIndependentSet, - new(3, vec![(0, 1), (1, 2)]), - solution: [1, 0, 1], - expected_value: SolutionSize::Valid(2), - is_max: true - ); + { + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); + let solution = vec![1, 0, 1]; + let result = problem.evaluate(&solution); + assert_eq!(result, SolutionSize::Valid(2)); + } // Test an invalid solution (adjacent vertices selected) -> returns Invalid - quick_problem_test!( - MaximumIndependentSet, - new(3, vec![(0, 1), (1, 2)]), - solution: [1, 1, 0], - expected_value: SolutionSize::Invalid, - is_max: true - ); + { + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); + let solution = vec![1, 1, 0]; + let result = problem.evaluate(&solution); + assert_eq!(result, SolutionSize::Invalid); + } } -// Test the complement_test macro -complement_test! { - name: test_is_vc_complement, - problem_a: MaximumIndependentSet, - problem_b: MinimumVertexCover, - test_graphs: [ - (3, [(0, 1), (1, 2)]), - (4, [(0, 1), (1, 2), (2, 3), (0, 3)]), - ] +// Test the complement_test macro - manually implemented since MIS constructor changed +#[test] +fn test_is_vc_complement() { + use crate::prelude::*; + + for (n, edges) in [ + (3usize, vec![(0, 1), (1, 2)]), + (4usize, vec![(0, 1), (1, 2), (2, 3), (0, 3)]), + ] { + let problem_a = MaximumIndependentSet::new(SimpleGraph::new(n, edges.clone()), vec![1i32; n]); + let problem_b = MinimumVertexCover::new(SimpleGraph::new(n, edges), vec![1i32; n]); + + let solver = BruteForce::new(); + let solutions_a = solver.find_all_best(&problem_a); + let solutions_b = solver.find_all_best(&problem_b); + + let size_a: usize = solutions_a[0].iter().sum(); + let size_b: usize = solutions_b[0].iter().sum(); + + assert_eq!( + size_a + size_b, + n, + "Complement relationship failed for graph with {} vertices", + n + ); + + for sol_a in &solutions_a { + let complement: Vec = sol_a.iter().map(|&x| 1 - x).collect(); + let value = problem_b.evaluate(&complement); + assert!( + value.is_valid(), + "Complement of A solution should be valid for B" + ); + } + } } diff --git a/src/unit_tests/trait_consistency.rs b/src/unit_tests/trait_consistency.rs index f85f457b1..d123c9c04 100644 --- a/src/unit_tests/trait_consistency.rs +++ b/src/unit_tests/trait_consistency.rs @@ -26,31 +26,31 @@ fn check_problem_trait(problem: &P, name: &str) { #[test] fn test_all_problems_implement_trait_correctly() { check_problem_trait( - &MaximumIndependentSet::::new(3, vec![(0, 1)]), + &MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1i32; 3]), "MaximumIndependentSet", ); check_problem_trait( - &MinimumVertexCover::::new(3, vec![(0, 1)]), + &MinimumVertexCover::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1i32; 3]), "MinimumVertexCover", ); check_problem_trait( - &MaxCut::::new(3, vec![(0, 1, 1)]), + &MaxCut::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1i32]), "MaxCut", ); check_problem_trait( - &KColoring::::new(3, vec![(0, 1)]), + &KColoring::::new(SimpleGraph::new(3, vec![(0, 1)])), "KColoring", ); check_problem_trait( - &MinimumDominatingSet::::new(3, vec![(0, 1)]), + &MinimumDominatingSet::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1i32; 3]), "MinimumDominatingSet", ); check_problem_trait( - &MaximalIS::::new(3, vec![(0, 1)]), + &MaximalIS::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1i32; 3]), "MaximalIS", ); check_problem_trait( - &MaximumMatching::::new(3, vec![(0, 1, 1)]), + &MaximumMatching::new(SimpleGraph::new(3, vec![(0, 1)]), vec![1i32]), "MaximumMatching", ); check_problem_trait( @@ -89,11 +89,11 @@ fn test_direction() { // Minimization problems assert_eq!( - MinimumVertexCover::::new(2, vec![(0, 1)]).direction(), + MinimumVertexCover::new(SimpleGraph::new(2, vec![(0, 1)]), vec![1i32; 2]).direction(), Direction::Minimize ); assert_eq!( - MinimumDominatingSet::::new(2, vec![(0, 1)]).direction(), + MinimumDominatingSet::new(SimpleGraph::new(2, vec![(0, 1)]), vec![1i32; 2]).direction(), Direction::Minimize ); assert_eq!( @@ -124,19 +124,19 @@ fn test_direction() { // Maximization problems assert_eq!( - MaximumIndependentSet::::new(2, vec![(0, 1)]).direction(), + MaximumIndependentSet::new(SimpleGraph::new(2, vec![(0, 1)]), vec![1i32; 2]).direction(), Direction::Maximize ); assert_eq!( - MaximalIS::::new(2, vec![(0, 1)]).direction(), + MaximalIS::new(SimpleGraph::new(2, vec![(0, 1)]), vec![1i32; 2]).direction(), Direction::Maximize ); assert_eq!( - MaxCut::::new(2, vec![(0, 1, 1)]).direction(), + MaxCut::new(SimpleGraph::new(2, vec![(0, 1)]), vec![1i32]).direction(), Direction::Maximize ); assert_eq!( - MaximumMatching::::new(2, vec![(0, 1, 1)]).direction(), + MaximumMatching::new(SimpleGraph::new(2, vec![(0, 1)]), vec![1i32]).direction(), Direction::Maximize ); assert_eq!( @@ -144,7 +144,7 @@ fn test_direction() { Direction::Maximize ); assert_eq!( - MaximumClique::::new(2, vec![(0, 1)]).direction(), + MaximumClique::new(SimpleGraph::new(2, vec![(0, 1)]), vec![1i32; 2]).direction(), Direction::Maximize ); } diff --git a/src/unit_tests/unitdiskmapping_algorithms/common.rs b/src/unit_tests/unitdiskmapping_algorithms/common.rs index 7a3a29488..a41e3836f 100644 --- a/src/unit_tests/unitdiskmapping_algorithms/common.rs +++ b/src/unit_tests/unitdiskmapping_algorithms/common.rs @@ -20,7 +20,7 @@ pub fn is_independent_set(edges: &[(usize, usize)], config: &[usize]) -> bool { /// Solve maximum independent set using ILP. /// Returns the size of the MIS. pub fn solve_mis(num_vertices: usize, edges: &[(usize, usize)]) -> usize { - let problem = MaximumIndependentSet::::new(num_vertices, edges.to_vec()); + let problem = MaximumIndependentSet::new(SimpleGraph::new(num_vertices, edges.to_vec()), vec![1i32; num_vertices]); let reduction = as ReduceTo>::reduce_to(&problem); let solver = ILPSolver::new(); if let Some(solution) = solver.solve(reduction.target_problem()) { @@ -32,7 +32,7 @@ pub fn solve_mis(num_vertices: usize, edges: &[(usize, usize)]) -> usize { /// Solve MIS and return the binary configuration. pub fn solve_mis_config(num_vertices: usize, edges: &[(usize, usize)]) -> Vec { - let problem = MaximumIndependentSet::::new(num_vertices, edges.to_vec()); + let problem = MaximumIndependentSet::new(SimpleGraph::new(num_vertices, edges.to_vec()), vec![1i32; num_vertices]); let reduction = as ReduceTo>::reduce_to(&problem); let solver = ILPSolver::new(); if let Some(solution) = solver.solve(reduction.target_problem()) { diff --git a/tests/suites/integration.rs b/tests/suites/integration.rs index 3db133681..20dcbbded 100644 --- a/tests/suites/integration.rs +++ b/tests/suites/integration.rs @@ -18,7 +18,7 @@ mod all_problems_solvable { #[test] fn test_independent_set_solvable() { let problem = - MaximumIndependentSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + MaximumIndependentSet::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1i32; 4]); let solver = BruteForce::new(); let solutions = solver.find_all_best(&problem); assert!(!solutions.is_empty()); @@ -29,7 +29,7 @@ mod all_problems_solvable { #[test] fn test_vertex_covering_solvable() { - let problem = MinimumVertexCover::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = MinimumVertexCover::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1i32; 4]); let solver = BruteForce::new(); let solutions = solver.find_all_best(&problem); assert!(!solutions.is_empty()); @@ -40,7 +40,7 @@ mod all_problems_solvable { #[test] fn test_max_cut_solvable() { - let problem = MaxCut::::new(4, vec![(0, 1, 1), (1, 2, 2), (2, 3, 1)]); + let problem = MaxCut::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1, 2, 1]); let solver = BruteForce::new(); let solutions = solver.find_all_best(&problem); assert!(!solutions.is_empty()); @@ -48,7 +48,7 @@ mod all_problems_solvable { #[test] fn test_coloring_solvable() { - let problem = KColoring::::new(3, vec![(0, 1), (1, 2)]); + let problem = KColoring::::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)])); let solver = BruteForce::new(); // KColoring returns bool, so we can use find_all_satisfying let satisfying = solver.find_all_satisfying(&problem); @@ -61,7 +61,7 @@ mod all_problems_solvable { #[test] fn test_dominating_set_solvable() { let problem = - MinimumDominatingSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + MinimumDominatingSet::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1i32; 4]); let solver = BruteForce::new(); let solutions = solver.find_all_best(&problem); assert!(!solutions.is_empty()); @@ -72,7 +72,7 @@ mod all_problems_solvable { #[test] fn test_maximal_is_solvable() { - let problem = MaximalIS::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = MaximalIS::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1i32; 4]); let solver = BruteForce::new(); let solutions = solver.find_all_best(&problem); assert!(!solutions.is_empty()); @@ -84,7 +84,7 @@ mod all_problems_solvable { #[test] fn test_matching_solvable() { let problem = - MaximumMatching::::new(4, vec![(0, 1, 1), (1, 2, 2), (2, 3, 1)]); + MaximumMatching::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1, 2, 1]); let solver = BruteForce::new(); let solutions = solver.find_all_best(&problem); assert!(!solutions.is_empty()); @@ -233,8 +233,8 @@ mod problem_relationships { let edges = vec![(0, 1), (1, 2), (2, 3), (0, 3)]; let n = 4; - let is_problem = MaximumIndependentSet::::new(n, edges.clone()); - let vc_problem = MinimumVertexCover::::new(n, edges); + let is_problem = MaximumIndependentSet::new(SimpleGraph::new(n, edges.clone()), vec![1i32; n]); + let vc_problem = MinimumVertexCover::new(SimpleGraph::new(n, edges), vec![1i32; n]); let solver = BruteForce::new(); let is_solutions = solver.find_all_best(&is_problem); @@ -253,8 +253,8 @@ mod problem_relationships { let edges = vec![(0, 1), (1, 2), (2, 3)]; let n = 4; - let maximal_is = MaximalIS::::new(n, edges.clone()); - let is_problem = MaximumIndependentSet::::new(n, edges); + let maximal_is = MaximalIS::new(SimpleGraph::new(n, edges.clone()), vec![1i32; n]); + let is_problem = MaximumIndependentSet::new(SimpleGraph::new(n, edges), vec![1i32; n]); let solver = BruteForce::new(); let maximal_solutions = solver.find_all_best(&maximal_is); @@ -332,7 +332,7 @@ mod edge_cases { #[test] fn test_empty_graph_independent_set() { - let problem = MaximumIndependentSet::::new(3, vec![]); + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![]), vec![1i32; 3]); let solver = BruteForce::new(); let solutions = solver.find_all_best(&problem); @@ -344,7 +344,7 @@ mod edge_cases { fn test_complete_graph_independent_set() { // K4 - complete graph on 4 vertices let edges = vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]; - let problem = MaximumIndependentSet::::new(4, edges); + let problem = MaximumIndependentSet::new(SimpleGraph::new(4, edges), vec![1i32; 4]); let solver = BruteForce::new(); let solutions = solver.find_all_best(&problem); @@ -401,9 +401,8 @@ mod weighted_problems { #[test] fn test_weighted_independent_set() { - let problem = MaximumIndependentSet::::with_weights( - 3, - vec![(0, 1)], + let problem = MaximumIndependentSet::new( + SimpleGraph::new(3, vec![(0, 1)]), vec![10, 1, 1], ); @@ -422,9 +421,8 @@ mod weighted_problems { #[test] fn test_weighted_vertex_cover() { - let problem = MinimumVertexCover::::with_weights( - 3, - vec![(0, 1), (1, 2)], + let problem = MinimumVertexCover::new( + SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1, 10, 1], ); @@ -442,7 +440,7 @@ mod weighted_problems { #[test] fn test_weighted_max_cut() { - let problem = MaxCut::new(3, vec![(0, 1, 10), (1, 2, 1)]); + let problem = MaxCut::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![10, 1]); let solver = BruteForce::new(); let solutions = solver.find_all_best(&problem); diff --git a/tests/suites/reductions.rs b/tests/suites/reductions.rs index d3ca5b784..96e53b717 100644 --- a/tests/suites/reductions.rs +++ b/tests/suites/reductions.rs @@ -14,7 +14,7 @@ mod is_vc_reductions { fn test_is_to_vc_basic() { // Triangle graph let is_problem = - MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2), (0, 2)]); + MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), vec![1i32; 3]); // Reduce IS to VC let result = ReduceTo::>::reduce_to(&is_problem); @@ -39,7 +39,7 @@ mod is_vc_reductions { fn test_vc_to_is_basic() { // Path graph let vc_problem = - MinimumVertexCover::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + MinimumVertexCover::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1i32; 4]); // Reduce VC to IS let result = ReduceTo::>::reduce_to(&vc_problem); @@ -63,7 +63,7 @@ mod is_vc_reductions { #[test] fn test_is_vc_roundtrip() { let original = - MaximumIndependentSet::::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]); + MaximumIndependentSet::new(SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]), vec![1i32; 5]); // IS -> VC let to_vc = ReduceTo::>::reduce_to(&original); @@ -94,7 +94,7 @@ mod is_vc_reductions { #[test] fn test_is_vc_weighted() { - let is_problem = MaximumIndependentSet::with_weights(3, vec![(0, 1)], vec![10, 1, 5]); + let is_problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1)]), vec![10, 1, 5]); let result = ReduceTo::>::reduce_to(&is_problem); let vc_problem = result.target_problem(); @@ -109,8 +109,8 @@ mod is_vc_reductions { let edges = vec![(0, 1), (1, 2), (2, 3), (0, 3)]; let n = 4; - let is_problem = MaximumIndependentSet::::new(n, edges.clone()); - let vc_problem = MinimumVertexCover::::new(n, edges); + let is_problem = MaximumIndependentSet::new(SimpleGraph::new(n, edges.clone()), vec![1i32; n]); + let vc_problem = MinimumVertexCover::new(SimpleGraph::new(n, edges), vec![1i32; n]); let solver = BruteForce::new(); @@ -133,7 +133,7 @@ mod is_sp_reductions { fn test_is_to_sp_basic() { // Triangle graph - each vertex's incident edges become a set let is_problem = - MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2), (0, 2)]); + MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), vec![1i32; 3]); let result = ReduceTo::>::reduce_to(&is_problem); let sp_problem = result.target_problem(); @@ -178,7 +178,7 @@ mod is_sp_reductions { #[test] fn test_is_sp_roundtrip() { let original = - MaximumIndependentSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + MaximumIndependentSet::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1i32; 4]); // IS -> SP let to_sp = ReduceTo::>::reduce_to(&original); @@ -310,7 +310,7 @@ mod sg_maxcut_reductions { #[test] fn test_maxcut_to_sg_basic() { - let maxcut = MaxCut::new(3, vec![(0, 1, 2), (1, 2, 1), (0, 2, 3)]); + let maxcut = MaxCut::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), vec![2, 1, 3]); let result = ReduceTo::>::reduce_to(&maxcut); let sg = result.target_problem(); @@ -394,7 +394,7 @@ mod topology_tests { // Extract edges let edges = udg.edges().to_vec(); - let is_problem = MaximumIndependentSet::::new(4, edges); + let is_problem = MaximumIndependentSet::new(SimpleGraph::new(4, edges), vec![1i32; 4]); let solver = BruteForce::new(); let solutions = solver.find_all_best(&is_problem); @@ -484,9 +484,10 @@ mod qubo_reductions { std::fs::read_to_string("tests/data/qubo/maximumindependentset_to_qubo.json").unwrap(); let data: ISToQuboData = serde_json::from_str(&json).unwrap(); - let is = MaximumIndependentSet::::new( - data.source.num_vertices, - data.source.edges, + let n = data.source.num_vertices; + let is = MaximumIndependentSet::new( + SimpleGraph::new(n, data.source.edges), + vec![1i32; n], ); let reduction = ReduceTo::::reduce_to(&is); let qubo = reduction.target_problem(); @@ -527,9 +528,10 @@ mod qubo_reductions { std::fs::read_to_string("tests/data/qubo/minimumvertexcover_to_qubo.json").unwrap(); let data: VCToQuboData = serde_json::from_str(&json).unwrap(); - let vc = MinimumVertexCover::::new( - data.source.num_vertices, - data.source.edges, + let n = data.source.num_vertices; + let vc = MinimumVertexCover::new( + SimpleGraph::new(n, data.source.edges), + vec![1i32; n], ); let reduction = ReduceTo::::reduce_to(&vc); let qubo = reduction.target_problem(); @@ -571,7 +573,7 @@ mod qubo_reductions { assert_eq!(data.source.num_colors, 3); - let kc = KColoring::::new(data.source.num_vertices, data.source.edges); + let kc = KColoring::::new(SimpleGraph::new(data.source.num_vertices, data.source.edges)); let reduction = ReduceTo::::reduce_to(&kc); let qubo = reduction.target_problem(); @@ -786,7 +788,7 @@ mod io_tests { #[test] fn test_serialize_reduce_deserialize() { let original = - MaximumIndependentSet::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + MaximumIndependentSet::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), vec![1i32; 4]); // Serialize let json = to_json(&original).unwrap(); @@ -841,9 +843,9 @@ mod end_to_end { #[test] fn test_full_pipeline_is_vc_sp() { // Start with an MaximumIndependentSet problem - let is = MaximumIndependentSet::::new( - 5, - vec![(0, 1), (1, 2), (2, 3), (3, 4), (0, 4)], + let is = MaximumIndependentSet::new( + SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4), (0, 4)]), + vec![1i32; 5], ); // Solve directly