1use pyo3::exceptions::PyRuntimeError;
7use pyo3::prelude::*;
8use pyo3::types::{PyAny, PyDict, PyList, PyTuple};
9
10use scirs2_numpy::{IntoPyArray, PyArray2};
12
13#[allow(unused_imports)]
15use scirs2_core::ndarray::{Array1, Array2};
16use scirs2_core::random::thread_rng;
17
18use scirs2_graph::{
20 articulation_points,
21 betweenness_centrality,
23 breadth_first_search,
25 bridges,
26 closeness_centrality,
27 connected_components,
29 depth_first_search,
30 diameter,
32 dijkstra_path,
34 floyd_warshall,
35 is_bipartite,
36 label_propagation_result,
37 louvain_communities_result,
39 minimum_spanning_tree,
41 modularity,
42 strongly_connected_components,
43 DiGraph,
44 Graph,
46};
47use scirs2_graph::generators::{
49 barabasi_albert_graph, complete_graph, cycle_graph, erdos_renyi_graph, path_graph, star_graph,
50 watts_strogatz_graph,
51};
52use scirs2_graph::measures::{clustering_coefficient, pagerank_centrality};
53
54#[pyfunction]
60fn graph_from_edges_py(
61 py: Python,
62 edges: &Bound<'_, PyList>,
63 num_nodes: Option<usize>,
64) -> PyResult<Py<PyAny>> {
65 let mut graph = Graph::<usize, f64, u32>::new();
66 let mut max_node = 0usize;
67
68 for edge in edges.iter() {
70 let tuple = edge.downcast::<PyTuple>()?;
71 let u: usize = tuple.get_item(0)?.extract()?;
72 let v: usize = tuple.get_item(1)?.extract()?;
73 let weight: f64 = if tuple.len() > 2 {
74 tuple.get_item(2)?.extract()?
75 } else {
76 1.0
77 };
78
79 max_node = max_node.max(u).max(v);
80 graph.add_edge(u, v, weight);
81 }
82
83 let n = num_nodes.unwrap_or(max_node + 1);
84
85 let edges_vec = graph.edges();
86 let dict = PyDict::new(py);
87 dict.set_item("num_nodes", n)?;
88 dict.set_item("num_edges", edges_vec.len())?;
89 dict.set_item("directed", false)?;
90
91 let mut adj_list: Vec<Vec<(usize, f64)>> = vec![Vec::new(); n];
93 for edge in &edges_vec {
94 adj_list[edge.source].push((edge.target, edge.weight));
95 adj_list[edge.target].push((edge.source, edge.weight)); }
97
98 let adj_py: Vec<Vec<(usize, f64)>> = adj_list;
99 dict.set_item("adjacency", adj_py)?;
100
101 Ok(dict.into())
102}
103
104#[pyfunction]
106fn digraph_from_edges_py(
107 py: Python,
108 edges: &Bound<'_, PyList>,
109 num_nodes: Option<usize>,
110) -> PyResult<Py<PyAny>> {
111 let mut graph = DiGraph::<usize, f64, u32>::new();
112 let mut max_node = 0usize;
113
114 for edge in edges.iter() {
115 let tuple = edge.downcast::<PyTuple>()?;
116 let u: usize = tuple.get_item(0)?.extract()?;
117 let v: usize = tuple.get_item(1)?.extract()?;
118 let weight: f64 = if tuple.len() > 2 {
119 tuple.get_item(2)?.extract()?
120 } else {
121 1.0
122 };
123
124 max_node = max_node.max(u).max(v);
125 graph.add_edge(u, v, weight);
126 }
127
128 let n = num_nodes.unwrap_or(max_node + 1);
129
130 let edges_vec = graph.edges();
131 let dict = PyDict::new(py);
132 dict.set_item("num_nodes", n)?;
133 dict.set_item("num_edges", edges_vec.len())?;
134 dict.set_item("directed", true)?;
135
136 let mut adj_list: Vec<Vec<(usize, f64)>> = vec![Vec::new(); n];
138 for edge in &edges_vec {
139 adj_list[edge.source].push((edge.target, edge.weight));
140 }
141
142 dict.set_item("adjacency", adj_list)?;
143
144 Ok(dict.into())
145}
146
147#[pyfunction]
153fn erdos_renyi_graph_py(py: Python, n: usize, p: f64) -> PyResult<Py<PyAny>> {
154 let mut rng = thread_rng();
155 let graph = erdos_renyi_graph(n, p, &mut rng)
156 .map_err(|e| PyRuntimeError::new_err(format!("Failed to generate graph: {}", e)))?;
157
158 let edges_vec = graph.edges();
159 let dict = PyDict::new(py);
160 dict.set_item("num_nodes", n)?;
161 dict.set_item("num_edges", edges_vec.len())?;
162 dict.set_item("directed", false)?;
163
164 let edges: Vec<(usize, usize, f64)> = edges_vec
166 .iter()
167 .map(|e| (e.source, e.target, e.weight))
168 .collect();
169 dict.set_item("edges", edges)?;
170
171 Ok(dict.into())
172}
173
174#[pyfunction]
176fn barabasi_albert_graph_py(py: Python, n: usize, m: usize) -> PyResult<Py<PyAny>> {
177 let mut rng = thread_rng();
178 let graph = barabasi_albert_graph(n, m, &mut rng)
179 .map_err(|e| PyRuntimeError::new_err(format!("Failed to generate graph: {}", e)))?;
180
181 let edges_vec = graph.edges();
182 let dict = PyDict::new(py);
183 dict.set_item("num_nodes", n)?;
184 dict.set_item("num_edges", edges_vec.len())?;
185 dict.set_item("directed", false)?;
186
187 let edges: Vec<(usize, usize, f64)> = edges_vec
188 .iter()
189 .map(|e| (e.source, e.target, e.weight))
190 .collect();
191 dict.set_item("edges", edges)?;
192
193 Ok(dict.into())
194}
195
196#[pyfunction]
198fn watts_strogatz_graph_py(py: Python, n: usize, k: usize, p: f64) -> PyResult<Py<PyAny>> {
199 let mut rng = thread_rng();
200 let graph = watts_strogatz_graph(n, k, p, &mut rng)
201 .map_err(|e| PyRuntimeError::new_err(format!("Failed to generate graph: {}", e)))?;
202
203 let edges_vec = graph.edges();
204 let dict = PyDict::new(py);
205 dict.set_item("num_nodes", n)?;
206 dict.set_item("num_edges", edges_vec.len())?;
207 dict.set_item("directed", false)?;
208
209 let edges: Vec<(usize, usize, f64)> = edges_vec
210 .iter()
211 .map(|e| (e.source, e.target, e.weight))
212 .collect();
213 dict.set_item("edges", edges)?;
214
215 Ok(dict.into())
216}
217
218#[pyfunction]
220fn complete_graph_py(py: Python, n: usize) -> PyResult<Py<PyAny>> {
221 let graph = complete_graph(n)
222 .map_err(|e| PyRuntimeError::new_err(format!("Failed to generate graph: {}", e)))?;
223
224 let edges_vec = graph.edges();
225 let dict = PyDict::new(py);
226 dict.set_item("num_nodes", n)?;
227 dict.set_item("num_edges", edges_vec.len())?;
228 dict.set_item("directed", false)?;
229
230 let edges: Vec<(usize, usize, f64)> = edges_vec
231 .iter()
232 .map(|e| (e.source, e.target, e.weight))
233 .collect();
234 dict.set_item("edges", edges)?;
235
236 Ok(dict.into())
237}
238
239#[pyfunction]
241fn path_graph_py(py: Python, n: usize) -> PyResult<Py<PyAny>> {
242 let graph = path_graph(n)
243 .map_err(|e| PyRuntimeError::new_err(format!("Failed to generate graph: {}", e)))?;
244
245 let edges_vec = graph.edges();
246 let dict = PyDict::new(py);
247 dict.set_item("num_nodes", n)?;
248 dict.set_item("num_edges", edges_vec.len())?;
249 dict.set_item("directed", false)?;
250
251 let edges: Vec<(usize, usize, f64)> = edges_vec
252 .iter()
253 .map(|e| (e.source, e.target, e.weight))
254 .collect();
255 dict.set_item("edges", edges)?;
256
257 Ok(dict.into())
258}
259
260#[pyfunction]
262fn cycle_graph_py(py: Python, n: usize) -> PyResult<Py<PyAny>> {
263 let graph = cycle_graph(n)
264 .map_err(|e| PyRuntimeError::new_err(format!("Failed to generate graph: {}", e)))?;
265
266 let edges_vec = graph.edges();
267 let dict = PyDict::new(py);
268 dict.set_item("num_nodes", n)?;
269 dict.set_item("num_edges", edges_vec.len())?;
270 dict.set_item("directed", false)?;
271
272 let edges: Vec<(usize, usize, f64)> = edges_vec
273 .iter()
274 .map(|e| (e.source, e.target, e.weight))
275 .collect();
276 dict.set_item("edges", edges)?;
277
278 Ok(dict.into())
279}
280
281#[pyfunction]
283fn star_graph_py(py: Python, n: usize) -> PyResult<Py<PyAny>> {
284 let graph = star_graph(n)
285 .map_err(|e| PyRuntimeError::new_err(format!("Failed to generate graph: {}", e)))?;
286
287 let edges_vec = graph.edges();
288 let dict = PyDict::new(py);
289 dict.set_item("num_nodes", n)?;
290 dict.set_item("num_edges", edges_vec.len())?;
291 dict.set_item("directed", false)?;
292
293 let edges: Vec<(usize, usize, f64)> = edges_vec
294 .iter()
295 .map(|e| (e.source, e.target, e.weight))
296 .collect();
297 dict.set_item("edges", edges)?;
298
299 Ok(dict.into())
300}
301
302#[pyfunction]
308fn bfs_py(
309 py: Python,
310 edges: &Bound<'_, PyList>,
311 source: usize,
312 num_nodes: Option<usize>,
313) -> PyResult<Py<PyAny>> {
314 let mut graph = Graph::<usize, f64, u32>::new();
315 let mut max_node = 0usize;
316
317 for edge in edges.iter() {
318 let tuple = edge.downcast::<PyTuple>()?;
319 let u: usize = tuple.get_item(0)?.extract()?;
320 let v: usize = tuple.get_item(1)?.extract()?;
321 max_node = max_node.max(u).max(v);
322 graph.add_edge(u, v, 1.0);
323 }
324
325 let _n = num_nodes.unwrap_or(max_node + 1);
326
327 let result = breadth_first_search(&graph, &source)
328 .map_err(|e| PyRuntimeError::new_err(format!("BFS failed: {}", e)))?;
329
330 let dict = PyDict::new(py);
331 dict.set_item("order", result)?;
332
333 Ok(dict.into())
334}
335
336#[pyfunction]
338fn dfs_py(
339 py: Python,
340 edges: &Bound<'_, PyList>,
341 source: usize,
342 num_nodes: Option<usize>,
343) -> PyResult<Py<PyAny>> {
344 let mut graph = Graph::<usize, f64, u32>::new();
345 let mut max_node = 0usize;
346
347 for edge in edges.iter() {
348 let tuple = edge.downcast::<PyTuple>()?;
349 let u: usize = tuple.get_item(0)?.extract()?;
350 let v: usize = tuple.get_item(1)?.extract()?;
351 max_node = max_node.max(u).max(v);
352 graph.add_edge(u, v, 1.0);
353 }
354
355 let _n = num_nodes.unwrap_or(max_node + 1);
356
357 let result = depth_first_search(&graph, &source)
358 .map_err(|e| PyRuntimeError::new_err(format!("DFS failed: {}", e)))?;
359
360 let dict = PyDict::new(py);
361 dict.set_item("order", result)?;
362
363 Ok(dict.into())
364}
365
366#[pyfunction]
372fn dijkstra_py(
373 py: Python,
374 edges: &Bound<'_, PyList>,
375 source: usize,
376 target: usize,
377) -> PyResult<Py<PyAny>> {
378 let mut graph = Graph::<usize, f64, u32>::new();
379
380 for edge in edges.iter() {
381 let tuple: &Bound<'_, PyTuple> = edge.downcast()?;
382 let u: usize = tuple.get_item(0)?.extract()?;
383 let v: usize = tuple.get_item(1)?.extract()?;
384 let weight: f64 = if tuple.len() > 2 {
385 tuple.get_item(2)?.extract()?
386 } else {
387 1.0
388 };
389 graph.add_edge(u, v, weight);
390 }
391
392 let result = dijkstra_path(&graph, &source, &target)
393 .map_err(|e| PyRuntimeError::new_err(format!("Dijkstra failed: {}", e)))?;
394
395 let dict = PyDict::new(py);
396 if let Some(path) = result {
397 dict.set_item("path", path.nodes)?;
398 dict.set_item("distance", path.total_weight)?;
399 } else {
400 return Err(PyRuntimeError::new_err("No path found"));
401 }
402
403 Ok(dict.into())
404}
405
406#[pyfunction]
408#[allow(unused_variables)]
409fn floyd_warshall_py(
410 py: Python,
411 edges: &Bound<'_, PyList>,
412 num_nodes: usize,
413) -> PyResult<Py<PyArray2<f64>>> {
414 let mut graph = Graph::<usize, f64, u32>::new();
415
416 for edge in edges.iter() {
417 let tuple: &Bound<'_, PyTuple> = edge.downcast()?;
418 let u: usize = tuple.get_item(0)?.extract()?;
419 let v: usize = tuple.get_item(1)?.extract()?;
420 let weight: f64 = if tuple.len() > 2 {
421 tuple.get_item(2)?.extract()?
422 } else {
423 1.0
424 };
425 graph.add_edge(u, v, weight);
426 }
427
428 let result = floyd_warshall(&graph)
429 .map_err(|e| PyRuntimeError::new_err(format!("Floyd-Warshall failed: {}", e)))?;
430
431 Ok(result.into_pyarray(py).unbind())
432}
433
434#[pyfunction]
440#[allow(unused_variables)]
441fn connected_components_py(
442 py: Python,
443 edges: &Bound<'_, PyList>,
444 num_nodes: usize,
445) -> PyResult<Py<PyAny>> {
446 let mut graph = Graph::<usize, f64, u32>::new();
447
448 for edge in edges.iter() {
449 let tuple: &Bound<'_, PyTuple> = edge.downcast()?;
450 let u: usize = tuple.get_item(0)?.extract()?;
451 let v: usize = tuple.get_item(1)?.extract()?;
452 graph.add_edge(u, v, 1.0);
453 }
454
455 let components = connected_components(&graph);
456
457 let dict = PyDict::new(py);
458 dict.set_item("n_components", components.len())?;
459
460 let comp_list: Vec<Vec<usize>> = components
462 .into_iter()
463 .map(|c: std::collections::HashSet<usize>| c.into_iter().collect())
464 .collect();
465 dict.set_item("components", comp_list)?;
466
467 Ok(dict.into())
468}
469
470#[pyfunction]
472#[allow(unused_variables)]
473fn strongly_connected_components_py(
474 py: Python,
475 edges: &Bound<'_, PyList>,
476 num_nodes: usize,
477) -> PyResult<Py<PyAny>> {
478 let mut graph = DiGraph::<usize, f64, u32>::new();
479
480 for edge in edges.iter() {
481 let tuple: &Bound<'_, PyTuple> = edge.downcast()?;
482 let u: usize = tuple.get_item(0)?.extract()?;
483 let v: usize = tuple.get_item(1)?.extract()?;
484 graph.add_edge(u, v, 1.0);
485 }
486
487 let components = strongly_connected_components(&graph);
488
489 let dict = PyDict::new(py);
490 dict.set_item("n_components", components.len())?;
491
492 let comp_list: Vec<Vec<usize>> = components
493 .into_iter()
494 .map(|c: std::collections::HashSet<usize>| c.into_iter().collect())
495 .collect();
496 dict.set_item("components", comp_list)?;
497
498 Ok(dict.into())
499}
500
501#[pyfunction]
503#[allow(unused_variables)]
504fn articulation_points_py(
505 py: Python,
506 edges: &Bound<'_, PyList>,
507 num_nodes: usize,
508) -> PyResult<Py<PyAny>> {
509 let mut graph = Graph::<usize, f64, u32>::new();
510
511 for edge in edges.iter() {
512 let tuple = edge.downcast::<PyTuple>()?;
513 let u: usize = tuple.get_item(0)?.extract()?;
514 let v: usize = tuple.get_item(1)?.extract()?;
515 graph.add_edge(u, v, 1.0);
516 }
517
518 let points = articulation_points(&graph);
520
521 let dict = PyDict::new(py);
522 let points_vec: Vec<usize> = points.into_iter().collect();
523 dict.set_item("articulation_points", points_vec)?;
524
525 Ok(dict.into())
526}
527
528#[pyfunction]
530#[allow(unused_variables)]
531fn bridges_py(py: Python, edges: &Bound<'_, PyList>, num_nodes: usize) -> PyResult<Py<PyAny>> {
532 let mut graph = Graph::<usize, f64, u32>::new();
533
534 for edge in edges.iter() {
535 let tuple = edge.downcast::<PyTuple>()?;
536 let u: usize = tuple.get_item(0)?.extract()?;
537 let v: usize = tuple.get_item(1)?.extract()?;
538 graph.add_edge(u, v, 1.0);
539 }
540
541 let bridge_list = bridges(&graph);
543
544 let dict = PyDict::new(py);
545 let bridges_vec: Vec<(usize, usize)> = bridge_list;
546 dict.set_item("bridges", bridges_vec)?;
547
548 Ok(dict.into())
549}
550
551#[pyfunction]
553#[allow(unused_variables)]
554fn is_bipartite_py(py: Python, edges: &Bound<'_, PyList>, num_nodes: usize) -> PyResult<Py<PyAny>> {
555 let mut graph = Graph::<usize, f64, u32>::new();
556
557 for edge in edges.iter() {
558 let tuple = edge.downcast::<PyTuple>()?;
559 let u: usize = tuple.get_item(0)?.extract()?;
560 let v: usize = tuple.get_item(1)?.extract()?;
561 graph.add_edge(u, v, 1.0);
562 }
563
564 let result = is_bipartite(&graph);
566
567 let dict = PyDict::new(py);
568 dict.set_item("is_bipartite", result.is_bipartite)?;
569
570 if result.is_bipartite {
571 let left: Vec<usize> = result
573 .coloring
574 .iter()
575 .filter(|(_, &color)| color == 0)
576 .map(|(&node, _)| node)
577 .collect();
578 let right: Vec<usize> = result
579 .coloring
580 .iter()
581 .filter(|(_, &color)| color == 1)
582 .map(|(&node, _)| node)
583 .collect();
584 dict.set_item("left", left)?;
585 dict.set_item("right", right)?;
586 }
587
588 Ok(dict.into())
589}
590
591#[pyfunction]
597#[pyo3(signature = (edges, num_nodes, normalized=true))]
598fn betweenness_centrality_py(
599 py: Python,
600 edges: &Bound<'_, PyList>,
601 num_nodes: usize,
602 normalized: bool,
603) -> PyResult<Py<PyAny>> {
604 let mut graph = Graph::<usize, f64, u32>::new();
605
606 for edge in edges.iter() {
607 let tuple = edge.downcast::<PyTuple>()?;
608 let u: usize = tuple.get_item(0)?.extract()?;
609 let v: usize = tuple.get_item(1)?.extract()?;
610 graph.add_edge(u, v, 1.0);
611 }
612
613 let centrality = betweenness_centrality(&graph, normalized);
615
616 let dict = PyDict::new(py);
617 let centrality_vec: Vec<f64> = (0..num_nodes)
618 .map(|i| *centrality.get(&i).unwrap_or(&0.0))
619 .collect();
620 dict.set_item("centrality", centrality_vec)?;
621
622 Ok(dict.into())
623}
624
625#[pyfunction]
627#[pyo3(signature = (edges, num_nodes, normalized=true))]
628fn closeness_centrality_py(
629 py: Python,
630 edges: &Bound<'_, PyList>,
631 num_nodes: usize,
632 normalized: bool,
633) -> PyResult<Py<PyAny>> {
634 let mut graph = Graph::<usize, f64, u32>::new();
635
636 for edge in edges.iter() {
637 let tuple = edge.downcast::<PyTuple>()?;
638 let u: usize = tuple.get_item(0)?.extract()?;
639 let v: usize = tuple.get_item(1)?.extract()?;
640 graph.add_edge(u, v, 1.0);
641 }
642
643 let centrality = closeness_centrality(&graph, normalized);
645
646 let dict = PyDict::new(py);
647 let centrality_vec: Vec<f64> = (0..num_nodes)
648 .map(|i| *centrality.get(&i).unwrap_or(&0.0))
649 .collect();
650 dict.set_item("centrality", centrality_vec)?;
651
652 Ok(dict.into())
653}
654
655#[pyfunction]
657#[pyo3(signature = (edges, num_nodes, damping=0.85, _max_iter=100, tol=1e-6))]
658#[allow(unused_variables)]
659fn pagerank_py(
660 py: Python,
661 edges: &Bound<'_, PyList>,
662 num_nodes: usize,
663 damping: f64,
664 _max_iter: usize,
665 tol: f64,
666) -> PyResult<Py<PyAny>> {
667 let mut graph = Graph::<usize, f64, u32>::new();
668
669 for edge in edges.iter() {
670 let tuple = edge.downcast::<PyTuple>()?;
671 let u: usize = tuple.get_item(0)?.extract()?;
672 let v: usize = tuple.get_item(1)?.extract()?;
673 graph.add_edge(u, v, 1.0);
674 }
675
676 let pr = pagerank_centrality(&graph, damping, tol)
678 .map_err(|e| PyRuntimeError::new_err(format!("PageRank failed: {}", e)))?;
679
680 let dict = PyDict::new(py);
681 let pr_vec: Vec<f64> = (0..num_nodes)
682 .map(|i| *pr.get(&i).unwrap_or(&0.0))
683 .collect();
684 dict.set_item("pagerank", pr_vec)?;
685
686 Ok(dict.into())
687}
688
689#[pyfunction]
695#[allow(unused_variables)]
696fn louvain_communities_py(
697 py: Python,
698 edges: &Bound<'_, PyList>,
699 num_nodes: usize,
700) -> PyResult<Py<PyAny>> {
701 let mut graph = Graph::<usize, f64, u32>::new();
702
703 for edge in edges.iter() {
704 let tuple = edge.downcast::<PyTuple>()?;
705 let u: usize = tuple.get_item(0)?.extract()?;
706 let v: usize = tuple.get_item(1)?.extract()?;
707 let weight: f64 = if tuple.len() > 2 {
708 tuple.get_item(2)?.extract()?
709 } else {
710 1.0
711 };
712 graph.add_edge(u, v, weight);
713 }
714
715 let result = louvain_communities_result(&graph);
717
718 let dict = PyDict::new(py);
719 dict.set_item("n_communities", result.communities.len())?;
720 dict.set_item("modularity", result.quality_score)?;
722
723 let comm_list: Vec<Vec<usize>> = result
725 .communities
726 .into_iter()
727 .map(|c: std::collections::HashSet<usize>| c.into_iter().collect())
728 .collect();
729 dict.set_item("communities", comm_list)?;
730
731 Ok(dict.into())
732}
733
734#[pyfunction]
736#[pyo3(signature = (edges, num_nodes, max_iterations=100))]
737#[allow(unused_variables)]
738fn label_propagation_py(
739 py: Python,
740 edges: &Bound<'_, PyList>,
741 num_nodes: usize,
742 max_iterations: usize,
743) -> PyResult<Py<PyAny>> {
744 let mut graph = Graph::<usize, f64, u32>::new();
745
746 for edge in edges.iter() {
747 let tuple = edge.downcast::<PyTuple>()?;
748 let u: usize = tuple.get_item(0)?.extract()?;
749 let v: usize = tuple.get_item(1)?.extract()?;
750 graph.add_edge(u, v, 1.0);
751 }
752
753 let result = label_propagation_result(&graph, max_iterations);
755
756 let dict = PyDict::new(py);
757 dict.set_item("n_communities", result.communities.len())?;
758
759 let comm_list: Vec<Vec<usize>> = result
760 .communities
761 .into_iter()
762 .map(|c: std::collections::HashSet<usize>| c.into_iter().collect())
763 .collect();
764 dict.set_item("communities", comm_list)?;
765
766 Ok(dict.into())
767}
768
769#[pyfunction]
771#[allow(unused_variables)]
772fn modularity_py(
773 edges: &Bound<'_, PyList>,
774 communities: &Bound<'_, PyList>,
775 num_nodes: usize,
776) -> PyResult<f64> {
777 let mut graph = Graph::<usize, f64, u32>::new();
778
779 for edge in edges.iter() {
780 let tuple = edge.downcast::<PyTuple>()?;
781 let u: usize = tuple.get_item(0)?.extract()?;
782 let v: usize = tuple.get_item(1)?.extract()?;
783 let weight: f64 = if tuple.len() > 2 {
784 tuple.get_item(2)?.extract()?
785 } else {
786 1.0
787 };
788 graph.add_edge(u, v, weight);
789 }
790
791 let mut comm_map: std::collections::HashMap<usize, usize> = std::collections::HashMap::new();
793 for (comm_id, comm) in communities.iter().enumerate() {
794 let nodes: Vec<usize> = comm.extract()?;
795 for node in nodes {
796 comm_map.insert(node, comm_id);
797 }
798 }
799
800 Ok(modularity(&graph, &comm_map))
802}
803
804#[pyfunction]
810#[allow(unused_variables)]
811fn diameter_py(edges: &Bound<'_, PyList>, num_nodes: usize) -> PyResult<f64> {
812 let mut graph = Graph::<usize, f64, u32>::new();
813
814 for edge in edges.iter() {
815 let tuple = edge.downcast::<PyTuple>()?;
816 let u: usize = tuple.get_item(0)?.extract()?;
817 let v: usize = tuple.get_item(1)?.extract()?;
818 graph.add_edge(u, v, 1.0);
819 }
820
821 diameter(&graph).ok_or_else(|| PyRuntimeError::new_err("Graph is not connected or empty"))
823}
824
825#[pyfunction]
827fn clustering_coefficient_py(
828 py: Python,
829 edges: &Bound<'_, PyList>,
830 num_nodes: usize,
831) -> PyResult<Py<PyAny>> {
832 let mut graph = Graph::<usize, f64, u32>::new();
833
834 for edge in edges.iter() {
835 let tuple = edge.downcast::<PyTuple>()?;
836 let u: usize = tuple.get_item(0)?.extract()?;
837 let v: usize = tuple.get_item(1)?.extract()?;
838 graph.add_edge(u, v, 1.0);
839 }
840
841 let coeffs = clustering_coefficient(&graph)
843 .map_err(|e| PyRuntimeError::new_err(format!("Clustering coefficient failed: {}", e)))?;
844
845 let dict = PyDict::new(py);
846 let coeff_vec: Vec<f64> = (0..num_nodes)
847 .map(|i| *coeffs.get(&i).unwrap_or(&0.0))
848 .collect();
849
850 let avg: f64 = coeff_vec.iter().sum::<f64>() / num_nodes as f64;
852 dict.set_item("coefficients", coeff_vec)?;
853 dict.set_item("average", avg)?;
854
855 Ok(dict.into())
856}
857
858#[pyfunction]
860fn graph_density_py(edges: &Bound<'_, PyList>, num_nodes: usize, directed: bool) -> PyResult<f64> {
861 let num_edges = edges.len();
862
863 if directed {
864 let max_edges = num_nodes * (num_nodes - 1);
865 Ok(num_edges as f64 / max_edges as f64)
866 } else {
867 let max_edges = num_nodes * (num_nodes - 1) / 2;
868 Ok(num_edges as f64 / max_edges as f64)
869 }
870}
871
872#[pyfunction]
878#[allow(unused_variables)]
879fn minimum_spanning_tree_py(
880 py: Python,
881 edges: &Bound<'_, PyList>,
882 num_nodes: usize,
883) -> PyResult<Py<PyAny>> {
884 let mut graph = Graph::<usize, f64, u32>::new();
885
886 for edge in edges.iter() {
887 let tuple = edge.downcast::<PyTuple>()?;
888 let u: usize = tuple.get_item(0)?.extract()?;
889 let v: usize = tuple.get_item(1)?.extract()?;
890 let weight: f64 = if tuple.len() > 2 {
891 tuple.get_item(2)?.extract()?
892 } else {
893 1.0
894 };
895 graph.add_edge(u, v, weight);
896 }
897
898 let mst = minimum_spanning_tree(&graph)
900 .map_err(|e| PyRuntimeError::new_err(format!("MST failed: {}", e)))?;
901
902 let dict = PyDict::new(py);
903 let mst_edges: Vec<(usize, usize, f64)> =
905 mst.iter().map(|e| (e.source, e.target, e.weight)).collect();
906 dict.set_item("edges", mst_edges.clone())?;
907
908 let total_weight: f64 = mst_edges.iter().map(|(_, _, w)| *w).sum();
909 dict.set_item("total_weight", total_weight)?;
910
911 Ok(dict.into())
912}
913
914pub fn register_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
916 m.add_function(wrap_pyfunction!(graph_from_edges_py, m)?)?;
918 m.add_function(wrap_pyfunction!(digraph_from_edges_py, m)?)?;
919
920 m.add_function(wrap_pyfunction!(erdos_renyi_graph_py, m)?)?;
922 m.add_function(wrap_pyfunction!(barabasi_albert_graph_py, m)?)?;
923 m.add_function(wrap_pyfunction!(watts_strogatz_graph_py, m)?)?;
924 m.add_function(wrap_pyfunction!(complete_graph_py, m)?)?;
925 m.add_function(wrap_pyfunction!(path_graph_py, m)?)?;
926 m.add_function(wrap_pyfunction!(cycle_graph_py, m)?)?;
927 m.add_function(wrap_pyfunction!(star_graph_py, m)?)?;
928
929 m.add_function(wrap_pyfunction!(bfs_py, m)?)?;
931 m.add_function(wrap_pyfunction!(dfs_py, m)?)?;
932
933 m.add_function(wrap_pyfunction!(dijkstra_py, m)?)?;
935 m.add_function(wrap_pyfunction!(floyd_warshall_py, m)?)?;
936
937 m.add_function(wrap_pyfunction!(connected_components_py, m)?)?;
939 m.add_function(wrap_pyfunction!(strongly_connected_components_py, m)?)?;
940 m.add_function(wrap_pyfunction!(articulation_points_py, m)?)?;
941 m.add_function(wrap_pyfunction!(bridges_py, m)?)?;
942 m.add_function(wrap_pyfunction!(is_bipartite_py, m)?)?;
943
944 m.add_function(wrap_pyfunction!(betweenness_centrality_py, m)?)?;
946 m.add_function(wrap_pyfunction!(closeness_centrality_py, m)?)?;
947 m.add_function(wrap_pyfunction!(pagerank_py, m)?)?;
948
949 m.add_function(wrap_pyfunction!(louvain_communities_py, m)?)?;
951 m.add_function(wrap_pyfunction!(label_propagation_py, m)?)?;
952 m.add_function(wrap_pyfunction!(modularity_py, m)?)?;
953
954 m.add_function(wrap_pyfunction!(diameter_py, m)?)?;
956 m.add_function(wrap_pyfunction!(clustering_coefficient_py, m)?)?;
957 m.add_function(wrap_pyfunction!(graph_density_py, m)?)?;
958
959 m.add_function(wrap_pyfunction!(minimum_spanning_tree_py, m)?)?;
961
962 Ok(())
963}