Skip to main content

rust_igraph/algorithms/
cliques.rs

1//! Clique algorithms (ALGO-CL-001).
2//!
3//! Bron-Kerbosch algorithm with pivot for enumerating maximal cliques,
4//! plus convenience functions for clique number and largest cliques.
5
6use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
7
8/// Returns the clique number of the graph (size of the largest clique).
9///
10/// A clique is a complete subgraph. The clique number is the vertex count
11/// of the largest clique. For a graph with no edges, the clique number is 1
12/// (each vertex is a clique of size 1). For an empty graph (no vertices),
13/// returns 0.
14///
15/// Edge directions are ignored for directed graphs.
16///
17/// # Examples
18///
19/// ```
20/// use rust_igraph::{Graph, clique_number};
21///
22/// // K4 (complete graph on 4 vertices)
23/// let mut g = Graph::with_vertices(4);
24/// for i in 0..4u32 {
25///     for j in (i+1)..4 {
26///         g.add_edge(i, j).unwrap();
27///     }
28/// }
29/// assert_eq!(clique_number(&g).unwrap(), 4);
30/// ```
31pub fn clique_number(graph: &Graph) -> IgraphResult<u32> {
32    let n = graph.vcount();
33    if n == 0 {
34        return Ok(0);
35    }
36
37    let adj = build_neighbor_set(graph)?;
38    let mut max_size: u32 = 0;
39
40    let all_vertices: Vec<VertexId> = (0..n).collect();
41    bron_kerbosch_max(
42        &adj,
43        &mut Vec::new(),
44        &mut all_vertices.clone(),
45        &mut Vec::new(),
46        &mut max_size,
47    );
48
49    Ok(max_size)
50}
51
52/// Returns all maximal cliques in the graph.
53///
54/// A maximal clique is a clique that cannot be extended by adding another
55/// adjacent vertex. Uses the Bron-Kerbosch algorithm with pivoting.
56///
57/// Edge directions are ignored for directed graphs.
58///
59/// # Examples
60///
61/// ```
62/// use rust_igraph::{Graph, maximal_cliques};
63///
64/// let mut g = Graph::with_vertices(4);
65/// g.add_edge(0, 1).unwrap();
66/// g.add_edge(1, 2).unwrap();
67/// g.add_edge(2, 3).unwrap();
68///
69/// let cliques = maximal_cliques(&g).unwrap();
70/// // Path graph: each edge is a maximal clique
71/// assert_eq!(cliques.len(), 3);
72/// ```
73pub fn maximal_cliques(graph: &Graph) -> IgraphResult<Vec<Vec<VertexId>>> {
74    let n = graph.vcount();
75    if n == 0 {
76        return Ok(Vec::new());
77    }
78
79    let adj = build_neighbor_set(graph)?;
80    let mut result: Vec<Vec<VertexId>> = Vec::new();
81
82    let all_vertices: Vec<VertexId> = (0..n).collect();
83    bron_kerbosch_all(
84        &adj,
85        &mut Vec::new(),
86        &mut all_vertices.clone(),
87        &mut Vec::new(),
88        &mut result,
89    );
90
91    // Include isolated vertices as cliques of size 1
92    for v in 0..n {
93        if adj[v as usize].is_empty() {
94            result.push(vec![v]);
95        }
96    }
97
98    Ok(result)
99}
100
101/// Returns only the largest cliques in the graph.
102///
103/// A largest clique is a maximal clique whose size equals the clique
104/// number. There may be multiple largest cliques.
105///
106/// Edge directions are ignored for directed graphs.
107///
108/// # Examples
109///
110/// ```
111/// use rust_igraph::{Graph, largest_cliques};
112///
113/// let mut g = Graph::with_vertices(4);
114/// g.add_edge(0, 1).unwrap();
115/// g.add_edge(0, 2).unwrap();
116/// g.add_edge(1, 2).unwrap();
117/// g.add_edge(2, 3).unwrap();
118///
119/// let cliques = largest_cliques(&g).unwrap();
120/// // The triangle {0,1,2} is the only largest clique
121/// assert_eq!(cliques.len(), 1);
122/// assert_eq!(cliques[0].len(), 3);
123/// ```
124pub fn largest_cliques(graph: &Graph) -> IgraphResult<Vec<Vec<VertexId>>> {
125    let all = maximal_cliques(graph)?;
126    let max_size = all.iter().map(Vec::len).max().unwrap_or(0);
127    Ok(all.into_iter().filter(|c| c.len() == max_size).collect())
128}
129
130/// Returns the independence number of the graph (size of the largest
131/// independent set).
132///
133/// An independent set is a set of vertices with no edges between them.
134/// Equivalently, it is a clique in the complement graph. For a graph
135/// with no edges, the independence number equals the vertex count.
136///
137/// Edge directions are ignored for directed graphs.
138///
139/// # Examples
140///
141/// ```
142/// use rust_igraph::{Graph, independence_number};
143///
144/// // Path 0-1-2: largest independent set is {0, 2}
145/// let mut g = Graph::with_vertices(3);
146/// g.add_edge(0, 1).unwrap();
147/// g.add_edge(1, 2).unwrap();
148/// assert_eq!(independence_number(&g).unwrap(), 2);
149/// ```
150pub fn independence_number(graph: &Graph) -> IgraphResult<u32> {
151    let n = graph.vcount();
152    if n == 0 {
153        return Ok(0);
154    }
155
156    let adj = build_complement_neighbor_set(graph)?;
157    let mut max_size: u32 = 0;
158
159    let all_vertices: Vec<VertexId> = (0..n).collect();
160    bron_kerbosch_max(
161        &adj,
162        &mut Vec::new(),
163        &mut all_vertices.clone(),
164        &mut Vec::new(),
165        &mut max_size,
166    );
167
168    Ok(max_size)
169}
170
171/// Returns all maximal independent vertex sets in the graph.
172///
173/// A maximal independent set cannot be extended by adding another vertex
174/// without creating an edge within the set. Uses Bron-Kerbosch on the
175/// complement adjacency.
176///
177/// Edge directions are ignored for directed graphs.
178///
179/// # Examples
180///
181/// ```
182/// use rust_igraph::{Graph, maximal_independent_vertex_sets};
183///
184/// // Triangle: each single vertex is a maximal independent set
185/// let mut g = Graph::with_vertices(3);
186/// g.add_edge(0, 1).unwrap();
187/// g.add_edge(1, 2).unwrap();
188/// g.add_edge(0, 2).unwrap();
189///
190/// let sets = maximal_independent_vertex_sets(&g).unwrap();
191/// assert_eq!(sets.len(), 3);
192/// for s in &sets {
193///     assert_eq!(s.len(), 1);
194/// }
195/// ```
196pub fn maximal_independent_vertex_sets(graph: &Graph) -> IgraphResult<Vec<Vec<VertexId>>> {
197    let n = graph.vcount();
198    if n == 0 {
199        return Ok(Vec::new());
200    }
201
202    let adj = build_complement_neighbor_set(graph)?;
203    let mut result: Vec<Vec<VertexId>> = Vec::new();
204
205    let all_vertices: Vec<VertexId> = (0..n).collect();
206    bron_kerbosch_all_independent(
207        &adj,
208        &mut Vec::new(),
209        &mut all_vertices.clone(),
210        &mut Vec::new(),
211        &mut result,
212    );
213
214    Ok(result)
215}
216
217/// Returns only the largest independent vertex sets in the graph.
218///
219/// A largest independent set is a maximal independent set whose size
220/// equals the independence number.
221///
222/// Edge directions are ignored for directed graphs.
223///
224/// # Examples
225///
226/// ```
227/// use rust_igraph::{Graph, largest_independent_vertex_sets};
228///
229/// // Path 0-1-2-3: largest independent sets are {0,2}, {0,3}, {1,3}
230/// let mut g = Graph::with_vertices(4);
231/// g.add_edge(0, 1).unwrap();
232/// g.add_edge(1, 2).unwrap();
233/// g.add_edge(2, 3).unwrap();
234///
235/// let sets = largest_independent_vertex_sets(&g).unwrap();
236/// assert_eq!(sets.len(), 3);
237/// for s in &sets {
238///     assert_eq!(s.len(), 2);
239/// }
240/// ```
241pub fn largest_independent_vertex_sets(graph: &Graph) -> IgraphResult<Vec<Vec<VertexId>>> {
242    let all = maximal_independent_vertex_sets(graph)?;
243    let max_size = all.iter().map(Vec::len).max().unwrap_or(0);
244    Ok(all.into_iter().filter(|s| s.len() == max_size).collect())
245}
246
247/// Bron-Kerbosch with pivot — collects all maximal cliques (independent sets).
248/// Unlike `bron_kerbosch_all`, this includes cliques of size 1 (isolated
249/// vertices in the complement = vertices with all edges in the original).
250fn bron_kerbosch_all_independent(
251    adj: &[Vec<VertexId>],
252    r_clique: &mut Vec<VertexId>,
253    p_candidates: &mut Vec<VertexId>,
254    x_excluded: &mut Vec<VertexId>,
255    result: &mut Vec<Vec<VertexId>>,
256) {
257    if p_candidates.is_empty() && x_excluded.is_empty() {
258        let mut clique = r_clique.clone();
259        clique.sort_unstable();
260        result.push(clique);
261        return;
262    }
263
264    if p_candidates.is_empty() {
265        return;
266    }
267
268    let pivot = choose_pivot(adj, p_candidates, x_excluded);
269    let pivot_neighbors = &adj[pivot as usize];
270
271    let candidates: Vec<VertexId> = p_candidates
272        .iter()
273        .filter(|&&v| !pivot_neighbors.contains(&v))
274        .copied()
275        .collect();
276
277    for v in candidates {
278        let v_neighbors = &adj[v as usize];
279
280        r_clique.push(v);
281
282        let mut new_p: Vec<VertexId> = p_candidates
283            .iter()
284            .filter(|&&u| v_neighbors.contains(&u))
285            .copied()
286            .collect();
287        let mut new_x: Vec<VertexId> = x_excluded
288            .iter()
289            .filter(|&&u| v_neighbors.contains(&u))
290            .copied()
291            .collect();
292
293        bron_kerbosch_all_independent(adj, r_clique, &mut new_p, &mut new_x, result);
294
295        r_clique.pop();
296
297        p_candidates.retain(|&u| u != v);
298        x_excluded.push(v);
299    }
300}
301
302/// Build complement neighbor lists (non-neighbors, ignoring self-loops).
303fn build_complement_neighbor_set(graph: &Graph) -> IgraphResult<Vec<Vec<VertexId>>> {
304    let n = graph.vcount();
305    let adj = build_neighbor_set(graph)?;
306
307    let mut complement: Vec<Vec<VertexId>> = Vec::with_capacity(n as usize);
308    for v in 0..n {
309        let neighbors = &adj[v as usize];
310        let non_neighbors: Vec<VertexId> = (0..n)
311            .filter(|&u| u != v && !neighbors.contains(&u))
312            .collect();
313        complement.push(non_neighbors);
314    }
315
316    Ok(complement)
317}
318
319/// Bron-Kerbosch with pivot — only tracks maximum clique size.
320fn bron_kerbosch_max(
321    adj: &[Vec<VertexId>],
322    r_clique: &mut Vec<VertexId>,
323    p_candidates: &mut Vec<VertexId>,
324    x_excluded: &mut Vec<VertexId>,
325    max_size: &mut u32,
326) {
327    if p_candidates.is_empty() && x_excluded.is_empty() {
328        #[allow(clippy::cast_possible_truncation)]
329        let size = r_clique.len() as u32;
330        if size > *max_size {
331            *max_size = size;
332        }
333        return;
334    }
335
336    if p_candidates.is_empty() {
337        return;
338    }
339
340    // Choose pivot: vertex in P ∪ X with most neighbors in P
341    let pivot = choose_pivot(adj, p_candidates, x_excluded);
342    let pivot_neighbors = &adj[pivot as usize];
343
344    // Vertices in P that are NOT neighbors of pivot
345    let candidates: Vec<VertexId> = p_candidates
346        .iter()
347        .filter(|&&v| !pivot_neighbors.contains(&v))
348        .copied()
349        .collect();
350
351    for v in candidates {
352        let v_neighbors = &adj[v as usize];
353
354        r_clique.push(v);
355
356        let mut new_p: Vec<VertexId> = p_candidates
357            .iter()
358            .filter(|&&u| v_neighbors.contains(&u))
359            .copied()
360            .collect();
361        let mut new_x: Vec<VertexId> = x_excluded
362            .iter()
363            .filter(|&&u| v_neighbors.contains(&u))
364            .copied()
365            .collect();
366
367        bron_kerbosch_max(adj, r_clique, &mut new_p, &mut new_x, max_size);
368
369        r_clique.pop();
370
371        p_candidates.retain(|&u| u != v);
372        x_excluded.push(v);
373    }
374}
375
376/// Bron-Kerbosch with pivot — collects all maximal cliques.
377fn bron_kerbosch_all(
378    adj: &[Vec<VertexId>],
379    r_clique: &mut Vec<VertexId>,
380    p_candidates: &mut Vec<VertexId>,
381    x_excluded: &mut Vec<VertexId>,
382    result: &mut Vec<Vec<VertexId>>,
383) {
384    if p_candidates.is_empty() && x_excluded.is_empty() {
385        if r_clique.len() >= 2 {
386            let mut clique = r_clique.clone();
387            clique.sort_unstable();
388            result.push(clique);
389        }
390        return;
391    }
392
393    if p_candidates.is_empty() {
394        return;
395    }
396
397    let pivot = choose_pivot(adj, p_candidates, x_excluded);
398    let pivot_neighbors = &adj[pivot as usize];
399
400    let candidates: Vec<VertexId> = p_candidates
401        .iter()
402        .filter(|&&v| !pivot_neighbors.contains(&v))
403        .copied()
404        .collect();
405
406    for v in candidates {
407        let v_neighbors = &adj[v as usize];
408
409        r_clique.push(v);
410
411        let mut new_p: Vec<VertexId> = p_candidates
412            .iter()
413            .filter(|&&u| v_neighbors.contains(&u))
414            .copied()
415            .collect();
416        let mut new_x: Vec<VertexId> = x_excluded
417            .iter()
418            .filter(|&&u| v_neighbors.contains(&u))
419            .copied()
420            .collect();
421
422        bron_kerbosch_all(adj, r_clique, &mut new_p, &mut new_x, result);
423
424        r_clique.pop();
425
426        p_candidates.retain(|&u| u != v);
427        x_excluded.push(v);
428    }
429}
430
431/// Counts the number of maximal cliques without storing them.
432///
433/// More memory-efficient than [`maximal_cliques`] when only the count
434/// is needed.
435///
436/// Edge directions are ignored for directed graphs.
437///
438/// # Examples
439///
440/// ```
441/// use rust_igraph::{Graph, maximal_cliques_count};
442///
443/// let mut g = Graph::with_vertices(4);
444/// g.add_edge(0, 1).unwrap();
445/// g.add_edge(1, 2).unwrap();
446/// g.add_edge(2, 3).unwrap();
447/// // Path graph: 3 maximal cliques (each edge) + 0 isolated
448/// assert_eq!(maximal_cliques_count(&g).unwrap(), 3);
449/// ```
450pub fn maximal_cliques_count(graph: &Graph) -> IgraphResult<u64> {
451    let n = graph.vcount();
452    if n == 0 {
453        return Ok(0);
454    }
455
456    let adj = build_neighbor_set(graph)?;
457    let mut count: u64 = 0;
458
459    let all_vertices: Vec<VertexId> = (0..n).collect();
460    bron_kerbosch_count(
461        &adj,
462        &mut Vec::new(),
463        &mut all_vertices.clone(),
464        &mut Vec::new(),
465        &mut count,
466    );
467
468    // Include isolated vertices as cliques of size 1
469    for v in 0..n {
470        if adj[v as usize].is_empty() {
471            count = count.saturating_add(1);
472        }
473    }
474
475    Ok(count)
476}
477
478/// Returns a histogram of clique sizes, counting **all** cliques.
479///
480/// A clique is any complete subgraph (not necessarily maximal). The result
481/// is a vector where index `k` holds the number of cliques of size `k`,
482/// including every single vertex (size 1) and every edge (size 2). Index 0
483/// is always 0 (no cliques of size 0 exist). The vector length is
484/// `clique_number + 1`.
485///
486/// This is the counterpart of igraph's `igraph_clique_size_hist`. Note that
487/// igraph stores the size-1 count in element 0; here index equals the clique
488/// size with element 0 left as a zero placeholder, matching the convention
489/// of the rest of this crate. For maximal cliques only, see
490/// [`maximal_cliques_hist`].
491///
492/// Edge directions are ignored for directed graphs.
493///
494/// # Examples
495///
496/// ```
497/// use rust_igraph::{Graph, clique_size_hist};
498///
499/// let mut g = Graph::with_vertices(4);
500/// g.add_edge(0, 1).unwrap();
501/// g.add_edge(0, 2).unwrap();
502/// g.add_edge(1, 2).unwrap();
503/// g.add_edge(2, 3).unwrap();
504/// let hist = clique_size_hist(&g).unwrap();
505/// // 4 vertices, 4 edges, 1 triangle {0,1,2}
506/// assert_eq!(hist, vec![0, 4, 4, 1]);
507/// ```
508pub fn clique_size_hist(graph: &Graph) -> IgraphResult<Vec<u64>> {
509    let n = graph.vcount();
510    if n == 0 {
511        return Ok(Vec::new());
512    }
513
514    let adj = build_neighbor_set(graph)?;
515    let mut hist: Vec<u64> = Vec::new();
516
517    let mut current: Vec<VertexId> = Vec::new();
518    count_cliques_by_size(&adj, &mut current, 0, n, &mut hist);
519
520    Ok(hist)
521}
522
523/// Returns a histogram of **maximal** clique sizes.
524///
525/// A maximal clique cannot be extended by adding an adjacent vertex. The
526/// result is a vector where index `k` holds the number of maximal cliques of
527/// size `k`. Maximal cliques of size 1 are exactly the isolated vertices.
528/// Index 0 is always 0. The vector length is `clique_number + 1`.
529///
530/// This is the counterpart of igraph's `igraph_maximal_cliques_hist`. As with
531/// [`clique_size_hist`], index equals the clique size (element 0 is a zero
532/// placeholder), whereas igraph stores the size-1 count in element 0. To count
533/// all cliques (not just maximal ones), use [`clique_size_hist`].
534///
535/// Edge directions are ignored for directed graphs.
536///
537/// # Examples
538///
539/// ```
540/// use rust_igraph::{Graph, maximal_cliques_hist};
541///
542/// let mut g = Graph::with_vertices(4);
543/// g.add_edge(0, 1).unwrap();
544/// g.add_edge(0, 2).unwrap();
545/// g.add_edge(1, 2).unwrap();
546/// g.add_edge(2, 3).unwrap();
547/// let hist = maximal_cliques_hist(&g).unwrap();
548/// // maximal cliques: edge {2,3} (size 2), triangle {0,1,2} (size 3)
549/// assert_eq!(hist, vec![0, 0, 1, 1]);
550/// ```
551pub fn maximal_cliques_hist(graph: &Graph) -> IgraphResult<Vec<u64>> {
552    let n = graph.vcount();
553    if n == 0 {
554        return Ok(Vec::new());
555    }
556
557    let adj = build_neighbor_set(graph)?;
558    let mut hist: Vec<u64> = Vec::new();
559
560    let all_vertices: Vec<VertexId> = (0..n).collect();
561    bron_kerbosch_hist(
562        &adj,
563        &mut Vec::new(),
564        &mut all_vertices.clone(),
565        &mut Vec::new(),
566        &mut hist,
567    );
568
569    // A size-1 maximal clique is exactly an isolated vertex.
570    for v in 0..n {
571        if adj[v as usize].is_empty() {
572            if hist.len() < 2 {
573                hist.resize(2, 0);
574            }
575            hist[1] = hist[1].saturating_add(1);
576        }
577    }
578
579    Ok(hist)
580}
581
582/// Returns maximal cliques containing at least one vertex from `subset`.
583///
584/// Enumerates all maximal cliques of the graph, then filters to keep
585/// only those that contain at least one vertex from the given subset.
586/// Optional `min_size` / `max_size` bounds filter by clique size, and
587/// `max_results` caps output count.
588///
589/// Edge directions are ignored for directed graphs.
590///
591/// # Arguments
592///
593/// * `graph` — the input graph.
594/// * `subset` — vertex IDs to seed the search. A clique is included if
595///   it contains at least one vertex from this set.
596/// * `min_size` — minimum clique size (0 = no bound).
597/// * `max_size` — maximum clique size (0 = no bound).
598/// * `max_results` — maximum number of cliques to return (`None` = unlimited).
599///
600/// # Errors
601///
602/// Returns `InvalidArgument` if any vertex in `subset` is out of range.
603///
604/// # Examples
605///
606/// ```
607/// use rust_igraph::{Graph, maximal_cliques_subset};
608///
609/// // Triangle {0,1,2} plus edge {2,3}
610/// let mut g = Graph::with_vertices(4);
611/// g.add_edge(0, 1).unwrap();
612/// g.add_edge(0, 2).unwrap();
613/// g.add_edge(1, 2).unwrap();
614/// g.add_edge(2, 3).unwrap();
615///
616/// // Cliques touching vertex 3: only {2,3}
617/// let cliques = maximal_cliques_subset(&g, &[3], 0, 0, None).unwrap();
618/// assert_eq!(cliques.len(), 1);
619/// assert_eq!(cliques[0].len(), 2);
620///
621/// // Cliques touching vertex 0: only {0,1,2}
622/// let cliques = maximal_cliques_subset(&g, &[0], 0, 0, None).unwrap();
623/// assert_eq!(cliques.len(), 1);
624/// assert_eq!(cliques[0].len(), 3);
625/// ```
626pub fn maximal_cliques_subset(
627    graph: &Graph,
628    subset: &[VertexId],
629    min_size: u32,
630    max_size: u32,
631    max_results: Option<usize>,
632) -> IgraphResult<Vec<Vec<VertexId>>> {
633    let n = graph.vcount();
634
635    for &v in subset {
636        if v >= n {
637            return Err(IgraphError::InvalidArgument(format!(
638                "vertex id {v} is out of range [0, {n})"
639            )));
640        }
641    }
642
643    if n == 0 || subset.is_empty() {
644        return Ok(Vec::new());
645    }
646
647    let adj = build_neighbor_set(graph)?;
648    let mut all_cliques: Vec<Vec<VertexId>> = Vec::new();
649
650    let all_vertices: Vec<VertexId> = (0..n).collect();
651    bron_kerbosch_all(
652        &adj,
653        &mut Vec::new(),
654        &mut all_vertices.clone(),
655        &mut Vec::new(),
656        &mut all_cliques,
657    );
658
659    // Include isolated vertices as cliques of size 1
660    for v in 0..n {
661        if adj[v as usize].is_empty() {
662            all_cliques.push(vec![v]);
663        }
664    }
665
666    // Build a fast lookup set for subset membership
667    let mut in_subset = vec![false; n as usize];
668    for &v in subset {
669        in_subset[v as usize] = true;
670    }
671
672    let effective_min = if min_size == 0 { 1 } else { min_size };
673    let effective_max = if max_size == 0 { u32::MAX } else { max_size };
674    let limit = max_results.unwrap_or(usize::MAX);
675
676    let mut result: Vec<Vec<VertexId>> = Vec::new();
677    for clique in all_cliques {
678        #[allow(clippy::cast_possible_truncation)]
679        let csize = clique.len() as u32;
680        if csize < effective_min || csize > effective_max {
681            continue;
682        }
683        if !clique.iter().any(|&v| in_subset[v as usize]) {
684            continue;
685        }
686        result.push(clique);
687        if result.len() >= limit {
688            break;
689        }
690    }
691
692    Ok(result)
693}
694
695/// Tally every clique (complete subgraph) by size into `hist[size]`.
696fn count_cliques_by_size(
697    adj: &[Vec<VertexId>],
698    current: &mut Vec<VertexId>,
699    start: u32,
700    n: u32,
701    hist: &mut Vec<u64>,
702) {
703    let len = current.len();
704    if len >= 1 {
705        if hist.len() <= len {
706            hist.resize(len + 1, 0);
707        }
708        hist[len] = hist[len].saturating_add(1);
709    }
710
711    for v in start..n {
712        let v_adj = &adj[v as usize];
713        if current.iter().all(|&u| v_adj.contains(&u)) {
714            current.push(v);
715            count_cliques_by_size(adj, current, v + 1, n, hist);
716            current.pop();
717        }
718    }
719}
720
721/// Bron-Kerbosch with pivot — count-only variant.
722fn bron_kerbosch_count(
723    adj: &[Vec<VertexId>],
724    r_clique: &mut Vec<VertexId>,
725    p_candidates: &mut Vec<VertexId>,
726    x_excluded: &mut Vec<VertexId>,
727    count: &mut u64,
728) {
729    if p_candidates.is_empty() && x_excluded.is_empty() {
730        if r_clique.len() >= 2 {
731            *count = count.saturating_add(1);
732        }
733        return;
734    }
735
736    if p_candidates.is_empty() {
737        return;
738    }
739
740    let pivot = choose_pivot(adj, p_candidates, x_excluded);
741    let pivot_neighbors = &adj[pivot as usize];
742
743    let candidates: Vec<VertexId> = p_candidates
744        .iter()
745        .filter(|&&v| !pivot_neighbors.contains(&v))
746        .copied()
747        .collect();
748
749    for v in candidates {
750        let v_neighbors = &adj[v as usize];
751
752        r_clique.push(v);
753
754        let mut new_p: Vec<VertexId> = p_candidates
755            .iter()
756            .filter(|&&u| v_neighbors.contains(&u))
757            .copied()
758            .collect();
759        let mut new_x: Vec<VertexId> = x_excluded
760            .iter()
761            .filter(|&&u| v_neighbors.contains(&u))
762            .copied()
763            .collect();
764
765        bron_kerbosch_count(adj, r_clique, &mut new_p, &mut new_x, count);
766
767        r_clique.pop();
768
769        p_candidates.retain(|&u| u != v);
770        x_excluded.push(v);
771    }
772}
773
774/// Bron-Kerbosch with pivot — histogram variant.
775fn bron_kerbosch_hist(
776    adj: &[Vec<VertexId>],
777    r_clique: &mut Vec<VertexId>,
778    p_candidates: &mut Vec<VertexId>,
779    x_excluded: &mut Vec<VertexId>,
780    hist: &mut Vec<u64>,
781) {
782    if p_candidates.is_empty() && x_excluded.is_empty() {
783        let size = r_clique.len();
784        if size >= 2 {
785            if hist.len() <= size {
786                hist.resize(size + 1, 0);
787            }
788            hist[size] = hist[size].saturating_add(1);
789        }
790        return;
791    }
792
793    if p_candidates.is_empty() {
794        return;
795    }
796
797    let pivot = choose_pivot(adj, p_candidates, x_excluded);
798    let pivot_neighbors = &adj[pivot as usize];
799
800    let candidates: Vec<VertexId> = p_candidates
801        .iter()
802        .filter(|&&v| !pivot_neighbors.contains(&v))
803        .copied()
804        .collect();
805
806    for v in candidates {
807        let v_neighbors = &adj[v as usize];
808
809        r_clique.push(v);
810
811        let mut new_p: Vec<VertexId> = p_candidates
812            .iter()
813            .filter(|&&u| v_neighbors.contains(&u))
814            .copied()
815            .collect();
816        let mut new_x: Vec<VertexId> = x_excluded
817            .iter()
818            .filter(|&&u| v_neighbors.contains(&u))
819            .copied()
820            .collect();
821
822        bron_kerbosch_hist(adj, r_clique, &mut new_p, &mut new_x, hist);
823
824        r_clique.pop();
825
826        p_candidates.retain(|&u| u != v);
827        x_excluded.push(v);
828    }
829}
830
831/// Choose pivot vertex with maximum connections to P.
832fn choose_pivot(
833    adj: &[Vec<VertexId>],
834    p_candidates: &[VertexId],
835    x_excluded: &[VertexId],
836) -> VertexId {
837    let mut best = p_candidates[0];
838    let mut best_count = 0usize;
839
840    for &v in p_candidates.iter().chain(x_excluded.iter()) {
841        let count = p_candidates
842            .iter()
843            .filter(|&&u| adj[v as usize].contains(&u))
844            .count();
845        if count > best_count {
846            best_count = count;
847            best = v;
848        }
849    }
850
851    best
852}
853
854/// Finds all cliques (complete subgraphs) in the graph within a size range.
855///
856/// Returns all cliques of size `min_size..=max_size`. Unlike
857/// [`maximal_cliques`], this includes non-maximal cliques (subsets of
858/// larger cliques). If `max_results` is `Some(n)`, at most `n` cliques
859/// are returned.
860///
861/// Edge directions are ignored for directed graphs.
862///
863/// # Examples
864///
865/// ```
866/// use rust_igraph::{Graph, cliques};
867///
868/// // Triangle 0-1-2: contains 3 edges (cliques of size 2) + 1 triangle (size 3)
869/// let mut g = Graph::with_vertices(3);
870/// g.add_edge(0, 1).unwrap();
871/// g.add_edge(1, 2).unwrap();
872/// g.add_edge(0, 2).unwrap();
873/// let all = cliques(&g, 2, 3, None).unwrap();
874/// assert_eq!(all.len(), 4); // 3 edges + 1 triangle
875/// ```
876pub fn cliques(
877    graph: &Graph,
878    min_size: u32,
879    max_size: u32,
880    max_results: Option<usize>,
881) -> IgraphResult<Vec<Vec<VertexId>>> {
882    let n = graph.vcount();
883    if n == 0 || min_size > n || max_size == 0 {
884        return Ok(Vec::new());
885    }
886
887    let min_sz = if min_size == 0 { 1 } else { min_size } as usize;
888    let max_sz = max_size as usize;
889    let limit = max_results.unwrap_or(usize::MAX);
890
891    let adj = build_neighbor_set(graph)?;
892    let mut result: Vec<Vec<VertexId>> = Vec::new();
893
894    // Enumerate all cliques using backtracking: for each vertex v (in
895    // order), extend the current clique with v if v is adjacent to all
896    // vertices already in the clique.
897    let mut current: Vec<VertexId> = Vec::new();
898    enumerate_cliques(&adj, &mut current, 0, n, min_sz, max_sz, limit, &mut result);
899
900    Ok(result)
901}
902
903/// Returns the weight of the maximum-weight clique.
904///
905/// The weight of a clique is the sum of its vertex weights. Only positive
906/// integer vertex weights are supported; following igraph, each weight is
907/// truncated to its integer part. Because every weight is positive, the
908/// maximum-weight clique is always maximal, so the search ranges over
909/// maximal cliques.
910///
911/// `vertex_weights` must have one entry per vertex. For an empty graph the
912/// weight is `0.0`. This is the counterpart of igraph's
913/// `igraph_weighted_clique_number`.
914///
915/// Edge directions are ignored for directed graphs.
916///
917/// # Errors
918///
919/// Returns [`IgraphError::InvalidArgument`] if `vertex_weights` has the wrong
920/// length or any (truncated) weight is not a positive integer.
921///
922/// # Examples
923///
924/// ```
925/// use rust_igraph::{Graph, weighted_clique_number};
926///
927/// let mut g = Graph::with_vertices(4);
928/// g.add_edge(0, 1).unwrap();
929/// g.add_edge(0, 2).unwrap();
930/// g.add_edge(1, 2).unwrap();
931/// g.add_edge(2, 3).unwrap();
932/// // {2,3} has weight 1 + 5 = 6, beating the triangle's weight 3.
933/// let w = weighted_clique_number(&g, &[1.0, 1.0, 1.0, 5.0]).unwrap();
934/// assert!((w - 6.0).abs() < 1e-9);
935/// ```
936// Weights are truncated positive integers; igraph returns the result as a
937// double, so the i64 -> f64 widening is intentional.
938#[allow(clippy::cast_precision_loss)]
939pub fn weighted_clique_number(graph: &Graph, vertex_weights: &[f64]) -> IgraphResult<f64> {
940    let n = graph.vcount();
941    let weights = validate_clique_weights(vertex_weights, n)?;
942    if n == 0 {
943        return Ok(0.0);
944    }
945
946    let mut best: i64 = 0;
947    for clique in maximal_cliques(graph)? {
948        let w = clique_weight(&weights, &clique)?;
949        if w > best {
950            best = w;
951        }
952    }
953    Ok(best as f64)
954}
955
956/// Returns every clique of maximum total weight.
957///
958/// The weight of a clique is the sum of its vertex weights (positive integer
959/// weights, truncated to their integer parts). There may be several cliques
960/// tying for the largest weight; all of them are returned, each sorted
961/// ascending, and the list itself is sorted lexicographically. Because all
962/// weights are positive, every maximum-weight clique is maximal.
963///
964/// This is the counterpart of igraph's `igraph_largest_weighted_cliques`.
965///
966/// Edge directions are ignored for directed graphs.
967///
968/// # Errors
969///
970/// Returns [`IgraphError::InvalidArgument`] if `vertex_weights` has the wrong
971/// length or any (truncated) weight is not a positive integer.
972///
973/// # Examples
974///
975/// ```
976/// use rust_igraph::{Graph, largest_weighted_cliques};
977///
978/// let mut g = Graph::with_vertices(4);
979/// g.add_edge(0, 1).unwrap();
980/// g.add_edge(0, 2).unwrap();
981/// g.add_edge(1, 2).unwrap();
982/// g.add_edge(2, 3).unwrap();
983/// let cs = largest_weighted_cliques(&g, &[1.0, 1.0, 1.0, 5.0]).unwrap();
984/// assert_eq!(cs, vec![vec![2, 3]]);
985/// ```
986pub fn largest_weighted_cliques(
987    graph: &Graph,
988    vertex_weights: &[f64],
989) -> IgraphResult<Vec<Vec<VertexId>>> {
990    let n = graph.vcount();
991    let weights = validate_clique_weights(vertex_weights, n)?;
992    if n == 0 {
993        return Ok(Vec::new());
994    }
995
996    let mut best: i64 = 0;
997    let mut weighted: Vec<(i64, Vec<VertexId>)> = Vec::new();
998    for mut clique in maximal_cliques(graph)? {
999        clique.sort_unstable();
1000        let w = clique_weight(&weights, &clique)?;
1001        if w > best {
1002            best = w;
1003        }
1004        weighted.push((w, clique));
1005    }
1006
1007    let mut result: Vec<Vec<VertexId>> = weighted
1008        .into_iter()
1009        .filter(|(w, _)| *w == best)
1010        .map(|(_, clique)| clique)
1011        .collect();
1012    result.sort_unstable();
1013    Ok(result)
1014}
1015
1016/// Finds all cliques whose total weight lies in a given range.
1017///
1018/// The weight of a clique is the sum of its vertex weights (positive integer
1019/// weights, truncated to their integer parts). When `maximal` is `true`, only
1020/// maximal cliques are considered; otherwise every clique is. A clique is
1021/// kept when its weight is `>= min_weight` (unless `min_weight <= 0`, meaning
1022/// no lower bound) and `<= max_weight` (unless `max_weight <= 0`, meaning no
1023/// upper bound). If `max_results` is `Some(n)`, at most `n` cliques are
1024/// returned. Results are sorted for determinism.
1025///
1026/// This is the counterpart of igraph's `igraph_weighted_cliques`.
1027///
1028/// Edge directions are ignored for directed graphs.
1029///
1030/// # Errors
1031///
1032/// Returns [`IgraphError::InvalidArgument`] if `vertex_weights` has the wrong
1033/// length or any (truncated) weight is not a positive integer.
1034///
1035/// # Examples
1036///
1037/// ```
1038/// use rust_igraph::{Graph, weighted_cliques};
1039///
1040/// let mut g = Graph::with_vertices(4);
1041/// g.add_edge(0, 1).unwrap();
1042/// g.add_edge(0, 2).unwrap();
1043/// g.add_edge(1, 2).unwrap();
1044/// g.add_edge(2, 3).unwrap();
1045/// // All maximal cliques, unbounded weight range.
1046/// let cs = weighted_cliques(&g, &[1.0, 1.0, 1.0, 1.0], true, 0.0, 0.0, None).unwrap();
1047/// assert_eq!(cs, vec![vec![0, 1, 2], vec![2, 3]]);
1048/// ```
1049// Clique weight (truncated positive integers) is compared against the f64
1050// weight bounds; the i64 -> f64 widening is intentional.
1051#[allow(clippy::cast_precision_loss)]
1052pub fn weighted_cliques(
1053    graph: &Graph,
1054    vertex_weights: &[f64],
1055    maximal: bool,
1056    min_weight: f64,
1057    max_weight: f64,
1058    max_results: Option<usize>,
1059) -> IgraphResult<Vec<Vec<VertexId>>> {
1060    let n = graph.vcount();
1061    let weights = validate_clique_weights(vertex_weights, n)?;
1062    if n == 0 {
1063        return Ok(Vec::new());
1064    }
1065
1066    let mut candidates = if maximal {
1067        let mut cs = maximal_cliques(graph)?;
1068        for clique in &mut cs {
1069            clique.sort_unstable();
1070        }
1071        cs
1072    } else {
1073        cliques(graph, 1, n, None)?
1074    };
1075    candidates.sort_unstable();
1076
1077    let has_min = min_weight > 0.0;
1078    let has_max = max_weight > 0.0;
1079    let limit = max_results.unwrap_or(usize::MAX);
1080
1081    let mut result: Vec<Vec<VertexId>> = Vec::new();
1082    for clique in candidates {
1083        let w = clique_weight(&weights, &clique)? as f64;
1084        if (has_min && w < min_weight) || (has_max && w > max_weight) {
1085            continue;
1086        }
1087        result.push(clique);
1088        if result.len() >= limit {
1089            break;
1090        }
1091    }
1092    Ok(result)
1093}
1094
1095/// Validate vertex-weight slice and truncate to positive integer weights.
1096// The f64 -> i64 conversion is range-guarded (1 <= w <= i64::MAX), and the
1097// i64::MAX -> f64 bound check is the standard idiom for that guard.
1098#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
1099fn validate_clique_weights(vertex_weights: &[f64], n: u32) -> IgraphResult<Vec<i64>> {
1100    if vertex_weights.len() != n as usize {
1101        return Err(IgraphError::InvalidArgument(format!(
1102            "vertex_weights length {} does not match vertex count {n}",
1103            vertex_weights.len()
1104        )));
1105    }
1106    let mut weights = Vec::with_capacity(vertex_weights.len());
1107    for (v, &w) in vertex_weights.iter().enumerate() {
1108        let truncated = w.trunc();
1109        if !truncated.is_finite() || truncated < 1.0 {
1110            return Err(IgraphError::InvalidArgument(format!(
1111                "weighted cliques require positive integer vertex weights; vertex {v} has weight {w}"
1112            )));
1113        }
1114        if truncated > i64::MAX as f64 {
1115            return Err(IgraphError::InvalidArgument(format!(
1116                "vertex weight for vertex {v} is too large"
1117            )));
1118        }
1119        weights.push(truncated as i64);
1120    }
1121    Ok(weights)
1122}
1123
1124/// Sum vertex weights over a clique with overflow checking.
1125fn clique_weight(weights: &[i64], clique: &[VertexId]) -> IgraphResult<i64> {
1126    let mut total: i64 = 0;
1127    for &v in clique {
1128        total = total
1129            .checked_add(weights[v as usize])
1130            .ok_or(IgraphError::Internal("clique weight sum overflows i64"))?;
1131    }
1132    Ok(total)
1133}
1134
1135/// Finds all independent vertex sets within a size range.
1136///
1137/// An independent set is a set of vertices with no edges between them.
1138/// Returns all independent sets of size `min_size..=max_size`.
1139/// If `max_results` is `Some(n)`, at most `n` sets are returned.
1140///
1141/// Edge directions are ignored for directed graphs.
1142///
1143/// # Examples
1144///
1145/// ```
1146/// use rust_igraph::{Graph, independent_vertex_sets};
1147///
1148/// // Path 0-1-2: independent sets of size 2 are {0,2}
1149/// let mut g = Graph::with_vertices(3);
1150/// g.add_edge(0, 1).unwrap();
1151/// g.add_edge(1, 2).unwrap();
1152/// let sets = independent_vertex_sets(&g, 2, 3, None).unwrap();
1153/// assert_eq!(sets.len(), 1);
1154/// assert_eq!(sets[0], vec![0, 2]);
1155/// ```
1156pub fn independent_vertex_sets(
1157    graph: &Graph,
1158    min_size: u32,
1159    max_size: u32,
1160    max_results: Option<usize>,
1161) -> IgraphResult<Vec<Vec<VertexId>>> {
1162    let n = graph.vcount();
1163    if n == 0 || min_size > n || max_size == 0 {
1164        return Ok(Vec::new());
1165    }
1166
1167    let min_sz = if min_size == 0 { 1 } else { min_size } as usize;
1168    let max_sz = max_size as usize;
1169    let limit = max_results.unwrap_or(usize::MAX);
1170
1171    let adj = build_complement_neighbor_set(graph)?;
1172    let mut result: Vec<Vec<VertexId>> = Vec::new();
1173
1174    let mut current: Vec<VertexId> = Vec::new();
1175    enumerate_cliques(&adj, &mut current, 0, n, min_sz, max_sz, limit, &mut result);
1176
1177    Ok(result)
1178}
1179
1180#[allow(clippy::too_many_arguments)]
1181fn enumerate_cliques(
1182    adj: &[Vec<VertexId>],
1183    current: &mut Vec<VertexId>,
1184    start: u32,
1185    n: u32,
1186    min_sz: usize,
1187    max_sz: usize,
1188    limit: usize,
1189    result: &mut Vec<Vec<VertexId>>,
1190) {
1191    let cur_len = current.len();
1192
1193    if cur_len >= min_sz {
1194        let mut clique = current.clone();
1195        clique.sort_unstable();
1196        result.push(clique);
1197        if result.len() >= limit {
1198            return;
1199        }
1200    }
1201
1202    if cur_len >= max_sz {
1203        return;
1204    }
1205
1206    for v in start..n {
1207        // v must be adjacent to all vertices in current.
1208        let v_adj = &adj[v as usize];
1209        let all_connected = current.iter().all(|&u| v_adj.contains(&u));
1210        if !all_connected {
1211            continue;
1212        }
1213
1214        current.push(v);
1215        enumerate_cliques(adj, current, v + 1, n, min_sz, max_sz, limit, result);
1216        current.pop();
1217
1218        if result.len() >= limit {
1219            return;
1220        }
1221    }
1222}
1223
1224/// Build undirected neighbor lists (ignoring edge direction).
1225fn build_neighbor_set(graph: &Graph) -> IgraphResult<Vec<Vec<VertexId>>> {
1226    let n = graph.vcount() as usize;
1227    let ecount = graph.ecount();
1228    let mut adj: Vec<Vec<VertexId>> = vec![Vec::new(); n];
1229
1230    for eid in 0..ecount {
1231        #[allow(clippy::cast_possible_truncation)]
1232        let (src, tgt) = graph.edge(eid as u32)?;
1233        if src != tgt {
1234            adj[src as usize].push(tgt);
1235            adj[tgt as usize].push(src);
1236        }
1237    }
1238
1239    // Deduplicate (handles multi-edges)
1240    for neighbors in &mut adj {
1241        neighbors.sort_unstable();
1242        neighbors.dedup();
1243    }
1244
1245    Ok(adj)
1246}
1247
1248#[cfg(test)]
1249mod tests {
1250    use super::*;
1251
1252    #[test]
1253    fn test_clique_number_empty() {
1254        let g = Graph::with_vertices(0);
1255        assert_eq!(clique_number(&g).unwrap(), 0);
1256    }
1257
1258    #[test]
1259    fn test_clique_number_isolated() {
1260        let g = Graph::with_vertices(5);
1261        assert_eq!(clique_number(&g).unwrap(), 1);
1262    }
1263
1264    #[test]
1265    fn test_clique_number_single_edge() {
1266        let mut g = Graph::with_vertices(3);
1267        g.add_edge(0, 1).unwrap();
1268        assert_eq!(clique_number(&g).unwrap(), 2);
1269    }
1270
1271    #[test]
1272    fn test_clique_number_triangle() {
1273        let mut g = Graph::with_vertices(4);
1274        g.add_edge(0, 1).unwrap();
1275        g.add_edge(1, 2).unwrap();
1276        g.add_edge(0, 2).unwrap();
1277        g.add_edge(2, 3).unwrap();
1278        assert_eq!(clique_number(&g).unwrap(), 3);
1279    }
1280
1281    #[test]
1282    fn test_clique_number_k5() {
1283        let mut g = Graph::with_vertices(5);
1284        for i in 0..5u32 {
1285            for j in (i + 1)..5 {
1286                g.add_edge(i, j).unwrap();
1287            }
1288        }
1289        assert_eq!(clique_number(&g).unwrap(), 5);
1290    }
1291
1292    #[test]
1293    fn test_clique_number_directed_ignores_direction() {
1294        let mut g = Graph::new(3, true).unwrap();
1295        g.add_edge(0, 1).unwrap();
1296        g.add_edge(1, 2).unwrap();
1297        g.add_edge(2, 0).unwrap();
1298        // Triangle when ignoring directions
1299        assert_eq!(clique_number(&g).unwrap(), 3);
1300    }
1301
1302    #[test]
1303    fn test_maximal_cliques_path() {
1304        let mut g = Graph::with_vertices(4);
1305        g.add_edge(0, 1).unwrap();
1306        g.add_edge(1, 2).unwrap();
1307        g.add_edge(2, 3).unwrap();
1308
1309        let cliques = maximal_cliques(&g).unwrap();
1310        assert_eq!(cliques.len(), 3);
1311        for c in &cliques {
1312            assert_eq!(c.len(), 2);
1313        }
1314    }
1315
1316    #[test]
1317    fn test_maximal_cliques_triangle_plus_edge() {
1318        let mut g = Graph::with_vertices(4);
1319        g.add_edge(0, 1).unwrap();
1320        g.add_edge(1, 2).unwrap();
1321        g.add_edge(0, 2).unwrap();
1322        g.add_edge(2, 3).unwrap();
1323
1324        let cliques = maximal_cliques(&g).unwrap();
1325        // {0,1,2} and {2,3}
1326        assert_eq!(cliques.len(), 2);
1327        let sizes: Vec<usize> = cliques.iter().map(Vec::len).collect();
1328        assert!(sizes.contains(&3));
1329        assert!(sizes.contains(&2));
1330    }
1331
1332    #[test]
1333    fn test_maximal_cliques_isolated_vertices() {
1334        let mut g = Graph::with_vertices(5);
1335        g.add_edge(0, 1).unwrap();
1336        // vertices 2, 3, 4 are isolated
1337
1338        let cliques = maximal_cliques(&g).unwrap();
1339        // {0,1} + 3 isolated = 4 cliques
1340        assert_eq!(cliques.len(), 4);
1341    }
1342
1343    #[test]
1344    fn test_largest_cliques() {
1345        let mut g = Graph::with_vertices(6);
1346        // Two triangles sharing an edge
1347        g.add_edge(0, 1).unwrap();
1348        g.add_edge(1, 2).unwrap();
1349        g.add_edge(0, 2).unwrap();
1350        g.add_edge(2, 3).unwrap();
1351        g.add_edge(3, 4).unwrap();
1352        g.add_edge(2, 4).unwrap();
1353
1354        let cliques = largest_cliques(&g).unwrap();
1355        // Two triangles: {0,1,2} and {2,3,4}
1356        assert_eq!(cliques.len(), 2);
1357        for c in &cliques {
1358            assert_eq!(c.len(), 3);
1359        }
1360    }
1361
1362    #[test]
1363    fn test_largest_cliques_k4() {
1364        let mut g = Graph::with_vertices(4);
1365        for i in 0..4u32 {
1366            for j in (i + 1)..4 {
1367                g.add_edge(i, j).unwrap();
1368            }
1369        }
1370
1371        let cliques = largest_cliques(&g).unwrap();
1372        assert_eq!(cliques.len(), 1);
1373        assert_eq!(cliques[0].len(), 4);
1374    }
1375
1376    #[test]
1377    fn test_clique_number_petersen() {
1378        // Petersen graph has clique number 2 (no triangles)
1379        let mut g = Graph::with_vertices(10);
1380        // Outer 5-cycle
1381        g.add_edge(0, 1).unwrap();
1382        g.add_edge(1, 2).unwrap();
1383        g.add_edge(2, 3).unwrap();
1384        g.add_edge(3, 4).unwrap();
1385        g.add_edge(4, 0).unwrap();
1386        // Inner pentagram
1387        g.add_edge(5, 7).unwrap();
1388        g.add_edge(7, 9).unwrap();
1389        g.add_edge(9, 6).unwrap();
1390        g.add_edge(6, 8).unwrap();
1391        g.add_edge(8, 5).unwrap();
1392        // Spokes
1393        g.add_edge(0, 5).unwrap();
1394        g.add_edge(1, 6).unwrap();
1395        g.add_edge(2, 7).unwrap();
1396        g.add_edge(3, 8).unwrap();
1397        g.add_edge(4, 9).unwrap();
1398
1399        assert_eq!(clique_number(&g).unwrap(), 2);
1400    }
1401
1402    // --- Independent vertex set tests ---
1403
1404    #[test]
1405    fn test_independence_number_empty() {
1406        let g = Graph::with_vertices(0);
1407        assert_eq!(independence_number(&g).unwrap(), 0);
1408    }
1409
1410    #[test]
1411    fn test_independence_number_isolated() {
1412        let g = Graph::with_vertices(5);
1413        assert_eq!(independence_number(&g).unwrap(), 5);
1414    }
1415
1416    #[test]
1417    fn test_independence_number_complete() {
1418        let mut g = Graph::with_vertices(5);
1419        for i in 0..5u32 {
1420            for j in (i + 1)..5 {
1421                g.add_edge(i, j).unwrap();
1422            }
1423        }
1424        assert_eq!(independence_number(&g).unwrap(), 1);
1425    }
1426
1427    #[test]
1428    fn test_independence_number_path() {
1429        let mut g = Graph::with_vertices(4);
1430        g.add_edge(0, 1).unwrap();
1431        g.add_edge(1, 2).unwrap();
1432        g.add_edge(2, 3).unwrap();
1433        // Largest independent set: {0,2} or {0,3} or {1,3} — all size 2
1434        assert_eq!(independence_number(&g).unwrap(), 2);
1435    }
1436
1437    #[test]
1438    fn test_independence_number_cycle_5() {
1439        let mut g = Graph::with_vertices(5);
1440        g.add_edge(0, 1).unwrap();
1441        g.add_edge(1, 2).unwrap();
1442        g.add_edge(2, 3).unwrap();
1443        g.add_edge(3, 4).unwrap();
1444        g.add_edge(4, 0).unwrap();
1445        // C5: independence number = 2
1446        assert_eq!(independence_number(&g).unwrap(), 2);
1447    }
1448
1449    #[test]
1450    fn test_independence_number_bipartite() {
1451        // K_{2,3}: independence number = max(2, 3) = 3
1452        let mut g = Graph::with_vertices(5);
1453        for &a in &[0u32, 1] {
1454            for &b in &[2u32, 3, 4] {
1455                g.add_edge(a, b).unwrap();
1456            }
1457        }
1458        assert_eq!(independence_number(&g).unwrap(), 3);
1459    }
1460
1461    #[test]
1462    fn test_maximal_independent_sets_triangle() {
1463        let mut g = Graph::with_vertices(3);
1464        g.add_edge(0, 1).unwrap();
1465        g.add_edge(1, 2).unwrap();
1466        g.add_edge(0, 2).unwrap();
1467
1468        let sets = maximal_independent_vertex_sets(&g).unwrap();
1469        // Each single vertex is a maximal independent set in K3
1470        assert_eq!(sets.len(), 3);
1471        for s in &sets {
1472            assert_eq!(s.len(), 1);
1473        }
1474    }
1475
1476    #[test]
1477    fn test_maximal_independent_sets_path_4() {
1478        let mut g = Graph::with_vertices(4);
1479        g.add_edge(0, 1).unwrap();
1480        g.add_edge(1, 2).unwrap();
1481        g.add_edge(2, 3).unwrap();
1482
1483        let sets = maximal_independent_vertex_sets(&g).unwrap();
1484        // Maximal independent sets: {0,2}, {0,3}, {1,3}
1485        assert_eq!(sets.len(), 3);
1486        for s in &sets {
1487            assert_eq!(s.len(), 2);
1488        }
1489    }
1490
1491    #[test]
1492    fn test_largest_independent_vertex_sets_star() {
1493        // Star K_{1,4}: center connected to all leaves
1494        let mut g = Graph::with_vertices(5);
1495        g.add_edge(0, 1).unwrap();
1496        g.add_edge(0, 2).unwrap();
1497        g.add_edge(0, 3).unwrap();
1498        g.add_edge(0, 4).unwrap();
1499
1500        let sets = largest_independent_vertex_sets(&g).unwrap();
1501        // Largest independent set: {1,2,3,4} (all leaves)
1502        assert_eq!(sets.len(), 1);
1503        assert_eq!(sets[0].len(), 4);
1504    }
1505
1506    #[test]
1507    fn test_independence_number_petersen() {
1508        // Petersen graph has independence number 4
1509        let mut g = Graph::with_vertices(10);
1510        g.add_edge(0, 1).unwrap();
1511        g.add_edge(1, 2).unwrap();
1512        g.add_edge(2, 3).unwrap();
1513        g.add_edge(3, 4).unwrap();
1514        g.add_edge(4, 0).unwrap();
1515        g.add_edge(5, 7).unwrap();
1516        g.add_edge(7, 9).unwrap();
1517        g.add_edge(9, 6).unwrap();
1518        g.add_edge(6, 8).unwrap();
1519        g.add_edge(8, 5).unwrap();
1520        g.add_edge(0, 5).unwrap();
1521        g.add_edge(1, 6).unwrap();
1522        g.add_edge(2, 7).unwrap();
1523        g.add_edge(3, 8).unwrap();
1524        g.add_edge(4, 9).unwrap();
1525
1526        assert_eq!(independence_number(&g).unwrap(), 4);
1527    }
1528
1529    #[test]
1530    fn test_independent_sets_directed_ignores_direction() {
1531        // Directed triangle: independence number = 1
1532        let mut g = Graph::new(3, true).unwrap();
1533        g.add_edge(0, 1).unwrap();
1534        g.add_edge(1, 2).unwrap();
1535        g.add_edge(2, 0).unwrap();
1536        assert_eq!(independence_number(&g).unwrap(), 1);
1537    }
1538
1539    // --- maximal_cliques_count tests ---
1540
1541    #[test]
1542    fn test_count_empty() {
1543        let g = Graph::with_vertices(0);
1544        assert_eq!(maximal_cliques_count(&g).unwrap(), 0);
1545    }
1546
1547    #[test]
1548    fn test_count_isolated() {
1549        let g = Graph::with_vertices(3);
1550        // 3 isolated vertices → 3 cliques of size 1
1551        assert_eq!(maximal_cliques_count(&g).unwrap(), 3);
1552    }
1553
1554    #[test]
1555    fn test_count_path() {
1556        let mut g = Graph::with_vertices(4);
1557        g.add_edge(0, 1).unwrap();
1558        g.add_edge(1, 2).unwrap();
1559        g.add_edge(2, 3).unwrap();
1560        // Path: 3 maximal cliques (each edge)
1561        assert_eq!(maximal_cliques_count(&g).unwrap(), 3);
1562    }
1563
1564    #[test]
1565    fn test_count_triangle_plus_edge() {
1566        let mut g = Graph::with_vertices(4);
1567        g.add_edge(0, 1).unwrap();
1568        g.add_edge(0, 2).unwrap();
1569        g.add_edge(1, 2).unwrap();
1570        g.add_edge(2, 3).unwrap();
1571        // Triangle {0,1,2} + edge {2,3} = 2 maximal cliques
1572        assert_eq!(maximal_cliques_count(&g).unwrap(), 2);
1573    }
1574
1575    #[test]
1576    fn test_count_matches_enum() {
1577        let mut g = Graph::with_vertices(5);
1578        g.add_edge(0, 1).unwrap();
1579        g.add_edge(0, 2).unwrap();
1580        g.add_edge(1, 2).unwrap();
1581        g.add_edge(2, 3).unwrap();
1582        g.add_edge(3, 4).unwrap();
1583        let enumerated = maximal_cliques(&g).unwrap();
1584        assert_eq!(maximal_cliques_count(&g).unwrap(), enumerated.len() as u64);
1585    }
1586
1587    // --- clique_size_hist tests ---
1588
1589    #[test]
1590    fn test_hist_empty() {
1591        let g = Graph::with_vertices(0);
1592        assert!(clique_size_hist(&g).unwrap().is_empty());
1593    }
1594
1595    #[test]
1596    fn test_hist_isolated() {
1597        let g = Graph::with_vertices(3);
1598        let hist = clique_size_hist(&g).unwrap();
1599        // 3 cliques of size 1
1600        assert_eq!(hist, vec![0, 3]);
1601    }
1602
1603    #[test]
1604    fn test_hist_triangle_plus_edge() {
1605        let mut g = Graph::with_vertices(4);
1606        g.add_edge(0, 1).unwrap();
1607        g.add_edge(0, 2).unwrap();
1608        g.add_edge(1, 2).unwrap();
1609        g.add_edge(2, 3).unwrap();
1610        let hist = clique_size_hist(&g).unwrap();
1611        // all cliques: 4 vertices, 4 edges, 1 triangle
1612        assert_eq!(hist, vec![0, 4, 4, 1]);
1613    }
1614
1615    #[test]
1616    fn test_hist_complete_4() {
1617        let mut g = Graph::with_vertices(4);
1618        for i in 0..4u32 {
1619            for j in (i + 1)..4 {
1620                g.add_edge(i, j).unwrap();
1621            }
1622        }
1623        let hist = clique_size_hist(&g).unwrap();
1624        // K4: C(4,1)=4, C(4,2)=6, C(4,3)=4, C(4,4)=1
1625        assert_eq!(hist, vec![0, 4, 6, 4, 1]);
1626    }
1627
1628    #[test]
1629    fn test_hist_path() {
1630        let mut g = Graph::with_vertices(4);
1631        g.add_edge(0, 1).unwrap();
1632        g.add_edge(1, 2).unwrap();
1633        g.add_edge(2, 3).unwrap();
1634        let hist = clique_size_hist(&g).unwrap();
1635        // 4 vertices, 3 edges, no triangles
1636        assert_eq!(hist, vec![0, 4, 3]);
1637    }
1638
1639    #[test]
1640    fn test_hist_matches_cliques_enumeration() {
1641        let mut g = Graph::with_vertices(6);
1642        for &(a, b) in &[(0, 1), (0, 2), (1, 2), (2, 3), (3, 4), (3, 5), (4, 5)] {
1643            g.add_edge(a, b).unwrap();
1644        }
1645        let hist = clique_size_hist(&g).unwrap();
1646        // Cross-check against the all-cliques enumerator.
1647        let all = cliques(&g, 1, g.vcount(), None).unwrap();
1648        let mut expected = vec![0u64; hist.len()];
1649        for c in &all {
1650            expected[c.len()] += 1;
1651        }
1652        assert_eq!(hist, expected);
1653    }
1654
1655    // --- maximal_cliques_hist tests ---
1656
1657    #[test]
1658    fn test_max_hist_empty() {
1659        let g = Graph::with_vertices(0);
1660        assert!(maximal_cliques_hist(&g).unwrap().is_empty());
1661    }
1662
1663    #[test]
1664    fn test_max_hist_isolated() {
1665        let g = Graph::with_vertices(3);
1666        let hist = maximal_cliques_hist(&g).unwrap();
1667        // 3 size-1 maximal cliques (isolated vertices)
1668        assert_eq!(hist, vec![0, 3]);
1669    }
1670
1671    #[test]
1672    fn test_max_hist_triangle_plus_edge() {
1673        let mut g = Graph::with_vertices(4);
1674        g.add_edge(0, 1).unwrap();
1675        g.add_edge(0, 2).unwrap();
1676        g.add_edge(1, 2).unwrap();
1677        g.add_edge(2, 3).unwrap();
1678        let hist = maximal_cliques_hist(&g).unwrap();
1679        // maximal: edge {2,3} (size 2), triangle {0,1,2} (size 3)
1680        assert_eq!(hist, vec![0, 0, 1, 1]);
1681    }
1682
1683    #[test]
1684    fn test_max_hist_complete_4() {
1685        let mut g = Graph::with_vertices(4);
1686        for i in 0..4u32 {
1687            for j in (i + 1)..4 {
1688                g.add_edge(i, j).unwrap();
1689            }
1690        }
1691        let hist = maximal_cliques_hist(&g).unwrap();
1692        // K4: single maximal clique of size 4
1693        assert_eq!(hist, vec![0, 0, 0, 0, 1]);
1694    }
1695
1696    #[test]
1697    fn test_max_hist_path() {
1698        let mut g = Graph::with_vertices(4);
1699        g.add_edge(0, 1).unwrap();
1700        g.add_edge(1, 2).unwrap();
1701        g.add_edge(2, 3).unwrap();
1702        let hist = maximal_cliques_hist(&g).unwrap();
1703        // 3 maximal cliques, each an edge
1704        assert_eq!(hist, vec![0, 0, 3]);
1705    }
1706
1707    #[test]
1708    fn test_max_hist_total_matches_count() {
1709        let mut g = Graph::with_vertices(6);
1710        for &(a, b) in &[(0, 1), (0, 2), (1, 2), (2, 3), (3, 4), (3, 5), (4, 5)] {
1711            g.add_edge(a, b).unwrap();
1712        }
1713        let hist = maximal_cliques_hist(&g).unwrap();
1714        let total: u64 = hist.iter().sum();
1715        assert_eq!(total, maximal_cliques_count(&g).unwrap());
1716    }
1717
1718    // --- weighted clique tests ---
1719
1720    fn triangle_plus_pendant() -> Graph {
1721        let mut g = Graph::with_vertices(4);
1722        g.add_edge(0, 1).unwrap();
1723        g.add_edge(0, 2).unwrap();
1724        g.add_edge(1, 2).unwrap();
1725        g.add_edge(2, 3).unwrap();
1726        g
1727    }
1728
1729    #[test]
1730    fn test_weighted_clique_number_pendant_wins() {
1731        let g = triangle_plus_pendant();
1732        let w = weighted_clique_number(&g, &[1.0, 1.0, 1.0, 5.0]).unwrap();
1733        assert!((w - 6.0).abs() < 1e-9);
1734    }
1735
1736    #[test]
1737    fn test_weighted_clique_number_triangle_wins() {
1738        let g = triangle_plus_pendant();
1739        // Heavy triangle vertices outweigh the pendant edge.
1740        let w = weighted_clique_number(&g, &[3.0, 3.0, 3.0, 1.0]).unwrap();
1741        assert!((w - 9.0).abs() < 1e-9);
1742    }
1743
1744    #[test]
1745    fn test_weighted_clique_number_unit_weights_equals_clique_number() {
1746        let g = triangle_plus_pendant();
1747        let w = weighted_clique_number(&g, &[1.0, 1.0, 1.0, 1.0]).unwrap();
1748        assert!((w - f64::from(clique_number(&g).unwrap())).abs() < 1e-9);
1749    }
1750
1751    #[test]
1752    fn test_weighted_clique_number_truncates() {
1753        let g = triangle_plus_pendant();
1754        // 2.9 truncates to 2, so {2,3} weighs 2 + 2 = 4 < triangle's 6.
1755        let w = weighted_clique_number(&g, &[2.9, 2.9, 2.9, 2.9]).unwrap();
1756        assert!((w - 6.0).abs() < 1e-9);
1757    }
1758
1759    #[test]
1760    fn test_weighted_clique_number_empty_graph() {
1761        let g = Graph::with_vertices(0);
1762        assert!(weighted_clique_number(&g, &[]).unwrap().abs() < 1e-9);
1763    }
1764
1765    #[test]
1766    fn test_weighted_clique_number_isolated_vertices() {
1767        let g = Graph::with_vertices(3);
1768        let w = weighted_clique_number(&g, &[2.0, 7.0, 3.0]).unwrap();
1769        assert!((w - 7.0).abs() < 1e-9);
1770    }
1771
1772    #[test]
1773    fn test_largest_weighted_cliques_single_winner() {
1774        let g = triangle_plus_pendant();
1775        let cs = largest_weighted_cliques(&g, &[1.0, 1.0, 1.0, 5.0]).unwrap();
1776        assert_eq!(cs, vec![vec![2, 3]]);
1777    }
1778
1779    #[test]
1780    fn test_largest_weighted_cliques_tie() {
1781        // Two disjoint edges with equal weight tie for the maximum.
1782        let mut g = Graph::with_vertices(4);
1783        g.add_edge(0, 1).unwrap();
1784        g.add_edge(2, 3).unwrap();
1785        let cs = largest_weighted_cliques(&g, &[1.0, 1.0, 1.0, 1.0]).unwrap();
1786        assert_eq!(cs, vec![vec![0, 1], vec![2, 3]]);
1787    }
1788
1789    #[test]
1790    fn test_weighted_cliques_all_maximal() {
1791        let g = triangle_plus_pendant();
1792        let cs = weighted_cliques(&g, &[1.0, 1.0, 1.0, 1.0], true, 0.0, 0.0, None).unwrap();
1793        assert_eq!(cs, vec![vec![0, 1, 2], vec![2, 3]]);
1794    }
1795
1796    #[test]
1797    fn test_weighted_cliques_min_weight_filter() {
1798        let g = triangle_plus_pendant();
1799        // Only cliques with weight >= 3 among all (non-maximal) cliques.
1800        let cs = weighted_cliques(&g, &[1.0, 1.0, 1.0, 5.0], false, 3.0, 0.0, None).unwrap();
1801        // Cliques of weight >= 3: {2,3}(6), {0,1,2}(3); edges {0,2},{1,2}=2, etc. excluded.
1802        assert!(cs.contains(&vec![2, 3]));
1803        assert!(cs.contains(&vec![0, 1, 2]));
1804        for c in &cs {
1805            let total: f64 = c.iter().map(|&v| [1.0, 1.0, 1.0, 5.0][v as usize]).sum();
1806            assert!(total >= 3.0);
1807        }
1808    }
1809
1810    #[test]
1811    fn test_weighted_cliques_max_weight_filter() {
1812        let g = triangle_plus_pendant();
1813        // All cliques of weight <= 2 with unit weights: singles (1) and edges (2).
1814        let cs = weighted_cliques(&g, &[1.0, 1.0, 1.0, 1.0], false, 0.0, 2.0, None).unwrap();
1815        for c in &cs {
1816            assert!(c.len() <= 2);
1817        }
1818        assert!(cs.contains(&vec![0]));
1819        assert!(cs.contains(&vec![0, 1]));
1820        assert!(!cs.contains(&vec![0, 1, 2]));
1821    }
1822
1823    #[test]
1824    fn test_weighted_cliques_max_results() {
1825        let g = triangle_plus_pendant();
1826        let cs = weighted_cliques(&g, &[1.0, 1.0, 1.0, 1.0], false, 0.0, 0.0, Some(2)).unwrap();
1827        assert_eq!(cs.len(), 2);
1828    }
1829
1830    #[test]
1831    fn test_weighted_cliques_wrong_weight_length_errors() {
1832        let g = triangle_plus_pendant();
1833        assert!(weighted_clique_number(&g, &[1.0, 1.0]).is_err());
1834        assert!(largest_weighted_cliques(&g, &[1.0, 1.0]).is_err());
1835        assert!(weighted_cliques(&g, &[1.0, 1.0], true, 0.0, 0.0, None).is_err());
1836    }
1837
1838    #[test]
1839    fn test_weighted_cliques_non_positive_weight_errors() {
1840        let g = triangle_plus_pendant();
1841        assert!(weighted_clique_number(&g, &[1.0, 0.0, 1.0, 1.0]).is_err());
1842        assert!(weighted_clique_number(&g, &[1.0, -2.0, 1.0, 1.0]).is_err());
1843        // 0.5 truncates to 0, which is not a positive integer.
1844        assert!(weighted_clique_number(&g, &[0.5, 1.0, 1.0, 1.0]).is_err());
1845    }
1846
1847    // --- cliques (all) tests ---
1848
1849    #[test]
1850    fn test_cliques_triangle() {
1851        let mut g = Graph::with_vertices(3);
1852        g.add_edge(0, 1).unwrap();
1853        g.add_edge(1, 2).unwrap();
1854        g.add_edge(0, 2).unwrap();
1855        let all = cliques(&g, 1, 3, None).unwrap();
1856        // 3 singles + 3 edges + 1 triangle = 7
1857        assert_eq!(all.len(), 7);
1858    }
1859
1860    #[test]
1861    fn test_cliques_size_filter() {
1862        let mut g = Graph::with_vertices(3);
1863        g.add_edge(0, 1).unwrap();
1864        g.add_edge(1, 2).unwrap();
1865        g.add_edge(0, 2).unwrap();
1866        // Only size 2
1867        let edges = cliques(&g, 2, 2, None).unwrap();
1868        assert_eq!(edges.len(), 3);
1869        for c in &edges {
1870            assert_eq!(c.len(), 2);
1871        }
1872    }
1873
1874    #[test]
1875    fn test_cliques_max_results() {
1876        let mut g = Graph::with_vertices(3);
1877        g.add_edge(0, 1).unwrap();
1878        g.add_edge(1, 2).unwrap();
1879        g.add_edge(0, 2).unwrap();
1880        let limited = cliques(&g, 1, 3, Some(4)).unwrap();
1881        assert_eq!(limited.len(), 4);
1882    }
1883
1884    #[test]
1885    fn test_cliques_k4_size_3() {
1886        let mut g = Graph::with_vertices(4);
1887        for i in 0..4u32 {
1888            for j in (i + 1)..4 {
1889                g.add_edge(i, j).unwrap();
1890            }
1891        }
1892        // C(4,3) = 4 triangles in K4
1893        let tri = cliques(&g, 3, 3, None).unwrap();
1894        assert_eq!(tri.len(), 4);
1895    }
1896
1897    #[test]
1898    fn test_cliques_empty_graph() {
1899        let g = Graph::with_vertices(0);
1900        assert!(cliques(&g, 1, 5, None).unwrap().is_empty());
1901    }
1902
1903    #[test]
1904    fn test_cliques_no_edges_min_2() {
1905        let g = Graph::with_vertices(5);
1906        let result = cliques(&g, 2, 5, None).unwrap();
1907        assert!(result.is_empty());
1908    }
1909
1910    // --- independent_vertex_sets tests ---
1911
1912    #[test]
1913    fn test_ivs_path_size_2() {
1914        let mut g = Graph::with_vertices(3);
1915        g.add_edge(0, 1).unwrap();
1916        g.add_edge(1, 2).unwrap();
1917        // Independent sets of size 2: {0,2}
1918        let sets = independent_vertex_sets(&g, 2, 3, None).unwrap();
1919        assert_eq!(sets.len(), 1);
1920        assert_eq!(sets[0], vec![0, 2]);
1921    }
1922
1923    #[test]
1924    fn test_ivs_complete_graph() {
1925        let mut g = Graph::with_vertices(4);
1926        for i in 0..4u32 {
1927            for j in (i + 1)..4 {
1928                g.add_edge(i, j).unwrap();
1929            }
1930        }
1931        // In K4, only size-1 independent sets exist
1932        let sets = independent_vertex_sets(&g, 2, 4, None).unwrap();
1933        assert!(sets.is_empty());
1934    }
1935
1936    #[test]
1937    fn test_ivs_no_edges() {
1938        let g = Graph::with_vertices(4);
1939        // All subsets of size 2 are independent: C(4,2) = 6
1940        let sets = independent_vertex_sets(&g, 2, 2, None).unwrap();
1941        assert_eq!(sets.len(), 6);
1942    }
1943
1944    #[test]
1945    fn test_ivs_max_results() {
1946        let g = Graph::with_vertices(5);
1947        let sets = independent_vertex_sets(&g, 1, 5, Some(10)).unwrap();
1948        assert_eq!(sets.len(), 10);
1949    }
1950
1951    // ---- maximal_cliques_subset tests ----
1952
1953    #[test]
1954    fn test_mcs_empty_graph() {
1955        let g = Graph::with_vertices(0);
1956        let r = maximal_cliques_subset(&g, &[], 0, 0, None).unwrap();
1957        assert!(r.is_empty());
1958    }
1959
1960    #[test]
1961    fn test_mcs_empty_subset() {
1962        let mut g = Graph::with_vertices(3);
1963        g.add_edge(0, 1).unwrap();
1964        let r = maximal_cliques_subset(&g, &[], 0, 0, None).unwrap();
1965        assert!(r.is_empty());
1966    }
1967
1968    #[test]
1969    fn test_mcs_invalid_vertex() {
1970        let g = Graph::with_vertices(3);
1971        assert!(maximal_cliques_subset(&g, &[5], 0, 0, None).is_err());
1972    }
1973
1974    #[test]
1975    fn test_mcs_triangle_plus_edge() {
1976        // Triangle {0,1,2} + edge {2,3}
1977        let mut g = Graph::with_vertices(4);
1978        g.add_edge(0, 1).unwrap();
1979        g.add_edge(0, 2).unwrap();
1980        g.add_edge(1, 2).unwrap();
1981        g.add_edge(2, 3).unwrap();
1982
1983        // Subset {3}: only {2,3} touches vertex 3
1984        let r = maximal_cliques_subset(&g, &[3], 0, 0, None).unwrap();
1985        assert_eq!(r.len(), 1);
1986        assert!(r[0].contains(&2));
1987        assert!(r[0].contains(&3));
1988
1989        // Subset {0}: only the triangle touches vertex 0
1990        let r = maximal_cliques_subset(&g, &[0], 0, 0, None).unwrap();
1991        assert_eq!(r.len(), 1);
1992        assert_eq!(r[0].len(), 3);
1993
1994        // Subset {2}: both cliques touch vertex 2
1995        let r = maximal_cliques_subset(&g, &[2], 0, 0, None).unwrap();
1996        assert_eq!(r.len(), 2);
1997    }
1998
1999    #[test]
2000    fn test_mcs_size_filter() {
2001        // Triangle {0,1,2} + edge {2,3}
2002        let mut g = Graph::with_vertices(4);
2003        g.add_edge(0, 1).unwrap();
2004        g.add_edge(0, 2).unwrap();
2005        g.add_edge(1, 2).unwrap();
2006        g.add_edge(2, 3).unwrap();
2007
2008        // Subset {2}, min_size=3 → only the triangle
2009        let r = maximal_cliques_subset(&g, &[2], 3, 0, None).unwrap();
2010        assert_eq!(r.len(), 1);
2011        assert_eq!(r[0].len(), 3);
2012
2013        // Subset {2}, max_size=2 → only {2,3}
2014        let r = maximal_cliques_subset(&g, &[2], 0, 2, None).unwrap();
2015        assert_eq!(r.len(), 1);
2016        assert_eq!(r[0].len(), 2);
2017    }
2018
2019    #[test]
2020    fn test_mcs_max_results() {
2021        // K4: 4 triangles + 1 clique of size 4
2022        let mut g = Graph::with_vertices(4);
2023        for i in 0..4u32 {
2024            for j in (i + 1)..4 {
2025                g.add_edge(i, j).unwrap();
2026            }
2027        }
2028        // All vertices in subset → all cliques touch subset
2029        let all = maximal_cliques_subset(&g, &[0, 1, 2, 3], 0, 0, None).unwrap();
2030        let limited = maximal_cliques_subset(&g, &[0, 1, 2, 3], 0, 0, Some(1)).unwrap();
2031        assert!(!all.is_empty());
2032        assert_eq!(limited.len(), 1);
2033    }
2034
2035    #[test]
2036    fn test_mcs_isolated_vertex_in_subset() {
2037        // 0-1, vertex 2 isolated
2038        let mut g = Graph::with_vertices(3);
2039        g.add_edge(0, 1).unwrap();
2040
2041        // Subset {2}: isolated vertex forms a size-1 maximal clique
2042        let r = maximal_cliques_subset(&g, &[2], 0, 0, None).unwrap();
2043        assert_eq!(r.len(), 1);
2044        assert_eq!(r[0], vec![2]);
2045    }
2046
2047    #[test]
2048    fn test_mcs_directed_ignores_direction() {
2049        // Directed triangle 0→1→2→0 treated as undirected
2050        let mut g = Graph::new(3, true).unwrap();
2051        g.add_edge(0, 1).unwrap();
2052        g.add_edge(1, 2).unwrap();
2053        g.add_edge(2, 0).unwrap();
2054
2055        let r = maximal_cliques_subset(&g, &[0], 0, 0, None).unwrap();
2056        assert_eq!(r.len(), 1);
2057        assert_eq!(r[0].len(), 3);
2058    }
2059
2060    #[test]
2061    fn test_mcs_all_vertices_subset() {
2062        // Same result as maximal_cliques when all vertices are in subset
2063        let mut g = Graph::with_vertices(5);
2064        g.add_edge(0, 1).unwrap();
2065        g.add_edge(1, 2).unwrap();
2066        g.add_edge(2, 3).unwrap();
2067        g.add_edge(3, 4).unwrap();
2068
2069        let subset: Vec<u32> = (0..5).collect();
2070        let mcs = maximal_cliques_subset(&g, &subset, 0, 0, None).unwrap();
2071        let mc = maximal_cliques(&g).unwrap();
2072        assert_eq!(mcs.len(), mc.len());
2073    }
2074}