Skip to main content

quantrs2_core/quantum_walk/
continuous.rs

1//! Continuous-time and Szegedy quantum walk implementations.
2
3use super::graph::Graph;
4use crate::complex_ext::QuantumComplexExt;
5use scirs2_core::ndarray::Array2;
6use scirs2_core::Complex64;
7use std::collections::HashMap;
8
9/// Continuous-time quantum walk
10pub struct ContinuousQuantumWalk {
11    graph: Graph,
12    hamiltonian: Array2<Complex64>,
13    state: Vec<Complex64>,
14}
15
16impl ContinuousQuantumWalk {
17    /// Create a new continuous quantum walk
18    pub fn new(graph: Graph) -> Self {
19        let adj_matrix = graph.adjacency_matrix();
20        let hamiltonian = adj_matrix.mapv(|x| Complex64::new(x, 0.0));
21        let num_vertices = graph.num_vertices;
22
23        Self {
24            graph,
25            hamiltonian,
26            state: vec![Complex64::new(0.0, 0.0); num_vertices],
27        }
28    }
29
30    /// Initialize walker at a specific vertex
31    pub fn initialize_vertex(&mut self, vertex: usize) {
32        self.state = vec![Complex64::new(0.0, 0.0); self.graph.num_vertices];
33        if vertex < self.graph.num_vertices {
34            self.state[vertex] = Complex64::new(1.0, 0.0);
35        }
36    }
37
38    /// Evolve the quantum walk for time t
39    pub fn evolve(&mut self, time: f64) {
40        // This is a simplified version using first-order approximation
41        // For a full implementation, we would diagonalize the Hamiltonian
42
43        let dt = 0.01; // Time step
44        let steps = (time / dt) as usize;
45
46        for _ in 0..steps {
47            let mut new_state = self.state.clone();
48
49            // Apply exp(-iHt) ≈ I - iHdt for small dt
50            for (i, ns) in new_state
51                .iter_mut()
52                .enumerate()
53                .take(self.graph.num_vertices)
54            {
55                let mut sum = Complex64::new(0.0, 0.0);
56                for j in 0..self.graph.num_vertices {
57                    sum += self.hamiltonian[[i, j]] * self.state[j];
58                }
59                *ns = self.state[i] - Complex64::new(0.0, dt) * sum;
60            }
61
62            // Normalize
63            let norm: f64 = new_state.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
64
65            if norm > 0.0 {
66                for amp in &mut new_state {
67                    *amp /= norm;
68                }
69            }
70
71            self.state = new_state;
72        }
73    }
74
75    /// Get vertex probabilities
76    pub fn vertex_probabilities(&self) -> Vec<f64> {
77        self.state.iter().map(|c| c.probability()).collect()
78    }
79
80    /// Calculate transport probability between two vertices at time t
81    pub fn transport_probability(&mut self, from: usize, to: usize, time: f64) -> f64 {
82        // Initialize at 'from' vertex
83        self.initialize_vertex(from);
84
85        // Evolve for time t
86        self.evolve(time);
87
88        // Return probability at 'to' vertex
89        if to < self.state.len() {
90            self.state[to].probability()
91        } else {
92            0.0
93        }
94    }
95
96    /// Get the probability distribution
97    pub fn get_probabilities(&self, state: &[Complex64]) -> Vec<f64> {
98        state.iter().map(|c| c.probability()).collect()
99    }
100}
101
102/// Szegedy quantum walk for arbitrary graphs
103/// This provides better mixing properties on irregular graphs
104pub struct SzegedyQuantumWalk {
105    graph: Graph,
106    /// State lives on edges: |u,v> where edge (u,v) exists
107    state: HashMap<(usize, usize), Complex64>,
108    num_edges: usize,
109}
110
111impl SzegedyQuantumWalk {
112    /// Create a new Szegedy quantum walk
113    pub fn new(graph: Graph) -> Self {
114        let mut num_edges = 0;
115        for v in 0..graph.num_vertices {
116            num_edges += graph.edges[v].len();
117        }
118
119        Self {
120            graph,
121            state: HashMap::new(),
122            num_edges,
123        }
124    }
125
126    /// Initialize in uniform superposition over all edges
127    pub fn initialize_uniform(&mut self) {
128        self.state.clear();
129
130        if self.num_edges == 0 {
131            return;
132        }
133
134        let amplitude = Complex64::new(1.0 / (self.num_edges as f64).sqrt(), 0.0);
135
136        for u in 0..self.graph.num_vertices {
137            for &v in &self.graph.edges[u] {
138                self.state.insert((u, v), amplitude);
139            }
140        }
141    }
142
143    /// Initialize at a specific edge
144    pub fn initialize_edge(&mut self, u: usize, v: usize) {
145        self.state.clear();
146
147        if u < self.graph.num_vertices && self.graph.edges[u].contains(&v) {
148            self.state.insert((u, v), Complex64::new(1.0, 0.0));
149        }
150    }
151
152    /// Perform one step of Szegedy walk
153    pub fn step(&mut self) {
154        // Szegedy walk: (2P - I)(2Q - I) where P and Q are projections
155
156        // Apply reflection around vertex-uniform states
157        self.reflect_vertex_uniform();
158
159        // Apply reflection around edge-uniform states
160        self.reflect_edge_uniform();
161    }
162
163    /// Reflect around vertex-uniform subspaces
164    fn reflect_vertex_uniform(&mut self) {
165        let mut vertex_sums: Vec<Complex64> =
166            vec![Complex64::new(0.0, 0.0); self.graph.num_vertices];
167
168        // Calculate sum of amplitudes for each vertex
169        for (&(u, _), &amplitude) in &self.state {
170            vertex_sums[u] += amplitude;
171        }
172
173        // Apply reflection: 2|psi_u><psi_u| - I
174        let mut new_state = HashMap::new();
175
176        for (&(u, v), &old_amp) in &self.state {
177            let degree = self.graph.degree(u) as f64;
178            if degree > 0.0 {
179                let vertex_avg = vertex_sums[u] / degree;
180                let new_amp = 2.0 * vertex_avg - old_amp;
181                new_state.insert((u, v), new_amp);
182            }
183        }
184
185        self.state = new_state;
186    }
187
188    /// Reflect around edge-uniform subspace
189    fn reflect_edge_uniform(&mut self) {
190        if self.num_edges == 0 {
191            return;
192        }
193
194        // Calculate total amplitude
195        let total_amp: Complex64 = self.state.values().sum();
196        let uniform_amp = total_amp / self.num_edges as f64;
197
198        // Apply reflection: 2|uniform><uniform| - I
199        for amplitude in self.state.values_mut() {
200            *amplitude = 2.0 * uniform_amp - *amplitude;
201        }
202    }
203
204    /// Get vertex probabilities by summing over outgoing edges
205    pub fn vertex_probabilities(&self) -> Vec<f64> {
206        let mut probs = vec![0.0; self.graph.num_vertices];
207
208        for (&(u, _), &amplitude) in &self.state {
209            probs[u] += amplitude.norm_sqr();
210        }
211
212        probs
213    }
214
215    /// Get edge probabilities
216    pub fn edge_probabilities(&self) -> Vec<((usize, usize), f64)> {
217        self.state
218            .iter()
219            .map(|(&edge, &amplitude)| (edge, amplitude.norm_sqr()))
220            .collect()
221    }
222
223    /// Calculate mixing time to epsilon-close to uniform distribution
224    pub fn estimate_mixing_time(&mut self, epsilon: f64) -> usize {
225        let uniform_prob = 1.0 / self.graph.num_vertices as f64;
226
227        // Reset to uniform
228        self.initialize_uniform();
229
230        for steps in 1..1000 {
231            self.step();
232
233            let probs = self.vertex_probabilities();
234            let max_deviation = probs
235                .iter()
236                .map(|&p| (p - uniform_prob).abs())
237                .fold(0.0, f64::max);
238
239            if max_deviation < epsilon {
240                return steps;
241            }
242        }
243
244        1000 // Return max if not converged
245    }
246}