1use crate::complex_ext::QuantumComplexExt;
12use ndarray::{Array1, Array2};
13use num_complex::Complex64;
14
15#[derive(Debug, Clone, Copy, PartialEq)]
17pub enum GraphType {
18 Line,
20 Cycle,
22 Complete,
24 Hypercube,
26 Grid2D,
28 Custom,
30}
31
32#[derive(Debug, Clone)]
34pub enum CoinOperator {
35 Hadamard,
37 Grover,
39 DFT,
41 Custom(Array2<Complex64>),
43}
44
45#[derive(Debug, Clone)]
47pub struct SearchOracle {
48 pub marked: Vec<usize>,
50}
51
52impl SearchOracle {
53 pub fn new(marked: Vec<usize>) -> Self {
55 Self { marked }
56 }
57
58 pub fn is_marked(&self, vertex: usize) -> bool {
60 self.marked.contains(&vertex)
61 }
62}
63
64#[derive(Debug, Clone)]
66pub struct Graph {
67 pub num_vertices: usize,
69 pub edges: Vec<Vec<usize>>,
71 pub weights: Option<Vec<Vec<f64>>>,
73}
74
75impl Graph {
76 pub fn new(graph_type: GraphType, size: usize) -> Self {
78 let mut graph = Self {
79 num_vertices: match graph_type {
80 GraphType::Hypercube => 1 << size, GraphType::Grid2D => size * size, _ => size,
83 },
84 edges: vec![],
85 weights: None,
86 };
87
88 graph.edges = vec![Vec::new(); graph.num_vertices];
90
91 match graph_type {
92 GraphType::Line => {
93 for i in 0..size.saturating_sub(1) {
94 graph.add_edge(i, i + 1);
95 }
96 }
97 GraphType::Cycle => {
98 for i in 0..size {
99 graph.add_edge(i, (i + 1) % size);
100 }
101 }
102 GraphType::Complete => {
103 for i in 0..size {
104 for j in i + 1..size {
105 graph.add_edge(i, j);
106 }
107 }
108 }
109 GraphType::Hypercube => {
110 let n = size; for i in 0..(1 << n) {
112 for j in 0..n {
113 let neighbor = i ^ (1 << j);
114 if neighbor > i {
115 graph.add_edge(i, neighbor);
116 }
117 }
118 }
119 }
120 GraphType::Grid2D => {
121 for i in 0..size {
122 for j in 0..size {
123 let idx = i * size + j;
124 if j < size - 1 {
126 graph.add_edge(idx, idx + 1);
127 }
128 if i < size - 1 {
130 graph.add_edge(idx, idx + size);
131 }
132 }
133 }
134 }
135 GraphType::Custom => {
136 }
138 }
139
140 graph
141 }
142
143 pub fn new_empty(num_vertices: usize) -> Self {
145 Self {
146 num_vertices,
147 edges: vec![Vec::new(); num_vertices],
148 weights: None,
149 }
150 }
151
152 pub fn add_edge(&mut self, u: usize, v: usize) {
154 if u < self.num_vertices && v < self.num_vertices && u != v && !self.edges[u].contains(&v) {
155 self.edges[u].push(v);
156 self.edges[v].push(u);
157 }
158 }
159
160 pub fn add_weighted_edge(&mut self, u: usize, v: usize, weight: f64) {
162 if self.weights.is_none() {
163 self.weights = Some(vec![vec![0.0; self.num_vertices]; self.num_vertices]);
164 }
165
166 self.add_edge(u, v);
167
168 if let Some(ref mut weights) = self.weights {
169 weights[u][v] = weight;
170 weights[v][u] = weight;
171 }
172 }
173
174 pub fn degree(&self, vertex: usize) -> usize {
176 if vertex < self.num_vertices {
177 self.edges[vertex].len()
178 } else {
179 0
180 }
181 }
182
183 pub fn adjacency_matrix(&self) -> Array2<f64> {
185 let mut matrix = Array2::zeros((self.num_vertices, self.num_vertices));
186
187 for (u, neighbors) in self.edges.iter().enumerate() {
188 for &v in neighbors {
189 if let Some(ref weights) = self.weights {
190 matrix[[u, v]] = weights[u][v];
191 } else {
192 matrix[[u, v]] = 1.0;
193 }
194 }
195 }
196
197 matrix
198 }
199}
200
201pub struct DiscreteQuantumWalk {
203 graph: Graph,
204 coin_operator: CoinOperator,
205 coin_dimension: usize,
206 hilbert_dim: usize,
208 state: Vec<Complex64>,
210}
211
212impl DiscreteQuantumWalk {
213 pub fn new(graph: Graph, coin_operator: CoinOperator) -> Self {
215 let coin_dimension = match graph.num_vertices {
218 n if n > 0 => {
219 (0..graph.num_vertices)
220 .map(|v| graph.degree(v))
221 .max()
222 .unwrap_or(2)
223 .max(2) }
225 _ => 2,
226 };
227
228 let hilbert_dim = coin_dimension * graph.num_vertices;
229
230 Self {
231 graph,
232 coin_operator,
233 coin_dimension,
234 hilbert_dim,
235 state: vec![Complex64::new(0.0, 0.0); hilbert_dim],
236 }
237 }
238
239 pub fn initialize_position(&mut self, position: usize) {
241 self.state = vec![Complex64::new(0.0, 0.0); self.hilbert_dim];
242
243 let degree = self.graph.degree(position) as f64;
245 if degree > 0.0 {
246 let amplitude = Complex64::new(1.0 / degree.sqrt(), 0.0);
247
248 for coin in 0..self.coin_dimension.min(self.graph.degree(position)) {
249 let index = self.state_index(position, coin);
250 if index < self.state.len() {
251 self.state[index] = amplitude;
252 }
253 }
254 }
255 }
256
257 pub fn step(&mut self) {
259 self.apply_coin();
261
262 self.apply_shift();
264 }
265
266 pub fn position_probabilities(&self) -> Vec<f64> {
268 let mut probs = vec![0.0; self.graph.num_vertices];
269
270 for (vertex, prob) in probs.iter_mut().enumerate() {
271 for coin in 0..self.coin_dimension {
272 let idx = self.state_index(vertex, coin);
273 if idx < self.state.len() {
274 *prob += self.state[idx].norm_sqr();
275 }
276 }
277 }
278
279 probs
280 }
281
282 fn state_index(&self, vertex: usize, coin: usize) -> usize {
284 vertex * self.coin_dimension + coin
285 }
286
287 fn apply_coin(&mut self) {
289 match &self.coin_operator {
290 CoinOperator::Hadamard => self.apply_hadamard_coin(),
291 CoinOperator::Grover => self.apply_grover_coin(),
292 CoinOperator::DFT => self.apply_dft_coin(),
293 CoinOperator::Custom(matrix) => self.apply_custom_coin(matrix.clone()),
294 }
295 }
296
297 fn apply_hadamard_coin(&mut self) {
299 let h = 1.0 / std::f64::consts::SQRT_2;
300
301 for vertex in 0..self.graph.num_vertices {
302 if self.coin_dimension == 2 {
303 let idx0 = self.state_index(vertex, 0);
304 let idx1 = self.state_index(vertex, 1);
305
306 if idx1 < self.state.len() {
307 let a0 = self.state[idx0];
308 let a1 = self.state[idx1];
309
310 self.state[idx0] = h * (a0 + a1);
311 self.state[idx1] = h * (a0 - a1);
312 }
313 }
314 }
315 }
316
317 fn apply_grover_coin(&mut self) {
319 for vertex in 0..self.graph.num_vertices {
321 let degree = self.graph.degree(vertex);
322 if degree <= 1 {
323 continue; }
325
326 let mut sum = Complex64::new(0.0, 0.0);
328 for coin in 0..degree.min(self.coin_dimension) {
329 let idx = self.state_index(vertex, coin);
330 if idx < self.state.len() {
331 sum += self.state[idx];
332 }
333 }
334
335 let factor = Complex64::new(2.0 / degree as f64, 0.0);
337 for coin in 0..degree.min(self.coin_dimension) {
338 let idx = self.state_index(vertex, coin);
339 if idx < self.state.len() {
340 let old_amp = self.state[idx];
341 self.state[idx] = factor * sum - old_amp;
342 }
343 }
344 }
345 }
346
347 fn apply_dft_coin(&mut self) {
349 if self.coin_dimension == 2 {
351 self.apply_hadamard_coin(); }
353 }
355
356 fn apply_custom_coin(&mut self, matrix: Array2<Complex64>) {
358 if matrix.shape() != [self.coin_dimension, self.coin_dimension] {
359 return; }
361
362 for vertex in 0..self.graph.num_vertices {
363 let mut coin_state = vec![Complex64::new(0.0, 0.0); self.coin_dimension];
364
365 for (coin, cs) in coin_state.iter_mut().enumerate() {
367 let idx = self.state_index(vertex, coin);
368 if idx < self.state.len() {
369 *cs = self.state[idx];
370 }
371 }
372
373 let new_coin_state = matrix.dot(&Array1::from(coin_state));
375
376 for coin in 0..self.coin_dimension {
378 let idx = self.state_index(vertex, coin);
379 if idx < self.state.len() {
380 self.state[idx] = new_coin_state[coin];
381 }
382 }
383 }
384 }
385
386 fn apply_shift(&mut self) {
388 let mut new_state = vec![Complex64::new(0.0, 0.0); self.hilbert_dim];
389
390 for vertex in 0..self.graph.num_vertices {
391 for (coin, &neighbor) in self.graph.edges[vertex].iter().enumerate() {
392 if coin < self.coin_dimension {
393 let from_idx = self.state_index(vertex, coin);
394
395 let to_coin = self.graph.edges[neighbor]
397 .iter()
398 .position(|&v| v == vertex)
399 .unwrap_or(0);
400
401 if to_coin < self.coin_dimension && from_idx < self.state.len() {
402 let to_idx = self.state_index(neighbor, to_coin);
403 if to_idx < new_state.len() {
404 new_state[to_idx] = self.state[from_idx];
405 }
406 }
407 }
408 }
409 }
410
411 self.state.copy_from_slice(&new_state);
412 }
413}
414
415pub struct ContinuousQuantumWalk {
417 graph: Graph,
418 hamiltonian: Array2<Complex64>,
419 state: Vec<Complex64>,
420}
421
422impl ContinuousQuantumWalk {
423 pub fn new(graph: Graph) -> Self {
425 let adj_matrix = graph.adjacency_matrix();
426 let hamiltonian = adj_matrix.mapv(|x| Complex64::new(x, 0.0));
427 let num_vertices = graph.num_vertices;
428
429 Self {
430 graph,
431 hamiltonian,
432 state: vec![Complex64::new(0.0, 0.0); num_vertices],
433 }
434 }
435
436 pub fn initialize_vertex(&mut self, vertex: usize) {
438 self.state = vec![Complex64::new(0.0, 0.0); self.graph.num_vertices];
439 if vertex < self.graph.num_vertices {
440 self.state[vertex] = Complex64::new(1.0, 0.0);
441 }
442 }
443
444 pub fn evolve(&mut self, time: f64) {
446 let dt = 0.01; let steps = (time / dt) as usize;
451
452 for _ in 0..steps {
453 let mut new_state = self.state.clone();
454
455 for (i, ns) in new_state
457 .iter_mut()
458 .enumerate()
459 .take(self.graph.num_vertices)
460 {
461 let mut sum = Complex64::new(0.0, 0.0);
462 for j in 0..self.graph.num_vertices {
463 sum += self.hamiltonian[[i, j]] * self.state[j];
464 }
465 *ns = self.state[i] - Complex64::new(0.0, dt) * sum;
466 }
467
468 let norm: f64 = new_state.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
470
471 if norm > 0.0 {
472 for amp in new_state.iter_mut() {
473 *amp /= norm;
474 }
475 }
476
477 self.state = new_state;
478 }
479 }
480
481 pub fn vertex_probabilities(&self) -> Vec<f64> {
483 self.state.iter().map(|c| c.probability()).collect()
484 }
485
486 pub fn transport_probability(&mut self, from: usize, to: usize, time: f64) -> f64 {
488 self.initialize_vertex(from);
490
491 self.evolve(time);
493
494 if to < self.state.len() {
496 self.state[to].probability()
497 } else {
498 0.0
499 }
500 }
501
502 pub fn get_probabilities(&self, state: &[Complex64]) -> Vec<f64> {
504 state.iter().map(|c| c.probability()).collect()
505 }
506}
507
508pub struct QuantumWalkSearch {
510 #[allow(dead_code)]
511 graph: Graph,
512 oracle: SearchOracle,
513 walk: DiscreteQuantumWalk,
514}
515
516impl QuantumWalkSearch {
517 pub fn new(graph: Graph, oracle: SearchOracle) -> Self {
519 let walk = DiscreteQuantumWalk::new(graph.clone(), CoinOperator::Grover);
520 Self {
521 graph,
522 oracle,
523 walk,
524 }
525 }
526
527 fn apply_oracle(&mut self) {
529 for &vertex in &self.oracle.marked {
530 for coin in 0..self.walk.coin_dimension {
531 let idx = self.walk.state_index(vertex, coin);
532 if idx < self.walk.state.len() {
533 self.walk.state[idx] = -self.walk.state[idx]; }
535 }
536 }
537 }
538
539 pub fn run(&mut self, max_steps: usize) -> (usize, f64, usize) {
541 let amplitude = Complex64::new(1.0 / (self.walk.hilbert_dim as f64).sqrt(), 0.0);
543 self.walk.state.fill(amplitude);
544
545 let mut best_vertex = 0;
546 let mut best_prob = 0.0;
547 let mut best_step = 0;
548
549 for step in 1..=max_steps {
551 self.walk.step();
552 self.apply_oracle();
553
554 let probs = self.walk.position_probabilities();
556 for &marked in &self.oracle.marked {
557 if probs[marked] > best_prob {
558 best_prob = probs[marked];
559 best_vertex = marked;
560 best_step = step;
561 }
562 }
563
564 if best_prob > 0.5 {
566 break;
567 }
568 }
569
570 (best_vertex, best_prob, best_step)
571 }
572
573 pub fn vertex_probabilities(&self) -> Vec<f64> {
575 self.walk.position_probabilities()
576 }
577}
578
579pub fn quantum_walk_line_example() {
581 println!("Quantum Walk on a Line (10 vertices)");
582
583 let graph = Graph::new(GraphType::Line, 10);
584 let walk = DiscreteQuantumWalk::new(graph, CoinOperator::Hadamard);
585
586 let mut walk = walk;
588 walk.initialize_position(5);
589
590 for steps in [0, 5, 10, 20, 30] {
592 walk.initialize_position(5);
594 for _ in 0..steps {
595 walk.step();
596 }
597 let probs = walk.position_probabilities();
598
599 println!("\nAfter {} steps:", steps);
600 print!("Probabilities: ");
601 for (v, p) in probs.iter().enumerate() {
602 if *p > 0.01 {
603 print!("v{}: {:.3} ", v, p);
604 }
605 }
606 println!();
607 }
608}
609
610pub fn quantum_walk_search_example() {
612 println!("\nQuantum Walk Search on Complete Graph (8 vertices)");
613
614 let graph = Graph::new(GraphType::Complete, 8);
615 let marked = vec![3, 5]; let oracle = SearchOracle::new(marked.clone());
617
618 let mut search = QuantumWalkSearch::new(graph, oracle);
619
620 println!("Marked vertices: {:?}", marked);
621
622 let (found, prob, steps) = search.run(50);
624
625 println!(
626 "\nFound vertex {} with probability {:.3} after {} steps",
627 found, prob, steps
628 );
629}
630
631#[cfg(test)]
632mod tests {
633 use super::*;
634
635 #[test]
636 fn test_graph_creation() {
637 let graph = Graph::new(GraphType::Cycle, 4);
638 assert_eq!(graph.num_vertices, 4);
639 assert_eq!(graph.degree(0), 2);
640
641 let complete = Graph::new(GraphType::Complete, 5);
642 assert_eq!(complete.degree(0), 4);
643 }
644
645 #[test]
646 fn test_discrete_walk_initialization() {
647 let graph = Graph::new(GraphType::Line, 5);
648 let mut walk = DiscreteQuantumWalk::new(graph, CoinOperator::Hadamard);
649
650 walk.initialize_position(2);
651 let probs = walk.position_probabilities();
652
653 assert!((probs[2] - 1.0).abs() < 1e-10);
654 }
655
656 #[test]
657 fn test_continuous_walk() {
658 let graph = Graph::new(GraphType::Cycle, 4);
659 let mut walk = ContinuousQuantumWalk::new(graph);
660
661 walk.initialize_vertex(0);
662 walk.evolve(1.0);
663
664 let probs = walk.vertex_probabilities();
665 let total: f64 = probs.iter().sum();
666 assert!((total - 1.0).abs() < 1e-10);
667 }
668}