problemreductions/models/graph/template.rs
1//! Generic graph problem template.
2//!
3//! This module provides a template for defining binary graph problems with minimal
4//! boilerplate. Instead of writing ~400 lines per problem, you can define a new
5//! graph problem in ~15 lines by implementing [`GraphConstraint`].
6//!
7//! # Overview
8//!
9//! Binary graph problems share a common structure:
10//! - Each vertex can be selected (1) or not selected (0)
11//! - Edges impose constraints on which pairs can be simultaneously selected
12//! - The objective is to maximize or minimize the total weight of selected vertices
13//!
14//! This template captures this pattern via:
15//! - [`GraphConstraint`] - Trait defining problem-specific semantics
16//! - [`GraphProblem<C, W>`] - Generic struct implementing all standard traits
17//!
18//! # Quick Start
19//!
20//! ```rust
21//! use problemreductions::models::graph::{GraphConstraint, GraphProblem};
22//! use problemreductions::topology::SimpleGraph;
23//! use problemreductions::registry::GraphSubcategory;
24//! use problemreductions::types::EnergyMode;
25//!
26//! // Step 1: Define the constraint
27//! #[derive(Debug, Clone, Copy)]
28//! pub struct MyConstraint;
29//!
30//! impl GraphConstraint for MyConstraint {
31//! const NAME: &'static str = "My Problem";
32//! const DESCRIPTION: &'static str = "Description of my problem";
33//! const ENERGY_MODE: EnergyMode = EnergyMode::LargerSizeIsBetter;
34//! const SUBCATEGORY: GraphSubcategory = GraphSubcategory::Independent;
35//!
36//! fn edge_constraint_spec() -> [bool; 4] {
37//! // [neither, only_v, only_u, both] selected
38//! [true, true, true, false] // At most one endpoint
39//! }
40//! }
41//!
42//! // Step 2: Create a type alias (G defaults to SimpleGraph, W defaults to i32)
43//! pub type MyProblem<G = SimpleGraph, W = i32> = GraphProblem<MyConstraint, G, W>;
44//!
45//! // Step 3: Use it!
46//! let problem: MyProblem = MyProblem::new(3, vec![(0, 1), (1, 2)]);
47//! ```
48//!
49//! # Built-in Problem Types
50//!
51//! | Type Alias | Constraint | Energy Mode | Edge Spec |
52//! |------------|------------|-------------|-----------|
53//! | [`IndependentSetT`] | At most one selected | Maximize | `[T,T,T,F]` |
54//! | [`VertexCoverT`] | At least one selected | Minimize | `[F,T,T,T]` |
55//! | [`CliqueT`] | For complement graph | Maximize | `[T,T,T,F]` |
56//!
57//! # Edge Constraint Specification
58//!
59//! The [`edge_constraint_spec`](GraphConstraint::edge_constraint_spec) method returns
60//! a 4-element array `[bool; 4]` indexed by `(u_selected * 2 + v_selected)`:
61//!
62//! | Index | u | v | Meaning |
63//! |-------|---|---|---------|
64//! | `0` | `0` | `0` | Neither endpoint selected |
65//! | `1` | `0` | `1` | Only v selected |
66//! | `2` | `1` | `0` | Only u selected |
67//! | `3` | `1` | `1` | Both endpoints selected |
68//!
69//! Common patterns:
70//! - **Independent Set**: `[true, true, true, false]` - at most one selected
71//! - **Vertex Cover**: `[false, true, true, true]` - at least one selected
72//! - **Perfect Matching**: Define on edge graph with exactly one selected
73
74use crate::graph_types::SimpleGraph as SimpleGraphMarker;
75use crate::registry::{ComplexityClass, GraphSubcategory, ProblemCategory, ProblemInfo, ProblemMetadata};
76use crate::topology::{Graph, SimpleGraph};
77use crate::traits::{ConstraintSatisfactionProblem, Problem};
78use crate::types::{EnergyMode, LocalConstraint, LocalSolutionSize, ProblemSize, SolutionSize};
79use num_traits::{Num, Zero};
80use serde::{Deserialize, Serialize};
81use std::marker::PhantomData;
82use std::ops::AddAssign;
83
84/// Trait defining the constraint semantics for a binary graph problem.
85///
86/// Implement this trait to define a new graph problem type. The trait specifies
87/// how edges constrain the selection of vertices and what the optimization objective is.
88///
89/// # Example
90///
91/// ```rust,ignore
92/// pub struct IndependentSetConstraint;
93///
94/// impl GraphConstraint for IndependentSetConstraint {
95/// const NAME: &'static str = "Independent Set";
96/// const DESCRIPTION: &'static str = "Find maximum weight set of non-adjacent vertices";
97/// const ENERGY_MODE: EnergyMode = EnergyMode::LargerSizeIsBetter;
98/// const SUBCATEGORY: GraphSubcategory = GraphSubcategory::Independent;
99///
100/// fn edge_constraint_spec() -> [bool; 4] {
101/// [true, true, true, false] // (0,0), (0,1), (1,0) OK; (1,1) invalid
102/// }
103/// }
104/// ```
105pub trait GraphConstraint: Clone + Send + Sync + 'static {
106 /// The canonical name of this problem.
107 const NAME: &'static str;
108
109 /// Brief description of the problem.
110 const DESCRIPTION: &'static str;
111
112 /// Whether to maximize or minimize the objective.
113 const ENERGY_MODE: EnergyMode;
114
115 /// The graph subcategory this problem belongs to.
116 const SUBCATEGORY: GraphSubcategory;
117
118 /// Alternative names for this problem (default: empty).
119 const ALIASES: &'static [&'static str] = &[];
120
121 /// The problem this canonically reduces from (default: None).
122 const REDUCES_FROM: Option<&'static str> = None;
123
124 /// The edge constraint specification.
125 ///
126 /// Returns a 4-element array representing which (u_selected, v_selected)
127 /// combinations are valid for an edge (u, v):
128 /// - Index 0: (0, 0) - neither endpoint selected
129 /// - Index 1: (0, 1) - only v selected
130 /// - Index 2: (1, 0) - only u selected
131 /// - Index 3: (1, 1) - both endpoints selected
132 fn edge_constraint_spec() -> [bool; 4];
133
134 /// Check if an edge is satisfied given endpoint selection states.
135 ///
136 /// Default implementation uses `edge_constraint_spec()`.
137 fn is_edge_satisfied(u_selected: bool, v_selected: bool) -> bool {
138 let spec = Self::edge_constraint_spec();
139 let index = (u_selected as usize) * 2 + (v_selected as usize);
140 spec[index]
141 }
142
143 /// Get the problem info for this constraint type.
144 fn problem_info() -> ProblemInfo {
145 ProblemInfo::new(Self::NAME, Self::DESCRIPTION)
146 .with_aliases(Self::ALIASES)
147 .with_complexity(ComplexityClass::NpComplete)
148 }
149
150 /// Get the problem category.
151 fn category() -> ProblemCategory {
152 ProblemCategory::Graph(Self::SUBCATEGORY)
153 }
154}
155
156/// A generic graph problem parameterized by constraint type, graph type, and weight type.
157///
158/// This struct provides a standard implementation for binary graph problems where:
159/// - Each vertex can be either selected (1) or not selected (0)
160/// - Edges impose constraints on which pairs of vertices can be simultaneously selected
161/// - The objective is to maximize or minimize the total weight of selected vertices
162///
163/// # Type Parameters
164///
165/// - `C`: The constraint type implementing [`GraphConstraint`]
166/// - `G`: The graph type implementing [`Graph`] (default: [`SimpleGraph`])
167/// - `W`: The weight type (default: `i32`)
168///
169/// # Example
170///
171/// ```rust,ignore
172/// use problemreductions::topology::{SimpleGraph, UnitDiskGraph};
173///
174/// // Define Independent Set as a type alias (defaults to SimpleGraph)
175/// pub type IndependentSet<G = SimpleGraph, W = i32> = GraphProblem<IndependentSetConstraint, G, W>;
176///
177/// // Create an instance with SimpleGraph (default)
178/// let problem = IndependentSet::new(4, vec![(0, 1), (1, 2), (2, 3)]);
179///
180/// // Create an instance with UnitDiskGraph for quantum hardware
181/// let udg = UnitDiskGraph::new(vec![(0.0, 0.0), (1.0, 0.0), (2.0, 0.0)], 1.5);
182/// let problem_udg = IndependentSet::<UnitDiskGraph>::from_graph(udg);
183/// ```
184#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct GraphProblem<C: GraphConstraint, G: Graph = SimpleGraph, W = i32> {
186 /// The underlying graph structure.
187 graph: G,
188 /// Weights for each vertex.
189 weights: Vec<W>,
190 /// Phantom data to track the constraint type.
191 #[serde(skip)]
192 _constraint: PhantomData<C>,
193}
194
195impl<C: GraphConstraint, W: Clone + Default> GraphProblem<C, SimpleGraph, W> {
196 /// Create a new graph problem with unit weights using SimpleGraph.
197 ///
198 /// # Arguments
199 /// * `num_vertices` - Number of vertices in the graph
200 /// * `edges` - List of edges as (u, v) pairs
201 pub fn new(num_vertices: usize, edges: Vec<(usize, usize)>) -> Self
202 where
203 W: From<i32>,
204 {
205 let graph = SimpleGraph::new(num_vertices, edges);
206 let weights = vec![W::from(1); num_vertices];
207 Self {
208 graph,
209 weights,
210 _constraint: PhantomData,
211 }
212 }
213
214 /// Create a new graph problem with custom weights using SimpleGraph.
215 ///
216 /// # Arguments
217 /// * `num_vertices` - Number of vertices in the graph
218 /// * `edges` - List of edges as (u, v) pairs
219 /// * `weights` - Weight for each vertex
220 ///
221 /// # Panics
222 /// Panics if `weights.len() != num_vertices`.
223 pub fn with_weights(num_vertices: usize, edges: Vec<(usize, usize)>, weights: Vec<W>) -> Self {
224 assert_eq!(
225 weights.len(),
226 num_vertices,
227 "weights length must match num_vertices"
228 );
229 let graph = SimpleGraph::new(num_vertices, edges);
230 Self {
231 graph,
232 weights,
233 _constraint: PhantomData,
234 }
235 }
236}
237
238impl<C: GraphConstraint, G: Graph, W: Clone + Default> GraphProblem<C, G, W> {
239 /// Create a graph problem from an existing graph with unit weights.
240 pub fn from_graph(graph: G) -> Self
241 where
242 W: From<i32>,
243 {
244 let weights = vec![W::from(1); graph.num_vertices()];
245 Self {
246 graph,
247 weights,
248 _constraint: PhantomData,
249 }
250 }
251
252 /// Create a graph problem from an existing graph with custom weights.
253 ///
254 /// # Panics
255 /// Panics if `weights.len() != graph.num_vertices()`.
256 pub fn from_graph_with_weights(graph: G, weights: Vec<W>) -> Self {
257 assert_eq!(
258 weights.len(),
259 graph.num_vertices(),
260 "weights length must match num_vertices"
261 );
262 Self {
263 graph,
264 weights,
265 _constraint: PhantomData,
266 }
267 }
268
269 /// Get a reference to the underlying graph.
270 pub fn graph(&self) -> &G {
271 &self.graph
272 }
273
274 /// Get the number of vertices.
275 pub fn num_vertices(&self) -> usize {
276 self.graph.num_vertices()
277 }
278
279 /// Get the number of edges.
280 pub fn num_edges(&self) -> usize {
281 self.graph.num_edges()
282 }
283
284 /// Get the edges as a list of (u, v) pairs.
285 pub fn edges(&self) -> Vec<(usize, usize)> {
286 self.graph.edges()
287 }
288
289 /// Check if two vertices are adjacent.
290 pub fn has_edge(&self, u: usize, v: usize) -> bool {
291 self.graph.has_edge(u, v)
292 }
293
294 /// Check if a configuration satisfies all edge constraints.
295 fn is_valid_config(&self, config: &[usize]) -> bool {
296 for (u, v) in self.graph.edges() {
297 let u_selected = config.get(u).copied().unwrap_or(0) == 1;
298 let v_selected = config.get(v).copied().unwrap_or(0) == 1;
299 if !C::is_edge_satisfied(u_selected, v_selected) {
300 return false;
301 }
302 }
303 true
304 }
305}
306
307impl<C, G, W> Problem for GraphProblem<C, G, W>
308where
309 C: GraphConstraint,
310 G: Graph,
311 W: Clone + Default + PartialOrd + Num + Zero + AddAssign + 'static,
312{
313 const NAME: &'static str = C::NAME;
314 type GraphType = SimpleGraphMarker;
315 type Weight = W;
316 type Size = W;
317
318 fn num_variables(&self) -> usize {
319 self.graph.num_vertices()
320 }
321
322 fn num_flavors(&self) -> usize {
323 2 // Binary: 0 = not selected, 1 = selected
324 }
325
326 fn problem_size(&self) -> ProblemSize {
327 ProblemSize::new(vec![
328 ("num_vertices", self.graph.num_vertices()),
329 ("num_edges", self.graph.num_edges()),
330 ])
331 }
332
333 fn energy_mode(&self) -> EnergyMode {
334 C::ENERGY_MODE
335 }
336
337 fn solution_size(&self, config: &[usize]) -> SolutionSize<Self::Size> {
338 let is_valid = self.is_valid_config(config);
339 let mut total = W::zero();
340 for (i, &selected) in config.iter().enumerate() {
341 if selected == 1 {
342 total += self.weights[i].clone();
343 }
344 }
345 SolutionSize::new(total, is_valid)
346 }
347}
348
349impl<C, G, W> ConstraintSatisfactionProblem for GraphProblem<C, G, W>
350where
351 C: GraphConstraint,
352 G: Graph,
353 W: Clone + Default + PartialOrd + Num + Zero + AddAssign + 'static,
354{
355 fn constraints(&self) -> Vec<LocalConstraint> {
356 let spec = C::edge_constraint_spec();
357 self.graph
358 .edges()
359 .into_iter()
360 .map(|(u, v)| LocalConstraint::new(2, vec![u, v], spec.to_vec()))
361 .collect()
362 }
363
364 fn objectives(&self) -> Vec<LocalSolutionSize<Self::Size>> {
365 self.weights
366 .iter()
367 .enumerate()
368 .map(|(i, w)| LocalSolutionSize::new(2, vec![i], vec![W::zero(), w.clone()]))
369 .collect()
370 }
371
372 fn weights(&self) -> Vec<Self::Size> {
373 self.weights.clone()
374 }
375
376 fn set_weights(&mut self, weights: Vec<Self::Size>) {
377 assert_eq!(weights.len(), self.num_variables());
378 self.weights = weights;
379 }
380
381 fn is_weighted(&self) -> bool {
382 if self.weights.is_empty() {
383 return false;
384 }
385 let first = &self.weights[0];
386 !self.weights.iter().all(|w| w == first)
387 }
388}
389
390impl<C, G, W> ProblemMetadata for GraphProblem<C, G, W>
391where
392 C: GraphConstraint,
393 G: Graph,
394 W: Clone + Default,
395{
396 fn problem_info() -> ProblemInfo {
397 C::problem_info()
398 }
399
400 fn category() -> ProblemCategory {
401 C::category()
402 }
403}
404
405// ============================================================================
406// Built-in constraint types for common graph problems
407// ============================================================================
408
409/// Constraint for the Independent Set problem.
410///
411/// At most one endpoint of each edge can be selected.
412/// Objective: Maximize total weight.
413#[derive(Debug, Clone, Copy)]
414pub struct IndependentSetConstraint;
415
416impl GraphConstraint for IndependentSetConstraint {
417 const NAME: &'static str = "Independent Set";
418 const DESCRIPTION: &'static str = "Find a maximum weight set of non-adjacent vertices";
419 const ENERGY_MODE: EnergyMode = EnergyMode::LargerSizeIsBetter;
420 const SUBCATEGORY: GraphSubcategory = GraphSubcategory::Independent;
421 const ALIASES: &'static [&'static str] = &["MIS", "MWIS", "Stable Set"];
422 const REDUCES_FROM: Option<&'static str> = Some("3-SAT");
423
424 fn edge_constraint_spec() -> [bool; 4] {
425 // (0,0), (0,1), (1,0) OK; (1,1) invalid
426 [true, true, true, false]
427 }
428}
429
430/// Constraint for the Vertex Cover problem.
431///
432/// At least one endpoint of each edge must be selected.
433/// Objective: Minimize total weight.
434#[derive(Debug, Clone, Copy)]
435pub struct VertexCoverConstraint;
436
437impl GraphConstraint for VertexCoverConstraint {
438 const NAME: &'static str = "Vertex Cover";
439 const DESCRIPTION: &'static str = "Find a minimum weight set of vertices covering all edges";
440 const ENERGY_MODE: EnergyMode = EnergyMode::SmallerSizeIsBetter;
441 const SUBCATEGORY: GraphSubcategory = GraphSubcategory::Covering;
442 const ALIASES: &'static [&'static str] = &["VC", "Minimum Vertex Cover"];
443 const REDUCES_FROM: Option<&'static str> = Some("Independent Set");
444
445 fn edge_constraint_spec() -> [bool; 4] {
446 // (0,0) invalid; (0,1), (1,0), (1,1) OK
447 [false, true, true, true]
448 }
449}
450
451/// Constraint for the Clique problem.
452///
453/// All pairs of selected vertices must be adjacent.
454/// This is the complement of Independent Set on the complement graph.
455/// Objective: Maximize size.
456#[derive(Debug, Clone, Copy)]
457pub struct CliqueConstraint;
458
459impl GraphConstraint for CliqueConstraint {
460 const NAME: &'static str = "Clique";
461 const DESCRIPTION: &'static str = "Find a maximum clique (complete subgraph)";
462 const ENERGY_MODE: EnergyMode = EnergyMode::LargerSizeIsBetter;
463 const SUBCATEGORY: GraphSubcategory = GraphSubcategory::Independent;
464 const ALIASES: &'static [&'static str] = &["Maximum Clique", "Max Clique"];
465 const REDUCES_FROM: Option<&'static str> = Some("3-SAT");
466
467 fn edge_constraint_spec() -> [bool; 4] {
468 // For non-edges: if both are selected, invalid
469 // Note: This constraint is applied to NON-EDGES in complement graph,
470 // which means we use this on the original graph's edges to find cliques.
471 // Actually for Clique, we should use the complement graph.
472 // For now, this spec means: on non-edges, both selected is invalid.
473 [true, true, true, false]
474 }
475}
476
477// ============================================================================
478// Type aliases for convenient usage
479// ============================================================================
480
481/// Independent Set problem using the generic template.
482///
483/// Find a maximum weight set of vertices where no two are adjacent.
484///
485/// # Type Parameters
486/// - `G`: Graph type (default: [`SimpleGraph`])
487/// - `W`: Weight type (default: `i32`)
488///
489/// # Examples
490///
491/// ```rust,ignore
492/// use problemreductions::models::graph::IndependentSetT;
493/// use problemreductions::topology::{SimpleGraph, UnitDiskGraph};
494///
495/// // Default: SimpleGraph
496/// let is = IndependentSetT::new(4, vec![(0, 1), (1, 2)]);
497///
498/// // With UnitDiskGraph for quantum hardware
499/// let udg = UnitDiskGraph::new(positions, radius);
500/// let is_udg = IndependentSetT::<UnitDiskGraph>::from_graph(udg);
501/// ```
502pub type IndependentSetT<G = SimpleGraph, W = i32> = GraphProblem<IndependentSetConstraint, G, W>;
503
504/// Vertex Cover problem using the generic template.
505///
506/// Find a minimum weight set of vertices that covers all edges.
507///
508/// # Type Parameters
509/// - `G`: Graph type (default: [`SimpleGraph`])
510/// - `W`: Weight type (default: `i32`)
511pub type VertexCoverT<G = SimpleGraph, W = i32> = GraphProblem<VertexCoverConstraint, G, W>;
512
513/// Clique problem using the generic template.
514///
515/// Note: For finding cliques, create the complement graph first.
516///
517/// # Type Parameters
518/// - `G`: Graph type (default: [`SimpleGraph`])
519/// - `W`: Weight type (default: `i32`)
520pub type CliqueT<G = SimpleGraph, W = i32> = GraphProblem<CliqueConstraint, G, W>;
521
522#[cfg(test)]
523mod tests {
524 use super::*;
525 use crate::solvers::{BruteForce, Solver};
526 use crate::topology::UnitDiskGraph;
527
528 #[test]
529 fn test_independent_set_template() {
530 // Explicit type annotation needed for weight type inference
531 let problem: IndependentSetT = IndependentSetT::new(3, vec![(0, 1), (1, 2), (0, 2)]);
532 assert_eq!(problem.num_vertices(), 3);
533 assert_eq!(problem.num_edges(), 3);
534 assert!(problem.energy_mode().is_maximization());
535
536 let solver = BruteForce::new();
537 let solutions = solver.find_best(&problem);
538 // Maximum IS in triangle is size 1
539 assert_eq!(solutions.len(), 3);
540 for sol in &solutions {
541 assert_eq!(sol.iter().sum::<usize>(), 1);
542 }
543 }
544
545 #[test]
546 fn test_vertex_cover_template() {
547 let problem: VertexCoverT = VertexCoverT::new(3, vec![(0, 1), (1, 2)]);
548 assert!(problem.energy_mode().is_minimization());
549
550 let solver = BruteForce::new();
551 let solutions = solver.find_best(&problem);
552 // Minimum VC for path 0-1-2 is {1}
553 assert_eq!(solutions.len(), 1);
554 assert_eq!(solutions[0], vec![0, 1, 0]);
555 }
556
557 #[test]
558 fn test_weighted_problem() {
559 let problem: IndependentSetT =
560 IndependentSetT::with_weights(3, vec![(0, 1), (1, 2)], vec![1, 100, 1]);
561 let solver = BruteForce::new();
562 let solutions = solver.find_best(&problem);
563 // Should select vertex 1 (weight 100) over 0+2 (weight 2)
564 assert_eq!(solutions[0], vec![0, 1, 0]);
565 }
566
567 #[test]
568 fn test_problem_metadata() {
569 // Use explicit types for metadata (doesn't need instance)
570 let info = IndependentSetT::<SimpleGraph, i32>::problem_info();
571 assert_eq!(info.name, "Independent Set");
572 assert!(info.aliases.contains(&"MIS"));
573
574 let cat = IndependentSetT::<SimpleGraph, i32>::category();
575 assert_eq!(cat.path(), "graph/independent");
576 }
577
578 #[test]
579 fn test_unit_disk_graph_problem() {
580 // Create a UnitDiskGraph
581 let udg = UnitDiskGraph::new(
582 vec![(0.0, 0.0), (1.0, 0.0), (2.0, 0.0), (3.0, 0.0)],
583 1.5, // radius: connects adjacent vertices only
584 );
585 // Path: 0-1-2-3
586
587 // Create IndependentSet on UnitDiskGraph
588 let problem: IndependentSetT<UnitDiskGraph> = IndependentSetT::from_graph(udg);
589 assert_eq!(problem.num_vertices(), 4);
590
591 let solver = BruteForce::new();
592 let solutions = solver.find_best(&problem);
593
594 // Maximum IS on path of 4 is 2 (vertices 0,2 or 1,3)
595 for sol in &solutions {
596 assert_eq!(sol.iter().sum::<usize>(), 2);
597 }
598 }
599
600 #[test]
601 fn test_constraint_specs() {
602 // Independent Set: at most one selected per edge
603 let is_spec = IndependentSetConstraint::edge_constraint_spec();
604 assert!(is_spec[0]); // (0,0) OK
605 assert!(is_spec[1]); // (0,1) OK
606 assert!(is_spec[2]); // (1,0) OK
607 assert!(!is_spec[3]); // (1,1) invalid
608
609 // Vertex Cover: at least one selected per edge
610 let vc_spec = VertexCoverConstraint::edge_constraint_spec();
611 assert!(!vc_spec[0]); // (0,0) invalid
612 assert!(vc_spec[1]); // (0,1) OK
613 assert!(vc_spec[2]); // (1,0) OK
614 assert!(vc_spec[3]); // (1,1) OK
615 }
616
617 #[test]
618 fn test_csp_interface() {
619 let problem: IndependentSetT = IndependentSetT::new(3, vec![(0, 1), (1, 2)]);
620
621 let constraints = problem.constraints();
622 assert_eq!(constraints.len(), 2);
623
624 let objectives = problem.objectives();
625 assert_eq!(objectives.len(), 3);
626
627 assert!(problem.is_satisfied(&[1, 0, 1]));
628 assert!(!problem.is_satisfied(&[1, 1, 0]));
629 }
630
631 #[test]
632 fn test_edges_and_adjacency() {
633 let problem: IndependentSetT = IndependentSetT::new(4, vec![(0, 1), (2, 3)]);
634 assert!(problem.has_edge(0, 1));
635 assert!(problem.has_edge(1, 0)); // Undirected
636 assert!(!problem.has_edge(0, 2));
637
638 let edges = problem.edges();
639 assert_eq!(edges.len(), 2);
640 }
641
642 #[test]
643 fn test_empty_graph() {
644 let problem: IndependentSetT = IndependentSetT::new(3, vec![]);
645 let solver = BruteForce::new();
646 let solutions = solver.find_best(&problem);
647 // All vertices can be selected
648 assert_eq!(solutions[0], vec![1, 1, 1]);
649 }
650
651 #[test]
652 fn test_set_weights() {
653 let mut problem: IndependentSetT = IndependentSetT::new(3, vec![(0, 1)]);
654 assert!(!problem.is_weighted());
655 problem.set_weights(vec![1, 2, 3]);
656 assert!(problem.is_weighted());
657 assert_eq!(problem.weights(), vec![1, 2, 3]);
658 }
659
660 #[test]
661 fn test_graph_accessor() {
662 let problem: IndependentSetT = IndependentSetT::new(4, vec![(0, 1), (1, 2)]);
663 let graph = problem.graph();
664 assert_eq!(graph.num_vertices(), 4);
665 assert_eq!(graph.num_edges(), 2);
666 }
667}