Skip to content

Commit 3c859db

Browse files
refactor: remove unused refinement_gamma parameter
The refinement_gamma parameter was accepted but unused (prefixed with _) in refine_singleton_merge. Remove it from RunConfig, multilevel_leiden, refine_singleton_merge, CLI, and all test call sites. Incorporates the intent of PR #3 with additional missed call sites fixed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 71bda15 commit 3c859db

3 files changed

Lines changed: 60 additions & 48 deletions

File tree

src/cli/run.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,12 @@ pub fn run_from_cli(
3030

3131
let config = RunConfig {
3232
mode,
33-
graph_source: GraphSource::File, // Assuming file for now
33+
graph_source: Some(GraphSource::File),
3434
acceleration: AccelerationTarget::PureRust,
3535
quality_tolerance: 0.001,
3636
max_iterations: 10,
3737
pinned_profile: None,
3838
resolution: 1.0,
39-
refinement_gamma: 0.05,
4039
};
4140

4241
crate::run(graph, &config)

src/core/algorithm/hit_leiden.rs

Lines changed: 26 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ pub fn run(graph: &GraphInput, config: &RunConfig) -> Result<RunOutcome, HitLeid
5656
&mut partition_state,
5757
graph,
5858
config.resolution,
59-
config.refinement_gamma,
6059
config.mode,
6160
config.max_iterations,
6261
);
@@ -65,9 +64,10 @@ pub fn run(graph: &GraphInput, config: &RunConfig) -> Result<RunOutcome, HitLeid
6564
// across runs with identical partition structure.
6665
canonicalize_community_ids_in_place(&mut partition_state.node_to_comm);
6766

67+
let ds_id = graph.dataset_id.as_deref().unwrap_or("");
6868
let execution = RunExecution {
69-
run_id: format!("run:{}", graph.dataset_id),
70-
dataset_id: graph.dataset_id.clone(),
69+
run_id: format!("run:{}", ds_id),
70+
dataset_id: ds_id.to_string(),
7171
config_id: "default".to_string(),
7272
started_at,
7373
completed_at: Some(
@@ -78,15 +78,7 @@ pub fn run(graph: &GraphInput, config: &RunConfig) -> Result<RunOutcome, HitLeid
7878
),
7979
status: RunStatus::Succeeded,
8080
backend: BackendType::PureRust,
81-
graph_source_resolved: match resolution_meta.source_resolved {
82-
crate::core::backend::GraphSource::File => crate::core::types::GraphSourceType::File,
83-
crate::core::backend::GraphSource::Neo4jSnapshot => {
84-
crate::core::types::GraphSourceType::Neo4jSnapshot
85-
}
86-
crate::core::backend::GraphSource::LiveNeo4j => {
87-
crate::core::types::GraphSourceType::Neo4jSnapshot
88-
} // Fallback
89-
},
81+
graph_source_resolved: resolution_meta.source_resolved,
9082
fallback_reason: resolution_meta.fallback_reason,
9183
};
9284

@@ -119,7 +111,7 @@ pub fn run(graph: &GraphInput, config: &RunConfig) -> Result<RunOutcome, HitLeid
119111
}
120112

