quantrs2_core/
quantum_cellular_automata.rs

1//! Quantum Cellular Automata Simulation
2//!
3//! This module implements quantum cellular automata (QCA) which are quantum
4//! generalizations of classical cellular automata. QCA evolve quantum states
5//! on a lattice according to local unitary rules, providing a model for
6//! quantum computation and many-body quantum dynamics.
7
8use crate::error::{QuantRS2Error, QuantRS2Result};
9use scirs2_core::ndarray::{Array1, Array2, Array3};
10use scirs2_core::Complex64;
11
12/// Types of quantum cellular automata
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum QCAType {
15    /// Partitioned QCA (PQCA) - alternating between different partitions
16    Partitioned,
17    /// Margolus neighborhood QCA - 2x2 blocks with alternating shifts
18    Margolus,
19    /// Moore neighborhood QCA - 3x3 neighborhoods
20    Moore,
21    /// Von Neumann neighborhood QCA - plus-shaped neighborhoods
22    VonNeumann,
23}
24
25/// Boundary conditions for the lattice
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum BoundaryCondition {
28    /// Periodic boundary conditions (toroidal topology)
29    Periodic,
30    /// Fixed boundary conditions (edges are fixed states)
31    Fixed,
32    /// Open boundary conditions (no interactions across boundaries)
33    Open,
34}
35
36/// Update rule for quantum cellular automata
37pub trait QCARule {
38    /// Apply the local update rule to a neighborhood
39    fn apply(&self, neighborhood: &[Complex64]) -> QuantRS2Result<Vec<Complex64>>;
40
41    /// Get the size of the neighborhood this rule operates on
42    fn neighborhood_size(&self) -> usize;
43
44    /// Check if the rule is reversible (unitary)
45    fn is_reversible(&self) -> bool;
46}
47
48/// Quantum rule based on a unitary matrix
49#[derive(Debug, Clone)]
50pub struct UnitaryRule {
51    /// Unitary matrix defining the evolution
52    pub unitary: Array2<Complex64>,
53    /// Number of qubits the rule operates on
54    pub num_qubits: usize,
55}
56
57impl UnitaryRule {
58    /// Create a new unitary rule
59    pub fn new(unitary: Array2<Complex64>) -> QuantRS2Result<Self> {
60        let (rows, cols) = unitary.dim();
61        if rows != cols {
62            return Err(QuantRS2Error::InvalidInput(
63                "Unitary matrix must be square".to_string(),
64            ));
65        }
66
67        // Check if matrix is unitary (U†U = I)
68        let conjugate_transpose = unitary.t().mapv(|x| x.conj());
69        let product = conjugate_transpose.dot(&unitary);
70
71        for i in 0..rows {
72            for j in 0..cols {
73                let expected = if i == j {
74                    Complex64::new(1.0, 0.0)
75                } else {
76                    Complex64::new(0.0, 0.0)
77                };
78                if (product[[i, j]] - expected).norm() > 1e-10 {
79                    return Err(QuantRS2Error::InvalidInput(
80                        "Matrix is not unitary".to_string(),
81                    ));
82                }
83            }
84        }
85
86        let num_qubits = (rows as f64).log2() as usize;
87        if 1 << num_qubits != rows {
88            return Err(QuantRS2Error::InvalidInput(
89                "Matrix dimension must be a power of 2".to_string(),
90            ));
91        }
92
93        Ok(Self {
94            unitary,
95            num_qubits,
96        })
97    }
98
99    /// Create CNOT rule for 2 qubits
100    pub fn cnot() -> Self {
101        let unitary = Array2::from_shape_vec(
102            (4, 4),
103            vec![
104                Complex64::new(1.0, 0.0),
105                Complex64::new(0.0, 0.0),
106                Complex64::new(0.0, 0.0),
107                Complex64::new(0.0, 0.0),
108                Complex64::new(0.0, 0.0),
109                Complex64::new(1.0, 0.0),
110                Complex64::new(0.0, 0.0),
111                Complex64::new(0.0, 0.0),
112                Complex64::new(0.0, 0.0),
113                Complex64::new(0.0, 0.0),
114                Complex64::new(0.0, 0.0),
115                Complex64::new(1.0, 0.0),
116                Complex64::new(0.0, 0.0),
117                Complex64::new(0.0, 0.0),
118                Complex64::new(1.0, 0.0),
119                Complex64::new(0.0, 0.0),
120            ],
121        )
122        .expect("CNOT matrix has valid 4x4 shape");
123
124        Self::new(unitary).expect("CNOT matrix is a valid unitary")
125    }
126
127    /// Create Hadamard rule for 1 qubit
128    pub fn hadamard() -> Self {
129        let sqrt2_inv = 1.0 / 2.0_f64.sqrt();
130        let unitary = Array2::from_shape_vec(
131            (2, 2),
132            vec![
133                Complex64::new(sqrt2_inv, 0.0),
134                Complex64::new(sqrt2_inv, 0.0),
135                Complex64::new(sqrt2_inv, 0.0),
136                Complex64::new(-sqrt2_inv, 0.0),
137            ],
138        )
139        .expect("Hadamard matrix has valid 2x2 shape");
140
141        Self::new(unitary).expect("Hadamard matrix is a valid unitary")
142    }
143
144    /// Create Toffoli rule for 3 qubits
145    pub fn toffoli() -> Self {
146        let mut unitary = Array2::zeros((8, 8));
147
148        // Identity for most states
149        for i in 0..6 {
150            unitary[[i, i]] = Complex64::new(1.0, 0.0);
151        }
152
153        // Swap last two states (|110⟩ ↔ |111⟩)
154        unitary[[6, 7]] = Complex64::new(1.0, 0.0);
155        unitary[[7, 6]] = Complex64::new(1.0, 0.0);
156
157        Self::new(unitary).expect("Toffoli matrix is a valid unitary")
158    }
159}
160
161impl QCARule for UnitaryRule {
162    fn apply(&self, neighborhood: &[Complex64]) -> QuantRS2Result<Vec<Complex64>> {
163        if neighborhood.len() != 1 << self.num_qubits {
164            return Err(QuantRS2Error::InvalidInput(
165                "Neighborhood size doesn't match rule dimension".to_string(),
166            ));
167        }
168
169        let state = Array1::from_vec(neighborhood.to_vec());
170        let evolved = self.unitary.dot(&state);
171        Ok(evolved.to_vec())
172    }
173
174    fn neighborhood_size(&self) -> usize {
175        1 << self.num_qubits
176    }
177
178    fn is_reversible(&self) -> bool {
179        true // All unitary rules are reversible
180    }
181}
182
183/// Quantum cellular automaton on a 1D lattice
184pub struct QuantumCellularAutomaton1D {
185    /// Number of sites in the lattice
186    pub num_sites: usize,
187    /// Current state of each site (amplitude for |0⟩ and |1⟩)
188    pub state: Array2<Complex64>,
189    /// Update rule
190    rule: Box<dyn QCARule + Send + Sync>,
191    /// Boundary conditions
192    boundary: BoundaryCondition,
193    /// QCA type
194    qca_type: QCAType,
195    /// Current time step
196    pub time_step: usize,
197}
198
199impl QuantumCellularAutomaton1D {
200    /// Create a new 1D quantum cellular automaton
201    pub fn new(
202        num_sites: usize,
203        rule: Box<dyn QCARule + Send + Sync>,
204        boundary: BoundaryCondition,
205        qca_type: QCAType,
206    ) -> Self {
207        // Initialize with all sites in |0⟩ state
208        let mut state = Array2::zeros((num_sites, 2));
209        for i in 0..num_sites {
210            state[[i, 0]] = Complex64::new(1.0, 0.0); // |0⟩ state
211        }
212
213        Self {
214            num_sites,
215            state,
216            rule,
217            boundary,
218            qca_type,
219            time_step: 0,
220        }
221    }
222
223    /// Set the state of a specific site
224    pub fn set_site_state(
225        &mut self,
226        site: usize,
227        amplitudes: [Complex64; 2],
228    ) -> QuantRS2Result<()> {
229        if site >= self.num_sites {
230            return Err(QuantRS2Error::InvalidInput(
231                "Site index out of bounds".to_string(),
232            ));
233        }
234
235        // Normalize the state
236        let norm = (amplitudes[0].norm_sqr() + amplitudes[1].norm_sqr()).sqrt();
237        if norm < 1e-10 {
238            return Err(QuantRS2Error::InvalidInput(
239                "State cannot have zero norm".to_string(),
240            ));
241        }
242
243        self.state[[site, 0]] = amplitudes[0] / norm;
244        self.state[[site, 1]] = amplitudes[1] / norm;
245
246        Ok(())
247    }
248
249    /// Get the state of a specific site
250    pub fn get_site_state(&self, site: usize) -> QuantRS2Result<[Complex64; 2]> {
251        if site >= self.num_sites {
252            return Err(QuantRS2Error::InvalidInput(
253                "Site index out of bounds".to_string(),
254            ));
255        }
256
257        Ok([self.state[[site, 0]], self.state[[site, 1]]])
258    }
259
260    /// Initialize with a random state
261    pub fn initialize_random(
262        &mut self,
263        rng: &mut dyn scirs2_core::random::RngCore,
264    ) -> QuantRS2Result<()> {
265        use scirs2_core::random::prelude::*;
266
267        for i in 0..self.num_sites {
268            let theta = rng.gen_range(0.0..std::f64::consts::PI);
269            let phi = rng.gen_range(0.0..2.0 * std::f64::consts::PI);
270
271            let amp0 = Complex64::new(theta.cos(), 0.0);
272            let amp1 = Complex64::new(theta.sin() * phi.cos(), theta.sin() * phi.sin());
273
274            self.set_site_state(i, [amp0, amp1])?;
275        }
276
277        Ok(())
278    }
279
280    /// Perform one evolution step
281    pub fn step(&mut self) -> QuantRS2Result<()> {
282        match self.qca_type {
283            QCAType::Partitioned => self.step_partitioned(),
284            QCAType::Margolus => self.step_margolus(),
285            QCAType::Moore => self.step_moore(),
286            QCAType::VonNeumann => self.step_von_neumann(),
287        }
288    }
289
290    /// Partitioned QCA step (alternating even/odd partitions)
291    fn step_partitioned(&mut self) -> QuantRS2Result<()> {
292        let rule_size = self.rule.neighborhood_size();
293        let num_qubits = (rule_size as f64).log2() as usize;
294
295        if num_qubits != 2 {
296            return Err(QuantRS2Error::InvalidInput(
297                "Partitioned QCA currently supports only 2-qubit rules".to_string(),
298            ));
299        }
300
301        let mut new_state = self.state.clone();
302        let offset = self.time_step % 2;
303
304        // Apply rule to neighboring pairs
305        for i in (offset..self.num_sites.saturating_sub(1)).step_by(2) {
306            let j = (i + 1) % self.num_sites;
307
308            // Get neighborhood state vector
309            let neighborhood = self.get_pair_state_vector(i, j)?;
310
311            // Apply rule
312            let evolved = self.rule.apply(&neighborhood)?;
313
314            // Update states
315            new_state[[i, 0]] = evolved[0];
316            new_state[[i, 1]] = evolved[1];
317            new_state[[j, 0]] = evolved[2];
318            new_state[[j, 1]] = evolved[3];
319        }
320
321        self.state = new_state;
322        self.time_step += 1;
323        Ok(())
324    }
325
326    /// Get state vector for a pair of sites
327    fn get_pair_state_vector(&self, site1: usize, site2: usize) -> QuantRS2Result<Vec<Complex64>> {
328        let amp1_0 = self.state[[site1, 0]];
329        let amp1_1 = self.state[[site1, 1]];
330        let amp2_0 = self.state[[site2, 0]];
331        let amp2_1 = self.state[[site2, 1]];
332
333        // Tensor product: |site1⟩ ⊗ |site2⟩
334        Ok(vec![
335            amp1_0 * amp2_0, // |00⟩
336            amp1_0 * amp2_1, // |01⟩
337            amp1_1 * amp2_0, // |10⟩
338            amp1_1 * amp2_1, // |11⟩
339        ])
340    }
341
342    /// Margolus neighborhood step (2x2 blocks, alternating)
343    fn step_margolus(&mut self) -> QuantRS2Result<()> {
344        // For 1D, Margolus reduces to partitioned with 2-site neighborhoods
345        self.step_partitioned()
346    }
347
348    /// Moore neighborhood step (3x3 neighborhoods in 2D, 3-site in 1D)
349    fn step_moore(&mut self) -> QuantRS2Result<()> {
350        let rule_size = self.rule.neighborhood_size();
351        if rule_size != 8 {
352            // 3 qubits = 8 dimensional Hilbert space
353            return Err(QuantRS2Error::InvalidInput(
354                "Moore neighborhood requires 3-qubit rule".to_string(),
355            ));
356        }
357
358        let mut new_state = self.state.clone();
359
360        for i in 0..self.num_sites {
361            let left = self.get_neighbor(i, -1);
362            let center = i;
363            let right = self.get_neighbor(i, 1);
364
365            // Get 3-site neighborhood state
366            let neighborhood = self.get_triple_state_vector(left, center, right)?;
367
368            // Apply rule
369            let evolved = self.rule.apply(&neighborhood)?;
370
371            // Update center site only
372            new_state[[center, 0]] = evolved[2]; // Extract center qubit from |abc⟩ -> |b⟩
373            new_state[[center, 1]] = evolved[6];
374        }
375
376        self.state = new_state;
377        self.time_step += 1;
378        Ok(())
379    }
380
381    /// Von Neumann neighborhood step (plus-shaped, center + 4 neighbors)
382    fn step_von_neumann(&mut self) -> QuantRS2Result<()> {
383        // For 1D, Von Neumann is same as Moore (3 neighbors)
384        self.step_moore()
385    }
386
387    /// Get neighbor index with boundary conditions
388    const fn get_neighbor(&self, site: usize, offset: isize) -> usize {
389        match self.boundary {
390            BoundaryCondition::Periodic => {
391                let new_site = site as isize + offset;
392                ((new_site % self.num_sites as isize + self.num_sites as isize)
393                    % self.num_sites as isize) as usize
394            }
395            BoundaryCondition::Fixed | BoundaryCondition::Open => {
396                let new_site = site as isize + offset;
397                if new_site < 0 {
398                    0
399                } else if new_site >= self.num_sites as isize {
400                    self.num_sites - 1
401                } else {
402                    new_site as usize
403                }
404            }
405        }
406    }
407
408    /// Get state vector for three sites
409    fn get_triple_state_vector(
410        &self,
411        site1: usize,
412        site2: usize,
413        site3: usize,
414    ) -> QuantRS2Result<Vec<Complex64>> {
415        let mut state_vector = vec![Complex64::new(0.0, 0.0); 8];
416
417        // Build 3-qubit state vector
418        for i in 0..2 {
419            for j in 0..2 {
420                for k in 0..2 {
421                    let amp =
422                        self.state[[site1, i]] * self.state[[site2, j]] * self.state[[site3, k]];
423                    let idx = 4 * i + 2 * j + k;
424                    state_vector[idx] = amp;
425                }
426            }
427        }
428
429        Ok(state_vector)
430    }
431
432    /// Calculate the entanglement entropy of a region
433    pub fn entanglement_entropy(
434        &self,
435        region_start: usize,
436        region_size: usize,
437    ) -> QuantRS2Result<f64> {
438        if region_start + region_size > self.num_sites {
439            return Err(QuantRS2Error::InvalidInput(
440                "Region extends beyond lattice".to_string(),
441            ));
442        }
443
444        // For full implementation, we'd need to compute the reduced density matrix
445        // This is a simplified approximation based on site entropies
446        let mut entropy = 0.0;
447
448        for i in region_start..region_start + region_size {
449            let p0 = self.state[[i, 0]].norm_sqr();
450            let p1 = self.state[[i, 1]].norm_sqr();
451
452            if p0 > 1e-12 {
453                entropy -= p0 * p0.ln();
454            }
455            if p1 > 1e-12 {
456                entropy -= p1 * p1.ln();
457            }
458        }
459
460        Ok(entropy)
461    }
462
463    /// Get probability distribution at each site
464    pub fn site_probabilities(&self) -> Vec<[f64; 2]> {
465        self.state
466            .rows()
467            .into_iter()
468            .map(|row| [row[0].norm_sqr(), row[1].norm_sqr()])
469            .collect()
470    }
471
472    /// Compute correlation function between two sites
473    pub fn correlation(&self, site1: usize, site2: usize) -> QuantRS2Result<Complex64> {
474        if site1 >= self.num_sites || site2 >= self.num_sites {
475            return Err(QuantRS2Error::InvalidInput(
476                "Site index out of bounds".to_string(),
477            ));
478        }
479
480        // Simplified correlation: ⟨Z_i Z_j⟩
481        let z1 = self.state[[site1, 0]].norm_sqr() - self.state[[site1, 1]].norm_sqr();
482        let z2 = self.state[[site2, 0]].norm_sqr() - self.state[[site2, 1]].norm_sqr();
483
484        Ok(Complex64::new(z1 * z2, 0.0))
485    }
486}
487
488impl std::fmt::Debug for QuantumCellularAutomaton1D {
489    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
490        f.debug_struct("QuantumCellularAutomaton1D")
491            .field("num_sites", &self.num_sites)
492            .field("boundary", &self.boundary)
493            .field("qca_type", &self.qca_type)
494            .field("time_step", &self.time_step)
495            .finish()
496    }
497}
498
499/// 2D Quantum cellular automaton
500pub struct QuantumCellularAutomaton2D {
501    /// Width of the lattice
502    pub width: usize,
503    /// Height of the lattice
504    pub height: usize,
505    /// Current state (width × height × 2 for qubit amplitudes)
506    pub state: Array3<Complex64>,
507    /// Update rule
508    rule: Box<dyn QCARule + Send + Sync>,
509    /// Boundary conditions
510    boundary: BoundaryCondition,
511    /// QCA type
512    qca_type: QCAType,
513    /// Current time step
514    pub time_step: usize,
515}
516
517impl QuantumCellularAutomaton2D {
518    /// Create a new 2D quantum cellular automaton
519    pub fn new(
520        width: usize,
521        height: usize,
522        rule: Box<dyn QCARule + Send + Sync>,
523        boundary: BoundaryCondition,
524        qca_type: QCAType,
525    ) -> Self {
526        // Initialize with all sites in |0⟩ state
527        let mut state = Array3::zeros((width, height, 2));
528        for i in 0..width {
529            for j in 0..height {
530                state[[i, j, 0]] = Complex64::new(1.0, 0.0); // |0⟩ state
531            }
532        }
533
534        Self {
535            width,
536            height,
537            state,
538            rule,
539            boundary,
540            qca_type,
541            time_step: 0,
542        }
543    }
544
545    /// Set the state of a specific site
546    pub fn set_site_state(
547        &mut self,
548        x: usize,
549        y: usize,
550        amplitudes: [Complex64; 2],
551    ) -> QuantRS2Result<()> {
552        if x >= self.width || y >= self.height {
553            return Err(QuantRS2Error::InvalidInput(
554                "Site coordinates out of bounds".to_string(),
555            ));
556        }
557
558        // Normalize the state
559        let norm = (amplitudes[0].norm_sqr() + amplitudes[1].norm_sqr()).sqrt();
560        if norm < 1e-10 {
561            return Err(QuantRS2Error::InvalidInput(
562                "State cannot have zero norm".to_string(),
563            ));
564        }
565
566        self.state[[x, y, 0]] = amplitudes[0] / norm;
567        self.state[[x, y, 1]] = amplitudes[1] / norm;
568
569        Ok(())
570    }
571
572    /// Get the state of a specific site
573    pub fn get_site_state(&self, x: usize, y: usize) -> QuantRS2Result<[Complex64; 2]> {
574        if x >= self.width || y >= self.height {
575            return Err(QuantRS2Error::InvalidInput(
576                "Site coordinates out of bounds".to_string(),
577            ));
578        }
579
580        Ok([self.state[[x, y, 0]], self.state[[x, y, 1]]])
581    }
582
583    /// Perform one evolution step
584    pub fn step(&mut self) -> QuantRS2Result<()> {
585        match self.qca_type {
586            QCAType::Margolus => self.step_margolus_2d(),
587            QCAType::Moore => self.step_moore_2d(),
588            QCAType::VonNeumann => self.step_von_neumann_2d(),
589            QCAType::Partitioned => self.step_partitioned_2d(),
590        }
591    }
592
593    /// Margolus neighborhood step for 2D (2×2 blocks)
594    fn step_margolus_2d(&mut self) -> QuantRS2Result<()> {
595        let rule_size = self.rule.neighborhood_size();
596        if rule_size != 16 {
597            // 4 qubits = 16 dimensional Hilbert space
598            return Err(QuantRS2Error::InvalidInput(
599                "2D Margolus requires 4-qubit rule".to_string(),
600            ));
601        }
602
603        let mut new_state = self.state.clone();
604        let x_offset = self.time_step % 2;
605        let y_offset = self.time_step % 2;
606
607        // Process 2×2 blocks
608        for i in (x_offset..self.width.saturating_sub(1)).step_by(2) {
609            for j in (y_offset..self.height.saturating_sub(1)).step_by(2) {
610                let block_state = self.get_block_state_vector(i, j)?;
611                let evolved = self.rule.apply(&block_state)?;
612
613                // Update the 2×2 block
614                for (idx, &val) in evolved.iter().enumerate() {
615                    let local_x = idx % 2;
616                    let local_y = (idx / 2) % 2;
617                    let qubit = (idx / 4) % 2;
618
619                    if i + local_x < self.width && j + local_y < self.height {
620                        new_state[[i + local_x, j + local_y, qubit]] = val;
621                    }
622                }
623            }
624        }
625
626        self.state = new_state;
627        self.time_step += 1;
628        Ok(())
629    }
630
631    /// Get state vector for a 2×2 block
632    fn get_block_state_vector(
633        &self,
634        start_x: usize,
635        start_y: usize,
636    ) -> QuantRS2Result<Vec<Complex64>> {
637        let mut state_vector = vec![Complex64::new(0.0, 0.0); 16];
638
639        // Get states of 4 sites in the block
640        let sites = [
641            (start_x, start_y),
642            (start_x + 1, start_y),
643            (start_x, start_y + 1),
644            (start_x + 1, start_y + 1),
645        ];
646
647        // Build 4-qubit state vector
648        for i in 0..2 {
649            for j in 0..2 {
650                for k in 0..2 {
651                    for l in 0..2 {
652                        let amp = self.state[[sites[0].0, sites[0].1, i]]
653                            * self.state[[sites[1].0, sites[1].1, j]]
654                            * self.state[[sites[2].0, sites[2].1, k]]
655                            * self.state[[sites[3].0, sites[3].1, l]];
656
657                        let idx = 8 * i + 4 * j + 2 * k + l;
658                        state_vector[idx] = amp;
659                    }
660                }
661            }
662        }
663
664        Ok(state_vector)
665    }
666
667    /// Moore neighborhood step for 2D (3×3 neighborhoods)
668    fn step_moore_2d(&mut self) -> QuantRS2Result<()> {
669        // This would require 9-qubit rules, which is very large
670        // For practical purposes, we'll use a simplified version
671        self.step_von_neumann_2d()
672    }
673
674    /// Von Neumann neighborhood step for 2D (plus-shaped)
675    fn step_von_neumann_2d(&mut self) -> QuantRS2Result<()> {
676        let rule_size = self.rule.neighborhood_size();
677        if rule_size != 32 {
678            // 5 qubits = 32 dimensional Hilbert space
679            return Err(QuantRS2Error::InvalidInput(
680                "2D Von Neumann requires 5-qubit rule".to_string(),
681            ));
682        }
683
684        let mut new_state = self.state.clone();
685
686        for i in 0..self.width {
687            for j in 0..self.height {
688                // Get Von Neumann neighborhood (center + 4 neighbors)
689                let neighbors = self.get_von_neumann_neighbors(i, j);
690                let neighborhood_state = self.get_neighborhood_state_vector(&neighbors)?;
691
692                // Apply rule
693                let evolved = self.rule.apply(&neighborhood_state)?;
694
695                // Update center site only
696                new_state[[i, j, 0]] = evolved[16]; // Center qubit |0⟩ component
697                new_state[[i, j, 1]] = evolved[24]; // Center qubit |1⟩ component
698            }
699        }
700
701        self.state = new_state;
702        self.time_step += 1;
703        Ok(())
704    }
705
706    /// Partitioned step for 2D
707    fn step_partitioned_2d(&mut self) -> QuantRS2Result<()> {
708        // Apply horizontal and vertical 2-qubit rules alternately
709        if self.time_step % 2 == 0 {
710            self.step_horizontal_pairs()
711        } else {
712            self.step_vertical_pairs()
713        }
714    }
715
716    /// Apply rule to horizontal pairs
717    fn step_horizontal_pairs(&mut self) -> QuantRS2Result<()> {
718        let rule_size = self.rule.neighborhood_size();
719        if rule_size != 4 {
720            // 2 qubits = 4 dimensional
721            return Err(QuantRS2Error::InvalidInput(
722                "Horizontal pairs require 2-qubit rule".to_string(),
723            ));
724        }
725
726        let mut new_state = self.state.clone();
727
728        for j in 0..self.height {
729            for i in (0..self.width.saturating_sub(1)).step_by(2) {
730                let pair_state = self.get_pair_state_vector_2d(i, j, i + 1, j)?;
731                let evolved = self.rule.apply(&pair_state)?;
732
733                // Update pair
734                new_state[[i, j, 0]] = evolved[0];
735                new_state[[i, j, 1]] = evolved[1];
736                new_state[[i + 1, j, 0]] = evolved[2];
737                new_state[[i + 1, j, 1]] = evolved[3];
738            }
739        }
740
741        self.state = new_state;
742        self.time_step += 1;
743        Ok(())
744    }
745
746    /// Apply rule to vertical pairs
747    fn step_vertical_pairs(&mut self) -> QuantRS2Result<()> {
748        let rule_size = self.rule.neighborhood_size();
749        if rule_size != 4 {
750            // 2 qubits = 4 dimensional
751            return Err(QuantRS2Error::InvalidInput(
752                "Vertical pairs require 2-qubit rule".to_string(),
753            ));
754        }
755
756        let mut new_state = self.state.clone();
757
758        for i in 0..self.width {
759            for j in (0..self.height.saturating_sub(1)).step_by(2) {
760                let pair_state = self.get_pair_state_vector_2d(i, j, i, j + 1)?;
761                let evolved = self.rule.apply(&pair_state)?;
762
763                // Update pair
764                new_state[[i, j, 0]] = evolved[0];
765                new_state[[i, j, 1]] = evolved[1];
766                new_state[[i, j + 1, 0]] = evolved[2];
767                new_state[[i, j + 1, 1]] = evolved[3];
768            }
769        }
770
771        self.state = new_state;
772        self.time_step += 1;
773        Ok(())
774    }
775
776    /// Get Von Neumann neighbors (up, down, left, right, center)
777    fn get_von_neumann_neighbors(&self, x: usize, y: usize) -> Vec<(usize, usize)> {
778        vec![
779            (x, y),                          // center
780            (self.get_neighbor_x(x, -1), y), // left
781            (self.get_neighbor_x(x, 1), y),  // right
782            (x, self.get_neighbor_y(y, -1)), // up
783            (x, self.get_neighbor_y(y, 1)),  // down
784        ]
785    }
786
787    /// Get neighbor x-coordinate with boundary conditions
788    const fn get_neighbor_x(&self, x: usize, offset: isize) -> usize {
789        match self.boundary {
790            BoundaryCondition::Periodic => {
791                let new_x = x as isize + offset;
792                ((new_x % self.width as isize + self.width as isize) % self.width as isize) as usize
793            }
794            BoundaryCondition::Fixed | BoundaryCondition::Open => {
795                let new_x = x as isize + offset;
796                if new_x < 0 {
797                    0
798                } else if new_x >= self.width as isize {
799                    self.width - 1
800                } else {
801                    new_x as usize
802                }
803            }
804        }
805    }
806
807    /// Get neighbor y-coordinate with boundary conditions
808    const fn get_neighbor_y(&self, y: usize, offset: isize) -> usize {
809        match self.boundary {
810            BoundaryCondition::Periodic => {
811                let new_y = y as isize + offset;
812                ((new_y % self.height as isize + self.height as isize) % self.height as isize)
813                    as usize
814            }
815            BoundaryCondition::Fixed | BoundaryCondition::Open => {
816                let new_y = y as isize + offset;
817                if new_y < 0 {
818                    0
819                } else if new_y >= self.height as isize {
820                    self.height - 1
821                } else {
822                    new_y as usize
823                }
824            }
825        }
826    }
827
828    /// Get state vector for neighborhood sites
829    fn get_neighborhood_state_vector(
830        &self,
831        sites: &[(usize, usize)],
832    ) -> QuantRS2Result<Vec<Complex64>> {
833        let num_sites = sites.len();
834        let state_size = 1 << num_sites;
835        let mut state_vector = vec![Complex64::new(0.0, 0.0); state_size];
836
837        // This is a simplified implementation - full tensor product would be expensive
838        // For now, return a simplified state
839        state_vector[0] = Complex64::new(1.0, 0.0);
840        Ok(state_vector)
841    }
842
843    /// Get pair state vector for 2D coordinates
844    fn get_pair_state_vector_2d(
845        &self,
846        x1: usize,
847        y1: usize,
848        x2: usize,
849        y2: usize,
850    ) -> QuantRS2Result<Vec<Complex64>> {
851        let amp1_0 = self.state[[x1, y1, 0]];
852        let amp1_1 = self.state[[x1, y1, 1]];
853        let amp2_0 = self.state[[x2, y2, 0]];
854        let amp2_1 = self.state[[x2, y2, 1]];
855
856        Ok(vec![
857            amp1_0 * amp2_0, // |00⟩
858            amp1_0 * amp2_1, // |01⟩
859            amp1_1 * amp2_0, // |10⟩
860            amp1_1 * amp2_1, // |11⟩
861        ])
862    }
863
864    /// Get total probability distribution across the lattice
865    pub fn probability_distribution(&self) -> Array3<f64> {
866        let mut probs = Array3::zeros((self.width, self.height, 2));
867
868        for i in 0..self.width {
869            for j in 0..self.height {
870                probs[[i, j, 0]] = self.state[[i, j, 0]].norm_sqr();
871                probs[[i, j, 1]] = self.state[[i, j, 1]].norm_sqr();
872            }
873        }
874
875        probs
876    }
877
878    /// Calculate magnetization of the lattice
879    pub fn magnetization(&self) -> f64 {
880        let mut total_magnetization = 0.0;
881
882        for i in 0..self.width {
883            for j in 0..self.height {
884                let prob_0 = self.state[[i, j, 0]].norm_sqr();
885                let prob_1 = self.state[[i, j, 1]].norm_sqr();
886                total_magnetization += prob_0 - prob_1; // Z expectation value
887            }
888        }
889
890        total_magnetization / (self.width * self.height) as f64
891    }
892}
893
894impl std::fmt::Debug for QuantumCellularAutomaton2D {
895    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
896        f.debug_struct("QuantumCellularAutomaton2D")
897            .field("width", &self.width)
898            .field("height", &self.height)
899            .field("boundary", &self.boundary)
900            .field("qca_type", &self.qca_type)
901            .field("time_step", &self.time_step)
902            .finish()
903    }
904}
905
906#[cfg(test)]
907mod tests {
908    use super::*;
909
910    #[test]
911    fn test_unitary_rule_creation() {
912        let hadamard = UnitaryRule::hadamard();
913        assert_eq!(hadamard.num_qubits, 1);
914        assert!(hadamard.is_reversible());
915
916        let cnot = UnitaryRule::cnot();
917        assert_eq!(cnot.num_qubits, 2);
918        assert!(cnot.is_reversible());
919    }
920
921    #[test]
922    fn test_1d_qca_initialization() {
923        let rule = Box::new(UnitaryRule::cnot());
924        let qca = QuantumCellularAutomaton1D::new(
925            10,
926            rule,
927            BoundaryCondition::Periodic,
928            QCAType::Partitioned,
929        );
930
931        assert_eq!(qca.num_sites, 10);
932        assert_eq!(qca.time_step, 0);
933
934        // Check initial state (all |0⟩)
935        for i in 0..10 {
936            let state = qca
937                .get_site_state(i)
938                .expect("Site state should be retrievable");
939            assert!((state[0] - Complex64::new(1.0, 0.0)).norm() < 1e-10);
940            assert!((state[1] - Complex64::new(0.0, 0.0)).norm() < 1e-10);
941        }
942    }
943
944    #[test]
945    fn test_1d_qca_evolution() {
946        let rule = Box::new(UnitaryRule::cnot());
947        let mut qca = QuantumCellularAutomaton1D::new(
948            4,
949            rule,
950            BoundaryCondition::Periodic,
951            QCAType::Partitioned,
952        );
953
954        // Set middle site to |1⟩
955        qca.set_site_state(1, [Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)])
956            .expect("Site state should be set successfully");
957
958        // Evolve one step
959        qca.step().expect("QCA evolution step should succeed");
960        assert_eq!(qca.time_step, 1);
961
962        // Check that evolution occurred
963        let probs = qca.site_probabilities();
964        assert!(probs.len() == 4);
965    }
966
967    #[test]
968    fn test_2d_qca_initialization() {
969        let rule = Box::new(UnitaryRule::cnot());
970        let qca = QuantumCellularAutomaton2D::new(
971            5,
972            5,
973            rule,
974            BoundaryCondition::Periodic,
975            QCAType::Margolus,
976        );
977
978        assert_eq!(qca.width, 5);
979        assert_eq!(qca.height, 5);
980        assert_eq!(qca.time_step, 0);
981
982        // Check initial state
983        for i in 0..5 {
984            for j in 0..5 {
985                let state = qca
986                    .get_site_state(i, j)
987                    .expect("Site state should be retrievable");
988                assert!((state[0] - Complex64::new(1.0, 0.0)).norm() < 1e-10);
989                assert!((state[1] - Complex64::new(0.0, 0.0)).norm() < 1e-10);
990            }
991        }
992    }
993
994    #[test]
995    fn test_entanglement_entropy() {
996        let rule = Box::new(UnitaryRule::cnot());
997        let mut qca = QuantumCellularAutomaton1D::new(
998            4,
999            rule,
1000            BoundaryCondition::Periodic,
1001            QCAType::Partitioned,
1002        );
1003
1004        // Create some mixed state
1005        let sqrt2_inv = 1.0 / 2.0_f64.sqrt();
1006        qca.set_site_state(
1007            0,
1008            [
1009                Complex64::new(sqrt2_inv, 0.0),
1010                Complex64::new(sqrt2_inv, 0.0),
1011            ],
1012        )
1013        .expect("Site state should be set successfully");
1014
1015        let entropy = qca
1016            .entanglement_entropy(0, 2)
1017            .expect("Entanglement entropy calculation should succeed");
1018        assert!(entropy >= 0.0);
1019    }
1020
1021    #[test]
1022    fn test_correlation_function() {
1023        let rule = Box::new(UnitaryRule::cnot());
1024        let mut qca = QuantumCellularAutomaton1D::new(
1025            4,
1026            rule,
1027            BoundaryCondition::Periodic,
1028            QCAType::Partitioned,
1029        );
1030
1031        // Set sites to definite states
1032        qca.set_site_state(0, [Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)])
1033            .expect("Site 0 state should be set to |0>"); // |0⟩
1034        qca.set_site_state(1, [Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)])
1035            .expect("Site 1 state should be set to |1>"); // |1⟩
1036
1037        let correlation = qca
1038            .correlation(0, 1)
1039            .expect("Correlation calculation should succeed");
1040        assert!((correlation - Complex64::new(-1.0, 0.0)).norm() < 1e-10);
1041    }
1042
1043    #[test]
1044    fn test_2d_magnetization() {
1045        let rule = Box::new(UnitaryRule::cnot());
1046        let mut qca = QuantumCellularAutomaton2D::new(
1047            3,
1048            3,
1049            rule,
1050            BoundaryCondition::Periodic,
1051            QCAType::Partitioned,
1052        );
1053
1054        // Set all sites to |0⟩ (already the default)
1055        let magnetization = qca.magnetization();
1056        assert!((magnetization - 1.0).abs() < 1e-10);
1057
1058        // Set all sites to |1⟩
1059        for i in 0..3 {
1060            for j in 0..3 {
1061                qca.set_site_state(i, j, [Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)])
1062                    .expect("Site state should be set to |1>");
1063            }
1064        }
1065
1066        let magnetization = qca.magnetization();
1067        assert!((magnetization - (-1.0)).abs() < 1e-10);
1068    }
1069
1070    #[test]
1071    fn test_toffoli_rule() {
1072        let toffoli = UnitaryRule::toffoli();
1073        assert_eq!(toffoli.num_qubits, 3);
1074        assert_eq!(toffoli.neighborhood_size(), 8);
1075
1076        // Test that |110⟩ → |111⟩
1077        let input = vec![
1078            Complex64::new(0.0, 0.0), // |000⟩
1079            Complex64::new(0.0, 0.0), // |001⟩
1080            Complex64::new(0.0, 0.0), // |010⟩
1081            Complex64::new(0.0, 0.0), // |011⟩
1082            Complex64::new(0.0, 0.0), // |100⟩
1083            Complex64::new(0.0, 0.0), // |101⟩
1084            Complex64::new(1.0, 0.0), // |110⟩
1085            Complex64::new(0.0, 0.0), // |111⟩
1086        ];
1087
1088        let output = toffoli
1089            .apply(&input)
1090            .expect("Toffoli rule application should succeed");
1091
1092        // Should flip to |111⟩
1093        assert!((output[6] - Complex64::new(0.0, 0.0)).norm() < 1e-10);
1094        assert!((output[7] - Complex64::new(1.0, 0.0)).norm() < 1e-10);
1095    }
1096}