Skip to main content

quantrs2_core/quantum_walk/
multi.rs

1//! Multi-walker and decoherent quantum walk implementations.
2
3use super::discrete::DiscreteQuantumWalk;
4use super::graph::{CoinOperator, Graph};
5use crate::error::{QuantRS2Error, QuantRS2Result};
6use scirs2_core::ndarray::{Array1, Array2};
7use scirs2_core::Complex64;
8
9/// Multi-walker quantum walk for studying entanglement and correlations
10pub struct MultiWalkerQuantumWalk {
11    graph: Graph,
12    num_walkers: usize,
13    /// State tensor product space: walker1_pos ⊗ walker2_pos ⊗ ...
14    state: Array1<Complex64>,
15    /// Dimension of single walker space
16    single_walker_dim: usize,
17}
18
19impl MultiWalkerQuantumWalk {
20    /// Create a new multi-walker quantum walk
21    pub fn new(graph: Graph, num_walkers: usize) -> Self {
22        let single_walker_dim = graph.num_vertices;
23        let total_dim = single_walker_dim.pow(num_walkers as u32);
24
25        Self {
26            graph,
27            num_walkers,
28            state: Array1::zeros(total_dim),
29            single_walker_dim,
30        }
31    }
32
33    /// Initialize walkers at specific positions
34    pub fn initialize_positions(&mut self, positions: &[usize]) -> QuantRS2Result<()> {
35        if positions.len() != self.num_walkers {
36            return Err(QuantRS2Error::InvalidInput(
37                "Number of positions must match number of walkers".to_string(),
38            ));
39        }
40
41        for &pos in positions {
42            if pos >= self.single_walker_dim {
43                return Err(QuantRS2Error::InvalidInput(format!(
44                    "Position {pos} out of bounds"
45                )));
46            }
47        }
48
49        // Reset state
50        self.state.fill(Complex64::new(0.0, 0.0));
51
52        // Set amplitude for initial configuration
53        let index = self.positions_to_index(positions);
54        self.state[index] = Complex64::new(1.0, 0.0);
55
56        Ok(())
57    }
58
59    /// Initialize in entangled superposition
60    pub fn initialize_entangled_bell_state(
61        &mut self,
62        pos1: usize,
63        pos2: usize,
64    ) -> QuantRS2Result<()> {
65        if self.num_walkers != 2 {
66            return Err(QuantRS2Error::InvalidInput(
67                "Bell state initialization only works for 2 walkers".to_string(),
68            ));
69        }
70
71        self.state.fill(Complex64::new(0.0, 0.0));
72
73        let amplitude = Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0);
74
75        // |pos1,pos2> + |pos2,pos1>
76        let idx1 = self.positions_to_index(&[pos1, pos2]);
77        let idx2 = self.positions_to_index(&[pos2, pos1]);
78
79        self.state[idx1] = amplitude;
80        self.state[idx2] = amplitude;
81
82        Ok(())
83    }
84
85    /// Convert walker positions to state vector index
86    fn positions_to_index(&self, positions: &[usize]) -> usize {
87        let mut index = 0;
88        let mut multiplier = 1;
89
90        for &pos in positions.iter().rev() {
91            index += pos * multiplier;
92            multiplier *= self.single_walker_dim;
93        }
94
95        index
96    }
97
98    /// Convert state vector index to walker positions
99    fn index_to_positions(&self, mut index: usize) -> Vec<usize> {
100        let mut positions = Vec::with_capacity(self.num_walkers);
101
102        for _ in 0..self.num_walkers {
103            positions.push(index % self.single_walker_dim);
104            index /= self.single_walker_dim;
105        }
106
107        positions.reverse();
108        positions
109    }
110
111    /// Perform one step (simplified version - each walker evolves independently)
112    pub fn step_independent(&mut self) {
113        let mut new_state = Array1::zeros(self.state.len());
114
115        for (index, &amplitude) in self.state.iter().enumerate() {
116            if amplitude.norm_sqr() < 1e-15 {
117                continue;
118            }
119
120            let positions = self.index_to_positions(index);
121
122            // Each walker moves to neighboring vertices with equal probability
123            let mut total_neighbors = 1;
124            for &pos in &positions {
125                total_neighbors *= self.graph.degree(pos).max(1);
126            }
127
128            let neighbor_amplitude = amplitude / (total_neighbors as f64).sqrt();
129
130            // Generate all possible neighbor configurations
131            self.add_neighbor_amplitudes(
132                &positions,
133                0,
134                &mut Vec::new(),
135                neighbor_amplitude,
136                &mut new_state,
137            );
138        }
139
140        self.state = new_state;
141    }
142
143    /// Recursively add amplitudes for all neighbor configurations
144    fn add_neighbor_amplitudes(
145        &self,
146        original_positions: &[usize],
147        walker_idx: usize,
148        current_positions: &mut Vec<usize>,
149        amplitude: Complex64,
150        new_state: &mut Array1<Complex64>,
151    ) {
152        if walker_idx >= self.num_walkers {
153            let index = self.positions_to_index(current_positions);
154            new_state[index] += amplitude;
155            return;
156        }
157
158        let pos = original_positions[walker_idx];
159        let neighbors = &self.graph.edges[pos];
160
161        if neighbors.is_empty() {
162            // Stay at same position if no neighbors
163            current_positions.push(pos);
164            self.add_neighbor_amplitudes(
165                original_positions,
166                walker_idx + 1,
167                current_positions,
168                amplitude,
169                new_state,
170            );
171            current_positions.pop();
172        } else {
173            for &neighbor in neighbors {
174                current_positions.push(neighbor);
175                self.add_neighbor_amplitudes(
176                    original_positions,
177                    walker_idx + 1,
178                    current_positions,
179                    amplitude,
180                    new_state,
181                );
182                current_positions.pop();
183            }
184        }
185    }
186
187    /// Get marginal probability distribution for a specific walker
188    pub fn marginal_probabilities(&self, walker_idx: usize) -> Vec<f64> {
189        let mut probs = vec![0.0; self.single_walker_dim];
190
191        for (index, &amplitude) in self.state.iter().enumerate() {
192            let positions = self.index_to_positions(index);
193            probs[positions[walker_idx]] += amplitude.norm_sqr();
194        }
195
196        probs
197    }
198
199    /// Calculate entanglement entropy between walkers
200    pub fn entanglement_entropy(&self) -> f64 {
201        if self.num_walkers != 2 {
202            return 0.0; // Only implemented for 2 walkers
203        }
204
205        // Compute reduced density matrix for walker 1
206        let mut reduced_dm = Array2::zeros((self.single_walker_dim, self.single_walker_dim));
207
208        for i in 0..self.single_walker_dim {
209            for j in 0..self.single_walker_dim {
210                for k in 0..self.single_walker_dim {
211                    let idx1 = self.positions_to_index(&[i, k]);
212                    let idx2 = self.positions_to_index(&[j, k]);
213
214                    reduced_dm[[i, j]] += self.state[idx1].conj() * self.state[idx2];
215                }
216            }
217        }
218
219        // Calculate von Neumann entropy (simplified - would use eigenvalues in practice)
220        let trace = reduced_dm.diag().mapv(|x: Complex64| x.re).sum();
221        -trace * trace.ln() // Simplified approximation
222    }
223}
224
225/// Quantum walk with environmental decoherence
226pub struct DecoherentQuantumWalk {
227    base_walk: DiscreteQuantumWalk,
228    decoherence_rate: f64,
229    measurement_probability: f64,
230}
231
232impl DecoherentQuantumWalk {
233    /// Create a new decoherent quantum walk
234    pub fn new(graph: Graph, coin_operator: CoinOperator, decoherence_rate: f64) -> Self {
235        Self {
236            base_walk: DiscreteQuantumWalk::new(graph, coin_operator),
237            decoherence_rate,
238            measurement_probability: 0.0,
239        }
240    }
241
242    /// Initialize walker position
243    pub fn initialize_position(&mut self, position: usize) {
244        self.base_walk.initialize_position(position);
245    }
246
247    /// Perform one step with decoherence
248    pub fn step(&mut self) {
249        // Apply unitary evolution
250        self.base_walk.step();
251
252        // Apply decoherence
253        self.apply_decoherence();
254    }
255
256    /// Apply decoherence by mixing with classical random walk
257    fn apply_decoherence(&mut self) {
258        if self.decoherence_rate <= 0.0 {
259            return;
260        }
261
262        // Get current probabilities
263        let quantum_probs = self.base_walk.position_probabilities();
264
265        // Classical random walk step
266        let mut classical_probs = vec![0.0; quantum_probs.len()];
267        for (v, &prob) in quantum_probs.iter().enumerate() {
268            if prob > 0.0 {
269                let degree = self.base_walk.graph.degree(v) as f64;
270                if degree > 0.0 {
271                    for &neighbor in &self.base_walk.graph.edges[v] {
272                        classical_probs[neighbor] += prob / degree;
273                    }
274                } else {
275                    classical_probs[v] += prob; // Stay if isolated
276                }
277            }
278        }
279
280        // Mix quantum and classical
281        let quantum_weight = 1.0 - self.decoherence_rate;
282        let classical_weight = self.decoherence_rate;
283
284        // Update quantum state to match mixed probabilities (approximate)
285        for v in 0..quantum_probs.len() {
286            let mixed_prob =
287                quantum_weight * quantum_probs[v] + classical_weight * classical_probs[v];
288
289            // Scale amplitudes to match mixed probabilities (simplified)
290            if quantum_probs[v] > 0.0 {
291                let scale_factor = (mixed_prob / quantum_probs[v]).sqrt();
292
293                for coin in 0..self.base_walk.coin_dimension {
294                    let idx = self.base_walk.state_index(v, coin);
295                    if idx < self.base_walk.state.len() {
296                        self.base_walk.state[idx] *= scale_factor;
297                    }
298                }
299            }
300        }
301
302        // Renormalize
303        let total_norm: f64 = self.base_walk.state.iter().map(|c| c.norm_sqr()).sum();
304        if total_norm > 0.0 {
305            let norm_factor = 1.0 / total_norm.sqrt();
306            for amplitude in &mut self.base_walk.state {
307                *amplitude *= norm_factor;
308            }
309        }
310    }
311
312    /// Get position probabilities
313    pub fn position_probabilities(&self) -> Vec<f64> {
314        self.base_walk.position_probabilities()
315    }
316
317    /// Set decoherence rate
318    pub const fn set_decoherence_rate(&mut self, rate: f64) {
319        self.decoherence_rate = rate.clamp(0.0, 1.0);
320    }
321}