1use 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#[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| {
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 fn estimate_fiedler_value(&self, _laplacian: &Array2<f64>) -> f64 {
348 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 }
355
356 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 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 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 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
421pub struct DiscreteQuantumWalk {
423 graph: Graph,
424 coin_operator: CoinOperator,
425 coin_dimension: usize,
426 hilbert_dim: usize,
428 state: Vec<Complex64>,
430}
431
432impl DiscreteQuantumWalk {
433 pub fn new(graph: Graph, coin_operator: CoinOperator) -> Self {
435 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) }
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 pub fn initialize_position(&mut self, position: usize) {
461 self.state = vec![Complex64::new(0.0, 0.0); self.hilbert_dim];
462
463 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 pub fn step(&mut self) {
479 self.apply_coin();
481
482 self.apply_shift();
484 }
485
486 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 fn state_index(&self, vertex: usize, coin: usize) -> usize {
504 vertex * self.coin_dimension + coin
505 }
506
507 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 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 fn apply_grover_coin(&mut self) {
539 for vertex in 0..self.graph.num_vertices {
541 let degree = self.graph.degree(vertex);
542 if degree <= 1 {
543 continue; }
545
546 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 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 fn apply_dft_coin(&mut self) {
569 if self.coin_dimension == 2 {
571 self.apply_hadamard_coin(); }
573 }
575
576 fn apply_custom_coin(&mut self, matrix: Array2<Complex64>) {
578 if matrix.shape() != [self.coin_dimension, self.coin_dimension] {
579 return; }
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 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 let new_coin_state = matrix.dot(&Array1::from(coin_state));
595
596 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 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 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
635pub struct ContinuousQuantumWalk {
637 graph: Graph,
638 hamiltonian: Array2<Complex64>,
639 state: Vec<Complex64>,
640}
641
642impl ContinuousQuantumWalk {
643 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 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 pub fn evolve(&mut self, time: f64) {
666 let dt = 0.01; let steps = (time / dt) as usize;
671
672 for _ in 0..steps {
673 let mut new_state = self.state.clone();
674
675 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 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 pub fn vertex_probabilities(&self) -> Vec<f64> {
703 self.state.iter().map(|c| c.probability()).collect()
704 }
705
706 pub fn transport_probability(&mut self, from: usize, to: usize, time: f64) -> f64 {
708 self.initialize_vertex(from);
710
711 self.evolve(time);
713
714 if to < self.state.len() {
716 self.state[to].probability()
717 } else {
718 0.0
719 }
720 }
721
722 pub fn get_probabilities(&self, state: &[Complex64]) -> Vec<f64> {
724 state.iter().map(|c| c.probability()).collect()
725 }
726}
727
728pub struct SzegedyQuantumWalk {
731 graph: Graph,
732 state: HashMap<(usize, usize), Complex64>,
734 num_edges: usize,
735}
736
737impl SzegedyQuantumWalk {
738 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 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 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 pub fn step(&mut self) {
780 self.reflect_vertex_uniform();
784
785 self.reflect_edge_uniform();
787 }
788
789 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 for (&(u, _), &litude) in &self.state {
796 vertex_sums[u] += amplitude;
797 }
798
799 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 fn reflect_edge_uniform(&mut self) {
816 if self.num_edges == 0 {
817 return;
818 }
819
820 let total_amp: Complex64 = self.state.values().sum();
822 let uniform_amp = total_amp / self.num_edges as f64;
823
824 for (_, amplitude) in self.state.iter_mut() {
826 *amplitude = 2.0 * uniform_amp - *amplitude;
827 }
828 }
829
830 pub fn vertex_probabilities(&self) -> Vec<f64> {
832 let mut probs = vec![0.0; self.graph.num_vertices];
833
834 for (&(u, _), &litude) in &self.state {
835 probs[u] += amplitude.norm_sqr();
836 }
837
838 probs
839 }
840
841 pub fn edge_probabilities(&self) -> Vec<((usize, usize), f64)> {
843 self.state
844 .iter()
845 .map(|(&edge, &litude)| (edge, amplitude.norm_sqr()))
846 .collect()
847 }
848
849 pub fn estimate_mixing_time(&mut self, epsilon: f64) -> usize {
851 let uniform_prob = 1.0 / self.graph.num_vertices as f64;
852
853 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 }
872}
873
874pub struct MultiWalkerQuantumWalk {
876 graph: Graph,
877 num_walkers: usize,
878 state: Array1<Complex64>,
880 single_walker_dim: usize,
882}
883
884impl MultiWalkerQuantumWalk {
885 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 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 self.state.fill(Complex64::new(0.0, 0.0));
917
918 let index = self.positions_to_index(positions);
920 self.state[index] = Complex64::new(1.0, 0.0);
921
922 Ok(())
923 }
924
925 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 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 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 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 pub fn step_independent(&mut self) {
979 let mut new_state = Array1::zeros(self.state.len());
980
981 for (index, &litude) 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 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 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 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 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 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, &litude) 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 pub fn entanglement_entropy(&self) -> f64 {
1067 if self.num_walkers != 2 {
1068 return 0.0; }
1070
1071 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 let trace = reduced_dm.diag().mapv(|x: Complex64| x.re).sum();
1087 -trace * trace.ln() }
1089}
1090
1091pub struct DecoherentQuantumWalk {
1093 base_walk: DiscreteQuantumWalk,
1094 decoherence_rate: f64,
1095 measurement_probability: f64,
1096}
1097
1098impl DecoherentQuantumWalk {
1099 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 pub fn initialize_position(&mut self, position: usize) {
1110 self.base_walk.initialize_position(position);
1111 }
1112
1113 pub fn step(&mut self) {
1115 self.base_walk.step();
1117
1118 self.apply_decoherence();
1120 }
1121
1122 fn apply_decoherence(&mut self) {
1124 if self.decoherence_rate <= 0.0 {
1125 return;
1126 }
1127
1128 let quantum_probs = self.base_walk.position_probabilities();
1130
1131 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; }
1143 }
1144 }
1145
1146 let quantum_weight = 1.0 - self.decoherence_rate;
1148 let classical_weight = self.decoherence_rate;
1149
1150 for v in 0..quantum_probs.len() {
1152 let mixed_prob =
1153 quantum_weight * quantum_probs[v] + classical_weight * classical_probs[v];
1154
1155 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 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 pub fn position_probabilities(&self) -> Vec<f64> {
1180 self.base_walk.position_probabilities()
1181 }
1182
1183 pub fn set_decoherence_rate(&mut self, rate: f64) {
1185 self.decoherence_rate = rate.clamp(0.0, 1.0);
1186 }
1187}
1188
1189pub struct QuantumWalkSearch {
1191 #[allow(dead_code)]
1192 graph: Graph,
1193 oracle: SearchOracle,
1194 walk: DiscreteQuantumWalk,
1195}
1196
1197impl QuantumWalkSearch {
1198 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 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]; }
1216 }
1217 }
1218 }
1219
1220 pub fn run(&mut self, max_steps: usize) -> (usize, f64, usize) {
1222 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 for step in 1..=max_steps {
1232 self.walk.step();
1233 self.apply_oracle();
1234
1235 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 if best_prob > 0.5 {
1247 break;
1248 }
1249 }
1250
1251 (best_vertex, best_prob, best_step)
1252 }
1253
1254 pub fn vertex_probabilities(&self) -> Vec<f64> {
1256 self.walk.position_probabilities()
1257 }
1258}
1259
1260pub 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 let mut walk = walk;
1269 walk.initialize_position(5);
1270
1271 for steps in [0, 5, 10, 20, 30] {
1273 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
1291pub 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]; let oracle = SearchOracle::new(marked.clone());
1298
1299 let mut search = QuantumWalkSearch::new(graph, oracle);
1300
1301 println!("Marked vertices: {:?}", marked);
1302
1303 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 #[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 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 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); assert!(bipartite.is_bipartite());
1400
1401 let non_bipartite = Graph::new(GraphType::Cycle, 3); assert!(!non_bipartite.is_bipartite());
1403
1404 let complete = Graph::new(GraphType::Complete, 3); assert!(!complete.is_bipartite());
1406 }
1407
1408 #[test]
1409 fn test_shortest_paths() {
1410 let graph = Graph::new(GraphType::Line, 4); 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 for &prob in &initial_probs {
1430 assert!(prob > 0.0);
1431 }
1432
1433 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 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 multi_walk.step_independent();
1476
1477 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 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 assert!(multi_walk.initialize_positions(&[0]).is_err());
1511
1512 assert!(multi_walk.initialize_positions(&[0, 5]).is_err());
1514
1515 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 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 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 decoherent.set_decoherence_rate(-0.1);
1549 decoherent.initialize_position(0);
1550 decoherent.step(); decoherent.set_decoherence_rate(1.5);
1553 decoherent.step(); }
1555
1556 #[test]
1557 fn test_transition_matrix() {
1558 let graph = Graph::new(GraphType::Cycle, 3);
1559 let transition = graph.transition_matrix();
1560
1561 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 for i in 0..3 {
1578 assert!((norm_laplacian[[i, i]] - 1.0).abs() < 1e-10);
1579 }
1580
1581 let expected_off_diag = -1.0 / 2.0; 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); 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); }
1608
1609 #[test]
1610 fn test_quantum_walk_search_on_custom_graph() {
1611 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]); 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 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 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); assert_eq!(hypercube.num_vertices, 8);
1664
1665 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); assert_eq!(grid.num_vertices, 9);
1675
1676 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);
1684 }
1685}