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, Eq)]
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 const 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 const 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 &mut new_state {
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.values_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 {pos} out of bounds"
910 )));
911 }
912 }
913
914 self.state.fill(Complex64::new(0.0, 0.0));
916
917 let index = self.positions_to_index(positions);
919 self.state[index] = Complex64::new(1.0, 0.0);
920
921 Ok(())
922 }
923
924 pub fn initialize_entangled_bell_state(
926 &mut self,
927 pos1: usize,
928 pos2: usize,
929 ) -> QuantRS2Result<()> {
930 if self.num_walkers != 2 {
931 return Err(QuantRS2Error::InvalidInput(
932 "Bell state initialization only works for 2 walkers".to_string(),
933 ));
934 }
935
936 self.state.fill(Complex64::new(0.0, 0.0));
937
938 let amplitude = Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0);
939
940 let idx1 = self.positions_to_index(&[pos1, pos2]);
942 let idx2 = self.positions_to_index(&[pos2, pos1]);
943
944 self.state[idx1] = amplitude;
945 self.state[idx2] = amplitude;
946
947 Ok(())
948 }
949
950 fn positions_to_index(&self, positions: &[usize]) -> usize {
952 let mut index = 0;
953 let mut multiplier = 1;
954
955 for &pos in positions.iter().rev() {
956 index += pos * multiplier;
957 multiplier *= self.single_walker_dim;
958 }
959
960 index
961 }
962
963 fn index_to_positions(&self, mut index: usize) -> Vec<usize> {
965 let mut positions = Vec::with_capacity(self.num_walkers);
966
967 for _ in 0..self.num_walkers {
968 positions.push(index % self.single_walker_dim);
969 index /= self.single_walker_dim;
970 }
971
972 positions.reverse();
973 positions
974 }
975
976 pub fn step_independent(&mut self) {
978 let mut new_state = Array1::zeros(self.state.len());
979
980 for (index, &litude) in self.state.iter().enumerate() {
981 if amplitude.norm_sqr() < 1e-15 {
982 continue;
983 }
984
985 let positions = self.index_to_positions(index);
986
987 let mut total_neighbors = 1;
989 for &pos in &positions {
990 total_neighbors *= self.graph.degree(pos).max(1);
991 }
992
993 let neighbor_amplitude = amplitude / (total_neighbors as f64).sqrt();
994
995 self.add_neighbor_amplitudes(
997 &positions,
998 0,
999 &mut Vec::new(),
1000 neighbor_amplitude,
1001 &mut new_state,
1002 );
1003 }
1004
1005 self.state = new_state;
1006 }
1007
1008 fn add_neighbor_amplitudes(
1010 &self,
1011 original_positions: &[usize],
1012 walker_idx: usize,
1013 current_positions: &mut Vec<usize>,
1014 amplitude: Complex64,
1015 new_state: &mut Array1<Complex64>,
1016 ) {
1017 if walker_idx >= self.num_walkers {
1018 let index = self.positions_to_index(current_positions);
1019 new_state[index] += amplitude;
1020 return;
1021 }
1022
1023 let pos = original_positions[walker_idx];
1024 let neighbors = &self.graph.edges[pos];
1025
1026 if neighbors.is_empty() {
1027 current_positions.push(pos);
1029 self.add_neighbor_amplitudes(
1030 original_positions,
1031 walker_idx + 1,
1032 current_positions,
1033 amplitude,
1034 new_state,
1035 );
1036 current_positions.pop();
1037 } else {
1038 for &neighbor in neighbors {
1039 current_positions.push(neighbor);
1040 self.add_neighbor_amplitudes(
1041 original_positions,
1042 walker_idx + 1,
1043 current_positions,
1044 amplitude,
1045 new_state,
1046 );
1047 current_positions.pop();
1048 }
1049 }
1050 }
1051
1052 pub fn marginal_probabilities(&self, walker_idx: usize) -> Vec<f64> {
1054 let mut probs = vec![0.0; self.single_walker_dim];
1055
1056 for (index, &litude) in self.state.iter().enumerate() {
1057 let positions = self.index_to_positions(index);
1058 probs[positions[walker_idx]] += amplitude.norm_sqr();
1059 }
1060
1061 probs
1062 }
1063
1064 pub fn entanglement_entropy(&self) -> f64 {
1066 if self.num_walkers != 2 {
1067 return 0.0; }
1069
1070 let mut reduced_dm = Array2::zeros((self.single_walker_dim, self.single_walker_dim));
1072
1073 for i in 0..self.single_walker_dim {
1074 for j in 0..self.single_walker_dim {
1075 for k in 0..self.single_walker_dim {
1076 let idx1 = self.positions_to_index(&[i, k]);
1077 let idx2 = self.positions_to_index(&[j, k]);
1078
1079 reduced_dm[[i, j]] += self.state[idx1].conj() * self.state[idx2];
1080 }
1081 }
1082 }
1083
1084 let trace = reduced_dm.diag().mapv(|x: Complex64| x.re).sum();
1086 -trace * trace.ln() }
1088}
1089
1090pub struct DecoherentQuantumWalk {
1092 base_walk: DiscreteQuantumWalk,
1093 decoherence_rate: f64,
1094 measurement_probability: f64,
1095}
1096
1097impl DecoherentQuantumWalk {
1098 pub fn new(graph: Graph, coin_operator: CoinOperator, decoherence_rate: f64) -> Self {
1100 Self {
1101 base_walk: DiscreteQuantumWalk::new(graph, coin_operator),
1102 decoherence_rate,
1103 measurement_probability: 0.0,
1104 }
1105 }
1106
1107 pub fn initialize_position(&mut self, position: usize) {
1109 self.base_walk.initialize_position(position);
1110 }
1111
1112 pub fn step(&mut self) {
1114 self.base_walk.step();
1116
1117 self.apply_decoherence();
1119 }
1120
1121 fn apply_decoherence(&mut self) {
1123 if self.decoherence_rate <= 0.0 {
1124 return;
1125 }
1126
1127 let quantum_probs = self.base_walk.position_probabilities();
1129
1130 let mut classical_probs = vec![0.0; quantum_probs.len()];
1132 for (v, &prob) in quantum_probs.iter().enumerate() {
1133 if prob > 0.0 {
1134 let degree = self.base_walk.graph.degree(v) as f64;
1135 if degree > 0.0 {
1136 for &neighbor in &self.base_walk.graph.edges[v] {
1137 classical_probs[neighbor] += prob / degree;
1138 }
1139 } else {
1140 classical_probs[v] += prob; }
1142 }
1143 }
1144
1145 let quantum_weight = 1.0 - self.decoherence_rate;
1147 let classical_weight = self.decoherence_rate;
1148
1149 for v in 0..quantum_probs.len() {
1151 let mixed_prob =
1152 quantum_weight * quantum_probs[v] + classical_weight * classical_probs[v];
1153
1154 if quantum_probs[v] > 0.0 {
1156 let scale_factor = (mixed_prob / quantum_probs[v]).sqrt();
1157
1158 for coin in 0..self.base_walk.coin_dimension {
1159 let idx = self.base_walk.state_index(v, coin);
1160 if idx < self.base_walk.state.len() {
1161 self.base_walk.state[idx] *= scale_factor;
1162 }
1163 }
1164 }
1165 }
1166
1167 let total_norm: f64 = self.base_walk.state.iter().map(|c| c.norm_sqr()).sum();
1169 if total_norm > 0.0 {
1170 let norm_factor = 1.0 / total_norm.sqrt();
1171 for amplitude in &mut self.base_walk.state {
1172 *amplitude *= norm_factor;
1173 }
1174 }
1175 }
1176
1177 pub fn position_probabilities(&self) -> Vec<f64> {
1179 self.base_walk.position_probabilities()
1180 }
1181
1182 pub const fn set_decoherence_rate(&mut self, rate: f64) {
1184 self.decoherence_rate = rate.clamp(0.0, 1.0);
1185 }
1186}
1187
1188pub struct QuantumWalkSearch {
1190 #[allow(dead_code)]
1191 graph: Graph,
1192 oracle: SearchOracle,
1193 walk: DiscreteQuantumWalk,
1194}
1195
1196impl QuantumWalkSearch {
1197 pub fn new(graph: Graph, oracle: SearchOracle) -> Self {
1199 let walk = DiscreteQuantumWalk::new(graph.clone(), CoinOperator::Grover);
1200 Self {
1201 graph,
1202 oracle,
1203 walk,
1204 }
1205 }
1206
1207 fn apply_oracle(&mut self) {
1209 for &vertex in &self.oracle.marked {
1210 for coin in 0..self.walk.coin_dimension {
1211 let idx = self.walk.state_index(vertex, coin);
1212 if idx < self.walk.state.len() {
1213 self.walk.state[idx] = -self.walk.state[idx]; }
1215 }
1216 }
1217 }
1218
1219 pub fn run(&mut self, max_steps: usize) -> (usize, f64, usize) {
1221 let amplitude = Complex64::new(1.0 / (self.walk.hilbert_dim as f64).sqrt(), 0.0);
1223 self.walk.state.fill(amplitude);
1224
1225 let mut best_vertex = 0;
1226 let mut best_prob = 0.0;
1227 let mut best_step = 0;
1228
1229 for step in 1..=max_steps {
1231 self.walk.step();
1232 self.apply_oracle();
1233
1234 let probs = self.walk.position_probabilities();
1236 for &marked in &self.oracle.marked {
1237 if probs[marked] > best_prob {
1238 best_prob = probs[marked];
1239 best_vertex = marked;
1240 best_step = step;
1241 }
1242 }
1243
1244 if best_prob > 0.5 {
1246 break;
1247 }
1248 }
1249
1250 (best_vertex, best_prob, best_step)
1251 }
1252
1253 pub fn vertex_probabilities(&self) -> Vec<f64> {
1255 self.walk.position_probabilities()
1256 }
1257}
1258
1259pub fn quantum_walk_line_example() {
1261 println!("Quantum Walk on a Line (10 vertices)");
1262
1263 let graph = Graph::new(GraphType::Line, 10);
1264 let walk = DiscreteQuantumWalk::new(graph, CoinOperator::Hadamard);
1265
1266 let mut walk = walk;
1268 walk.initialize_position(5);
1269
1270 for steps in [0, 5, 10, 20, 30] {
1272 walk.initialize_position(5);
1274 for _ in 0..steps {
1275 walk.step();
1276 }
1277 let probs = walk.position_probabilities();
1278
1279 println!("\nAfter {steps} steps:");
1280 print!("Probabilities: ");
1281 for (v, p) in probs.iter().enumerate() {
1282 if *p > 0.01 {
1283 print!("v{v}: {p:.3} ");
1284 }
1285 }
1286 println!();
1287 }
1288}
1289
1290pub fn quantum_walk_search_example() {
1292 println!("\nQuantum Walk Search on Complete Graph (8 vertices)");
1293
1294 let graph = Graph::new(GraphType::Complete, 8);
1295 let marked = vec![3, 5]; let oracle = SearchOracle::new(marked.clone());
1297
1298 let mut search = QuantumWalkSearch::new(graph, oracle);
1299
1300 println!("Marked vertices: {marked:?}");
1301
1302 let (found, prob, steps) = search.run(50);
1304
1305 println!("\nFound vertex {found} with probability {prob:.3} after {steps} steps");
1306}
1307
1308#[cfg(test)]
1309mod tests {
1310 use super::*;
1311 #[test]
1314 fn test_graph_creation() {
1315 let graph = Graph::new(GraphType::Cycle, 4);
1316 assert_eq!(graph.num_vertices, 4);
1317 assert_eq!(graph.degree(0), 2);
1318
1319 let complete = Graph::new(GraphType::Complete, 5);
1320 assert_eq!(complete.degree(0), 4);
1321 }
1322
1323 #[test]
1324 fn test_discrete_walk_initialization() {
1325 let graph = Graph::new(GraphType::Line, 5);
1326 let mut walk = DiscreteQuantumWalk::new(graph, CoinOperator::Hadamard);
1327
1328 walk.initialize_position(2);
1329 let probs = walk.position_probabilities();
1330
1331 assert!((probs[2] - 1.0).abs() < 1e-10);
1332 }
1333
1334 #[test]
1335 fn test_continuous_walk() {
1336 let graph = Graph::new(GraphType::Cycle, 4);
1337 let mut walk = ContinuousQuantumWalk::new(graph);
1338
1339 walk.initialize_vertex(0);
1340 walk.evolve(1.0);
1341
1342 let probs = walk.vertex_probabilities();
1343 let total: f64 = probs.iter().sum();
1344 assert!((total - 1.0).abs() < 1e-10);
1345 }
1346
1347 #[test]
1348 fn test_weighted_graph() {
1349 let mut graph = Graph::new_empty(3);
1350 graph.add_weighted_edge(0, 1, 2.0);
1351 graph.add_weighted_edge(1, 2, 3.0);
1352
1353 let adj_matrix = graph.adjacency_matrix();
1354 assert_eq!(adj_matrix[[0, 1]], 2.0);
1355 assert_eq!(adj_matrix[[1, 2]], 3.0);
1356 assert_eq!(adj_matrix[[0, 2]], 0.0);
1357 }
1358
1359 #[test]
1360 fn test_graph_from_adjacency_matrix() {
1361 let mut matrix = Array2::zeros((3, 3));
1362 matrix[[0, 1]] = 1.0;
1363 matrix[[1, 0]] = 1.0;
1364 matrix[[1, 2]] = 2.0;
1365 matrix[[2, 1]] = 2.0;
1366
1367 let graph = Graph::from_adjacency_matrix(&matrix).expect(
1368 "Failed to create graph from adjacency matrix in test_graph_from_adjacency_matrix",
1369 );
1370 assert_eq!(graph.num_vertices, 3);
1371 assert_eq!(graph.degree(0), 1);
1372 assert_eq!(graph.degree(1), 2);
1373 assert_eq!(graph.degree(2), 1);
1374 }
1375
1376 #[test]
1377 fn test_laplacian_matrix() {
1378 let graph = Graph::new(GraphType::Cycle, 3);
1379 let laplacian = graph.laplacian_matrix();
1380
1381 assert_eq!(laplacian[[0, 0]], 2.0);
1383 assert_eq!(laplacian[[1, 1]], 2.0);
1384 assert_eq!(laplacian[[2, 2]], 2.0);
1385
1386 assert_eq!(laplacian[[0, 1]], -1.0);
1388 assert_eq!(laplacian[[1, 2]], -1.0);
1389 assert_eq!(laplacian[[2, 0]], -1.0);
1390 }
1391
1392 #[test]
1393 fn test_bipartite_detection() {
1394 let bipartite = Graph::new(GraphType::Cycle, 4); assert!(bipartite.is_bipartite());
1396
1397 let non_bipartite = Graph::new(GraphType::Cycle, 3); assert!(!non_bipartite.is_bipartite());
1399
1400 let complete = Graph::new(GraphType::Complete, 3); assert!(!complete.is_bipartite());
1402 }
1403
1404 #[test]
1405 fn test_shortest_paths() {
1406 let graph = Graph::new(GraphType::Line, 4); let distances = graph.all_pairs_shortest_paths();
1408
1409 assert_eq!(distances[[0, 0]], 0.0);
1410 assert_eq!(distances[[0, 1]], 1.0);
1411 assert_eq!(distances[[0, 2]], 2.0);
1412 assert_eq!(distances[[0, 3]], 3.0);
1413 assert_eq!(distances[[1, 3]], 2.0);
1414 }
1415
1416 #[test]
1417 fn test_szegedy_walk() {
1418 let graph = Graph::new(GraphType::Cycle, 4);
1419 let mut szegedy = SzegedyQuantumWalk::new(graph);
1420
1421 szegedy.initialize_uniform();
1422 let initial_probs = szegedy.vertex_probabilities();
1423
1424 for &prob in &initial_probs {
1426 assert!(prob > 0.0);
1427 }
1428
1429 for _ in 0..5 {
1431 szegedy.step();
1432 }
1433
1434 let final_probs = szegedy.vertex_probabilities();
1435 let total: f64 = final_probs.iter().sum();
1436 assert!((total - 1.0).abs() < 1e-10);
1437 }
1438
1439 #[test]
1440 fn test_szegedy_edge_initialization() {
1441 let mut graph = Graph::new_empty(3);
1442 graph.add_edge(0, 1);
1443 graph.add_edge(1, 2);
1444
1445 let mut szegedy = SzegedyQuantumWalk::new(graph);
1446 szegedy.initialize_edge(0, 1);
1447
1448 let edge_probs = szegedy.edge_probabilities();
1449 assert_eq!(edge_probs.len(), 1);
1450 assert_eq!(edge_probs[0].0, (0, 1));
1451 assert!((edge_probs[0].1 - 1.0).abs() < 1e-10);
1452 }
1453
1454 #[test]
1455 fn test_multi_walker_quantum_walk() {
1456 let graph = Graph::new(GraphType::Cycle, 3);
1457 let mut multi_walk = MultiWalkerQuantumWalk::new(graph, 2);
1458
1459 multi_walk
1461 .initialize_positions(&[0, 1])
1462 .expect("Failed to initialize positions in test_multi_walker_quantum_walk");
1463
1464 let marginal_0 = multi_walk.marginal_probabilities(0);
1465 let marginal_1 = multi_walk.marginal_probabilities(1);
1466
1467 assert!((marginal_0[0] - 1.0).abs() < 1e-10);
1468 assert!((marginal_1[1] - 1.0).abs() < 1e-10);
1469
1470 multi_walk.step_independent();
1472
1473 let new_marginal_0 = multi_walk.marginal_probabilities(0);
1475 let new_marginal_1 = multi_walk.marginal_probabilities(1);
1476
1477 assert!(new_marginal_0[0] < 1.0);
1478 assert!(new_marginal_1[1] < 1.0);
1479 }
1480
1481 #[test]
1482 fn test_multi_walker_bell_state() {
1483 let graph = Graph::new(GraphType::Cycle, 4);
1484 let mut multi_walk = MultiWalkerQuantumWalk::new(graph, 2);
1485
1486 multi_walk
1487 .initialize_entangled_bell_state(0, 1)
1488 .expect("Failed to initialize entangled Bell state in test_multi_walker_bell_state");
1489
1490 let marginal_0 = multi_walk.marginal_probabilities(0);
1491 let marginal_1 = multi_walk.marginal_probabilities(1);
1492
1493 assert!((marginal_0[0] - 0.5).abs() < 1e-10);
1495 assert!((marginal_0[1] - 0.5).abs() < 1e-10);
1496 assert!((marginal_1[0] - 0.5).abs() < 1e-10);
1497 assert!((marginal_1[1] - 0.5).abs() < 1e-10);
1498 }
1499
1500 #[test]
1501 fn test_multi_walker_error_handling() {
1502 let graph = Graph::new(GraphType::Line, 3);
1503 let mut multi_walk = MultiWalkerQuantumWalk::new(graph.clone(), 2);
1504
1505 assert!(multi_walk.initialize_positions(&[0]).is_err());
1507
1508 assert!(multi_walk.initialize_positions(&[0, 5]).is_err());
1510
1511 let mut single_walk = MultiWalkerQuantumWalk::new(graph, 1);
1513 assert!(single_walk.initialize_entangled_bell_state(0, 1).is_err());
1514 }
1515
1516 #[test]
1517 fn test_decoherent_quantum_walk() {
1518 let graph = Graph::new(GraphType::Line, 5);
1519 let mut decoherent = DecoherentQuantumWalk::new(graph, CoinOperator::Hadamard, 0.1);
1520
1521 decoherent.initialize_position(2);
1522 let initial_probs = decoherent.position_probabilities();
1523 assert!((initial_probs[2] - 1.0).abs() < 1e-10);
1524
1525 for _ in 0..10 {
1527 decoherent.step();
1528 }
1529
1530 let final_probs = decoherent.position_probabilities();
1531 let total: f64 = final_probs.iter().sum();
1532 assert!((total - 1.0).abs() < 1e-10);
1533
1534 assert!(final_probs[2] < 1.0);
1536 }
1537
1538 #[test]
1539 fn test_decoherence_rate_bounds() {
1540 let graph = Graph::new(GraphType::Cycle, 4);
1541 let mut decoherent = DecoherentQuantumWalk::new(graph, CoinOperator::Grover, 0.5);
1542
1543 decoherent.set_decoherence_rate(-0.1);
1545 decoherent.initialize_position(0);
1546 decoherent.step(); decoherent.set_decoherence_rate(1.5);
1549 decoherent.step(); }
1551
1552 #[test]
1553 fn test_transition_matrix() {
1554 let graph = Graph::new(GraphType::Cycle, 3);
1555 let transition = graph.transition_matrix();
1556
1557 for i in 0..3 {
1559 let mut row_sum = 0.0;
1560 for j in 0..3 {
1561 row_sum += transition[[i, j]];
1562 }
1563 assert!((row_sum - 1.0).abs() < 1e-10);
1564 }
1565 }
1566
1567 #[test]
1568 fn test_normalized_laplacian() {
1569 let graph = Graph::new(GraphType::Complete, 3);
1570 let norm_laplacian = graph.normalized_laplacian_matrix();
1571
1572 for i in 0..3 {
1574 assert!((norm_laplacian[[i, i]] - 1.0).abs() < 1e-10);
1575 }
1576
1577 let expected_off_diag = -1.0 / 2.0; assert!((norm_laplacian[[0, 1]] - expected_off_diag).abs() < 1e-10);
1580 assert!((norm_laplacian[[1, 2]] - expected_off_diag).abs() < 1e-10);
1581 assert!((norm_laplacian[[0, 2]] - expected_off_diag).abs() < 1e-10);
1582 }
1583
1584 #[test]
1585 fn test_algebraic_connectivity() {
1586 let complete_3 = Graph::new(GraphType::Complete, 3);
1587 let connectivity = complete_3.algebraic_connectivity();
1588 assert!(connectivity > 0.0); let line_5 = Graph::new(GraphType::Line, 5);
1591 let line_connectivity = line_5.algebraic_connectivity();
1592 assert!(line_connectivity > 0.0);
1593 }
1594
1595 #[test]
1596 fn test_mixing_time_estimation() {
1597 let graph = Graph::new(GraphType::Complete, 4);
1598 let mut szegedy = SzegedyQuantumWalk::new(graph);
1599
1600 let mixing_time = szegedy.estimate_mixing_time(0.1);
1601 assert!(mixing_time > 0);
1602 assert!(mixing_time <= 1000); }
1604
1605 #[test]
1606 fn test_quantum_walk_search_on_custom_graph() {
1607 let mut graph = Graph::new_empty(5);
1609 for i in 1..5 {
1610 graph.add_edge(0, i);
1611 }
1612
1613 let oracle = SearchOracle::new(vec![3]); let mut search = QuantumWalkSearch::new(graph, oracle);
1615
1616 let (found_vertex, prob, steps) = search.run(20);
1617 assert_eq!(found_vertex, 3);
1618 assert!(prob > 0.0);
1619 assert!(steps <= 20);
1620 }
1621
1622 #[test]
1623 fn test_custom_coin_operator() {
1624 let graph = Graph::new(GraphType::Line, 3);
1625
1626 let mut coin_matrix = Array2::zeros((2, 2));
1628 coin_matrix[[0, 1]] = Complex64::new(1.0, 0.0);
1629 coin_matrix[[1, 0]] = Complex64::new(1.0, 0.0);
1630
1631 let custom_coin = CoinOperator::Custom(coin_matrix);
1632 let mut walk = DiscreteQuantumWalk::new(graph, custom_coin);
1633
1634 walk.initialize_position(1);
1635 walk.step();
1636
1637 let probs = walk.position_probabilities();
1638 let total: f64 = probs.iter().sum();
1639 assert!((total - 1.0).abs() < 1e-10);
1640 }
1641
1642 #[test]
1643 fn test_empty_graph_edge_cases() {
1644 let empty_graph = Graph::new_empty(3);
1645 let mut szegedy = SzegedyQuantumWalk::new(empty_graph);
1646
1647 szegedy.initialize_uniform();
1648 let probs = szegedy.vertex_probabilities();
1649
1650 for &prob in &probs {
1652 assert_eq!(prob, 0.0);
1653 }
1654 }
1655
1656 #[test]
1657 fn test_hypercube_graph() {
1658 let hypercube = Graph::new(GraphType::Hypercube, 3); assert_eq!(hypercube.num_vertices, 8);
1660
1661 for i in 0..8 {
1663 assert_eq!(hypercube.degree(i), 3);
1664 }
1665 }
1666
1667 #[test]
1668 fn test_grid_2d_graph() {
1669 let grid = Graph::new(GraphType::Grid2D, 3); assert_eq!(grid.num_vertices, 9);
1671
1672 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);
1680 }
1681}