scirs2_graph/algorithms/
transformations.rs

1//! Graph transformation algorithms
2//!
3//! This module provides algorithms for transforming graphs into different
4//! representations and extracting subgraphs.
5
6use crate::base::{DiGraph, EdgeWeight, Graph, IndexType, Node};
7use std::collections::HashSet;
8
9/// Creates a line graph from the input graph
10///
11/// In a line graph, each edge of the original graph becomes a vertex,
12/// and two vertices in the line graph are connected if the corresponding
13/// edges in the original graph share a common vertex.
14///
15/// # Arguments
16/// * `graph` - The input graph
17///
18/// # Returns
19/// * A new graph representing the line graph
20pub fn line_graph<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Graph<(N, N), (), Ix>
21where
22    N: Node + Clone,
23    E: EdgeWeight + Clone,
24    Ix: IndexType,
25{
26    let mut line_graph = Graph::new();
27    let edges = graph.edges();
28
29    // Each edge becomes a node in the line graph
30    let edge_nodes: Vec<(N, N)> = edges
31        .iter()
32        .map(|e| (e.source.clone(), e.target.clone()))
33        .collect();
34
35    // Add all edge nodes to the line graph
36    for edge_node in &edge_nodes {
37        line_graph.add_node(edge_node.clone());
38    }
39
40    // Connect edges that share a vertex
41    for (i, edge1) in edge_nodes.iter().enumerate() {
42        for (_j, edge2) in edge_nodes.iter().enumerate().skip(i + 1) {
43            // Check if edges share a vertex
44            if edge1.0 == edge2.0 || edge1.0 == edge2.1 || edge1.1 == edge2.0 || edge1.1 == edge2.1
45            {
46                // Connect the corresponding nodes in the line graph
47                let _ = line_graph.add_edge(edge1.clone(), edge2.clone(), ());
48            }
49        }
50    }
51
52    line_graph
53}
54
55/// Creates a line graph from a directed graph
56///
57/// For directed graphs, two vertices in the line graph are connected
58/// if the head of one edge equals the tail of another.
59pub fn line_digraph<N, E, Ix>(digraph: &DiGraph<N, E, Ix>) -> DiGraph<(N, N), (), Ix>
60where
61    N: Node + Clone,
62    E: EdgeWeight + Clone,
63    Ix: IndexType,
64{
65    let mut line_digraph = DiGraph::new();
66    let edges = digraph.edges();
67
68    // Each edge becomes a node in the line graph
69    let edge_nodes: Vec<(N, N)> = edges
70        .iter()
71        .map(|e| (e.source.clone(), e.target.clone()))
72        .collect();
73
74    // Add all edge nodes to the line graph
75    for edge_node in &edge_nodes {
76        line_digraph.add_node(edge_node.clone());
77    }
78
79    // Connect edges where head of first equals tail of second
80    for edge1 in &edge_nodes {
81        for edge2 in &edge_nodes {
82            if edge1 != edge2 && edge1.1 == edge2.0 {
83                // Head of edge1 equals tail of edge2
84                let _ = line_digraph.add_edge(edge1.clone(), edge2.clone(), ());
85            }
86        }
87    }
88
89    line_digraph
90}
91
92/// Extracts a subgraph containing only the specified nodes
93///
94/// # Arguments
95/// * `graph` - The input graph
96/// * `nodes` - The set of nodes to include in the subgraph
97///
98/// # Returns
99/// * A new graph containing only the specified nodes and edges between them
100pub fn subgraph<N, E, Ix>(graph: &Graph<N, E, Ix>, nodes: &HashSet<N>) -> Graph<N, E, Ix>
101where
102    N: Node + Clone,
103    E: EdgeWeight + Clone,
104    Ix: IndexType,
105{
106    let mut sub = Graph::new();
107
108    // Add specified nodes
109    for node in nodes {
110        if graph.has_node(node) {
111            sub.add_node(node.clone());
112        }
113    }
114
115    // Add edges between included nodes
116    for edge in graph.edges() {
117        if nodes.contains(&edge.source) && nodes.contains(&edge.target) {
118            let _ = sub.add_edge(
119                edge.source.clone(),
120                edge.target.clone(),
121                edge.weight.clone(),
122            );
123        }
124    }
125
126    sub
127}
128
129/// Extracts a subgraph from a directed graph
130pub fn subdigraph<N, E, Ix>(digraph: &DiGraph<N, E, Ix>, nodes: &HashSet<N>) -> DiGraph<N, E, Ix>
131where
132    N: Node + Clone,
133    E: EdgeWeight + Clone,
134    Ix: IndexType,
135{
136    let mut sub = DiGraph::new();
137
138    // Add specified nodes
139    for node in nodes {
140        if digraph.has_node(node) {
141            sub.add_node(node.clone());
142        }
143    }
144
145    // Add edges between included nodes
146    for edge in digraph.edges() {
147        if nodes.contains(&edge.source) && nodes.contains(&edge.target) {
148            let _ = sub.add_edge(
149                edge.source.clone(),
150                edge.target.clone(),
151                edge.weight.clone(),
152            );
153        }
154    }
155
156    sub
157}
158
159/// Extracts an edge-induced subgraph
160///
161/// Creates a subgraph containing the specified edges and their endpoints.
162///
163/// # Arguments
164/// * `graph` - The input graph
165/// * `edges` - The set of edges to include
166///
167/// # Returns
168/// * A new graph containing the specified edges and their endpoints
169pub fn edge_subgraph<N, E, Ix>(graph: &Graph<N, E, Ix>, edges: &[(N, N)]) -> Graph<N, E, Ix>
170where
171    N: Node + Clone,
172    E: EdgeWeight + Clone,
173    Ix: IndexType,
174{
175    let mut sub = Graph::new();
176    let mut included_nodes = HashSet::new();
177
178    // Collect all nodes from the specified edges
179    for (u, v) in edges {
180        included_nodes.insert(u.clone());
181        included_nodes.insert(v.clone());
182    }
183
184    // Add nodes
185    for node in &included_nodes {
186        if graph.has_node(node) {
187            sub.add_node(node.clone());
188        }
189    }
190
191    // Add specified edges
192    for (u, v) in edges {
193        if graph.has_edge(u, v) {
194            if let Ok(weight) = graph.edge_weight(u, v) {
195                let _ = sub.add_edge(u.clone(), v.clone(), weight);
196            }
197        }
198    }
199
200    sub
201}
202
203/// Computes the Cartesian product of two graphs
204///
205/// The Cartesian product G □ H has vertex set V(G) × V(H) and
206/// edge set {((u₁,v₁),(u₂,v₂)) : (u₁=u₂ and (v₁,v₂) ∈ E(H)) or (v₁=v₂ and (u₁,u₂) ∈ E(G))}.
207///
208/// # Arguments
209/// * `graph1` - First input graph
210/// * `graph2` - Second input graph
211///
212/// # Returns
213/// * The Cartesian product graph
214pub fn cartesian_product<N1, N2, E1, E2, Ix>(
215    graph1: &Graph<N1, E1, Ix>,
216    graph2: &Graph<N2, E2, Ix>,
217) -> Graph<(N1, N2), (), Ix>
218where
219    N1: Node + Clone,
220    N2: Node + Clone,
221    E1: EdgeWeight,
222    E2: EdgeWeight,
223    Ix: IndexType,
224{
225    let mut product = Graph::new();
226
227    let nodes1: Vec<N1> = graph1.nodes().into_iter().cloned().collect();
228    let nodes2: Vec<N2> = graph2.nodes().into_iter().cloned().collect();
229
230    // Add all combinations of nodes
231    for n1 in &nodes1 {
232        for n2 in &nodes2 {
233            product.add_node((n1.clone(), n2.clone()));
234        }
235    }
236
237    // Add edges according to Cartesian product rules
238    for n1 in &nodes1 {
239        for n2 in &nodes2 {
240            // Connect (n1, n2) to (n1, m2) if (n2, m2) is an edge in graph2
241            if let Ok(neighbors2) = graph2.neighbors(n2) {
242                for m2 in neighbors2 {
243                    if n2 != &m2 {
244                        // Avoid self-loops from undirected graph
245                        let _ = product.add_edge((n1.clone(), n2.clone()), (n1.clone(), m2), ());
246                    }
247                }
248            }
249
250            // Connect (n1, n2) to (m1, n2) if (n1, m1) is an edge in graph1
251            if let Ok(neighbors1) = graph1.neighbors(n1) {
252                for m1 in neighbors1 {
253                    if n1 != &m1 {
254                        // Avoid self-loops from undirected graph
255                        let _ = product.add_edge((n1.clone(), n2.clone()), (m1, n2.clone()), ());
256                    }
257                }
258            }
259        }
260    }
261
262    product
263}
264
265/// Computes the tensor product (Kronecker product) of two graphs
266///
267/// The tensor product G ⊗ H has vertex set V(G) × V(H) and
268/// edge set {((u₁,v₁),(u₂,v₂)) : (u₁,u₂) ∈ E(G) and (v₁,v₂) ∈ E(H)}.
269pub fn tensor_product<N1, N2, E1, E2, Ix>(
270    graph1: &Graph<N1, E1, Ix>,
271    graph2: &Graph<N2, E2, Ix>,
272) -> Graph<(N1, N2), (), Ix>
273where
274    N1: Node + Clone,
275    N2: Node + Clone,
276    E1: EdgeWeight,
277    E2: EdgeWeight,
278    Ix: IndexType,
279{
280    let mut product = Graph::new();
281
282    let nodes1: Vec<N1> = graph1.nodes().into_iter().cloned().collect();
283    let nodes2: Vec<N2> = graph2.nodes().into_iter().cloned().collect();
284
285    // Add all combinations of nodes
286    for n1 in &nodes1 {
287        for n2 in &nodes2 {
288            product.add_node((n1.clone(), n2.clone()));
289        }
290    }
291
292    // Add edges according to tensor product rules
293    for n1 in &nodes1 {
294        for n2 in &nodes2 {
295            if let Ok(neighbors1) = graph1.neighbors(n1) {
296                if let Ok(neighbors2) = graph2.neighbors(n2) {
297                    for m1 in neighbors1 {
298                        for m2 in &neighbors2 {
299                            if n1 != &m1 && n2 != m2 {
300                                // Avoid self-loops
301                                let _ = product.add_edge(
302                                    (n1.clone(), n2.clone()),
303                                    (m1.clone(), m2.clone()),
304                                    (),
305                                );
306                            }
307                        }
308                    }
309                }
310            }
311        }
312    }
313
314    product
315}
316
317/// Computes the complement of a graph
318///
319/// The complement G̅ of a graph G has the same vertex set as G,
320/// but edge (u,v) is in G̅ if and only if (u,v) is not in G.
321pub fn complement<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Graph<N, (), Ix>
322where
323    N: Node + Clone,
324    E: EdgeWeight,
325    Ix: IndexType,
326{
327    let mut comp = Graph::new();
328    let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
329
330    // Add all nodes
331    for node in &nodes {
332        comp.add_node(node.clone());
333    }
334
335    // Add edges that are NOT in the original graph
336    for (i, u) in nodes.iter().enumerate() {
337        for v in nodes.iter().skip(i + 1) {
338            if !graph.has_edge(u, v) {
339                let _ = comp.add_edge(u.clone(), v.clone(), ());
340            }
341        }
342    }
343
344    comp
345}
346
347/// Creates a spanning subgraph with only edges of specified weights
348///
349/// # Arguments
350/// * `graph` - The input graph
351/// * `valid_weights` - Set of edge weights to include
352///
353/// # Returns
354/// * A subgraph containing only edges with specified weights
355pub fn weight_filtered_subgraph<N, E, Ix>(
356    graph: &Graph<N, E, Ix>,
357    valid_weights: &HashSet<E>,
358) -> Graph<N, E, Ix>
359where
360    N: Node + Clone,
361    E: EdgeWeight + Clone + std::hash::Hash + Eq,
362    Ix: IndexType,
363{
364    let mut filtered = Graph::new();
365
366    // Add all nodes
367    for node in graph.nodes() {
368        filtered.add_node(node.clone());
369    }
370
371    // Add edges with valid weights
372    for edge in graph.edges() {
373        if valid_weights.contains(&edge.weight) {
374            let _ = filtered.add_edge(
375                edge.source.clone(),
376                edge.target.clone(),
377                edge.weight.clone(),
378            );
379        }
380    }
381
382    filtered
383}
384
385#[cfg(test)]
386mod tests {
387    use super::*;
388    use crate::generators::create_graph;
389
390    #[test]
391    fn test_line_graph() {
392        let mut graph = create_graph::<&str, ()>();
393        graph.add_edge("A", "B", ()).unwrap();
394        graph.add_edge("B", "C", ()).unwrap();
395        graph.add_edge("C", "A", ()).unwrap();
396
397        let line = line_graph(&graph);
398
399        // Line graph of triangle should have 3 nodes (one per edge)
400        assert_eq!(line.node_count(), 3);
401
402        // Each pair of edges shares a vertex, so line graph should have 3 edges
403        assert_eq!(line.edge_count(), 3);
404    }
405
406    #[test]
407    fn test_subgraph() {
408        let mut graph = create_graph::<i32, ()>();
409        graph.add_edge(1, 2, ()).unwrap();
410        graph.add_edge(2, 3, ()).unwrap();
411        graph.add_edge(3, 4, ()).unwrap();
412        graph.add_edge(1, 4, ()).unwrap();
413
414        let mut nodes = HashSet::new();
415        nodes.insert(1);
416        nodes.insert(2);
417        nodes.insert(3);
418
419        let sub = subgraph(&graph, &nodes);
420
421        // Should have 3 nodes
422        assert_eq!(sub.node_count(), 3);
423
424        // Should have 2 edges: (1,2) and (2,3)
425        assert_eq!(sub.edge_count(), 2);
426        assert!(sub.has_edge(&1, &2));
427        assert!(sub.has_edge(&2, &3));
428        assert!(!sub.has_edge(&3, &4)); // Not included since 4 is not in subgraph
429    }
430
431    #[test]
432    fn test_edge_subgraph() {
433        let mut graph = create_graph::<i32, ()>();
434        graph.add_edge(1, 2, ()).unwrap();
435        graph.add_edge(2, 3, ()).unwrap();
436        graph.add_edge(3, 4, ()).unwrap();
437        graph.add_edge(1, 4, ()).unwrap();
438
439        let edges = vec![(1, 2), (3, 4)];
440        let sub = edge_subgraph(&graph, &edges);
441
442        // Should have 4 nodes (endpoints of selected edges)
443        assert_eq!(sub.node_count(), 4);
444
445        // Should have 2 edges
446        assert_eq!(sub.edge_count(), 2);
447        assert!(sub.has_edge(&1, &2));
448        assert!(sub.has_edge(&3, &4));
449        assert!(!sub.has_edge(&2, &3));
450        assert!(!sub.has_edge(&1, &4));
451    }
452
453    #[test]
454    fn test_cartesian_product() {
455        // Create K2 (complete graph on 2 vertices)
456        let mut k2 = create_graph::<i32, ()>();
457        k2.add_edge(1, 2, ()).unwrap();
458
459        // Create P2 (path graph on 2 vertices)
460        let mut p2 = create_graph::<char, ()>();
461        p2.add_edge('A', 'B', ()).unwrap();
462
463        let product = cartesian_product(&k2, &p2);
464
465        // Should have 4 nodes: (1,A), (1,B), (2,A), (2,B)
466        assert_eq!(product.node_count(), 4);
467
468        // Cartesian product K2 □ P2 creates a 4-cycle with 8 directed edges in undirected representation
469        // Since we're using undirected graph, edges are bidirectional
470        assert_eq!(product.edge_count(), 8);
471    }
472
473    #[test]
474    fn test_tensor_product() {
475        // Create K2
476        let mut k2_1 = create_graph::<i32, ()>();
477        k2_1.add_edge(1, 2, ()).unwrap();
478
479        // Create another K2
480        let mut k2_2 = create_graph::<char, ()>();
481        k2_2.add_edge('A', 'B', ()).unwrap();
482
483        let product = tensor_product(&k2_1, &k2_2);
484
485        // Should have 4 nodes
486        assert_eq!(product.node_count(), 4);
487
488        // Tensor product of K2 ⊗ K2 creates 4 edges since each direction is counted
489        // ((1,A),(2,B)), ((1,B),(2,A)), ((2,A),(1,B)), ((2,B),(1,A))
490        assert_eq!(product.edge_count(), 4);
491    }
492
493    #[test]
494    fn test_complement() {
495        let mut graph = create_graph::<i32, ()>();
496        graph.add_edge(1, 2, ()).unwrap();
497        graph.add_edge(2, 3, ()).unwrap();
498
499        let comp = complement(&graph);
500
501        // Should have same number of nodes
502        assert_eq!(comp.node_count(), 3);
503
504        // Complement should have edge (1,3) but not (1,2) or (2,3)
505        assert_eq!(comp.edge_count(), 1);
506        assert!(comp.has_edge(&1, &3));
507        assert!(!comp.has_edge(&1, &2));
508        assert!(!comp.has_edge(&2, &3));
509    }
510
511    #[test]
512    fn test_weight_filtered_subgraph() {
513        let mut graph = create_graph::<i32, i32>();
514        graph.add_edge(1, 2, 10).unwrap();
515        graph.add_edge(2, 3, 20).unwrap();
516        graph.add_edge(3, 4, 10).unwrap();
517
518        let mut valid_weights = HashSet::new();
519        valid_weights.insert(10);
520
521        let filtered = weight_filtered_subgraph(&graph, &valid_weights);
522
523        // Should have all 4 nodes
524        assert_eq!(filtered.node_count(), 4);
525
526        // Should have 2 edges with weight 10
527        assert_eq!(filtered.edge_count(), 2);
528        assert!(filtered.has_edge(&1, &2));
529        assert!(filtered.has_edge(&3, &4));
530        assert!(!filtered.has_edge(&2, &3)); // Has weight 20
531    }
532}