Skip to main content

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.random_range(0.0..std::f64::consts::PI);
269            let phi = rng.random_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    ///
830    /// Builds the full `2^N` tensor product of the per-site single-qubit states
831    /// over `sites`. The amplitude of basis index `b` is the product over each
832    /// site `k` of `self.state[[x_k, y_k, bit_k(b)]]`, where `bit_k(b)` is the
833    /// bit of `b` for site `k`. The convention matches the sibling
834    /// [`Self::get_pair_state_vector_2d`]: site 0 is the most-significant bit, so
835    /// for `N` sites `bit_k(b) = (b >> (N - 1 - k)) & 1`.
836    fn get_neighborhood_state_vector(
837        &self,
838        sites: &[(usize, usize)],
839    ) -> QuantRS2Result<Vec<Complex64>> {
840        let num_sites = sites.len();
841        let state_size = 1usize << num_sites;
842        let mut state_vector = vec![Complex64::new(1.0, 0.0); state_size];
843
844        for (basis_index, amplitude) in state_vector.iter_mut().enumerate() {
845            for (site_index, &(x, y)) in sites.iter().enumerate() {
846                let bit = (basis_index >> (num_sites - 1 - site_index)) & 1;
847                *amplitude *= self.state[[x, y, bit]];
848            }
849        }
850
851        Ok(state_vector)
852    }
853
854    /// Get pair state vector for 2D coordinates
855    fn get_pair_state_vector_2d(
856        &self,
857        x1: usize,
858        y1: usize,
859        x2: usize,
860        y2: usize,
861    ) -> QuantRS2Result<Vec<Complex64>> {
862        let amp1_0 = self.state[[x1, y1, 0]];
863        let amp1_1 = self.state[[x1, y1, 1]];
864        let amp2_0 = self.state[[x2, y2, 0]];
865        let amp2_1 = self.state[[x2, y2, 1]];
866
867        Ok(vec![
868            amp1_0 * amp2_0, // |00⟩
869            amp1_0 * amp2_1, // |01⟩
870            amp1_1 * amp2_0, // |10⟩
871            amp1_1 * amp2_1, // |11⟩
872        ])
873    }
874
875    /// Get total probability distribution across the lattice
876    pub fn probability_distribution(&self) -> Array3<f64> {
877        let mut probs = Array3::zeros((self.width, self.height, 2));
878
879        for i in 0..self.width {
880            for j in 0..self.height {
881                probs[[i, j, 0]] = self.state[[i, j, 0]].norm_sqr();
882                probs[[i, j, 1]] = self.state[[i, j, 1]].norm_sqr();
883            }
884        }
885
886        probs
887    }
888
889    /// Calculate magnetization of the lattice
890    pub fn magnetization(&self) -> f64 {
891        let mut total_magnetization = 0.0;
892
893        for i in 0..self.width {
894            for j in 0..self.height {
895                let prob_0 = self.state[[i, j, 0]].norm_sqr();
896                let prob_1 = self.state[[i, j, 1]].norm_sqr();
897                total_magnetization += prob_0 - prob_1; // Z expectation value
898            }
899        }
900
901        total_magnetization / (self.width * self.height) as f64
902    }
903}
904
905impl std::fmt::Debug for QuantumCellularAutomaton2D {
906    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
907        f.debug_struct("QuantumCellularAutomaton2D")
908            .field("width", &self.width)
909            .field("height", &self.height)
910            .field("boundary", &self.boundary)
911            .field("qca_type", &self.qca_type)
912            .field("time_step", &self.time_step)
913            .finish()
914    }
915}
916
917#[cfg(test)]
918mod tests {
919    use super::*;
920
921    #[test]
922    fn test_unitary_rule_creation() {
923        let hadamard = UnitaryRule::hadamard();
924        assert_eq!(hadamard.num_qubits, 1);
925        assert!(hadamard.is_reversible());
926
927        let cnot = UnitaryRule::cnot();
928        assert_eq!(cnot.num_qubits, 2);
929        assert!(cnot.is_reversible());
930    }
931
932    #[test]
933    fn test_1d_qca_initialization() {
934        let rule = Box::new(UnitaryRule::cnot());
935        let qca = QuantumCellularAutomaton1D::new(
936            10,
937            rule,
938            BoundaryCondition::Periodic,
939            QCAType::Partitioned,
940        );
941
942        assert_eq!(qca.num_sites, 10);
943        assert_eq!(qca.time_step, 0);
944
945        // Check initial state (all |0⟩)
946        for i in 0..10 {
947            let state = qca
948                .get_site_state(i)
949                .expect("Site state should be retrievable");
950            assert!((state[0] - Complex64::new(1.0, 0.0)).norm() < 1e-10);
951            assert!((state[1] - Complex64::new(0.0, 0.0)).norm() < 1e-10);
952        }
953    }
954
955    #[test]
956    fn test_1d_qca_evolution() {
957        let rule = Box::new(UnitaryRule::cnot());
958        let mut qca = QuantumCellularAutomaton1D::new(
959            4,
960            rule,
961            BoundaryCondition::Periodic,
962            QCAType::Partitioned,
963        );
964
965        // Set middle site to |1⟩
966        qca.set_site_state(1, [Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)])
967            .expect("Site state should be set successfully");
968
969        // Evolve one step
970        qca.step().expect("QCA evolution step should succeed");
971        assert_eq!(qca.time_step, 1);
972
973        // Check that evolution occurred
974        let probs = qca.site_probabilities();
975        assert!(probs.len() == 4);
976    }
977
978    #[test]
979    fn test_2d_qca_initialization() {
980        let rule = Box::new(UnitaryRule::cnot());
981        let qca = QuantumCellularAutomaton2D::new(
982            5,
983            5,
984            rule,
985            BoundaryCondition::Periodic,
986            QCAType::Margolus,
987        );
988
989        assert_eq!(qca.width, 5);
990        assert_eq!(qca.height, 5);
991        assert_eq!(qca.time_step, 0);
992
993        // Check initial state
994        for i in 0..5 {
995            for j in 0..5 {
996                let state = qca
997                    .get_site_state(i, j)
998                    .expect("Site state should be retrievable");
999                assert!((state[0] - Complex64::new(1.0, 0.0)).norm() < 1e-10);
1000                assert!((state[1] - Complex64::new(0.0, 0.0)).norm() < 1e-10);
1001            }
1002        }
1003    }
1004
1005    #[test]
1006    fn test_entanglement_entropy() {
1007        let rule = Box::new(UnitaryRule::cnot());
1008        let mut qca = QuantumCellularAutomaton1D::new(
1009            4,
1010            rule,
1011            BoundaryCondition::Periodic,
1012            QCAType::Partitioned,
1013        );
1014
1015        // Create some mixed state
1016        let sqrt2_inv = 1.0 / 2.0_f64.sqrt();
1017        qca.set_site_state(
1018            0,
1019            [
1020                Complex64::new(sqrt2_inv, 0.0),
1021                Complex64::new(sqrt2_inv, 0.0),
1022            ],
1023        )
1024        .expect("Site state should be set successfully");
1025
1026        let entropy = qca
1027            .entanglement_entropy(0, 2)
1028            .expect("Entanglement entropy calculation should succeed");
1029        assert!(entropy >= 0.0);
1030    }
1031
1032    #[test]
1033    fn test_correlation_function() {
1034        let rule = Box::new(UnitaryRule::cnot());
1035        let mut qca = QuantumCellularAutomaton1D::new(
1036            4,
1037            rule,
1038            BoundaryCondition::Periodic,
1039            QCAType::Partitioned,
1040        );
1041
1042        // Set sites to definite states
1043        qca.set_site_state(0, [Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)])
1044            .expect("Site 0 state should be set to |0>"); // |0⟩
1045        qca.set_site_state(1, [Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)])
1046            .expect("Site 1 state should be set to |1>"); // |1⟩
1047
1048        let correlation = qca
1049            .correlation(0, 1)
1050            .expect("Correlation calculation should succeed");
1051        assert!((correlation - Complex64::new(-1.0, 0.0)).norm() < 1e-10);
1052    }
1053
1054    #[test]
1055    fn test_2d_magnetization() {
1056        let rule = Box::new(UnitaryRule::cnot());
1057        let mut qca = QuantumCellularAutomaton2D::new(
1058            3,
1059            3,
1060            rule,
1061            BoundaryCondition::Periodic,
1062            QCAType::Partitioned,
1063        );
1064
1065        // Set all sites to |0⟩ (already the default)
1066        let magnetization = qca.magnetization();
1067        assert!((magnetization - 1.0).abs() < 1e-10);
1068
1069        // Set all sites to |1⟩
1070        for i in 0..3 {
1071            for j in 0..3 {
1072                qca.set_site_state(i, j, [Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)])
1073                    .expect("Site state should be set to |1>");
1074            }
1075        }
1076
1077        let magnetization = qca.magnetization();
1078        assert!((magnetization - (-1.0)).abs() < 1e-10);
1079    }
1080
1081    #[test]
1082    fn test_toffoli_rule() {
1083        let toffoli = UnitaryRule::toffoli();
1084        assert_eq!(toffoli.num_qubits, 3);
1085        assert_eq!(toffoli.neighborhood_size(), 8);
1086
1087        // Test that |110⟩ → |111⟩
1088        let input = vec![
1089            Complex64::new(0.0, 0.0), // |000⟩
1090            Complex64::new(0.0, 0.0), // |001⟩
1091            Complex64::new(0.0, 0.0), // |010⟩
1092            Complex64::new(0.0, 0.0), // |011⟩
1093            Complex64::new(0.0, 0.0), // |100⟩
1094            Complex64::new(0.0, 0.0), // |101⟩
1095            Complex64::new(1.0, 0.0), // |110⟩
1096            Complex64::new(0.0, 0.0), // |111⟩
1097        ];
1098
1099        let output = toffoli
1100            .apply(&input)
1101            .expect("Toffoli rule application should succeed");
1102
1103        // Should flip to |111⟩
1104        assert!((output[6] - Complex64::new(0.0, 0.0)).norm() < 1e-10);
1105        assert!((output[7] - Complex64::new(1.0, 0.0)).norm() < 1e-10);
1106    }
1107
1108    #[test]
1109    fn test_get_neighborhood_state_vector_tensor_product() {
1110        let rule = Box::new(UnitaryRule::cnot());
1111        let mut qca = QuantumCellularAutomaton2D::new(
1112            3,
1113            3,
1114            rule,
1115            BoundaryCondition::Periodic,
1116            QCAType::Margolus,
1117        );
1118
1119        // Site A at (0,0): |+> = (|0> + |1>)/sqrt(2)
1120        let inv_sqrt2 = 1.0 / std::f64::consts::SQRT_2;
1121        qca.set_site_state(
1122            0,
1123            0,
1124            [
1125                Complex64::new(inv_sqrt2, 0.0),
1126                Complex64::new(inv_sqrt2, 0.0),
1127            ],
1128        )
1129        .expect("set site A");
1130        // Site B at (1,0): a|0> + b|1> with distinct amplitudes.
1131        let a = Complex64::new(0.6, 0.0);
1132        let b = Complex64::new(0.8, 0.0);
1133        qca.set_site_state(1, 0, [a, b]).expect("set site B");
1134
1135        let sites = [(0usize, 0usize), (1usize, 0usize)];
1136        let sv = qca
1137            .get_neighborhood_state_vector(&sites)
1138            .expect("neighborhood state vector");
1139
1140        // Expected tensor product (site 0 = most-significant bit, matching the
1141        // sibling get_pair_state_vector_2d convention):
1142        //   |00> = A0*B0, |01> = A0*B1, |10> = A1*B0, |11> = A1*B1
1143        let a0 = Complex64::new(inv_sqrt2, 0.0);
1144        let a1 = Complex64::new(inv_sqrt2, 0.0);
1145        let expected = [a0 * a, a0 * b, a1 * a, a1 * b];
1146
1147        assert_eq!(sv.len(), 4);
1148        for (got, exp) in sv.iter().zip(expected.iter()) {
1149            assert!((got - exp).norm() < 1e-12, "got {got:?}, expected {exp:?}");
1150        }
1151
1152        // It must genuinely depend on the lattice, i.e. NOT be |0...0>.
1153        let zero_state = (sv[0] - Complex64::new(1.0, 0.0)).norm() < 1e-12
1154            && sv[1..].iter().all(|c| c.norm() < 1e-12);
1155        assert!(
1156            !zero_state,
1157            "neighborhood state must not be the fabricated |0...0> placeholder"
1158        );
1159
1160        // Cross-check against the dedicated pair helper for the same two sites.
1161        let pair = qca
1162            .get_pair_state_vector_2d(0, 0, 1, 0)
1163            .expect("pair state vector");
1164        for (got, exp) in sv.iter().zip(pair.iter()) {
1165            assert!((got - exp).norm() < 1e-12);
1166        }
1167    }
1168}