1use crate::{
12 complex_ext::QuantumComplexExt,
13 error::{QuantRS2Error, QuantRS2Result},
14};
15use ndarray::{Array1, Array2};
16use num_complex::Complex64;
17use std::collections::{HashMap, VecDeque};
18
19#[derive(Debug, Clone, Copy, PartialEq)]
21pub enum GraphType {
22 Line,
24 Cycle,
26 Complete,
28 Hypercube,
30 Grid2D,
32 Custom,
34}
35
36#[derive(Debug, Clone)]
38pub enum CoinOperator {
39 Hadamard,
41 Grover,
43 DFT,
45 Custom(Array2<Complex64>),
47}
48
49#[derive(Debug, Clone)]
51pub struct SearchOracle {
52 pub marked: Vec<usize>,
54}
55
56impl SearchOracle {
57 pub fn new(marked: Vec<usize>) -> Self {
59 Self { marked }
60 }
61
62 pub fn is_marked(&self, vertex: usize) -> bool {
64 self.marked.contains(&vertex)
65 }
66}
67
68#[derive(Debug, Clone)]
70pub struct Graph {
71 pub num_vertices: usize,
73 pub edges: Vec<Vec<usize>>,
75 pub weights: Option<Vec<Vec<f64>>>,
77}
78
79impl Graph {
80 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, GraphType::Grid2D => size * size, _ => size,
87 },
88 edges: vec![],
89 weights: None,
90 };
91
92 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; 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 if j < size - 1 {
130 graph.add_edge(idx, idx + 1);
131 }
132 if i < size - 1 {
134 graph.add_edge(idx, idx + size);
135 }
136 }
137 }
138 }
139 GraphType::Custom => {
140 }
142 }
143
144 graph
145 }
146
147 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 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 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 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 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 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 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 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 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 pub fn algebraic_connectivity(&self) -> f64 {
309 let laplacian = self.laplacian_matrix();
310
311 if self.num_vertices <= 10 {
314 self.compute_laplacian_eigenvalues(&laplacian)
315 .get(1)
316 .copied()
317 .unwrap_or(0.0)
318 } else {
319 self.estimate_fiedler_value(&laplacian)
321 }
322 }
323
324 fn compute_laplacian_eigenvalues(&self, _laplacian: &Array2<f64>) -> Vec<f64> {
326 let mut eigenvalues = vec![0.0]; 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| a.partial_cmp(b).unwrap());
339 eigenvalues.truncate(self.num_vertices);
340 eigenvalues
341 }
342
343 fn estimate_fiedler_value(&self, _laplacian: &Array2<f64>) -> f64 {
345 let max_degree = (0..self.num_vertices)
347 .map(|v| self.degree(v))
348 .max()
349 .unwrap_or(0);
350 2.0 / max_degree as f64 }
352
353 pub fn all_pairs_shortest_paths(&self) -> Array2<f64> {
355 let mut distances =
356 Array2::from_elem((self.num_vertices, self.num_vertices), f64::INFINITY);
357
358 for v in 0..self.num_vertices {
360 distances[[v, v]] = 0.0;
361 for &neighbor in &self.edges[v] {
362 let weight = if let Some(ref weights) = self.weights {
363 weights[v][neighbor]
364 } else {
365 1.0
366 };
367 distances[[v, neighbor]] = weight;
368 }
369 }
370
371 for k in 0..self.num_vertices {
373 for i in 0..self.num_vertices {
374 for j in 0..self.num_vertices {
375 let via_k = distances[[i, k]] + distances[[k, j]];
376 if via_k < distances[[i, j]] {
377 distances[[i, j]] = via_k;
378 }
379 }
380 }
381 }
382
383 distances
384 }
385
386 pub fn from_adjacency_matrix(matrix: &Array2<f64>) -> QuantRS2Result<Self> {
388 let (rows, cols) = matrix.dim();
389 if rows != cols {
390 return Err(QuantRS2Error::InvalidInput(
391 "Adjacency matrix must be square".to_string(),
392 ));
393 }
394
395 let mut graph = Self::new_empty(rows);
396 let mut has_weights = false;
397
398 for i in 0..rows {
399 for j in i + 1..cols {
400 let weight = matrix[[i, j]];
401 if weight != 0.0 {
402 if weight != 1.0 {
403 has_weights = true;
404 }
405 if has_weights {
406 graph.add_weighted_edge(i, j, weight);
407 } else {
408 graph.add_edge(i, j);
409 }
410 }
411 }
412 }
413
414 Ok(graph)
415 }
416}
417
418pub struct DiscreteQuantumWalk {
420 graph: Graph,
421 coin_operator: CoinOperator,
422 coin_dimension: usize,
423 hilbert_dim: usize,
425 state: Vec<Complex64>,
427}
428
429impl DiscreteQuantumWalk {
430 pub fn new(graph: Graph, coin_operator: CoinOperator) -> Self {
432 let coin_dimension = match graph.num_vertices {
435 n if n > 0 => {
436 (0..graph.num_vertices)
437 .map(|v| graph.degree(v))
438 .max()
439 .unwrap_or(2)
440 .max(2) }
442 _ => 2,
443 };
444
445 let hilbert_dim = coin_dimension * graph.num_vertices;
446
447 Self {
448 graph,
449 coin_operator,
450 coin_dimension,
451 hilbert_dim,
452 state: vec![Complex64::new(0.0, 0.0); hilbert_dim],
453 }
454 }
455
456 pub fn initialize_position(&mut self, position: usize) {
458 self.state = vec![Complex64::new(0.0, 0.0); self.hilbert_dim];
459
460 let degree = self.graph.degree(position) as f64;
462 if degree > 0.0 {
463 let amplitude = Complex64::new(1.0 / degree.sqrt(), 0.0);
464
465 for coin in 0..self.coin_dimension.min(self.graph.degree(position)) {
466 let index = self.state_index(position, coin);
467 if index < self.state.len() {
468 self.state[index] = amplitude;
469 }
470 }
471 }
472 }
473
474 pub fn step(&mut self) {
476 self.apply_coin();
478
479 self.apply_shift();
481 }
482
483 pub fn position_probabilities(&self) -> Vec<f64> {
485 let mut probs = vec![0.0; self.graph.num_vertices];
486
487 for (vertex, prob) in probs.iter_mut().enumerate() {
488 for coin in 0..self.coin_dimension {
489 let idx = self.state_index(vertex, coin);
490 if idx < self.state.len() {
491 *prob += self.state[idx].norm_sqr();
492 }
493 }
494 }
495
496 probs
497 }
498
499 fn state_index(&self, vertex: usize, coin: usize) -> usize {
501 vertex * self.coin_dimension + coin
502 }
503
504 fn apply_coin(&mut self) {
506 match &self.coin_operator {
507 CoinOperator::Hadamard => self.apply_hadamard_coin(),
508 CoinOperator::Grover => self.apply_grover_coin(),
509 CoinOperator::DFT => self.apply_dft_coin(),
510 CoinOperator::Custom(matrix) => self.apply_custom_coin(matrix.clone()),
511 }
512 }
513
514 fn apply_hadamard_coin(&mut self) {
516 let h = 1.0 / std::f64::consts::SQRT_2;
517
518 for vertex in 0..self.graph.num_vertices {
519 if self.coin_dimension == 2 {
520 let idx0 = self.state_index(vertex, 0);
521 let idx1 = self.state_index(vertex, 1);
522
523 if idx1 < self.state.len() {
524 let a0 = self.state[idx0];
525 let a1 = self.state[idx1];
526
527 self.state[idx0] = h * (a0 + a1);
528 self.state[idx1] = h * (a0 - a1);
529 }
530 }
531 }
532 }
533
534 fn apply_grover_coin(&mut self) {
536 for vertex in 0..self.graph.num_vertices {
538 let degree = self.graph.degree(vertex);
539 if degree <= 1 {
540 continue; }
542
543 let mut sum = Complex64::new(0.0, 0.0);
545 for coin in 0..degree.min(self.coin_dimension) {
546 let idx = self.state_index(vertex, coin);
547 if idx < self.state.len() {
548 sum += self.state[idx];
549 }
550 }
551
552 let factor = Complex64::new(2.0 / degree as f64, 0.0);
554 for coin in 0..degree.min(self.coin_dimension) {
555 let idx = self.state_index(vertex, coin);
556 if idx < self.state.len() {
557 let old_amp = self.state[idx];
558 self.state[idx] = factor * sum - old_amp;
559 }
560 }
561 }
562 }
563
564 fn apply_dft_coin(&mut self) {
566 if self.coin_dimension == 2 {
568 self.apply_hadamard_coin(); }
570 }
572
573 fn apply_custom_coin(&mut self, matrix: Array2<Complex64>) {
575 if matrix.shape() != [self.coin_dimension, self.coin_dimension] {
576 return; }
578
579 for vertex in 0..self.graph.num_vertices {
580 let mut coin_state = vec![Complex64::new(0.0, 0.0); self.coin_dimension];
581
582 for (coin, cs) in coin_state.iter_mut().enumerate() {
584 let idx = self.state_index(vertex, coin);
585 if idx < self.state.len() {
586 *cs = self.state[idx];
587 }
588 }
589
590 let new_coin_state = matrix.dot(&Array1::from(coin_state));
592
593 for coin in 0..self.coin_dimension {
595 let idx = self.state_index(vertex, coin);
596 if idx < self.state.len() {
597 self.state[idx] = new_coin_state[coin];
598 }
599 }
600 }
601 }
602
603 fn apply_shift(&mut self) {
605 let mut new_state = vec![Complex64::new(0.0, 0.0); self.hilbert_dim];
606
607 for vertex in 0..self.graph.num_vertices {
608 for (coin, &neighbor) in self.graph.edges[vertex].iter().enumerate() {
609 if coin < self.coin_dimension {
610 let from_idx = self.state_index(vertex, coin);
611
612 let to_coin = self.graph.edges[neighbor]
614 .iter()
615 .position(|&v| v == vertex)
616 .unwrap_or(0);
617
618 if to_coin < self.coin_dimension && from_idx < self.state.len() {
619 let to_idx = self.state_index(neighbor, to_coin);
620 if to_idx < new_state.len() {
621 new_state[to_idx] = self.state[from_idx];
622 }
623 }
624 }
625 }
626 }
627
628 self.state.copy_from_slice(&new_state);
629 }
630}
631
632pub struct ContinuousQuantumWalk {
634 graph: Graph,
635 hamiltonian: Array2<Complex64>,
636 state: Vec<Complex64>,
637}
638
639impl ContinuousQuantumWalk {
640 pub fn new(graph: Graph) -> Self {
642 let adj_matrix = graph.adjacency_matrix();
643 let hamiltonian = adj_matrix.mapv(|x| Complex64::new(x, 0.0));
644 let num_vertices = graph.num_vertices;
645
646 Self {
647 graph,
648 hamiltonian,
649 state: vec![Complex64::new(0.0, 0.0); num_vertices],
650 }
651 }
652
653 pub fn initialize_vertex(&mut self, vertex: usize) {
655 self.state = vec![Complex64::new(0.0, 0.0); self.graph.num_vertices];
656 if vertex < self.graph.num_vertices {
657 self.state[vertex] = Complex64::new(1.0, 0.0);
658 }
659 }
660
661 pub fn evolve(&mut self, time: f64) {
663 let dt = 0.01; let steps = (time / dt) as usize;
668
669 for _ in 0..steps {
670 let mut new_state = self.state.clone();
671
672 for (i, ns) in new_state
674 .iter_mut()
675 .enumerate()
676 .take(self.graph.num_vertices)
677 {
678 let mut sum = Complex64::new(0.0, 0.0);
679 for j in 0..self.graph.num_vertices {
680 sum += self.hamiltonian[[i, j]] * self.state[j];
681 }
682 *ns = self.state[i] - Complex64::new(0.0, dt) * sum;
683 }
684
685 let norm: f64 = new_state.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
687
688 if norm > 0.0 {
689 for amp in new_state.iter_mut() {
690 *amp /= norm;
691 }
692 }
693
694 self.state = new_state;
695 }
696 }
697
698 pub fn vertex_probabilities(&self) -> Vec<f64> {
700 self.state.iter().map(|c| c.probability()).collect()
701 }
702
703 pub fn transport_probability(&mut self, from: usize, to: usize, time: f64) -> f64 {
705 self.initialize_vertex(from);
707
708 self.evolve(time);
710
711 if to < self.state.len() {
713 self.state[to].probability()
714 } else {
715 0.0
716 }
717 }
718
719 pub fn get_probabilities(&self, state: &[Complex64]) -> Vec<f64> {
721 state.iter().map(|c| c.probability()).collect()
722 }
723}
724
725pub struct SzegedyQuantumWalk {
728 graph: Graph,
729 state: HashMap<(usize, usize), Complex64>,
731 num_edges: usize,
732}
733
734impl SzegedyQuantumWalk {
735 pub fn new(graph: Graph) -> Self {
737 let mut num_edges = 0;
738 for v in 0..graph.num_vertices {
739 num_edges += graph.edges[v].len();
740 }
741
742 Self {
743 graph,
744 state: HashMap::new(),
745 num_edges,
746 }
747 }
748
749 pub fn initialize_uniform(&mut self) {
751 self.state.clear();
752
753 if self.num_edges == 0 {
754 return;
755 }
756
757 let amplitude = Complex64::new(1.0 / (self.num_edges as f64).sqrt(), 0.0);
758
759 for u in 0..self.graph.num_vertices {
760 for &v in &self.graph.edges[u] {
761 self.state.insert((u, v), amplitude);
762 }
763 }
764 }
765
766 pub fn initialize_edge(&mut self, u: usize, v: usize) {
768 self.state.clear();
769
770 if u < self.graph.num_vertices && self.graph.edges[u].contains(&v) {
771 self.state.insert((u, v), Complex64::new(1.0, 0.0));
772 }
773 }
774
775 pub fn step(&mut self) {
777 self.reflect_vertex_uniform();
781
782 self.reflect_edge_uniform();
784 }
785
786 fn reflect_vertex_uniform(&mut self) {
788 let mut vertex_sums: Vec<Complex64> =
789 vec![Complex64::new(0.0, 0.0); self.graph.num_vertices];
790
791 for (&(u, _), &litude) in &self.state {
793 vertex_sums[u] += amplitude;
794 }
795
796 let mut new_state = HashMap::new();
798
799 for (&(u, v), &old_amp) in &self.state {
800 let degree = self.graph.degree(u) as f64;
801 if degree > 0.0 {
802 let vertex_avg = vertex_sums[u] / degree;
803 let new_amp = 2.0 * vertex_avg - old_amp;
804 new_state.insert((u, v), new_amp);
805 }
806 }
807
808 self.state = new_state;
809 }
810
811 fn reflect_edge_uniform(&mut self) {
813 if self.num_edges == 0 {
814 return;
815 }
816
817 let total_amp: Complex64 = self.state.values().sum();
819 let uniform_amp = total_amp / self.num_edges as f64;
820
821 for (_, amplitude) in self.state.iter_mut() {
823 *amplitude = 2.0 * uniform_amp - *amplitude;
824 }
825 }
826
827 pub fn vertex_probabilities(&self) -> Vec<f64> {
829 let mut probs = vec![0.0; self.graph.num_vertices];
830
831 for (&(u, _), &litude) in &self.state {
832 probs[u] += amplitude.norm_sqr();
833 }
834
835 probs
836 }
837
838 pub fn edge_probabilities(&self) -> Vec<((usize, usize), f64)> {
840 self.state
841 .iter()
842 .map(|(&edge, &litude)| (edge, amplitude.norm_sqr()))
843 .collect()
844 }
845
846 pub fn estimate_mixing_time(&mut self, epsilon: f64) -> usize {
848 let uniform_prob = 1.0 / self.graph.num_vertices as f64;
849
850 self.initialize_uniform();
852
853 for steps in 1..1000 {
854 self.step();
855
856 let probs = self.vertex_probabilities();
857 let max_deviation = probs
858 .iter()
859 .map(|&p| (p - uniform_prob).abs())
860 .fold(0.0, f64::max);
861
862 if max_deviation < epsilon {
863 return steps;
864 }
865 }
866
867 1000 }
869}
870
871pub struct MultiWalkerQuantumWalk {
873 graph: Graph,
874 num_walkers: usize,
875 state: Array1<Complex64>,
877 single_walker_dim: usize,
879}
880
881impl MultiWalkerQuantumWalk {
882 pub fn new(graph: Graph, num_walkers: usize) -> Self {
884 let single_walker_dim = graph.num_vertices;
885 let total_dim = single_walker_dim.pow(num_walkers as u32);
886
887 Self {
888 graph,
889 num_walkers,
890 state: Array1::zeros(total_dim),
891 single_walker_dim,
892 }
893 }
894
895 pub fn initialize_positions(&mut self, positions: &[usize]) -> QuantRS2Result<()> {
897 if positions.len() != self.num_walkers {
898 return Err(QuantRS2Error::InvalidInput(
899 "Number of positions must match number of walkers".to_string(),
900 ));
901 }
902
903 for &pos in positions {
904 if pos >= self.single_walker_dim {
905 return Err(QuantRS2Error::InvalidInput(format!(
906 "Position {} out of bounds",
907 pos
908 )));
909 }
910 }
911
912 self.state.fill(Complex64::new(0.0, 0.0));
914
915 let index = self.positions_to_index(positions);
917 self.state[index] = Complex64::new(1.0, 0.0);
918
919 Ok(())
920 }
921
922 pub fn initialize_entangled_bell_state(
924 &mut self,
925 pos1: usize,
926 pos2: usize,
927 ) -> QuantRS2Result<()> {
928 if self.num_walkers != 2 {
929 return Err(QuantRS2Error::InvalidInput(
930 "Bell state initialization only works for 2 walkers".to_string(),
931 ));
932 }
933
934 self.state.fill(Complex64::new(0.0, 0.0));
935
936 let amplitude = Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0);
937
938 let idx1 = self.positions_to_index(&[pos1, pos2]);
940 let idx2 = self.positions_to_index(&[pos2, pos1]);
941
942 self.state[idx1] = amplitude;
943 self.state[idx2] = amplitude;
944
945 Ok(())
946 }
947
948 fn positions_to_index(&self, positions: &[usize]) -> usize {
950 let mut index = 0;
951 let mut multiplier = 1;
952
953 for &pos in positions.iter().rev() {
954 index += pos * multiplier;
955 multiplier *= self.single_walker_dim;
956 }
957
958 index
959 }
960
961 fn index_to_positions(&self, mut index: usize) -> Vec<usize> {
963 let mut positions = Vec::with_capacity(self.num_walkers);
964
965 for _ in 0..self.num_walkers {
966 positions.push(index % self.single_walker_dim);
967 index /= self.single_walker_dim;
968 }
969
970 positions.reverse();
971 positions
972 }
973
974 pub fn step_independent(&mut self) {
976 let mut new_state = Array1::zeros(self.state.len());
977
978 for (index, &litude) in self.state.iter().enumerate() {
979 if amplitude.norm_sqr() < 1e-15 {
980 continue;
981 }
982
983 let positions = self.index_to_positions(index);
984
985 let mut total_neighbors = 1;
987 for &pos in &positions {
988 total_neighbors *= self.graph.degree(pos).max(1);
989 }
990
991 let neighbor_amplitude = amplitude / (total_neighbors as f64).sqrt();
992
993 self.add_neighbor_amplitudes(
995 &positions,
996 0,
997 &mut Vec::new(),
998 neighbor_amplitude,
999 &mut new_state,
1000 );
1001 }
1002
1003 self.state = new_state;
1004 }
1005
1006 fn add_neighbor_amplitudes(
1008 &self,
1009 original_positions: &[usize],
1010 walker_idx: usize,
1011 current_positions: &mut Vec<usize>,
1012 amplitude: Complex64,
1013 new_state: &mut Array1<Complex64>,
1014 ) {
1015 if walker_idx >= self.num_walkers {
1016 let index = self.positions_to_index(current_positions);
1017 new_state[index] += amplitude;
1018 return;
1019 }
1020
1021 let pos = original_positions[walker_idx];
1022 let neighbors = &self.graph.edges[pos];
1023
1024 if neighbors.is_empty() {
1025 current_positions.push(pos);
1027 self.add_neighbor_amplitudes(
1028 original_positions,
1029 walker_idx + 1,
1030 current_positions,
1031 amplitude,
1032 new_state,
1033 );
1034 current_positions.pop();
1035 } else {
1036 for &neighbor in neighbors {
1037 current_positions.push(neighbor);
1038 self.add_neighbor_amplitudes(
1039 original_positions,
1040 walker_idx + 1,
1041 current_positions,
1042 amplitude,
1043 new_state,
1044 );
1045 current_positions.pop();
1046 }
1047 }
1048 }
1049
1050 pub fn marginal_probabilities(&self, walker_idx: usize) -> Vec<f64> {
1052 let mut probs = vec![0.0; self.single_walker_dim];
1053
1054 for (index, &litude) in self.state.iter().enumerate() {
1055 let positions = self.index_to_positions(index);
1056 probs[positions[walker_idx]] += amplitude.norm_sqr();
1057 }
1058
1059 probs
1060 }
1061
1062 pub fn entanglement_entropy(&self) -> f64 {
1064 if self.num_walkers != 2 {
1065 return 0.0; }
1067
1068 let mut reduced_dm = Array2::zeros((self.single_walker_dim, self.single_walker_dim));
1070
1071 for i in 0..self.single_walker_dim {
1072 for j in 0..self.single_walker_dim {
1073 for k in 0..self.single_walker_dim {
1074 let idx1 = self.positions_to_index(&[i, k]);
1075 let idx2 = self.positions_to_index(&[j, k]);
1076
1077 reduced_dm[[i, j]] += self.state[idx1].conj() * self.state[idx2];
1078 }
1079 }
1080 }
1081
1082 let trace = reduced_dm.diag().mapv(|x: Complex64| x.re).sum();
1084 -trace * trace.ln() }
1086}
1087
1088pub struct DecoherentQuantumWalk {
1090 base_walk: DiscreteQuantumWalk,
1091 decoherence_rate: f64,
1092 measurement_probability: f64,
1093}
1094
1095impl DecoherentQuantumWalk {
1096 pub fn new(graph: Graph, coin_operator: CoinOperator, decoherence_rate: f64) -> Self {
1098 Self {
1099 base_walk: DiscreteQuantumWalk::new(graph, coin_operator),
1100 decoherence_rate,
1101 measurement_probability: 0.0,
1102 }
1103 }
1104
1105 pub fn initialize_position(&mut self, position: usize) {
1107 self.base_walk.initialize_position(position);
1108 }
1109
1110 pub fn step(&mut self) {
1112 self.base_walk.step();
1114
1115 self.apply_decoherence();
1117 }
1118
1119 fn apply_decoherence(&mut self) {
1121 if self.decoherence_rate <= 0.0 {
1122 return;
1123 }
1124
1125 let quantum_probs = self.base_walk.position_probabilities();
1127
1128 let mut classical_probs = vec![0.0; quantum_probs.len()];
1130 for (v, &prob) in quantum_probs.iter().enumerate() {
1131 if prob > 0.0 {
1132 let degree = self.base_walk.graph.degree(v) as f64;
1133 if degree > 0.0 {
1134 for &neighbor in &self.base_walk.graph.edges[v] {
1135 classical_probs[neighbor] += prob / degree;
1136 }
1137 } else {
1138 classical_probs[v] += prob; }
1140 }
1141 }
1142
1143 let quantum_weight = 1.0 - self.decoherence_rate;
1145 let classical_weight = self.decoherence_rate;
1146
1147 for v in 0..quantum_probs.len() {
1149 let mixed_prob =
1150 quantum_weight * quantum_probs[v] + classical_weight * classical_probs[v];
1151
1152 if quantum_probs[v] > 0.0 {
1154 let scale_factor = (mixed_prob / quantum_probs[v]).sqrt();
1155
1156 for coin in 0..self.base_walk.coin_dimension {
1157 let idx = self.base_walk.state_index(v, coin);
1158 if idx < self.base_walk.state.len() {
1159 self.base_walk.state[idx] *= scale_factor;
1160 }
1161 }
1162 }
1163 }
1164
1165 let total_norm: f64 = self.base_walk.state.iter().map(|c| c.norm_sqr()).sum();
1167 if total_norm > 0.0 {
1168 let norm_factor = 1.0 / total_norm.sqrt();
1169 for amplitude in &mut self.base_walk.state {
1170 *amplitude *= norm_factor;
1171 }
1172 }
1173 }
1174
1175 pub fn position_probabilities(&self) -> Vec<f64> {
1177 self.base_walk.position_probabilities()
1178 }
1179
1180 pub fn set_decoherence_rate(&mut self, rate: f64) {
1182 self.decoherence_rate = rate.clamp(0.0, 1.0);
1183 }
1184}
1185
1186pub struct QuantumWalkSearch {
1188 #[allow(dead_code)]
1189 graph: Graph,
1190 oracle: SearchOracle,
1191 walk: DiscreteQuantumWalk,
1192}
1193
1194impl QuantumWalkSearch {
1195 pub fn new(graph: Graph, oracle: SearchOracle) -> Self {
1197 let walk = DiscreteQuantumWalk::new(graph.clone(), CoinOperator::Grover);
1198 Self {
1199 graph,
1200 oracle,
1201 walk,
1202 }
1203 }
1204
1205 fn apply_oracle(&mut self) {
1207 for &vertex in &self.oracle.marked {
1208 for coin in 0..self.walk.coin_dimension {
1209 let idx = self.walk.state_index(vertex, coin);
1210 if idx < self.walk.state.len() {
1211 self.walk.state[idx] = -self.walk.state[idx]; }
1213 }
1214 }
1215 }
1216
1217 pub fn run(&mut self, max_steps: usize) -> (usize, f64, usize) {
1219 let amplitude = Complex64::new(1.0 / (self.walk.hilbert_dim as f64).sqrt(), 0.0);
1221 self.walk.state.fill(amplitude);
1222
1223 let mut best_vertex = 0;
1224 let mut best_prob = 0.0;
1225 let mut best_step = 0;
1226
1227 for step in 1..=max_steps {
1229 self.walk.step();
1230 self.apply_oracle();
1231
1232 let probs = self.walk.position_probabilities();
1234 for &marked in &self.oracle.marked {
1235 if probs[marked] > best_prob {
1236 best_prob = probs[marked];
1237 best_vertex = marked;
1238 best_step = step;
1239 }
1240 }
1241
1242 if best_prob > 0.5 {
1244 break;
1245 }
1246 }
1247
1248 (best_vertex, best_prob, best_step)
1249 }
1250
1251 pub fn vertex_probabilities(&self) -> Vec<f64> {
1253 self.walk.position_probabilities()
1254 }
1255}
1256
1257pub fn quantum_walk_line_example() {
1259 println!("Quantum Walk on a Line (10 vertices)");
1260
1261 let graph = Graph::new(GraphType::Line, 10);
1262 let walk = DiscreteQuantumWalk::new(graph, CoinOperator::Hadamard);
1263
1264 let mut walk = walk;
1266 walk.initialize_position(5);
1267
1268 for steps in [0, 5, 10, 20, 30] {
1270 walk.initialize_position(5);
1272 for _ in 0..steps {
1273 walk.step();
1274 }
1275 let probs = walk.position_probabilities();
1276
1277 println!("\nAfter {} steps:", steps);
1278 print!("Probabilities: ");
1279 for (v, p) in probs.iter().enumerate() {
1280 if *p > 0.01 {
1281 print!("v{}: {:.3} ", v, p);
1282 }
1283 }
1284 println!();
1285 }
1286}
1287
1288pub fn quantum_walk_search_example() {
1290 println!("\nQuantum Walk Search on Complete Graph (8 vertices)");
1291
1292 let graph = Graph::new(GraphType::Complete, 8);
1293 let marked = vec![3, 5]; let oracle = SearchOracle::new(marked.clone());
1295
1296 let mut search = QuantumWalkSearch::new(graph, oracle);
1297
1298 println!("Marked vertices: {:?}", marked);
1299
1300 let (found, prob, steps) = search.run(50);
1302
1303 println!(
1304 "\nFound vertex {} with probability {:.3} after {} steps",
1305 found, prob, steps
1306 );
1307}
1308
1309#[cfg(test)]
1310mod tests {
1311 use super::*;
1312 #[test]
1315 fn test_graph_creation() {
1316 let graph = Graph::new(GraphType::Cycle, 4);
1317 assert_eq!(graph.num_vertices, 4);
1318 assert_eq!(graph.degree(0), 2);
1319
1320 let complete = Graph::new(GraphType::Complete, 5);
1321 assert_eq!(complete.degree(0), 4);
1322 }
1323
1324 #[test]
1325 fn test_discrete_walk_initialization() {
1326 let graph = Graph::new(GraphType::Line, 5);
1327 let mut walk = DiscreteQuantumWalk::new(graph, CoinOperator::Hadamard);
1328
1329 walk.initialize_position(2);
1330 let probs = walk.position_probabilities();
1331
1332 assert!((probs[2] - 1.0).abs() < 1e-10);
1333 }
1334
1335 #[test]
1336 fn test_continuous_walk() {
1337 let graph = Graph::new(GraphType::Cycle, 4);
1338 let mut walk = ContinuousQuantumWalk::new(graph);
1339
1340 walk.initialize_vertex(0);
1341 walk.evolve(1.0);
1342
1343 let probs = walk.vertex_probabilities();
1344 let total: f64 = probs.iter().sum();
1345 assert!((total - 1.0).abs() < 1e-10);
1346 }
1347
1348 #[test]
1349 fn test_weighted_graph() {
1350 let mut graph = Graph::new_empty(3);
1351 graph.add_weighted_edge(0, 1, 2.0);
1352 graph.add_weighted_edge(1, 2, 3.0);
1353
1354 let adj_matrix = graph.adjacency_matrix();
1355 assert_eq!(adj_matrix[[0, 1]], 2.0);
1356 assert_eq!(adj_matrix[[1, 2]], 3.0);
1357 assert_eq!(adj_matrix[[0, 2]], 0.0);
1358 }
1359
1360 #[test]
1361 fn test_graph_from_adjacency_matrix() {
1362 let mut matrix = Array2::zeros((3, 3));
1363 matrix[[0, 1]] = 1.0;
1364 matrix[[1, 0]] = 1.0;
1365 matrix[[1, 2]] = 2.0;
1366 matrix[[2, 1]] = 2.0;
1367
1368 let graph = Graph::from_adjacency_matrix(&matrix).unwrap();
1369 assert_eq!(graph.num_vertices, 3);
1370 assert_eq!(graph.degree(0), 1);
1371 assert_eq!(graph.degree(1), 2);
1372 assert_eq!(graph.degree(2), 1);
1373 }
1374
1375 #[test]
1376 fn test_laplacian_matrix() {
1377 let graph = Graph::new(GraphType::Cycle, 3);
1378 let laplacian = graph.laplacian_matrix();
1379
1380 assert_eq!(laplacian[[0, 0]], 2.0);
1382 assert_eq!(laplacian[[1, 1]], 2.0);
1383 assert_eq!(laplacian[[2, 2]], 2.0);
1384
1385 assert_eq!(laplacian[[0, 1]], -1.0);
1387 assert_eq!(laplacian[[1, 2]], -1.0);
1388 assert_eq!(laplacian[[2, 0]], -1.0);
1389 }
1390
1391 #[test]
1392 fn test_bipartite_detection() {
1393 let bipartite = Graph::new(GraphType::Cycle, 4); assert!(bipartite.is_bipartite());
1395
1396 let non_bipartite = Graph::new(GraphType::Cycle, 3); assert!(!non_bipartite.is_bipartite());
1398
1399 let complete = Graph::new(GraphType::Complete, 3); assert!(!complete.is_bipartite());
1401 }
1402
1403 #[test]
1404 fn test_shortest_paths() {
1405 let graph = Graph::new(GraphType::Line, 4); let distances = graph.all_pairs_shortest_paths();
1407
1408 assert_eq!(distances[[0, 0]], 0.0);
1409 assert_eq!(distances[[0, 1]], 1.0);
1410 assert_eq!(distances[[0, 2]], 2.0);
1411 assert_eq!(distances[[0, 3]], 3.0);
1412 assert_eq!(distances[[1, 3]], 2.0);
1413 }
1414
1415 #[test]
1416 fn test_szegedy_walk() {
1417 let graph = Graph::new(GraphType::Cycle, 4);
1418 let mut szegedy = SzegedyQuantumWalk::new(graph);
1419
1420 szegedy.initialize_uniform();
1421 let initial_probs = szegedy.vertex_probabilities();
1422
1423 for &prob in &initial_probs {
1425 assert!(prob > 0.0);
1426 }
1427
1428 for _ in 0..5 {
1430 szegedy.step();
1431 }
1432
1433 let final_probs = szegedy.vertex_probabilities();
1434 let total: f64 = final_probs.iter().sum();
1435 assert!((total - 1.0).abs() < 1e-10);
1436 }
1437
1438 #[test]
1439 fn test_szegedy_edge_initialization() {
1440 let mut graph = Graph::new_empty(3);
1441 graph.add_edge(0, 1);
1442 graph.add_edge(1, 2);
1443
1444 let mut szegedy = SzegedyQuantumWalk::new(graph);
1445 szegedy.initialize_edge(0, 1);
1446
1447 let edge_probs = szegedy.edge_probabilities();
1448 assert_eq!(edge_probs.len(), 1);
1449 assert_eq!(edge_probs[0].0, (0, 1));
1450 assert!((edge_probs[0].1 - 1.0).abs() < 1e-10);
1451 }
1452
1453 #[test]
1454 fn test_multi_walker_quantum_walk() {
1455 let graph = Graph::new(GraphType::Cycle, 3);
1456 let mut multi_walk = MultiWalkerQuantumWalk::new(graph, 2);
1457
1458 multi_walk.initialize_positions(&[0, 1]).unwrap();
1460
1461 let marginal_0 = multi_walk.marginal_probabilities(0);
1462 let marginal_1 = multi_walk.marginal_probabilities(1);
1463
1464 assert!((marginal_0[0] - 1.0).abs() < 1e-10);
1465 assert!((marginal_1[1] - 1.0).abs() < 1e-10);
1466
1467 multi_walk.step_independent();
1469
1470 let new_marginal_0 = multi_walk.marginal_probabilities(0);
1472 let new_marginal_1 = multi_walk.marginal_probabilities(1);
1473
1474 assert!(new_marginal_0[0] < 1.0);
1475 assert!(new_marginal_1[1] < 1.0);
1476 }
1477
1478 #[test]
1479 fn test_multi_walker_bell_state() {
1480 let graph = Graph::new(GraphType::Cycle, 4);
1481 let mut multi_walk = MultiWalkerQuantumWalk::new(graph, 2);
1482
1483 multi_walk.initialize_entangled_bell_state(0, 1).unwrap();
1484
1485 let marginal_0 = multi_walk.marginal_probabilities(0);
1486 let marginal_1 = multi_walk.marginal_probabilities(1);
1487
1488 assert!((marginal_0[0] - 0.5).abs() < 1e-10);
1490 assert!((marginal_0[1] - 0.5).abs() < 1e-10);
1491 assert!((marginal_1[0] - 0.5).abs() < 1e-10);
1492 assert!((marginal_1[1] - 0.5).abs() < 1e-10);
1493 }
1494
1495 #[test]
1496 fn test_multi_walker_error_handling() {
1497 let graph = Graph::new(GraphType::Line, 3);
1498 let mut multi_walk = MultiWalkerQuantumWalk::new(graph.clone(), 2);
1499
1500 assert!(multi_walk.initialize_positions(&[0]).is_err());
1502
1503 assert!(multi_walk.initialize_positions(&[0, 5]).is_err());
1505
1506 let mut single_walk = MultiWalkerQuantumWalk::new(graph, 1);
1508 assert!(single_walk.initialize_entangled_bell_state(0, 1).is_err());
1509 }
1510
1511 #[test]
1512 fn test_decoherent_quantum_walk() {
1513 let graph = Graph::new(GraphType::Line, 5);
1514 let mut decoherent = DecoherentQuantumWalk::new(graph, CoinOperator::Hadamard, 0.1);
1515
1516 decoherent.initialize_position(2);
1517 let initial_probs = decoherent.position_probabilities();
1518 assert!((initial_probs[2] - 1.0).abs() < 1e-10);
1519
1520 for _ in 0..10 {
1522 decoherent.step();
1523 }
1524
1525 let final_probs = decoherent.position_probabilities();
1526 let total: f64 = final_probs.iter().sum();
1527 assert!((total - 1.0).abs() < 1e-10);
1528
1529 assert!(final_probs[2] < 1.0);
1531 }
1532
1533 #[test]
1534 fn test_decoherence_rate_bounds() {
1535 let graph = Graph::new(GraphType::Cycle, 4);
1536 let mut decoherent = DecoherentQuantumWalk::new(graph, CoinOperator::Grover, 0.5);
1537
1538 decoherent.set_decoherence_rate(-0.1);
1540 decoherent.initialize_position(0);
1541 decoherent.step(); decoherent.set_decoherence_rate(1.5);
1544 decoherent.step(); }
1546
1547 #[test]
1548 fn test_transition_matrix() {
1549 let graph = Graph::new(GraphType::Cycle, 3);
1550 let transition = graph.transition_matrix();
1551
1552 for i in 0..3 {
1554 let mut row_sum = 0.0;
1555 for j in 0..3 {
1556 row_sum += transition[[i, j]];
1557 }
1558 assert!((row_sum - 1.0).abs() < 1e-10);
1559 }
1560 }
1561
1562 #[test]
1563 fn test_normalized_laplacian() {
1564 let graph = Graph::new(GraphType::Complete, 3);
1565 let norm_laplacian = graph.normalized_laplacian_matrix();
1566
1567 for i in 0..3 {
1569 assert!((norm_laplacian[[i, i]] - 1.0).abs() < 1e-10);
1570 }
1571
1572 let expected_off_diag = -1.0 / 2.0; assert!((norm_laplacian[[0, 1]] - expected_off_diag).abs() < 1e-10);
1575 assert!((norm_laplacian[[1, 2]] - expected_off_diag).abs() < 1e-10);
1576 assert!((norm_laplacian[[0, 2]] - expected_off_diag).abs() < 1e-10);
1577 }
1578
1579 #[test]
1580 fn test_algebraic_connectivity() {
1581 let complete_3 = Graph::new(GraphType::Complete, 3);
1582 let connectivity = complete_3.algebraic_connectivity();
1583 assert!(connectivity > 0.0); let line_5 = Graph::new(GraphType::Line, 5);
1586 let line_connectivity = line_5.algebraic_connectivity();
1587 assert!(line_connectivity > 0.0);
1588 }
1589
1590 #[test]
1591 fn test_mixing_time_estimation() {
1592 let graph = Graph::new(GraphType::Complete, 4);
1593 let mut szegedy = SzegedyQuantumWalk::new(graph);
1594
1595 let mixing_time = szegedy.estimate_mixing_time(0.1);
1596 assert!(mixing_time > 0);
1597 assert!(mixing_time <= 1000); }
1599
1600 #[test]
1601 fn test_quantum_walk_search_on_custom_graph() {
1602 let mut graph = Graph::new_empty(5);
1604 for i in 1..5 {
1605 graph.add_edge(0, i);
1606 }
1607
1608 let oracle = SearchOracle::new(vec![3]); let mut search = QuantumWalkSearch::new(graph, oracle);
1610
1611 let (found_vertex, prob, steps) = search.run(20);
1612 assert_eq!(found_vertex, 3);
1613 assert!(prob > 0.0);
1614 assert!(steps <= 20);
1615 }
1616
1617 #[test]
1618 fn test_custom_coin_operator() {
1619 let graph = Graph::new(GraphType::Line, 3);
1620
1621 let mut coin_matrix = Array2::zeros((2, 2));
1623 coin_matrix[[0, 1]] = Complex64::new(1.0, 0.0);
1624 coin_matrix[[1, 0]] = Complex64::new(1.0, 0.0);
1625
1626 let custom_coin = CoinOperator::Custom(coin_matrix);
1627 let mut walk = DiscreteQuantumWalk::new(graph, custom_coin);
1628
1629 walk.initialize_position(1);
1630 walk.step();
1631
1632 let probs = walk.position_probabilities();
1633 let total: f64 = probs.iter().sum();
1634 assert!((total - 1.0).abs() < 1e-10);
1635 }
1636
1637 #[test]
1638 fn test_empty_graph_edge_cases() {
1639 let empty_graph = Graph::new_empty(3);
1640 let mut szegedy = SzegedyQuantumWalk::new(empty_graph);
1641
1642 szegedy.initialize_uniform();
1643 let probs = szegedy.vertex_probabilities();
1644
1645 for &prob in &probs {
1647 assert_eq!(prob, 0.0);
1648 }
1649 }
1650
1651 #[test]
1652 fn test_hypercube_graph() {
1653 let hypercube = Graph::new(GraphType::Hypercube, 3); assert_eq!(hypercube.num_vertices, 8);
1655
1656 for i in 0..8 {
1658 assert_eq!(hypercube.degree(i), 3);
1659 }
1660 }
1661
1662 #[test]
1663 fn test_grid_2d_graph() {
1664 let grid = Graph::new(GraphType::Grid2D, 3); assert_eq!(grid.num_vertices, 9);
1666
1667 assert_eq!(grid.degree(0), 2); assert_eq!(grid.degree(2), 2); assert_eq!(grid.degree(6), 2); assert_eq!(grid.degree(8), 2); assert_eq!(grid.degree(4), 4);
1675 }
1676}