quantrs2_core/
quantum_walk.rs

1//! Quantum Walk Algorithms
2//!
3//! This module implements various quantum walk algorithms, including:
4//! - Discrete-time quantum walks on graphs
5//! - Continuous-time quantum walks
6//! - Szegedy quantum walks
7//!
8//! Quantum walks are the quantum analog of classical random walks and form
9//! the basis for many quantum algorithms.
10
11use crate::{
12    complex_ext::QuantumComplexExt,
13    error::{QuantRS2Error, QuantRS2Result},
14};
15use scirs2_core::ndarray::{Array1, Array2};
16use scirs2_core::Complex64;
17use std::collections::{HashMap, VecDeque};
18
19/// Types of graphs for quantum walks
20#[derive(Debug, Clone, Copy, PartialEq)]
21pub enum GraphType {
22    /// Line graph (path graph)
23    Line,
24    /// Cycle graph
25    Cycle,
26    /// Complete graph
27    Complete,
28    /// Hypercube graph
29    Hypercube,
30    /// Grid graph (2D lattice)
31    Grid2D,
32    /// Custom graph
33    Custom,
34}
35
36/// Coin operators for discrete quantum walks
37#[derive(Debug, Clone)]
38pub enum CoinOperator {
39    /// Hadamard coin
40    Hadamard,
41    /// Grover coin
42    Grover,
43    /// DFT (Discrete Fourier Transform) coin
44    DFT,
45    /// Custom coin operator
46    Custom(Array2<Complex64>),
47}
48
49/// Search oracle for quantum walk search
50#[derive(Debug, Clone)]
51pub struct SearchOracle {
52    /// Marked vertices
53    pub marked: Vec<usize>,
54}
55
56impl SearchOracle {
57    /// Create a new search oracle with marked vertices
58    pub fn new(marked: Vec<usize>) -> Self {
59        Self { marked }
60    }
61
62    /// Check if a vertex is marked
63    pub fn is_marked(&self, vertex: usize) -> bool {
64        self.marked.contains(&vertex)
65    }
66}
67
68/// Graph representation for quantum walks
69#[derive(Debug, Clone)]
70pub struct Graph {
71    /// Number of vertices
72    pub num_vertices: usize,
73    /// Adjacency list representation
74    pub edges: Vec<Vec<usize>>,
75    /// Optional edge weights
76    pub weights: Option<Vec<Vec<f64>>>,
77}
78
79impl Graph {
80    /// Create a new graph of a specific type
81    pub fn new(graph_type: GraphType, size: usize) -> Self {
82        let mut graph = Self {
83            num_vertices: match graph_type {
84                GraphType::Hypercube => 1 << size, // 2^size vertices
85                GraphType::Grid2D => size * size,  // size x size grid
86                _ => size,
87            },
88            edges: vec![],
89            weights: None,
90        };
91
92        // Initialize edges based on graph type
93        graph.edges = vec![Vec::new(); graph.num_vertices];
94
95        match graph_type {
96            GraphType::Line => {
97                for i in 0..size.saturating_sub(1) {
98                    graph.add_edge(i, i + 1);
99                }
100            }
101            GraphType::Cycle => {
102                for i in 0..size {
103                    graph.add_edge(i, (i + 1) % size);
104                }
105            }
106            GraphType::Complete => {
107                for i in 0..size {
108                    for j in i + 1..size {
109                        graph.add_edge(i, j);
110                    }
111                }
112            }
113            GraphType::Hypercube => {
114                let n = size; // dimension
115                for i in 0..(1 << n) {
116                    for j in 0..n {
117                        let neighbor = i ^ (1 << j);
118                        if neighbor > i {
119                            graph.add_edge(i, neighbor);
120                        }
121                    }
122                }
123            }
124            GraphType::Grid2D => {
125                for i in 0..size {
126                    for j in 0..size {
127                        let idx = i * size + j;
128                        // Right neighbor
129                        if j < size - 1 {
130                            graph.add_edge(idx, idx + 1);
131                        }
132                        // Bottom neighbor
133                        if i < size - 1 {
134                            graph.add_edge(idx, idx + size);
135                        }
136                    }
137                }
138            }
139            GraphType::Custom => {
140                // Empty graph, user will add edges manually
141            }
142        }
143
144        graph
145    }
146
147    /// Create an empty graph with given number of vertices
148    pub fn new_empty(num_vertices: usize) -> Self {
149        Self {
150            num_vertices,
151            edges: vec![Vec::new(); num_vertices],
152            weights: None,
153        }
154    }
155
156    /// Add an undirected edge
157    pub fn add_edge(&mut self, u: usize, v: usize) {
158        if u < self.num_vertices && v < self.num_vertices && u != v && !self.edges[u].contains(&v) {
159            self.edges[u].push(v);
160            self.edges[v].push(u);
161        }
162    }
163
164    /// Add a weighted edge
165    pub fn add_weighted_edge(&mut self, u: usize, v: usize, weight: f64) {
166        if self.weights.is_none() {
167            self.weights = Some(vec![vec![0.0; self.num_vertices]; self.num_vertices]);
168        }
169
170        self.add_edge(u, v);
171
172        if let Some(ref mut weights) = self.weights {
173            weights[u][v] = weight;
174            weights[v][u] = weight;
175        }
176    }
177
178    /// Get the degree of a vertex
179    pub fn degree(&self, vertex: usize) -> usize {
180        if vertex < self.num_vertices {
181            self.edges[vertex].len()
182        } else {
183            0
184        }
185    }
186
187    /// Get the adjacency matrix
188    pub fn adjacency_matrix(&self) -> Array2<f64> {
189        let mut matrix = Array2::zeros((self.num_vertices, self.num_vertices));
190
191        for (u, neighbors) in self.edges.iter().enumerate() {
192            for &v in neighbors {
193                if let Some(ref weights) = self.weights {
194                    matrix[[u, v]] = weights[u][v];
195                } else {
196                    matrix[[u, v]] = 1.0;
197                }
198            }
199        }
200
201        matrix
202    }
203
204    /// Get the Laplacian matrix
205    pub fn laplacian_matrix(&self) -> Array2<f64> {
206        let mut laplacian = Array2::zeros((self.num_vertices, self.num_vertices));
207
208        for v in 0..self.num_vertices {
209            let degree = self.degree(v) as f64;
210            laplacian[[v, v]] = degree;
211
212            for &neighbor in &self.edges[v] {
213                if let Some(ref weights) = self.weights {
214                    laplacian[[v, neighbor]] = -weights[v][neighbor];
215                } else {
216                    laplacian[[v, neighbor]] = -1.0;
217                }
218            }
219        }
220
221        laplacian
222    }
223
224    /// Get the normalized Laplacian matrix
225    pub fn normalized_laplacian_matrix(&self) -> Array2<f64> {
226        let mut norm_laplacian = Array2::zeros((self.num_vertices, self.num_vertices));
227
228        for v in 0..self.num_vertices {
229            let degree_v = self.degree(v) as f64;
230            if degree_v == 0.0 {
231                continue;
232            }
233
234            norm_laplacian[[v, v]] = 1.0;
235
236            for &neighbor in &self.edges[v] {
237                let degree_n = self.degree(neighbor) as f64;
238                if degree_n == 0.0 {
239                    continue;
240                }
241
242                let weight = if let Some(ref weights) = self.weights {
243                    weights[v][neighbor]
244                } else {
245                    1.0
246                };
247
248                norm_laplacian[[v, neighbor]] = -weight / (degree_v * degree_n).sqrt();
249            }
250        }
251
252        norm_laplacian
253    }
254
255    /// Get the transition matrix for random walks
256    pub fn transition_matrix(&self) -> Array2<f64> {
257        let mut transition = Array2::zeros((self.num_vertices, self.num_vertices));
258
259        for v in 0..self.num_vertices {
260            let degree = self.degree(v) as f64;
261            if degree == 0.0 {
262                continue;
263            }
264
265            for &neighbor in &self.edges[v] {
266                let weight = if let Some(ref weights) = self.weights {
267                    weights[v][neighbor]
268                } else {
269                    1.0
270                };
271
272                transition[[v, neighbor]] = weight / degree;
273            }
274        }
275
276        transition
277    }
278
279    /// Check if the graph is bipartite
280    pub fn is_bipartite(&self) -> bool {
281        let mut colors = vec![-1; self.num_vertices];
282
283        for start in 0..self.num_vertices {
284            if colors[start] != -1 {
285                continue;
286            }
287
288            let mut queue = VecDeque::new();
289            queue.push_back(start);
290            colors[start] = 0;
291
292            while let Some(vertex) = queue.pop_front() {
293                for &neighbor in &self.edges[vertex] {
294                    if colors[neighbor] == -1 {
295                        colors[neighbor] = 1 - colors[vertex];
296                        queue.push_back(neighbor);
297                    } else if colors[neighbor] == colors[vertex] {
298                        return false;
299                    }
300                }
301            }
302        }
303
304        true
305    }
306
307    /// Calculate the algebraic connectivity (second smallest eigenvalue of Laplacian)
308    pub fn algebraic_connectivity(&self) -> f64 {
309        let laplacian = self.laplacian_matrix();
310
311        // For small graphs, we can compute eigenvalues directly
312        // In practice, you'd use more sophisticated numerical methods
313        if self.num_vertices <= 10 {
314            self.compute_laplacian_eigenvalues(&laplacian)
315                .get(1)
316                .copied()
317                .unwrap_or(0.0)
318        } else {
319            // Approximate using power iteration for larger graphs
320            self.estimate_fiedler_value(&laplacian)
321        }
322    }
323
324    /// Compute eigenvalues of Laplacian (simplified implementation)
325    fn compute_laplacian_eigenvalues(&self, _laplacian: &Array2<f64>) -> Vec<f64> {
326        // This is a placeholder - in practice you'd use a proper eigenvalue solver
327        // For now, return approximate values based on graph structure
328        let mut eigenvalues = vec![0.0]; // Always has 0 eigenvalue
329
330        // Rough approximation based on degree sequence
331        for v in 0..self.num_vertices {
332            let degree = self.degree(v) as f64;
333            if degree > 0.0 {
334                eigenvalues.push(degree);
335            }
336        }
337
338        eigenvalues.sort_by(|a, b| {
339            a.partial_cmp(b)
340                .expect("Failed to compare eigenvalues in Graph::compute_laplacian_eigenvalues")
341        });
342        eigenvalues.truncate(self.num_vertices);
343        eigenvalues
344    }
345
346    /// Estimate Fiedler value using power iteration
347    fn estimate_fiedler_value(&self, _laplacian: &Array2<f64>) -> f64 {
348        // Simplified estimation - in practice use inverse power iteration
349        let max_degree = (0..self.num_vertices)
350            .map(|v| self.degree(v))
351            .max()
352            .unwrap_or(0);
353        2.0 / max_degree as f64 // Rough approximation
354    }
355
356    /// Get shortest path distances between all pairs of vertices
357    pub fn all_pairs_shortest_paths(&self) -> Array2<f64> {
358        let mut distances =
359            Array2::from_elem((self.num_vertices, self.num_vertices), f64::INFINITY);
360
361        // Initialize distances
362        for v in 0..self.num_vertices {
363            distances[[v, v]] = 0.0;
364            for &neighbor in &self.edges[v] {
365                let weight = if let Some(ref weights) = self.weights {
366                    weights[v][neighbor]
367                } else {
368                    1.0
369                };
370                distances[[v, neighbor]] = weight;
371            }
372        }
373
374        // Floyd-Warshall algorithm
375        for k in 0..self.num_vertices {
376            for i in 0..self.num_vertices {
377                for j in 0..self.num_vertices {
378                    let via_k = distances[[i, k]] + distances[[k, j]];
379                    if via_k < distances[[i, j]] {
380                        distances[[i, j]] = via_k;
381                    }
382                }
383            }
384        }
385
386        distances
387    }
388
389    /// Create a graph from an adjacency matrix
390    pub fn from_adjacency_matrix(matrix: &Array2<f64>) -> QuantRS2Result<Self> {
391        let (rows, cols) = matrix.dim();
392        if rows != cols {
393            return Err(QuantRS2Error::InvalidInput(
394                "Adjacency matrix must be square".to_string(),
395            ));
396        }
397
398        let mut graph = Self::new_empty(rows);
399        let mut has_weights = false;
400
401        for i in 0..rows {
402            for j in i + 1..cols {
403                let weight = matrix[[i, j]];
404                if weight != 0.0 {
405                    if weight != 1.0 {
406                        has_weights = true;
407                    }
408                    if has_weights {
409                        graph.add_weighted_edge(i, j, weight);
410                    } else {
411                        graph.add_edge(i, j);
412                    }
413                }
414            }
415        }
416
417        Ok(graph)
418    }
419}
420
421/// Discrete-time quantum walk
422pub struct DiscreteQuantumWalk {
423    graph: Graph,
424    coin_operator: CoinOperator,
425    coin_dimension: usize,
426    /// Total Hilbert space dimension: coin_dimension * num_vertices
427    hilbert_dim: usize,
428    /// Current state vector
429    state: Vec<Complex64>,
430}
431
432impl DiscreteQuantumWalk {
433    /// Create a new discrete quantum walk with specified coin operator
434    pub fn new(graph: Graph, coin_operator: CoinOperator) -> Self {
435        // Coin dimension is the maximum degree for standard walks
436        // For hypercube, it's the dimension
437        let coin_dimension = match graph.num_vertices {
438            n if n > 0 => {
439                (0..graph.num_vertices)
440                    .map(|v| graph.degree(v))
441                    .max()
442                    .unwrap_or(2)
443                    .max(2) // At least 2-dimensional coin
444            }
445            _ => 2,
446        };
447
448        let hilbert_dim = coin_dimension * graph.num_vertices;
449
450        Self {
451            graph,
452            coin_operator,
453            coin_dimension,
454            hilbert_dim,
455            state: vec![Complex64::new(0.0, 0.0); hilbert_dim],
456        }
457    }
458
459    /// Initialize walker at a specific position
460    pub fn initialize_position(&mut self, position: usize) {
461        self.state = vec![Complex64::new(0.0, 0.0); self.hilbert_dim];
462
463        // Equal superposition over all coin states at the position
464        let degree = self.graph.degree(position) as f64;
465        if degree > 0.0 {
466            let amplitude = Complex64::new(1.0 / degree.sqrt(), 0.0);
467
468            for coin in 0..self.coin_dimension.min(self.graph.degree(position)) {
469                let index = self.state_index(position, coin);
470                if index < self.state.len() {
471                    self.state[index] = amplitude;
472                }
473            }
474        }
475    }
476
477    /// Perform one step of the quantum walk
478    pub fn step(&mut self) {
479        // Apply coin operator
480        self.apply_coin();
481
482        // Apply shift operator
483        self.apply_shift();
484    }
485
486    /// Get position probabilities
487    pub fn position_probabilities(&self) -> Vec<f64> {
488        let mut probs = vec![0.0; self.graph.num_vertices];
489
490        for (vertex, prob) in probs.iter_mut().enumerate() {
491            for coin in 0..self.coin_dimension {
492                let idx = self.state_index(vertex, coin);
493                if idx < self.state.len() {
494                    *prob += self.state[idx].norm_sqr();
495                }
496            }
497        }
498
499        probs
500    }
501
502    /// Get the index in the state vector for (vertex, coin) pair
503    fn state_index(&self, vertex: usize, coin: usize) -> usize {
504        vertex * self.coin_dimension + coin
505    }
506
507    /// Apply the coin operator
508    fn apply_coin(&mut self) {
509        match &self.coin_operator {
510            CoinOperator::Hadamard => self.apply_hadamard_coin(),
511            CoinOperator::Grover => self.apply_grover_coin(),
512            CoinOperator::DFT => self.apply_dft_coin(),
513            CoinOperator::Custom(matrix) => self.apply_custom_coin(matrix.clone()),
514        }
515    }
516
517    /// Apply Hadamard coin
518    fn apply_hadamard_coin(&mut self) {
519        let h = 1.0 / std::f64::consts::SQRT_2;
520
521        for vertex in 0..self.graph.num_vertices {
522            if self.coin_dimension == 2 {
523                let idx0 = self.state_index(vertex, 0);
524                let idx1 = self.state_index(vertex, 1);
525
526                if idx1 < self.state.len() {
527                    let a0 = self.state[idx0];
528                    let a1 = self.state[idx1];
529
530                    self.state[idx0] = h * (a0 + a1);
531                    self.state[idx1] = h * (a0 - a1);
532                }
533            }
534        }
535    }
536
537    /// Apply Grover coin
538    fn apply_grover_coin(&mut self) {
539        // Grover coin: 2|s><s| - I, where |s> is uniform superposition
540        for vertex in 0..self.graph.num_vertices {
541            let degree = self.graph.degree(vertex);
542            if degree <= 1 {
543                continue; // No coin needed for degree 0 or 1
544            }
545
546            // Calculate sum of amplitudes for this vertex
547            let mut sum = Complex64::new(0.0, 0.0);
548            for coin in 0..degree.min(self.coin_dimension) {
549                let idx = self.state_index(vertex, coin);
550                if idx < self.state.len() {
551                    sum += self.state[idx];
552                }
553            }
554
555            // Apply Grover coin
556            let factor = Complex64::new(2.0 / degree as f64, 0.0);
557            for coin in 0..degree.min(self.coin_dimension) {
558                let idx = self.state_index(vertex, coin);
559                if idx < self.state.len() {
560                    let old_amp = self.state[idx];
561                    self.state[idx] = factor * sum - old_amp;
562                }
563            }
564        }
565    }
566
567    /// Apply DFT coin
568    fn apply_dft_coin(&mut self) {
569        // DFT coin for 2-dimensional coin space
570        if self.coin_dimension == 2 {
571            self.apply_hadamard_coin(); // DFT is same as Hadamard for 2D
572        }
573        // For higher dimensions, would implement full DFT
574    }
575
576    /// Apply custom coin operator
577    fn apply_custom_coin(&mut self, matrix: Array2<Complex64>) {
578        if matrix.shape() != [self.coin_dimension, self.coin_dimension] {
579            return; // Matrix size mismatch
580        }
581
582        for vertex in 0..self.graph.num_vertices {
583            let mut coin_state = vec![Complex64::new(0.0, 0.0); self.coin_dimension];
584
585            // Extract coin state for this vertex
586            for (coin, cs) in coin_state.iter_mut().enumerate() {
587                let idx = self.state_index(vertex, coin);
588                if idx < self.state.len() {
589                    *cs = self.state[idx];
590                }
591            }
592
593            // Apply coin operator
594            let new_coin_state = matrix.dot(&Array1::from(coin_state));
595
596            // Write back
597            for coin in 0..self.coin_dimension {
598                let idx = self.state_index(vertex, coin);
599                if idx < self.state.len() {
600                    self.state[idx] = new_coin_state[coin];
601                }
602            }
603        }
604    }
605
606    /// Apply the shift operator
607    fn apply_shift(&mut self) {
608        let mut new_state = vec![Complex64::new(0.0, 0.0); self.hilbert_dim];
609
610        for vertex in 0..self.graph.num_vertices {
611            for (coin, &neighbor) in self.graph.edges[vertex].iter().enumerate() {
612                if coin < self.coin_dimension {
613                    let from_idx = self.state_index(vertex, coin);
614
615                    // Find which coin state corresponds to coming from 'vertex' at 'neighbor'
616                    let to_coin = self.graph.edges[neighbor]
617                        .iter()
618                        .position(|&v| v == vertex)
619                        .unwrap_or(0);
620
621                    if to_coin < self.coin_dimension && from_idx < self.state.len() {
622                        let to_idx = self.state_index(neighbor, to_coin);
623                        if to_idx < new_state.len() {
624                            new_state[to_idx] = self.state[from_idx];
625                        }
626                    }
627                }
628            }
629        }
630
631        self.state.copy_from_slice(&new_state);
632    }
633}
634
635/// Continuous-time quantum walk
636pub struct ContinuousQuantumWalk {
637    graph: Graph,
638    hamiltonian: Array2<Complex64>,
639    state: Vec<Complex64>,
640}
641
642impl ContinuousQuantumWalk {
643    /// Create a new continuous quantum walk
644    pub fn new(graph: Graph) -> Self {
645        let adj_matrix = graph.adjacency_matrix();
646        let hamiltonian = adj_matrix.mapv(|x| Complex64::new(x, 0.0));
647        let num_vertices = graph.num_vertices;
648
649        Self {
650            graph,
651            hamiltonian,
652            state: vec![Complex64::new(0.0, 0.0); num_vertices],
653        }
654    }
655
656    /// Initialize walker at a specific vertex
657    pub fn initialize_vertex(&mut self, vertex: usize) {
658        self.state = vec![Complex64::new(0.0, 0.0); self.graph.num_vertices];
659        if vertex < self.graph.num_vertices {
660            self.state[vertex] = Complex64::new(1.0, 0.0);
661        }
662    }
663
664    /// Evolve the quantum walk for time t
665    pub fn evolve(&mut self, time: f64) {
666        // This is a simplified version using first-order approximation
667        // For a full implementation, we would diagonalize the Hamiltonian
668
669        let dt = 0.01; // Time step
670        let steps = (time / dt) as usize;
671
672        for _ in 0..steps {
673            let mut new_state = self.state.clone();
674
675            // Apply exp(-iHt) ≈ I - iHdt for small dt
676            for (i, ns) in new_state
677                .iter_mut()
678                .enumerate()
679                .take(self.graph.num_vertices)
680            {
681                let mut sum = Complex64::new(0.0, 0.0);
682                for j in 0..self.graph.num_vertices {
683                    sum += self.hamiltonian[[i, j]] * self.state[j];
684                }
685                *ns = self.state[i] - Complex64::new(0.0, dt) * sum;
686            }
687
688            // Normalize
689            let norm: f64 = new_state.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
690
691            if norm > 0.0 {
692                for amp in new_state.iter_mut() {
693                    *amp /= norm;
694                }
695            }
696
697            self.state = new_state;
698        }
699    }
700
701    /// Get vertex probabilities
702    pub fn vertex_probabilities(&self) -> Vec<f64> {
703        self.state.iter().map(|c| c.probability()).collect()
704    }
705
706    /// Calculate transport probability between two vertices at time t
707    pub fn transport_probability(&mut self, from: usize, to: usize, time: f64) -> f64 {
708        // Initialize at 'from' vertex
709        self.initialize_vertex(from);
710
711        // Evolve for time t
712        self.evolve(time);
713
714        // Return probability at 'to' vertex
715        if to < self.state.len() {
716            self.state[to].probability()
717        } else {
718            0.0
719        }
720    }
721
722    /// Get the probability distribution
723    pub fn get_probabilities(&self, state: &[Complex64]) -> Vec<f64> {
724        state.iter().map(|c| c.probability()).collect()
725    }
726}
727
728/// Szegedy quantum walk for arbitrary graphs
729/// This provides better mixing properties on irregular graphs
730pub struct SzegedyQuantumWalk {
731    graph: Graph,
732    /// State lives on edges: |u,v> where edge (u,v) exists
733    state: HashMap<(usize, usize), Complex64>,
734    num_edges: usize,
735}
736
737impl SzegedyQuantumWalk {
738    /// Create a new Szegedy quantum walk
739    pub fn new(graph: Graph) -> Self {
740        let mut num_edges = 0;
741        for v in 0..graph.num_vertices {
742            num_edges += graph.edges[v].len();
743        }
744
745        Self {
746            graph,
747            state: HashMap::new(),
748            num_edges,
749        }
750    }
751
752    /// Initialize in uniform superposition over all edges
753    pub fn initialize_uniform(&mut self) {
754        self.state.clear();
755
756        if self.num_edges == 0 {
757            return;
758        }
759
760        let amplitude = Complex64::new(1.0 / (self.num_edges as f64).sqrt(), 0.0);
761
762        for u in 0..self.graph.num_vertices {
763            for &v in &self.graph.edges[u] {
764                self.state.insert((u, v), amplitude);
765            }
766        }
767    }
768
769    /// Initialize at a specific edge
770    pub fn initialize_edge(&mut self, u: usize, v: usize) {
771        self.state.clear();
772
773        if u < self.graph.num_vertices && self.graph.edges[u].contains(&v) {
774            self.state.insert((u, v), Complex64::new(1.0, 0.0));
775        }
776    }
777
778    /// Perform one step of Szegedy walk
779    pub fn step(&mut self) {
780        // Szegedy walk: (2P - I)(2Q - I) where P and Q are projections
781
782        // Apply reflection around vertex-uniform states
783        self.reflect_vertex_uniform();
784
785        // Apply reflection around edge-uniform states
786        self.reflect_edge_uniform();
787    }
788
789    /// Reflect around vertex-uniform subspaces
790    fn reflect_vertex_uniform(&mut self) {
791        let mut vertex_sums: Vec<Complex64> =
792            vec![Complex64::new(0.0, 0.0); self.graph.num_vertices];
793
794        // Calculate sum of amplitudes for each vertex
795        for (&(u, _), &amplitude) in &self.state {
796            vertex_sums[u] += amplitude;
797        }
798
799        // Apply reflection: 2|psi_u><psi_u| - I
800        let mut new_state = HashMap::new();
801
802        for (&(u, v), &old_amp) in &self.state {
803            let degree = self.graph.degree(u) as f64;
804            if degree > 0.0 {
805                let vertex_avg = vertex_sums[u] / degree;
806                let new_amp = 2.0 * vertex_avg - old_amp;
807                new_state.insert((u, v), new_amp);
808            }
809        }
810
811        self.state = new_state;
812    }
813
814    /// Reflect around edge-uniform subspace
815    fn reflect_edge_uniform(&mut self) {
816        if self.num_edges == 0 {
817            return;
818        }
819
820        // Calculate total amplitude
821        let total_amp: Complex64 = self.state.values().sum();
822        let uniform_amp = total_amp / self.num_edges as f64;
823
824        // Apply reflection: 2|uniform><uniform| - I
825        for (_, amplitude) in self.state.iter_mut() {
826            *amplitude = 2.0 * uniform_amp - *amplitude;
827        }
828    }
829
830    /// Get vertex probabilities by summing over outgoing edges
831    pub fn vertex_probabilities(&self) -> Vec<f64> {
832        let mut probs = vec![0.0; self.graph.num_vertices];
833
834        for (&(u, _), &amplitude) in &self.state {
835            probs[u] += amplitude.norm_sqr();
836        }
837
838        probs
839    }
840
841    /// Get edge probabilities
842    pub fn edge_probabilities(&self) -> Vec<((usize, usize), f64)> {
843        self.state
844            .iter()
845            .map(|(&edge, &amplitude)| (edge, amplitude.norm_sqr()))
846            .collect()
847    }
848
849    /// Calculate mixing time to epsilon-close to uniform distribution
850    pub fn estimate_mixing_time(&mut self, epsilon: f64) -> usize {
851        let uniform_prob = 1.0 / self.graph.num_vertices as f64;
852
853        // Reset to uniform
854        self.initialize_uniform();
855
856        for steps in 1..1000 {
857            self.step();
858
859            let probs = self.vertex_probabilities();
860            let max_deviation = probs
861                .iter()
862                .map(|&p| (p - uniform_prob).abs())
863                .fold(0.0, f64::max);
864
865            if max_deviation < epsilon {
866                return steps;
867            }
868        }
869
870        1000 // Return max if not converged
871    }
872}
873
874/// Multi-walker quantum walk for studying entanglement and correlations
875pub struct MultiWalkerQuantumWalk {
876    graph: Graph,
877    num_walkers: usize,
878    /// State tensor product space: walker1_pos ⊗ walker2_pos ⊗ ...
879    state: Array1<Complex64>,
880    /// Dimension of single walker space
881    single_walker_dim: usize,
882}
883
884impl MultiWalkerQuantumWalk {
885    /// Create a new multi-walker quantum walk
886    pub fn new(graph: Graph, num_walkers: usize) -> Self {
887        let single_walker_dim = graph.num_vertices;
888        let total_dim = single_walker_dim.pow(num_walkers as u32);
889
890        Self {
891            graph,
892            num_walkers,
893            state: Array1::zeros(total_dim),
894            single_walker_dim,
895        }
896    }
897
898    /// Initialize walkers at specific positions
899    pub fn initialize_positions(&mut self, positions: &[usize]) -> QuantRS2Result<()> {
900        if positions.len() != self.num_walkers {
901            return Err(QuantRS2Error::InvalidInput(
902                "Number of positions must match number of walkers".to_string(),
903            ));
904        }
905
906        for &pos in positions {
907            if pos >= self.single_walker_dim {
908                return Err(QuantRS2Error::InvalidInput(format!(
909                    "Position {} out of bounds",
910                    pos
911                )));
912            }
913        }
914
915        // Reset state
916        self.state.fill(Complex64::new(0.0, 0.0));
917
918        // Set amplitude for initial configuration
919        let index = self.positions_to_index(positions);
920        self.state[index] = Complex64::new(1.0, 0.0);
921
922        Ok(())
923    }
924
925    /// Initialize in entangled superposition
926    pub fn initialize_entangled_bell_state(
927        &mut self,
928        pos1: usize,
929        pos2: usize,
930    ) -> QuantRS2Result<()> {
931        if self.num_walkers != 2 {
932            return Err(QuantRS2Error::InvalidInput(
933                "Bell state initialization only works for 2 walkers".to_string(),
934            ));
935        }
936
937        self.state.fill(Complex64::new(0.0, 0.0));
938
939        let amplitude = Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0);
940
941        // |pos1,pos2> + |pos2,pos1>
942        let idx1 = self.positions_to_index(&[pos1, pos2]);
943        let idx2 = self.positions_to_index(&[pos2, pos1]);
944
945        self.state[idx1] = amplitude;
946        self.state[idx2] = amplitude;
947
948        Ok(())
949    }
950
951    /// Convert walker positions to state vector index
952    fn positions_to_index(&self, positions: &[usize]) -> usize {
953        let mut index = 0;
954        let mut multiplier = 1;
955
956        for &pos in positions.iter().rev() {
957            index += pos * multiplier;
958            multiplier *= self.single_walker_dim;
959        }
960
961        index
962    }
963
964    /// Convert state vector index to walker positions
965    fn index_to_positions(&self, mut index: usize) -> Vec<usize> {
966        let mut positions = Vec::with_capacity(self.num_walkers);
967
968        for _ in 0..self.num_walkers {
969            positions.push(index % self.single_walker_dim);
970            index /= self.single_walker_dim;
971        }
972
973        positions.reverse();
974        positions
975    }
976
977    /// Perform one step (simplified version - each walker evolves independently)
978    pub fn step_independent(&mut self) {
979        let mut new_state = Array1::zeros(self.state.len());
980
981        for (index, &amplitude) in self.state.iter().enumerate() {
982            if amplitude.norm_sqr() < 1e-15 {
983                continue;
984            }
985
986            let positions = self.index_to_positions(index);
987
988            // Each walker moves to neighboring vertices with equal probability
989            let mut total_neighbors = 1;
990            for &pos in &positions {
991                total_neighbors *= self.graph.degree(pos).max(1);
992            }
993
994            let neighbor_amplitude = amplitude / (total_neighbors as f64).sqrt();
995
996            // Generate all possible neighbor configurations
997            self.add_neighbor_amplitudes(
998                &positions,
999                0,
1000                &mut Vec::new(),
1001                neighbor_amplitude,
1002                &mut new_state,
1003            );
1004        }
1005
1006        self.state = new_state;
1007    }
1008
1009    /// Recursively add amplitudes for all neighbor configurations
1010    fn add_neighbor_amplitudes(
1011        &self,
1012        original_positions: &[usize],
1013        walker_idx: usize,
1014        current_positions: &mut Vec<usize>,
1015        amplitude: Complex64,
1016        new_state: &mut Array1<Complex64>,
1017    ) {
1018        if walker_idx >= self.num_walkers {
1019            let index = self.positions_to_index(current_positions);
1020            new_state[index] += amplitude;
1021            return;
1022        }
1023
1024        let pos = original_positions[walker_idx];
1025        let neighbors = &self.graph.edges[pos];
1026
1027        if neighbors.is_empty() {
1028            // Stay at same position if no neighbors
1029            current_positions.push(pos);
1030            self.add_neighbor_amplitudes(
1031                original_positions,
1032                walker_idx + 1,
1033                current_positions,
1034                amplitude,
1035                new_state,
1036            );
1037            current_positions.pop();
1038        } else {
1039            for &neighbor in neighbors {
1040                current_positions.push(neighbor);
1041                self.add_neighbor_amplitudes(
1042                    original_positions,
1043                    walker_idx + 1,
1044                    current_positions,
1045                    amplitude,
1046                    new_state,
1047                );
1048                current_positions.pop();
1049            }
1050        }
1051    }
1052
1053    /// Get marginal probability distribution for a specific walker
1054    pub fn marginal_probabilities(&self, walker_idx: usize) -> Vec<f64> {
1055        let mut probs = vec![0.0; self.single_walker_dim];
1056
1057        for (index, &amplitude) in self.state.iter().enumerate() {
1058            let positions = self.index_to_positions(index);
1059            probs[positions[walker_idx]] += amplitude.norm_sqr();
1060        }
1061
1062        probs
1063    }
1064
1065    /// Calculate entanglement entropy between walkers
1066    pub fn entanglement_entropy(&self) -> f64 {
1067        if self.num_walkers != 2 {
1068            return 0.0; // Only implemented for 2 walkers
1069        }
1070
1071        // Compute reduced density matrix for walker 1
1072        let mut reduced_dm = Array2::zeros((self.single_walker_dim, self.single_walker_dim));
1073
1074        for i in 0..self.single_walker_dim {
1075            for j in 0..self.single_walker_dim {
1076                for k in 0..self.single_walker_dim {
1077                    let idx1 = self.positions_to_index(&[i, k]);
1078                    let idx2 = self.positions_to_index(&[j, k]);
1079
1080                    reduced_dm[[i, j]] += self.state[idx1].conj() * self.state[idx2];
1081                }
1082            }
1083        }
1084
1085        // Calculate von Neumann entropy (simplified - would use eigenvalues in practice)
1086        let trace = reduced_dm.diag().mapv(|x: Complex64| x.re).sum();
1087        -trace * trace.ln() // Simplified approximation
1088    }
1089}
1090
1091/// Quantum walk with environmental decoherence
1092pub struct DecoherentQuantumWalk {
1093    base_walk: DiscreteQuantumWalk,
1094    decoherence_rate: f64,
1095    measurement_probability: f64,
1096}
1097
1098impl DecoherentQuantumWalk {
1099    /// Create a new decoherent quantum walk
1100    pub fn new(graph: Graph, coin_operator: CoinOperator, decoherence_rate: f64) -> Self {
1101        Self {
1102            base_walk: DiscreteQuantumWalk::new(graph, coin_operator),
1103            decoherence_rate,
1104            measurement_probability: 0.0,
1105        }
1106    }
1107
1108    /// Initialize walker position
1109    pub fn initialize_position(&mut self, position: usize) {
1110        self.base_walk.initialize_position(position);
1111    }
1112
1113    /// Perform one step with decoherence
1114    pub fn step(&mut self) {
1115        // Apply unitary evolution
1116        self.base_walk.step();
1117
1118        // Apply decoherence
1119        self.apply_decoherence();
1120    }
1121
1122    /// Apply decoherence by mixing with classical random walk
1123    fn apply_decoherence(&mut self) {
1124        if self.decoherence_rate <= 0.0 {
1125            return;
1126        }
1127
1128        // Get current probabilities
1129        let quantum_probs = self.base_walk.position_probabilities();
1130
1131        // Classical random walk step
1132        let mut classical_probs = vec![0.0; quantum_probs.len()];
1133        for (v, &prob) in quantum_probs.iter().enumerate() {
1134            if prob > 0.0 {
1135                let degree = self.base_walk.graph.degree(v) as f64;
1136                if degree > 0.0 {
1137                    for &neighbor in &self.base_walk.graph.edges[v] {
1138                        classical_probs[neighbor] += prob / degree;
1139                    }
1140                } else {
1141                    classical_probs[v] += prob; // Stay if isolated
1142                }
1143            }
1144        }
1145
1146        // Mix quantum and classical
1147        let quantum_weight = 1.0 - self.decoherence_rate;
1148        let classical_weight = self.decoherence_rate;
1149
1150        // Update quantum state to match mixed probabilities (approximate)
1151        for v in 0..quantum_probs.len() {
1152            let mixed_prob =
1153                quantum_weight * quantum_probs[v] + classical_weight * classical_probs[v];
1154
1155            // Scale amplitudes to match mixed probabilities (simplified)
1156            if quantum_probs[v] > 0.0 {
1157                let scale_factor = (mixed_prob / quantum_probs[v]).sqrt();
1158
1159                for coin in 0..self.base_walk.coin_dimension {
1160                    let idx = self.base_walk.state_index(v, coin);
1161                    if idx < self.base_walk.state.len() {
1162                        self.base_walk.state[idx] *= scale_factor;
1163                    }
1164                }
1165            }
1166        }
1167
1168        // Renormalize
1169        let total_norm: f64 = self.base_walk.state.iter().map(|c| c.norm_sqr()).sum();
1170        if total_norm > 0.0 {
1171            let norm_factor = 1.0 / total_norm.sqrt();
1172            for amplitude in &mut self.base_walk.state {
1173                *amplitude *= norm_factor;
1174            }
1175        }
1176    }
1177
1178    /// Get position probabilities
1179    pub fn position_probabilities(&self) -> Vec<f64> {
1180        self.base_walk.position_probabilities()
1181    }
1182
1183    /// Set decoherence rate
1184    pub fn set_decoherence_rate(&mut self, rate: f64) {
1185        self.decoherence_rate = rate.clamp(0.0, 1.0);
1186    }
1187}
1188
1189/// Search algorithm using quantum walks
1190pub struct QuantumWalkSearch {
1191    #[allow(dead_code)]
1192    graph: Graph,
1193    oracle: SearchOracle,
1194    walk: DiscreteQuantumWalk,
1195}
1196
1197impl QuantumWalkSearch {
1198    /// Create a new quantum walk search
1199    pub fn new(graph: Graph, oracle: SearchOracle) -> Self {
1200        let walk = DiscreteQuantumWalk::new(graph.clone(), CoinOperator::Grover);
1201        Self {
1202            graph,
1203            oracle,
1204            walk,
1205        }
1206    }
1207
1208    /// Apply the oracle that marks vertices
1209    fn apply_oracle(&mut self) {
1210        for &vertex in &self.oracle.marked {
1211            for coin in 0..self.walk.coin_dimension {
1212                let idx = self.walk.state_index(vertex, coin);
1213                if idx < self.walk.state.len() {
1214                    self.walk.state[idx] = -self.walk.state[idx]; // Phase flip
1215                }
1216            }
1217        }
1218    }
1219
1220    /// Run the search algorithm
1221    pub fn run(&mut self, max_steps: usize) -> (usize, f64, usize) {
1222        // Start in uniform superposition
1223        let amplitude = Complex64::new(1.0 / (self.walk.hilbert_dim as f64).sqrt(), 0.0);
1224        self.walk.state.fill(amplitude);
1225
1226        let mut best_vertex = 0;
1227        let mut best_prob = 0.0;
1228        let mut best_step = 0;
1229
1230        // Alternate between walk and oracle
1231        for step in 1..=max_steps {
1232            self.walk.step();
1233            self.apply_oracle();
1234
1235            // Check probabilities at marked vertices
1236            let probs = self.walk.position_probabilities();
1237            for &marked in &self.oracle.marked {
1238                if probs[marked] > best_prob {
1239                    best_prob = probs[marked];
1240                    best_vertex = marked;
1241                    best_step = step;
1242                }
1243            }
1244
1245            // Early stopping if we have high probability
1246            if best_prob > 0.5 {
1247                break;
1248            }
1249        }
1250
1251        (best_vertex, best_prob, best_step)
1252    }
1253
1254    /// Get vertex probabilities
1255    pub fn vertex_probabilities(&self) -> Vec<f64> {
1256        self.walk.position_probabilities()
1257    }
1258}
1259
1260/// Example: Quantum walk on a line
1261pub fn quantum_walk_line_example() {
1262    println!("Quantum Walk on a Line (10 vertices)");
1263
1264    let graph = Graph::new(GraphType::Line, 10);
1265    let walk = DiscreteQuantumWalk::new(graph, CoinOperator::Hadamard);
1266
1267    // Start at vertex 5 (middle)
1268    let mut walk = walk;
1269    walk.initialize_position(5);
1270
1271    // Evolve for different time steps
1272    for steps in [0, 5, 10, 20, 30] {
1273        // Reset and evolve
1274        walk.initialize_position(5);
1275        for _ in 0..steps {
1276            walk.step();
1277        }
1278        let probs = walk.position_probabilities();
1279
1280        println!("\nAfter {} steps:", steps);
1281        print!("Probabilities: ");
1282        for (v, p) in probs.iter().enumerate() {
1283            if *p > 0.01 {
1284                print!("v{}: {:.3} ", v, p);
1285            }
1286        }
1287        println!();
1288    }
1289}
1290
1291/// Example: Search on a complete graph
1292pub fn quantum_walk_search_example() {
1293    println!("\nQuantum Walk Search on Complete Graph (8 vertices)");
1294
1295    let graph = Graph::new(GraphType::Complete, 8);
1296    let marked = vec![3, 5]; // Mark vertices 3 and 5
1297    let oracle = SearchOracle::new(marked.clone());
1298
1299    let mut search = QuantumWalkSearch::new(graph, oracle);
1300
1301    println!("Marked vertices: {:?}", marked);
1302
1303    // Run search
1304    let (found, prob, steps) = search.run(50);
1305
1306    println!(
1307        "\nFound vertex {} with probability {:.3} after {} steps",
1308        found, prob, steps
1309    );
1310}
1311
1312#[cfg(test)]
1313mod tests {
1314    use super::*;
1315    // use approx::assert_relative_eq;
1316
1317    #[test]
1318    fn test_graph_creation() {
1319        let graph = Graph::new(GraphType::Cycle, 4);
1320        assert_eq!(graph.num_vertices, 4);
1321        assert_eq!(graph.degree(0), 2);
1322
1323        let complete = Graph::new(GraphType::Complete, 5);
1324        assert_eq!(complete.degree(0), 4);
1325    }
1326
1327    #[test]
1328    fn test_discrete_walk_initialization() {
1329        let graph = Graph::new(GraphType::Line, 5);
1330        let mut walk = DiscreteQuantumWalk::new(graph, CoinOperator::Hadamard);
1331
1332        walk.initialize_position(2);
1333        let probs = walk.position_probabilities();
1334
1335        assert!((probs[2] - 1.0).abs() < 1e-10);
1336    }
1337
1338    #[test]
1339    fn test_continuous_walk() {
1340        let graph = Graph::new(GraphType::Cycle, 4);
1341        let mut walk = ContinuousQuantumWalk::new(graph);
1342
1343        walk.initialize_vertex(0);
1344        walk.evolve(1.0);
1345
1346        let probs = walk.vertex_probabilities();
1347        let total: f64 = probs.iter().sum();
1348        assert!((total - 1.0).abs() < 1e-10);
1349    }
1350
1351    #[test]
1352    fn test_weighted_graph() {
1353        let mut graph = Graph::new_empty(3);
1354        graph.add_weighted_edge(0, 1, 2.0);
1355        graph.add_weighted_edge(1, 2, 3.0);
1356
1357        let adj_matrix = graph.adjacency_matrix();
1358        assert_eq!(adj_matrix[[0, 1]], 2.0);
1359        assert_eq!(adj_matrix[[1, 2]], 3.0);
1360        assert_eq!(adj_matrix[[0, 2]], 0.0);
1361    }
1362
1363    #[test]
1364    fn test_graph_from_adjacency_matrix() {
1365        let mut matrix = Array2::zeros((3, 3));
1366        matrix[[0, 1]] = 1.0;
1367        matrix[[1, 0]] = 1.0;
1368        matrix[[1, 2]] = 2.0;
1369        matrix[[2, 1]] = 2.0;
1370
1371        let graph = Graph::from_adjacency_matrix(&matrix).expect(
1372            "Failed to create graph from adjacency matrix in test_graph_from_adjacency_matrix",
1373        );
1374        assert_eq!(graph.num_vertices, 3);
1375        assert_eq!(graph.degree(0), 1);
1376        assert_eq!(graph.degree(1), 2);
1377        assert_eq!(graph.degree(2), 1);
1378    }
1379
1380    #[test]
1381    fn test_laplacian_matrix() {
1382        let graph = Graph::new(GraphType::Cycle, 3);
1383        let laplacian = graph.laplacian_matrix();
1384
1385        // Each vertex in a 3-cycle has degree 2
1386        assert_eq!(laplacian[[0, 0]], 2.0);
1387        assert_eq!(laplacian[[1, 1]], 2.0);
1388        assert_eq!(laplacian[[2, 2]], 2.0);
1389
1390        // Adjacent vertices have -1
1391        assert_eq!(laplacian[[0, 1]], -1.0);
1392        assert_eq!(laplacian[[1, 2]], -1.0);
1393        assert_eq!(laplacian[[2, 0]], -1.0);
1394    }
1395
1396    #[test]
1397    fn test_bipartite_detection() {
1398        let bipartite = Graph::new(GraphType::Cycle, 4); // Even cycle is bipartite
1399        assert!(bipartite.is_bipartite());
1400
1401        let non_bipartite = Graph::new(GraphType::Cycle, 3); // Odd cycle is not bipartite
1402        assert!(!non_bipartite.is_bipartite());
1403
1404        let complete = Graph::new(GraphType::Complete, 3); // Complete graph with >2 vertices is not bipartite
1405        assert!(!complete.is_bipartite());
1406    }
1407
1408    #[test]
1409    fn test_shortest_paths() {
1410        let graph = Graph::new(GraphType::Line, 4); // 0-1-2-3
1411        let distances = graph.all_pairs_shortest_paths();
1412
1413        assert_eq!(distances[[0, 0]], 0.0);
1414        assert_eq!(distances[[0, 1]], 1.0);
1415        assert_eq!(distances[[0, 2]], 2.0);
1416        assert_eq!(distances[[0, 3]], 3.0);
1417        assert_eq!(distances[[1, 3]], 2.0);
1418    }
1419
1420    #[test]
1421    fn test_szegedy_walk() {
1422        let graph = Graph::new(GraphType::Cycle, 4);
1423        let mut szegedy = SzegedyQuantumWalk::new(graph);
1424
1425        szegedy.initialize_uniform();
1426        let initial_probs = szegedy.vertex_probabilities();
1427
1428        // Should have some probability on each vertex
1429        for &prob in &initial_probs {
1430            assert!(prob > 0.0);
1431        }
1432
1433        // Take a few steps
1434        for _ in 0..5 {
1435            szegedy.step();
1436        }
1437
1438        let final_probs = szegedy.vertex_probabilities();
1439        let total: f64 = final_probs.iter().sum();
1440        assert!((total - 1.0).abs() < 1e-10);
1441    }
1442
1443    #[test]
1444    fn test_szegedy_edge_initialization() {
1445        let mut graph = Graph::new_empty(3);
1446        graph.add_edge(0, 1);
1447        graph.add_edge(1, 2);
1448
1449        let mut szegedy = SzegedyQuantumWalk::new(graph);
1450        szegedy.initialize_edge(0, 1);
1451
1452        let edge_probs = szegedy.edge_probabilities();
1453        assert_eq!(edge_probs.len(), 1);
1454        assert_eq!(edge_probs[0].0, (0, 1));
1455        assert!((edge_probs[0].1 - 1.0).abs() < 1e-10);
1456    }
1457
1458    #[test]
1459    fn test_multi_walker_quantum_walk() {
1460        let graph = Graph::new(GraphType::Cycle, 3);
1461        let mut multi_walk = MultiWalkerQuantumWalk::new(graph, 2);
1462
1463        // Initialize two walkers at positions 0 and 1
1464        multi_walk
1465            .initialize_positions(&[0, 1])
1466            .expect("Failed to initialize positions in test_multi_walker_quantum_walk");
1467
1468        let marginal_0 = multi_walk.marginal_probabilities(0);
1469        let marginal_1 = multi_walk.marginal_probabilities(1);
1470
1471        assert!((marginal_0[0] - 1.0).abs() < 1e-10);
1472        assert!((marginal_1[1] - 1.0).abs() < 1e-10);
1473
1474        // Take a step
1475        multi_walk.step_independent();
1476
1477        // Probabilities should have spread
1478        let new_marginal_0 = multi_walk.marginal_probabilities(0);
1479        let new_marginal_1 = multi_walk.marginal_probabilities(1);
1480
1481        assert!(new_marginal_0[0] < 1.0);
1482        assert!(new_marginal_1[1] < 1.0);
1483    }
1484
1485    #[test]
1486    fn test_multi_walker_bell_state() {
1487        let graph = Graph::new(GraphType::Cycle, 4);
1488        let mut multi_walk = MultiWalkerQuantumWalk::new(graph, 2);
1489
1490        multi_walk
1491            .initialize_entangled_bell_state(0, 1)
1492            .expect("Failed to initialize entangled Bell state in test_multi_walker_bell_state");
1493
1494        let marginal_0 = multi_walk.marginal_probabilities(0);
1495        let marginal_1 = multi_walk.marginal_probabilities(1);
1496
1497        // Each walker should have 50% probability at each of their initial positions
1498        assert!((marginal_0[0] - 0.5).abs() < 1e-10);
1499        assert!((marginal_0[1] - 0.5).abs() < 1e-10);
1500        assert!((marginal_1[0] - 0.5).abs() < 1e-10);
1501        assert!((marginal_1[1] - 0.5).abs() < 1e-10);
1502    }
1503
1504    #[test]
1505    fn test_multi_walker_error_handling() {
1506        let graph = Graph::new(GraphType::Line, 3);
1507        let mut multi_walk = MultiWalkerQuantumWalk::new(graph.clone(), 2);
1508
1509        // Wrong number of positions
1510        assert!(multi_walk.initialize_positions(&[0]).is_err());
1511
1512        // Position out of bounds
1513        assert!(multi_walk.initialize_positions(&[0, 5]).is_err());
1514
1515        // Bell state with wrong number of walkers
1516        let mut single_walk = MultiWalkerQuantumWalk::new(graph, 1);
1517        assert!(single_walk.initialize_entangled_bell_state(0, 1).is_err());
1518    }
1519
1520    #[test]
1521    fn test_decoherent_quantum_walk() {
1522        let graph = Graph::new(GraphType::Line, 5);
1523        let mut decoherent = DecoherentQuantumWalk::new(graph, CoinOperator::Hadamard, 0.1);
1524
1525        decoherent.initialize_position(2);
1526        let initial_probs = decoherent.position_probabilities();
1527        assert!((initial_probs[2] - 1.0).abs() < 1e-10);
1528
1529        // Take steps with decoherence
1530        for _ in 0..10 {
1531            decoherent.step();
1532        }
1533
1534        let final_probs = decoherent.position_probabilities();
1535        let total: f64 = final_probs.iter().sum();
1536        assert!((total - 1.0).abs() < 1e-10);
1537
1538        // Should have spread from initial position
1539        assert!(final_probs[2] < 1.0);
1540    }
1541
1542    #[test]
1543    fn test_decoherence_rate_bounds() {
1544        let graph = Graph::new(GraphType::Cycle, 4);
1545        let mut decoherent = DecoherentQuantumWalk::new(graph, CoinOperator::Grover, 0.5);
1546
1547        // Test clamping
1548        decoherent.set_decoherence_rate(-0.1);
1549        decoherent.initialize_position(0);
1550        decoherent.step(); // Should work without panicking
1551
1552        decoherent.set_decoherence_rate(1.5);
1553        decoherent.step(); // Should work without panicking
1554    }
1555
1556    #[test]
1557    fn test_transition_matrix() {
1558        let graph = Graph::new(GraphType::Cycle, 3);
1559        let transition = graph.transition_matrix();
1560
1561        // Each vertex has degree 2, so each transition probability is 1/2
1562        for i in 0..3 {
1563            let mut row_sum = 0.0;
1564            for j in 0..3 {
1565                row_sum += transition[[i, j]];
1566            }
1567            assert!((row_sum - 1.0).abs() < 1e-10);
1568        }
1569    }
1570
1571    #[test]
1572    fn test_normalized_laplacian() {
1573        let graph = Graph::new(GraphType::Complete, 3);
1574        let norm_laplacian = graph.normalized_laplacian_matrix();
1575
1576        // Diagonal entries should be 1
1577        for i in 0..3 {
1578            assert!((norm_laplacian[[i, i]] - 1.0).abs() < 1e-10);
1579        }
1580
1581        // Off-diagonal entries for complete graph K_3
1582        let expected_off_diag = -1.0 / 2.0; // -1/sqrt(2*2)
1583        assert!((norm_laplacian[[0, 1]] - expected_off_diag).abs() < 1e-10);
1584        assert!((norm_laplacian[[1, 2]] - expected_off_diag).abs() < 1e-10);
1585        assert!((norm_laplacian[[0, 2]] - expected_off_diag).abs() < 1e-10);
1586    }
1587
1588    #[test]
1589    fn test_algebraic_connectivity() {
1590        let complete_3 = Graph::new(GraphType::Complete, 3);
1591        let connectivity = complete_3.algebraic_connectivity();
1592        assert!(connectivity > 0.0); // Complete graphs have positive algebraic connectivity
1593
1594        let line_5 = Graph::new(GraphType::Line, 5);
1595        let line_connectivity = line_5.algebraic_connectivity();
1596        assert!(line_connectivity > 0.0);
1597    }
1598
1599    #[test]
1600    fn test_mixing_time_estimation() {
1601        let graph = Graph::new(GraphType::Complete, 4);
1602        let mut szegedy = SzegedyQuantumWalk::new(graph);
1603
1604        let mixing_time = szegedy.estimate_mixing_time(0.1);
1605        assert!(mixing_time > 0);
1606        assert!(mixing_time <= 1000); // Should converge within max steps
1607    }
1608
1609    #[test]
1610    fn test_quantum_walk_search_on_custom_graph() {
1611        // Create a star graph: central vertex connected to all others
1612        let mut graph = Graph::new_empty(5);
1613        for i in 1..5 {
1614            graph.add_edge(0, i);
1615        }
1616
1617        let oracle = SearchOracle::new(vec![3]); // Mark vertex 3
1618        let mut search = QuantumWalkSearch::new(graph, oracle);
1619
1620        let (found_vertex, prob, steps) = search.run(20);
1621        assert_eq!(found_vertex, 3);
1622        assert!(prob > 0.0);
1623        assert!(steps <= 20);
1624    }
1625
1626    #[test]
1627    fn test_custom_coin_operator() {
1628        let graph = Graph::new(GraphType::Line, 3);
1629
1630        // Create a custom 2x2 coin (Pauli-X)
1631        let mut coin_matrix = Array2::zeros((2, 2));
1632        coin_matrix[[0, 1]] = Complex64::new(1.0, 0.0);
1633        coin_matrix[[1, 0]] = Complex64::new(1.0, 0.0);
1634
1635        let custom_coin = CoinOperator::Custom(coin_matrix);
1636        let mut walk = DiscreteQuantumWalk::new(graph, custom_coin);
1637
1638        walk.initialize_position(1);
1639        walk.step();
1640
1641        let probs = walk.position_probabilities();
1642        let total: f64 = probs.iter().sum();
1643        assert!((total - 1.0).abs() < 1e-10);
1644    }
1645
1646    #[test]
1647    fn test_empty_graph_edge_cases() {
1648        let empty_graph = Graph::new_empty(3);
1649        let mut szegedy = SzegedyQuantumWalk::new(empty_graph);
1650
1651        szegedy.initialize_uniform();
1652        let probs = szegedy.vertex_probabilities();
1653
1654        // No edges means no probability distribution
1655        for &prob in &probs {
1656            assert_eq!(prob, 0.0);
1657        }
1658    }
1659
1660    #[test]
1661    fn test_hypercube_graph() {
1662        let hypercube = Graph::new(GraphType::Hypercube, 3); // 2^3 = 8 vertices
1663        assert_eq!(hypercube.num_vertices, 8);
1664
1665        // Each vertex in a 3D hypercube has degree 3
1666        for i in 0..8 {
1667            assert_eq!(hypercube.degree(i), 3);
1668        }
1669    }
1670
1671    #[test]
1672    fn test_grid_2d_graph() {
1673        let grid = Graph::new(GraphType::Grid2D, 3); // 3x3 grid
1674        assert_eq!(grid.num_vertices, 9);
1675
1676        // Corner vertices have degree 2
1677        assert_eq!(grid.degree(0), 2); // Top-left
1678        assert_eq!(grid.degree(2), 2); // Top-right
1679        assert_eq!(grid.degree(6), 2); // Bottom-left
1680        assert_eq!(grid.degree(8), 2); // Bottom-right
1681
1682        // Center vertex has degree 4
1683        assert_eq!(grid.degree(4), 4);
1684    }
1685}