Skip to main content

sdivi_detection/
warm_start.rs

1//! Warm-start support for the Leiden algorithm.
2//!
3//! The partition cache allows consecutive snapshot runs to start from the
4//! previous community assignments rather than all-singletons, improving
5//! stability across incremental changes.  FS I/O (load / save) lives in
6//! `sdivi-pipeline::cache`; this module contains only the pure mapping logic.
7
8use std::collections::BTreeMap;
9
10use crate::partition::LeidenPartition;
11
12/// Default cache path relative to the `.sdivi` directory.
13///
14/// The FS operations using this constant live in `sdivi-pipeline::cache`.
15pub const CACHE_FILENAME: &str = "cache/partition.json";
16
17/// Converts a cached partition into an initial assignment vector.
18///
19/// `node_count` is the number of nodes in the current graph.
20/// Nodes not present in `cached` fall back to singleton assignments
21/// (`assignment[i] = i`).
22///
23/// Community IDs from the cache are remapped so they stay in `[0, node_count)`.
24///
25/// # Examples
26///
27/// ```rust
28/// use std::collections::BTreeMap;
29/// use sdivi_detection::partition::LeidenPartition;
30/// use sdivi_detection::warm_start::initial_assignment_from_cache;
31///
32/// let cached = LeidenPartition {
33///     assignments: BTreeMap::from([(0, 0), (1, 0)]),
34///     stability: BTreeMap::from([(0, 0.9)]),
35///     modularity: 0.5,
36///     seed: 42,
37/// };
38/// let init = initial_assignment_from_cache(Some(&cached), 3);
39/// assert_eq!(init[0], init[1]); // same community
40/// assert_ne!(init[0], init[2]); // node 2 falls back to singleton
41/// ```
42pub fn initial_assignment_from_cache(
43    cached: Option<&LeidenPartition>,
44    node_count: usize,
45) -> Vec<usize> {
46    let mut assignment: Vec<usize> = (0..node_count).collect();
47
48    let cached = match cached {
49        Some(c) => c,
50        None => return assignment,
51    };
52
53    let mut community_remap: BTreeMap<usize, usize> = BTreeMap::new();
54    let mut next_comm = 0usize;
55
56    for (&node, &comm) in &cached.assignments {
57        if node >= node_count {
58            continue;
59        }
60        let remapped = *community_remap.entry(comm).or_insert_with(|| {
61            let c = next_comm;
62            next_comm += 1;
63            c
64        });
65        assignment[node] = remapped;
66    }
67
68    let mut used: std::collections::BTreeSet<usize> = community_remap.values().copied().collect();
69    let mut counter = 0usize;
70
71    for (i, slot) in assignment.iter_mut().enumerate().take(node_count) {
72        if !cached.assignments.contains_key(&i) {
73            while used.contains(&counter) {
74                counter += 1;
75            }
76            *slot = counter;
77            used.insert(counter);
78            counter += 1;
79        }
80    }
81
82    assignment
83}