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}