121113
/// Compute standard modularity: Q = (1/2m) * sum_ij [ A_ij - k_i*k_j/(2m) ] * delta(c_i, c_j)
122-
fn compute_modularity(graph: &GraphInput, node_to_community: &[usize]) -> f64 {
114+
pub(crate) fn compute_modularity(graph: &GraphInput, node_to_community: &[usize]) -> f64 {
123115
let n = graph.node_count;
124116

125117
// Compute node degrees from edge list
@@ -165,11 +157,10 @@ fn compute_modularity(graph: &GraphInput, node_to_community: &[usize]) -> f64 {
165157
/// subcommunities (connected components), aggregate based on subcommunities, and repeat.
166158
/// The refinement step prevents mega-communities by ensuring the coarsened graph
167159
/// represents subcommunity-level structure, following the standard Leiden approach.
168-
fn multilevel_leiden(
160+
pub(crate) fn multilevel_leiden(
169161
state: &mut PartitionState,
170162
graph: &GraphInput,
171163
gamma: f64,
172-
refinement_gamma: f64,
173164
mode: crate::core::config::RunMode,
174165
max_levels: usize,
175166
) -> (usize, Vec<Vec<usize>>) {
@@ -197,12 +188,10 @@ fn multilevel_leiden(
197188

198189
// Refinement: within each community, merge singletons into subcommunities.
199190
// Uses the SAME resolution as movement for the quality function.
200-
// The refinement_gamma (0.05) is only for the connectivity criterion.
201191
let mut subcommunities = refine_singleton_merge(
202192
&state.supergraphs[0],
203193
&state.community_mapping_per_level[0],
204194
gamma,
205-
refinement_gamma,
206195
);
207196

208197
// The community assignment (for final output) comes from movement
@@ -279,7 +268,6 @@ fn multilevel_leiden(
279268
&state.supergraphs[0],
280269
&state.node_to_comm,
281270
gamma,
282-
refinement_gamma,
283271
);
284272
let new_subcomm_count = count_unique(&subcommunities);
285273

@@ -360,7 +348,7 @@ fn count_unique(v: &[usize]) -> usize {
360348

361349
/// Deterministically rewrite community labels to contiguous IDs [0..k-1]
362350
/// by scanning nodes in index order and assigning first-seen labels.
363-
fn canonicalize_community_ids_in_place(node_to_community: &mut [usize]) {
351+
pub(crate) fn canonicalize_community_ids_in_place(node_to_community: &mut [usize]) {
364352
let mut remap: HashMap<usize, usize> = HashMap::new();
365353
let mut next_id = 0usize;
366354

@@ -455,7 +443,6 @@ fn refine_singleton_merge(
455443
graph: &crate::core::graph::in_memory::InMemoryGraph,
456444
node_to_community: &[usize],
457445
gamma: f64,
458-
_refinement_gamma: f64,
459446
) -> Vec<usize> {
460447
let n = graph.node_count;
461448
if n == 0 {
@@ -1809,7 +1796,7 @@ mod tests {
18091796
/// Helper: build a GraphInput from an edge list with unit weights.
18101797
fn graph(node_count: usize, edges: &[(usize, usize)]) -> GraphInput {
18111798
GraphInput {
1812-
dataset_id: "test".to_string(),
1799+
dataset_id: Some("test".to_string()),
18131800
node_count,
18141801
edges: edges.iter().map(|&(u, v)| (u, v, Some(1.0))).collect(),
18151802
}
@@ -1818,7 +1805,7 @@ mod tests {
18181805
/// Helper: build a GraphInput with explicit weights.
18191806
fn weighted_graph(node_count: usize, edges: &[(usize, usize, f64)]) -> GraphInput {
18201807
GraphInput {
1821-
dataset_id: "test".to_string(),
1808+
dataset_id: Some("test".to_string()),
18221809
node_count,
18231810
edges: edges.iter().map(|&(u, v, w)| (u, v, Some(w))).collect(),
18241811
}
@@ -1905,7 +1892,7 @@ mod tests {
19051892
#[test]
19061893
fn test_should_skip_aggregation_when_no_delta_and_no_refinement() {
19071894
let delta = GraphInput {
1908-
dataset_id: "test".to_string(),
1895+
dataset_id: Some("test".to_string()),
19091896
node_count: 4,
19101897
edges: vec![],
19111898
};
@@ -1916,15 +1903,15 @@ mod tests {
19161903
#[test]
19171904
fn test_should_not_skip_aggregation_when_delta_or_refinement_exists() {
19181905
let delta_non_empty = GraphInput {
1919-
dataset_id: "test".to_string(),
1906+
dataset_id: Some("test".to_string()),
19201907
node_count: 4,
19211908
edges: vec![(0, 1, Some(1.0))],
19221909
};
19231910
let refined_empty = bitvec![0, 0, 0, 0];
19241911
assert!(!should_skip_aggregation(&delta_non_empty, &refined_empty));
19251912

19261913
let delta_empty = GraphInput {
1927-
dataset_id: "test".to_string(),
1914+
dataset_id: Some("test".to_string()),
19281915
node_count: 4,
19291916
edges: vec![],
19301917
};
@@ -2138,7 +2125,7 @@ mod tests {
21382125
let inmem = InMemoryGraph::from(&g);
21392126

21402127
let delta = GraphInput {
2141-
dataset_id: "test".to_string(),
2128+
dataset_id: Some("test".to_string()),
21422129
node_count: 3,
21432130
edges: vec![],
21442131
};
@@ -2243,7 +2230,7 @@ mod tests {
22432230
fn test_modularity_empty_graph() {
22442231
// No edges => Q = 0
22452232
let g = GraphInput {
2246-
dataset_id: "test".to_string(),
2233+
dataset_id: Some("test".to_string()),
22472234
node_count: 3,
22482235
edges: vec![],
22492236
};
@@ -2373,7 +2360,7 @@ mod tests {
23732360
);
23742361
let mut state = PartitionState::identity(6);
23752362

2376-
let (iters, hierarchy) = multilevel_leiden(&mut state, &g, 1.0, 0.05, mode, 10);
2363+
let (iters, hierarchy) = multilevel_leiden(&mut state, &g, 1.0, mode, 10);
23772364

23782365
assert!(iters > 0, "should take at least 1 iteration");
23792366
assert!(
@@ -2400,13 +2387,13 @@ mod tests {
24002387
dual_mode_test!(test_multilevel_leiden_single_node, |mode| {
24012388
// Single node graph, no edges.
24022389
let g = GraphInput {
2403-
dataset_id: "test".to_string(),
2390+
dataset_id: Some("test".to_string()),
24042391
node_count: 1,
24052392
edges: vec![],
24062393
};
24072394
let mut state = PartitionState::identity(1);
24082395

2409-
let (iters, hierarchy) = multilevel_leiden(&mut state, &g, 1.0, 0.05, mode, 10);
2396+
let (iters, hierarchy) = multilevel_leiden(&mut state, &g, 1.0, mode, 10);
24102397

24112398
assert_eq!(state.node_to_comm, vec![0]);
24122399
assert!(iters >= 1, "should still complete at least 1 iteration");
@@ -2416,13 +2403,13 @@ mod tests {
24162403
dual_mode_test!(test_multilevel_leiden_disconnected_components, |mode| {
24172404
// 4 disconnected nodes: each should be its own community.
24182405
let g = GraphInput {
2419-
dataset_id: "test".to_string(),
2406+
dataset_id: Some("test".to_string()),
24202407
node_count: 4,
24212408
edges: vec![],
24222409
};
24232410
let mut state = PartitionState::identity(4);
24242411

2425-
let (_iters, _hierarchy) = multilevel_leiden(&mut state, &g, 1.0, 0.05, mode, 10);
2412+
let (_iters, _hierarchy) = multilevel_leiden(&mut state, &g, 1.0, mode, 10);
24262413

24272414
let comm_count = count_unique(&state.node_to_comm);
24282415
assert_eq!(
@@ -2437,7 +2424,7 @@ mod tests {
24372424
let g = graph(6, &[(0, 1), (1, 2), (0, 2), (3, 4), (4, 5), (3, 5), (2, 3)]);
24382425
let mut state = PartitionState::identity(6);
24392426

2440-
let (_iters, hierarchy) = multilevel_leiden(&mut state, &g, 1.0, 0.05, mode, 10);
2427+
let (_iters, hierarchy) = multilevel_leiden(&mut state, &g, 1.0, mode, 10);
24412428

24422429
// Each level should have exactly node_count entries
24432430
for (i, level) in hierarchy.iter().enumerate() {
@@ -2976,7 +2963,7 @@ mod tests {
29762963
refined.set(2, true); // Only node 2 was refined
29772964

29782965
let delta = GraphInput {
2979-
dataset_id: "test".to_string(),
2966+
dataset_id: Some("test".to_string()),
29802967
node_count: 3,
29812968
edges: vec![],
29822969
};
@@ -3124,7 +3111,7 @@ mod tests {
31243111
);
31253112
let mut state = PartitionState::identity(6);
31263113
state.supergraphs.push(InMemoryGraph::from(&GraphInput {
3127-
dataset_id: "test".to_string(),
3114+
dataset_id: Some("test".to_string()),
31283115
node_count: 6,
31293116
edges: vec![],
31303117
}));
@@ -3158,7 +3145,7 @@ mod tests {
31583145
);
31593146
let mut state = PartitionState::identity(6);
31603147
state.supergraphs.push(InMemoryGraph::from(&GraphInput {
3161-
dataset_id: "test".to_string(),
3148+
dataset_id: Some("test".to_string()),
31623149
node_count: 6,
31633150
edges: vec![],
31643151
}));
@@ -3190,7 +3177,7 @@ mod tests {
31903177
);
31913178
let mut state = PartitionState::identity(4);
31923179
state.supergraphs.push(InMemoryGraph::from(&GraphInput {
3193-
dataset_id: "test".to_string(),
3180+
dataset_id: Some("test".to_string()),
31943181
node_count: 4,
31953182
edges: vec![],
31963183
}));
@@ -3233,7 +3220,7 @@ mod tests {
32333220
);
32343221
let mut state = PartitionState::identity(8);
32353222
state.supergraphs.push(InMemoryGraph::from(&GraphInput {
3236-
dataset_id: "test".to_string(),
3223+
dataset_id: Some("test".to_string()),
32373224
node_count: 8,
32383225
edges: vec![],
32393226
}));
@@ -3343,7 +3330,7 @@ mod tests {
33433330
fn test_connected_components_single_node() {
33443331
// Single node with no edges: 1 component of size 1.
33453332
let g = GraphInput {
3346-
dataset_id: "test".to_string(),
3333+
dataset_id: Some("test".to_string()),
33473334
node_count: 1,
33483335
edges: vec![],
33493336
};

src/core/config.rs

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,21 @@
1616

1717
use crate::core::backend::{AccelerationTarget, GraphSource};
1818

19+
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1920
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2021
pub enum RunMode {
2122
Deterministic,
2223
Throughput,
2324
}
2425

26+
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2527
#[derive(Clone, Debug, PartialEq)]
2628
pub struct RunConfig {
2729
pub mode: RunMode,
28-
pub graph_source: GraphSource,
30+
/// Graph source hint. Only meaningful for CLI / orchestrator usage.
31+
/// Library consumers using `run()` or `run_simple()` with in-memory data
32+
/// can leave this as `None`.
33+
pub graph_source: Option<GraphSource>,
2934
pub acceleration: AccelerationTarget,
3035
pub quality_tolerance: f64,
3136
pub max_iterations: usize,
@@ -34,28 +39,24 @@ pub struct RunConfig {
3439
/// Default 1.0 matches the HIT-Leiden paper (standard modularity).
3540
/// Used for both movement and refinement quality functions.
3641
pub resolution: f64,
37-
/// Refinement connectivity criterion gamma. Controls which nodes participate
38-
/// in refinement merging (must satisfy cut_size >= gamma * v_total * (S - v_total)).
39-
/// Default 0.05. NOT used for quality function.
40-
pub refinement_gamma: f64,
4142
}
4243

4344
impl Default for RunConfig {
4445
fn default() -> Self {
4546
Self {
4647
mode: RunMode::Deterministic,
47-
graph_source: GraphSource::File,
48+
graph_source: None,
4849
acceleration: AccelerationTarget::PureRust,
4950
quality_tolerance: 0.001,
5051
max_iterations: 10,
5152
pinned_profile: None,
5253
resolution: 1.0,
53-
refinement_gamma: 0.05,
5454
}
5555
}
5656
}
5757

5858
impl RunConfig {
59+
/// Validate the configuration.
5960
pub fn validate(&self) -> Result<(), String> {
6061
if self.max_iterations == 0 {
6162
return Err("max_iterations must be > 0".to_string());
@@ -65,4 +66,29 @@ impl RunConfig {
6566
}
6667
Ok(())
6768
}
69+
70+
/// Set the run mode.
71+
pub fn with_mode(mut self, mode: RunMode) -> Self {
72+
self.mode = mode;
73+
self
74+
}
75+
76+
/// Set the resolution parameter (gamma).
77+
pub fn with_resolution(mut self, r: f64) -> Self {
78+
self.resolution = r;
79+
self
80+
}
81+
82+
/// Set the maximum number of iterations.
83+
pub fn with_max_iterations(mut self, n: usize) -> Self {
84+
self.max_iterations = n;
85+
self
86+
}
87+
88+
/// Set the quality tolerance for convergence.
89+
pub fn with_quality_tolerance(mut self, t: f64) -> Self {
90+
self.quality_tolerance = t;
91+
self
92+
}
93+
6894
}

0 commit comments

Comments
 (0)