Skip to main content

quantrs2_core/quantum_walk/
graph.rs

1//! Graph types and implementations for quantum walks.
2
3use crate::error::{QuantRS2Error, QuantRS2Result};
4use scirs2_core::ndarray::Array2;
5use scirs2_core::Complex64;
6use std::collections::VecDeque;
7
8use super::eigensolvers::{compute_laplacian_eigenvalues_impl, estimate_fiedler_value_impl};
9
10/// Types of graphs for quantum walks
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum GraphType {
13    /// Line graph (path graph)
14    Line,
15    /// Cycle graph
16    Cycle,
17    /// Complete graph
18    Complete,
19    /// Hypercube graph
20    Hypercube,
21    /// Grid graph (2D lattice)
22    Grid2D,
23    /// Custom graph
24    Custom,
25}
26
27/// Coin operators for discrete quantum walks
28#[derive(Debug, Clone)]
29pub enum CoinOperator {
30    /// Hadamard coin
31    Hadamard,
32    /// Grover coin
33    Grover,
34    /// DFT (Discrete Fourier Transform) coin
35    DFT,
36    /// Custom coin operator
37    Custom(Array2<Complex64>),
38}
39
40/// Search oracle for quantum walk search
41#[derive(Debug, Clone)]
42pub struct SearchOracle {
43    /// Marked vertices
44    pub marked: Vec<usize>,
45}
46
47impl SearchOracle {
48    /// Create a new search oracle with marked vertices
49    pub const fn new(marked: Vec<usize>) -> Self {
50        Self { marked }
51    }
52
53    /// Check if a vertex is marked
54    pub fn is_marked(&self, vertex: usize) -> bool {
55        self.marked.contains(&vertex)
56    }
57}
58
59/// Graph representation for quantum walks
60#[derive(Debug, Clone)]
61pub struct Graph {
62    /// Number of vertices
63    pub num_vertices: usize,
64    /// Adjacency list representation
65    pub edges: Vec<Vec<usize>>,
66    /// Optional edge weights
67    pub weights: Option<Vec<Vec<f64>>>,
68}
69
70impl Graph {
71    /// Create a new graph of a specific type
72    pub fn new(graph_type: GraphType, size: usize) -> Self {
73        let mut graph = Self {
74            num_vertices: match graph_type {
75                GraphType::Hypercube => 1 << size, // 2^size vertices
76                GraphType::Grid2D => size * size,  // size x size grid
77                _ => size,
78            },
79            edges: vec![],
80            weights: None,
81        };
82
83        // Initialize edges based on graph type
84        graph.edges = vec![Vec::new(); graph.num_vertices];
85
86        match graph_type {
87            GraphType::Line => {
88                for i in 0..size.saturating_sub(1) {
89                    graph.add_edge(i, i + 1);
90                }
91            }
92            GraphType::Cycle => {
93                for i in 0..size {
94                    graph.add_edge(i, (i + 1) % size);
95                }
96            }
97            GraphType::Complete => {
98                for i in 0..size {
99                    for j in i + 1..size {
100                        graph.add_edge(i, j);
101                    }
102                }
103            }
104            GraphType::Hypercube => {
105                let n = size; // dimension
106                for i in 0..(1 << n) {
107                    for j in 0..n {
108                        let neighbor = i ^ (1 << j);
109                        if neighbor > i {
110                            graph.add_edge(i, neighbor);
111                        }
112                    }
113                }
114            }
115            GraphType::Grid2D => {
116                for i in 0..size {
117                    for j in 0..size {
118                        let idx = i * size + j;
119                        // Right neighbor
120                        if j < size - 1 {
121                            graph.add_edge(idx, idx + 1);
122                        }
123                        // Bottom neighbor
124                        if i < size - 1 {
125                            graph.add_edge(idx, idx + size);
126                        }
127                    }
128                }
129            }
130            GraphType::Custom => {
131                // Empty graph, user will add edges manually
132            }
133        }
134
135        graph
136    }
137
138    /// Create an empty graph with given number of vertices
139    pub fn new_empty(num_vertices: usize) -> Self {
140        Self {
141            num_vertices,
142            edges: vec![Vec::new(); num_vertices],
143            weights: None,
144        }
145    }
146
147    /// Add an undirected edge
148    pub fn add_edge(&mut self, u: usize, v: usize) {
149        if u < self.num_vertices && v < self.num_vertices && u != v && !self.edges[u].contains(&v) {
150            self.edges[u].push(v);
151            self.edges[v].push(u);
152        }
153    }
154
155    /// Add a weighted edge
156    pub fn add_weighted_edge(&mut self, u: usize, v: usize, weight: f64) {
157        if self.weights.is_none() {
158            self.weights = Some(vec![vec![0.0; self.num_vertices]; self.num_vertices]);
159        }
160
161        self.add_edge(u, v);
162
163        if let Some(ref mut weights) = self.weights {
164            weights[u][v] = weight;
165            weights[v][u] = weight;
166        }
167    }
168
169    /// Get the degree of a vertex
170    pub fn degree(&self, vertex: usize) -> usize {
171        if vertex < self.num_vertices {
172            self.edges[vertex].len()
173        } else {
174            0
175        }
176    }
177
178    /// Get the adjacency matrix
179    pub fn adjacency_matrix(&self) -> Array2<f64> {
180        let mut matrix = Array2::zeros((self.num_vertices, self.num_vertices));
181
182        for (u, neighbors) in self.edges.iter().enumerate() {
183            for &v in neighbors {
184                if let Some(ref weights) = self.weights {
185                    matrix[[u, v]] = weights[u][v];
186                } else {
187                    matrix[[u, v]] = 1.0;
188                }
189            }
190        }
191
192        matrix
193    }
194
195    /// Get the Laplacian matrix
196    pub fn laplacian_matrix(&self) -> Array2<f64> {
197        let mut laplacian = Array2::zeros((self.num_vertices, self.num_vertices));
198
199        for v in 0..self.num_vertices {
200            let degree = self.degree(v) as f64;
201            laplacian[[v, v]] = degree;
202
203            for &neighbor in &self.edges[v] {
204                if let Some(ref weights) = self.weights {
205                    laplacian[[v, neighbor]] = -weights[v][neighbor];
206                } else {
207                    laplacian[[v, neighbor]] = -1.0;
208                }
209            }
210        }
211
212        laplacian
213    }
214
215    /// Get the normalized Laplacian matrix
216    pub fn normalized_laplacian_matrix(&self) -> Array2<f64> {
217        let mut norm_laplacian = Array2::zeros((self.num_vertices, self.num_vertices));
218
219        for v in 0..self.num_vertices {
220            let degree_v = self.degree(v) as f64;
221            if degree_v == 0.0 {
222                continue;
223            }
224
225            norm_laplacian[[v, v]] = 1.0;
226
227            for &neighbor in &self.edges[v] {
228                let degree_n = self.degree(neighbor) as f64;
229                if degree_n == 0.0 {
230                    continue;
231                }
232
233                let weight = if let Some(ref weights) = self.weights {
234                    weights[v][neighbor]
235                } else {
236                    1.0
237                };
238
239                norm_laplacian[[v, neighbor]] = -weight / (degree_v * degree_n).sqrt();
240            }
241        }
242
243        norm_laplacian
244    }
245
246    /// Get the transition matrix for random walks
247    pub fn transition_matrix(&self) -> Array2<f64> {
248        let mut transition = Array2::zeros((self.num_vertices, self.num_vertices));
249
250        for v in 0..self.num_vertices {
251            let degree = self.degree(v) as f64;
252            if degree == 0.0 {
253                continue;
254            }
255
256            for &neighbor in &self.edges[v] {
257                let weight = if let Some(ref weights) = self.weights {
258                    weights[v][neighbor]
259                } else {
260                    1.0
261                };
262
263                transition[[v, neighbor]] = weight / degree;
264            }
265        }
266
267        transition
268    }
269
270    /// Check if the graph is bipartite
271    pub fn is_bipartite(&self) -> bool {
272        let mut colors = vec![-1; self.num_vertices];
273
274        for start in 0..self.num_vertices {
275            if colors[start] != -1 {
276                continue;
277            }
278
279            let mut queue = VecDeque::new();
280            queue.push_back(start);
281            colors[start] = 0;
282
283            while let Some(vertex) = queue.pop_front() {
284                for &neighbor in &self.edges[vertex] {
285                    if colors[neighbor] == -1 {
286                        colors[neighbor] = 1 - colors[vertex];
287                        queue.push_back(neighbor);
288                    } else if colors[neighbor] == colors[vertex] {
289                        return false;
290                    }
291                }
292            }
293        }
294
295        true
296    }
297
298    /// Calculate the algebraic connectivity (second smallest eigenvalue of Laplacian)
299    pub fn algebraic_connectivity(&self) -> f64 {
300        let laplacian = self.laplacian_matrix();
301
302        // For small graphs, we can compute eigenvalues directly
303        // In practice, you'd use more sophisticated numerical methods
304        if self.num_vertices <= 10 {
305            self.compute_laplacian_eigenvalues(&laplacian)
306                .get(1)
307                .copied()
308                .unwrap_or(0.0)
309        } else {
310            // Approximate using power iteration for larger graphs
311            self.estimate_fiedler_value(&laplacian)
312        }
313    }
314
315    /// Compute eigenvalues of the Laplacian using Householder tridiagonalization
316    /// followed by Golub-Reinsch QR iteration with Wilkinson shifts.
317    fn compute_laplacian_eigenvalues(&self, laplacian: &Array2<f64>) -> Vec<f64> {
318        compute_laplacian_eigenvalues_impl(laplacian)
319            .unwrap_or_else(|_| vec![0.0; self.num_vertices])
320    }
321
322    /// Estimate Fiedler value (second smallest Laplacian eigenvalue) using
323    /// Rayleigh quotient power iteration restricted to the subspace
324    /// orthogonal to the all-ones vector.
325    fn estimate_fiedler_value(&self, laplacian: &Array2<f64>) -> f64 {
326        estimate_fiedler_value_impl(laplacian)
327    }
328
329    /// Get shortest path distances between all pairs of vertices
330    pub fn all_pairs_shortest_paths(&self) -> Array2<f64> {
331        let mut distances =
332            Array2::from_elem((self.num_vertices, self.num_vertices), f64::INFINITY);
333
334        // Initialize distances
335        for v in 0..self.num_vertices {
336            distances[[v, v]] = 0.0;
337            for &neighbor in &self.edges[v] {
338                let weight = if let Some(ref weights) = self.weights {
339                    weights[v][neighbor]
340                } else {
341                    1.0
342                };
343                distances[[v, neighbor]] = weight;
344            }
345        }
346
347        // Floyd-Warshall algorithm
348        for k in 0..self.num_vertices {
349            for i in 0..self.num_vertices {
350                for j in 0..self.num_vertices {
351                    let via_k = distances[[i, k]] + distances[[k, j]];
352                    if via_k < distances[[i, j]] {
353                        distances[[i, j]] = via_k;
354                    }
355                }
356            }
357        }
358
359        distances
360    }
361
362    /// Create a graph from an adjacency matrix
363    pub fn from_adjacency_matrix(matrix: &Array2<f64>) -> QuantRS2Result<Self> {
364        let (rows, cols) = matrix.dim();
365        if rows != cols {
366            return Err(QuantRS2Error::InvalidInput(
367                "Adjacency matrix must be square".to_string(),
368            ));
369        }
370
371        let mut graph = Self::new_empty(rows);
372        let mut has_weights = false;
373
374        for i in 0..rows {
375            for j in i + 1..cols {
376                let weight = matrix[[i, j]];
377                if weight != 0.0 {
378                    if weight != 1.0 {
379                        has_weights = true;
380                    }
381                    if has_weights {
382                        graph.add_weighted_edge(i, j, weight);
383                    } else {
384                        graph.add_edge(i, j);
385                    }
386                }
387            }
388        }
389
390        Ok(graph)
391    }
392}