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