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 ndarray::{Array1, Array2, Array3};
10use num_complex::Complex64;
11
12/// Types of quantum cellular automata
13#[derive(Debug, Clone, Copy, PartialEq)]
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)]
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        .unwrap();
123
124        Self::new(unitary).unwrap()
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        .unwrap();
140
141        Self::new(unitary).unwrap()
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).unwrap()
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(&mut self, rng: &mut dyn rand::RngCore) -> QuantRS2Result<()> {
262        use rand::Rng;
263
264        for i in 0..self.num_sites {
265            let theta = rng.random_range(0.0..std::f64::consts::PI);
266            let phi = rng.random_range(0.0..2.0 * std::f64::consts::PI);
267
268            let amp0 = Complex64::new(theta.cos(), 0.0);
269            let amp1 = Complex64::new(theta.sin() * phi.cos(), theta.sin() * phi.sin());
270
271            self.set_site_state(i, [amp0, amp1])?;
272        }
273
274        Ok(())
275    }
276
277    /// Perform one evolution step
278    pub fn step(&mut self) -> QuantRS2Result<()> {
279        match self.qca_type {
280            QCAType::Partitioned => self.step_partitioned(),
281            QCAType::Margolus => self.step_margolus(),
282            QCAType::Moore => self.step_moore(),
283            QCAType::VonNeumann => self.step_von_neumann(),
284        }
285    }
286
287    /// Partitioned QCA step (alternating even/odd partitions)
288    fn step_partitioned(&mut self) -> QuantRS2Result<()> {
289        let rule_size = self.rule.neighborhood_size();
290        let num_qubits = (rule_size as f64).log2() as usize;
291
292        if num_qubits != 2 {
293            return Err(QuantRS2Error::InvalidInput(
294                "Partitioned QCA currently supports only 2-qubit rules".to_string(),
295            ));
296        }
297
298        let mut new_state = self.state.clone();
299        let offset = self.time_step % 2;
300
301        // Apply rule to neighboring pairs
302        for i in (offset..self.num_sites.saturating_sub(1)).step_by(2) {
303            let j = (i + 1) % self.num_sites;
304
305            // Get neighborhood state vector
306            let neighborhood = self.get_pair_state_vector(i, j)?;
307
308            // Apply rule
309            let evolved = self.rule.apply(&neighborhood)?;
310
311            // Update states
312            new_state[[i, 0]] = evolved[0];
313            new_state[[i, 1]] = evolved[1];
314            new_state[[j, 0]] = evolved[2];
315            new_state[[j, 1]] = evolved[3];
316        }
317
318        self.state = new_state;
319        self.time_step += 1;
320        Ok(())
321    }
322
323    /// Get state vector for a pair of sites
324    fn get_pair_state_vector(&self, site1: usize, site2: usize) -> QuantRS2Result<Vec<Complex64>> {
325        let amp1_0 = self.state[[site1, 0]];
326        let amp1_1 = self.state[[site1, 1]];
327        let amp2_0 = self.state[[site2, 0]];
328        let amp2_1 = self.state[[site2, 1]];
329
330        // Tensor product: |site1⟩ ⊗ |site2⟩
331        Ok(vec![
332            amp1_0 * amp2_0, // |00⟩
333            amp1_0 * amp2_1, // |01⟩
334            amp1_1 * amp2_0, // |10⟩
335            amp1_1 * amp2_1, // |11⟩
336        ])
337    }
338
339    /// Margolus neighborhood step (2x2 blocks, alternating)
340    fn step_margolus(&mut self) -> QuantRS2Result<()> {
341        // For 1D, Margolus reduces to partitioned with 2-site neighborhoods
342        self.step_partitioned()
343    }
344
345    /// Moore neighborhood step (3x3 neighborhoods in 2D, 3-site in 1D)
346    fn step_moore(&mut self) -> QuantRS2Result<()> {
347        let rule_size = self.rule.neighborhood_size();
348        if rule_size != 8 {
349            // 3 qubits = 8 dimensional Hilbert space
350            return Err(QuantRS2Error::InvalidInput(
351                "Moore neighborhood requires 3-qubit rule".to_string(),
352            ));
353        }
354
355        let mut new_state = self.state.clone();
356
357        for i in 0..self.num_sites {
358            let left = self.get_neighbor(i, -1);
359            let center = i;
360            let right = self.get_neighbor(i, 1);
361
362            // Get 3-site neighborhood state
363            let neighborhood = self.get_triple_state_vector(left, center, right)?;
364
365            // Apply rule
366            let evolved = self.rule.apply(&neighborhood)?;
367
368            // Update center site only
369            new_state[[center, 0]] = evolved[2]; // Extract center qubit from |abc⟩ -> |b⟩
370            new_state[[center, 1]] = evolved[6];
371        }
372
373        self.state = new_state;
374        self.time_step += 1;
375        Ok(())
376    }
377
378    /// Von Neumann neighborhood step (plus-shaped, center + 4 neighbors)
379    fn step_von_neumann(&mut self) -> QuantRS2Result<()> {
380        // For 1D, Von Neumann is same as Moore (3 neighbors)
381        self.step_moore()
382    }
383
384    /// Get neighbor index with boundary conditions
385    fn get_neighbor(&self, site: usize, offset: isize) -> usize {
386        match self.boundary {
387            BoundaryCondition::Periodic => {
388                let new_site = site as isize + offset;
389                ((new_site % self.num_sites as isize + self.num_sites as isize)
390                    % self.num_sites as isize) as usize
391            }
392            BoundaryCondition::Fixed | BoundaryCondition::Open => {
393                let new_site = site as isize + offset;
394                if new_site < 0 {
395                    0
396                } else if new_site >= self.num_sites as isize {
397                    self.num_sites - 1
398                } else {
399                    new_site as usize
400                }
401            }
402        }
403    }
404
405    /// Get state vector for three sites
406    fn get_triple_state_vector(
407        &self,
408        site1: usize,
409        site2: usize,
410        site3: usize,
411    ) -> QuantRS2Result<Vec<Complex64>> {
412        let mut state_vector = vec![Complex64::new(0.0, 0.0); 8];
413
414        // Build 3-qubit state vector
415        for i in 0..2 {
416            for j in 0..2 {
417                for k in 0..2 {
418                    let amp =
419                        self.state[[site1, i]] * self.state[[site2, j]] * self.state[[site3, k]];
420                    let idx = 4 * i + 2 * j + k;
421                    state_vector[idx] = amp;
422                }
423            }
424        }
425
426        Ok(state_vector)
427    }
428
429    /// Calculate the entanglement entropy of a region
430    pub fn entanglement_entropy(
431        &self,
432        region_start: usize,
433        region_size: usize,
434    ) -> QuantRS2Result<f64> {
435        if region_start + region_size > self.num_sites {
436            return Err(QuantRS2Error::InvalidInput(
437                "Region extends beyond lattice".to_string(),
438            ));
439        }
440
441        // For full implementation, we'd need to compute the reduced density matrix
442        // This is a simplified approximation based on site entropies
443        let mut entropy = 0.0;
444
445        for i in region_start..region_start + region_size {
446            let p0 = self.state[[i, 0]].norm_sqr();
447            let p1 = self.state[[i, 1]].norm_sqr();
448
449            if p0 > 1e-12 {
450                entropy -= p0 * p0.ln();
451            }
452            if p1 > 1e-12 {
453                entropy -= p1 * p1.ln();
454            }
455        }
456
457        Ok(entropy)
458    }
459
460    /// Get probability distribution at each site
461    pub fn site_probabilities(&self) -> Vec<[f64; 2]> {
462        self.state
463            .rows()
464            .into_iter()
465            .map(|row| [row[0].norm_sqr(), row[1].norm_sqr()])
466            .collect()
467    }
468
469    /// Compute correlation function between two sites
470    pub fn correlation(&self, site1: usize, site2: usize) -> QuantRS2Result<Complex64> {
471        if site1 >= self.num_sites || site2 >= self.num_sites {
472            return Err(QuantRS2Error::InvalidInput(
473                "Site index out of bounds".to_string(),
474            ));
475        }
476
477        // Simplified correlation: ⟨Z_i Z_j⟩
478        let z1 = self.state[[site1, 0]].norm_sqr() - self.state[[site1, 1]].norm_sqr();
479        let z2 = self.state[[site2, 0]].norm_sqr() - self.state[[site2, 1]].norm_sqr();
480
481        Ok(Complex64::new(z1 * z2, 0.0))
482    }
483}
484
485impl std::fmt::Debug for QuantumCellularAutomaton1D {
486    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
487        f.debug_struct("QuantumCellularAutomaton1D")
488            .field("num_sites", &self.num_sites)
489            .field("boundary", &self.boundary)
490            .field("qca_type", &self.qca_type)
491            .field("time_step", &self.time_step)
492            .finish()
493    }
494}
495
496/// 2D Quantum cellular automaton
497pub struct QuantumCellularAutomaton2D {
498    /// Width of the lattice
499    pub width: usize,
500    /// Height of the lattice
501    pub height: usize,
502    /// Current state (width × height × 2 for qubit amplitudes)
503    pub state: Array3<Complex64>,
504    /// Update rule
505    rule: Box<dyn QCARule + Send + Sync>,
506    /// Boundary conditions
507    boundary: BoundaryCondition,
508    /// QCA type
509    qca_type: QCAType,
510    /// Current time step
511    pub time_step: usize,
512}
513
514impl QuantumCellularAutomaton2D {
515    /// Create a new 2D quantum cellular automaton
516    pub fn new(
517        width: usize,
518        height: usize,
519        rule: Box<dyn QCARule + Send + Sync>,
520        boundary: BoundaryCondition,
521        qca_type: QCAType,
522    ) -> Self {
523        // Initialize with all sites in |0⟩ state
524        let mut state = Array3::zeros((width, height, 2));
525        for i in 0..width {
526            for j in 0..height {
527                state[[i, j, 0]] = Complex64::new(1.0, 0.0); // |0⟩ state
528            }
529        }
530
531        Self {
532            width,
533            height,
534            state,
535            rule,
536            boundary,
537            qca_type,
538            time_step: 0,
539        }
540    }
541
542    /// Set the state of a specific site
543    pub fn set_site_state(
544        &mut self,
545        x: usize,
546        y: usize,
547        amplitudes: [Complex64; 2],
548    ) -> QuantRS2Result<()> {
549        if x >= self.width || y >= self.height {
550            return Err(QuantRS2Error::InvalidInput(
551                "Site coordinates out of bounds".to_string(),
552            ));
553        }
554
555        // Normalize the state
556        let norm = (amplitudes[0].norm_sqr() + amplitudes[1].norm_sqr()).sqrt();
557        if norm < 1e-10 {
558            return Err(QuantRS2Error::InvalidInput(
559                "State cannot have zero norm".to_string(),
560            ));
561        }
562
563        self.state[[x, y, 0]] = amplitudes[0] / norm;
564        self.state[[x, y, 1]] = amplitudes[1] / norm;
565
566        Ok(())
567    }
568
569    /// Get the state of a specific site
570    pub fn get_site_state(&self, x: usize, y: usize) -> QuantRS2Result<[Complex64; 2]> {
571        if x >= self.width || y >= self.height {
572            return Err(QuantRS2Error::InvalidInput(
573                "Site coordinates out of bounds".to_string(),
574            ));
575        }
576
577        Ok([self.state[[x, y, 0]], self.state[[x, y, 1]]])
578    }
579
580    /// Perform one evolution step
581    pub fn step(&mut self) -> QuantRS2Result<()> {
582        match self.qca_type {
583            QCAType::Margolus => self.step_margolus_2d(),
584            QCAType::Moore => self.step_moore_2d(),
585            QCAType::VonNeumann => self.step_von_neumann_2d(),
586            QCAType::Partitioned => self.step_partitioned_2d(),
587        }
588    }
589
590    /// Margolus neighborhood step for 2D (2×2 blocks)
591    fn step_margolus_2d(&mut self) -> QuantRS2Result<()> {
592        let rule_size = self.rule.neighborhood_size();
593        if rule_size != 16 {
594            // 4 qubits = 16 dimensional Hilbert space
595            return Err(QuantRS2Error::InvalidInput(
596                "2D Margolus requires 4-qubit rule".to_string(),
597            ));
598        }
599
600        let mut new_state = self.state.clone();
601        let x_offset = self.time_step % 2;
602        let y_offset = self.time_step % 2;
603
604        // Process 2×2 blocks
605        for i in (x_offset..self.width.saturating_sub(1)).step_by(2) {
606            for j in (y_offset..self.height.saturating_sub(1)).step_by(2) {
607                let block_state = self.get_block_state_vector(i, j)?;
608                let evolved = self.rule.apply(&block_state)?;
609
610                // Update the 2×2 block
611                for (idx, &val) in evolved.iter().enumerate() {
612                    let local_x = idx % 2;
613                    let local_y = (idx / 2) % 2;
614                    let qubit = (idx / 4) % 2;
615
616                    if i + local_x < self.width && j + local_y < self.height {
617                        new_state[[i + local_x, j + local_y, qubit]] = val;
618                    }
619                }
620            }
621        }
622
623        self.state = new_state;
624        self.time_step += 1;
625        Ok(())
626    }
627
628    /// Get state vector for a 2×2 block
629    fn get_block_state_vector(
630        &self,
631        start_x: usize,
632        start_y: usize,
633    ) -> QuantRS2Result<Vec<Complex64>> {
634        let mut state_vector = vec![Complex64::new(0.0, 0.0); 16];
635
636        // Get states of 4 sites in the block
637        let sites = [
638            (start_x, start_y),
639            (start_x + 1, start_y),
640            (start_x, start_y + 1),
641            (start_x + 1, start_y + 1),
642        ];
643
644        // Build 4-qubit state vector
645        for i in 0..2 {
646            for j in 0..2 {
647                for k in 0..2 {
648                    for l in 0..2 {
649                        let amp = self.state[[sites[0].0, sites[0].1, i]]
650                            * self.state[[sites[1].0, sites[1].1, j]]
651                            * self.state[[sites[2].0, sites[2].1, k]]
652                            * self.state[[sites[3].0, sites[3].1, l]];
653
654                        let idx = 8 * i + 4 * j + 2 * k + l;
655                        state_vector[idx] = amp;
656                    }
657                }
658            }
659        }
660
661        Ok(state_vector)
662    }
663
664    /// Moore neighborhood step for 2D (3×3 neighborhoods)
665    fn step_moore_2d(&mut self) -> QuantRS2Result<()> {
666        // This would require 9-qubit rules, which is very large
667        // For practical purposes, we'll use a simplified version
668        self.step_von_neumann_2d()
669    }
670
671    /// Von Neumann neighborhood step for 2D (plus-shaped)
672    fn step_von_neumann_2d(&mut self) -> QuantRS2Result<()> {
673        let rule_size = self.rule.neighborhood_size();
674        if rule_size != 32 {
675            // 5 qubits = 32 dimensional Hilbert space
676            return Err(QuantRS2Error::InvalidInput(
677                "2D Von Neumann requires 5-qubit rule".to_string(),
678            ));
679        }
680
681        let mut new_state = self.state.clone();
682
683        for i in 0..self.width {
684            for j in 0..self.height {
685                // Get Von Neumann neighborhood (center + 4 neighbors)
686                let neighbors = self.get_von_neumann_neighbors(i, j);
687                let neighborhood_state = self.get_neighborhood_state_vector(&neighbors)?;
688
689                // Apply rule
690                let evolved = self.rule.apply(&neighborhood_state)?;
691
692                // Update center site only
693                new_state[[i, j, 0]] = evolved[16]; // Center qubit |0⟩ component
694                new_state[[i, j, 1]] = evolved[24]; // Center qubit |1⟩ component
695            }
696        }
697
698        self.state = new_state;
699        self.time_step += 1;
700        Ok(())
701    }
702
703    /// Partitioned step for 2D
704    fn step_partitioned_2d(&mut self) -> QuantRS2Result<()> {
705        // Apply horizontal and vertical 2-qubit rules alternately
706        if self.time_step % 2 == 0 {
707            self.step_horizontal_pairs()
708        } else {
709            self.step_vertical_pairs()
710        }
711    }
712
713    /// Apply rule to horizontal pairs
714    fn step_horizontal_pairs(&mut self) -> QuantRS2Result<()> {
715        let rule_size = self.rule.neighborhood_size();
716        if rule_size != 4 {
717            // 2 qubits = 4 dimensional
718            return Err(QuantRS2Error::InvalidInput(
719                "Horizontal pairs require 2-qubit rule".to_string(),
720            ));
721        }
722
723        let mut new_state = self.state.clone();
724
725        for j in 0..self.height {
726            for i in (0..self.width.saturating_sub(1)).step_by(2) {
727                let pair_state = self.get_pair_state_vector_2d(i, j, i + 1, j)?;
728                let evolved = self.rule.apply(&pair_state)?;
729
730                // Update pair
731                new_state[[i, j, 0]] = evolved[0];
732                new_state[[i, j, 1]] = evolved[1];
733                new_state[[i + 1, j, 0]] = evolved[2];
734                new_state[[i + 1, j, 1]] = evolved[3];
735            }
736        }
737
738        self.state = new_state;
739        self.time_step += 1;
740        Ok(())
741    }
742
743    /// Apply rule to vertical pairs
744    fn step_vertical_pairs(&mut self) -> QuantRS2Result<()> {
745        let rule_size = self.rule.neighborhood_size();
746        if rule_size != 4 {
747            // 2 qubits = 4 dimensional
748            return Err(QuantRS2Error::InvalidInput(
749                "Vertical pairs require 2-qubit rule".to_string(),
750            ));
751        }
752
753        let mut new_state = self.state.clone();
754
755        for i in 0..self.width {
756            for j in (0..self.height.saturating_sub(1)).step_by(2) {
757                let pair_state = self.get_pair_state_vector_2d(i, j, i, j + 1)?;
758                let evolved = self.rule.apply(&pair_state)?;
759
760                // Update pair
761                new_state[[i, j, 0]] = evolved[0];
762                new_state[[i, j, 1]] = evolved[1];
763                new_state[[i, j + 1, 0]] = evolved[2];
764                new_state[[i, j + 1, 1]] = evolved[3];
765            }
766        }
767
768        self.state = new_state;
769        self.time_step += 1;
770        Ok(())
771    }
772
773    /// Get Von Neumann neighbors (up, down, left, right, center)
774    fn get_von_neumann_neighbors(&self, x: usize, y: usize) -> Vec<(usize, usize)> {
775        vec![
776            (x, y),                          // center
777            (self.get_neighbor_x(x, -1), y), // left
778            (self.get_neighbor_x(x, 1), y),  // right
779            (x, self.get_neighbor_y(y, -1)), // up
780            (x, self.get_neighbor_y(y, 1)),  // down
781        ]
782    }
783
784    /// Get neighbor x-coordinate with boundary conditions
785    fn get_neighbor_x(&self, x: usize, offset: isize) -> usize {
786        match self.boundary {
787            BoundaryCondition::Periodic => {
788                let new_x = x as isize + offset;
789                ((new_x % self.width as isize + self.width as isize) % self.width as isize) as usize
790            }
791            BoundaryCondition::Fixed | BoundaryCondition::Open => {
792                let new_x = x as isize + offset;
793                if new_x < 0 {
794                    0
795                } else if new_x >= self.width as isize {
796                    self.width - 1
797                } else {
798                    new_x as usize
799                }
800            }
801        }
802    }
803
804    /// Get neighbor y-coordinate with boundary conditions
805    fn get_neighbor_y(&self, y: usize, offset: isize) -> usize {
806        match self.boundary {
807            BoundaryCondition::Periodic => {
808                let new_y = y as isize + offset;
809                ((new_y % self.height as isize + self.height as isize) % self.height as isize)
810                    as usize
811            }
812            BoundaryCondition::Fixed | BoundaryCondition::Open => {
813                let new_y = y as isize + offset;
814                if new_y < 0 {
815                    0
816                } else if new_y >= self.height as isize {
817                    self.height - 1
818                } else {
819                    new_y as usize
820                }
821            }
822        }
823    }
824
825    /// Get state vector for neighborhood sites
826    fn get_neighborhood_state_vector(
827        &self,
828        sites: &[(usize, usize)],
829    ) -> QuantRS2Result<Vec<Complex64>> {
830        let num_sites = sites.len();
831        let state_size = 1 << num_sites;
832        let mut state_vector = vec![Complex64::new(0.0, 0.0); state_size];
833
834        // This is a simplified implementation - full tensor product would be expensive
835        // For now, return a simplified state
836        state_vector[0] = Complex64::new(1.0, 0.0);
837        Ok(state_vector)
838    }
839
840    /// Get pair state vector for 2D coordinates
841    fn get_pair_state_vector_2d(
842        &self,
843        x1: usize,
844        y1: usize,
845        x2: usize,
846        y2: usize,
847    ) -> QuantRS2Result<Vec<Complex64>> {
848        let amp1_0 = self.state[[x1, y1, 0]];
849        let amp1_1 = self.state[[x1, y1, 1]];
850        let amp2_0 = self.state[[x2, y2, 0]];
851        let amp2_1 = self.state[[x2, y2, 1]];
852
853        Ok(vec![
854            amp1_0 * amp2_0, // |00⟩
855            amp1_0 * amp2_1, // |01⟩
856            amp1_1 * amp2_0, // |10⟩
857            amp1_1 * amp2_1, // |11⟩
858        ])
859    }
860
861    /// Get total probability distribution across the lattice
862    pub fn probability_distribution(&self) -> Array3<f64> {
863        let mut probs = Array3::zeros((self.width, self.height, 2));
864
865        for i in 0..self.width {
866            for j in 0..self.height {
867                probs[[i, j, 0]] = self.state[[i, j, 0]].norm_sqr();
868                probs[[i, j, 1]] = self.state[[i, j, 1]].norm_sqr();
869            }
870        }
871
872        probs
873    }
874
875    /// Calculate magnetization of the lattice
876    pub fn magnetization(&self) -> f64 {
877        let mut total_magnetization = 0.0;
878
879        for i in 0..self.width {
880            for j in 0..self.height {
881                let prob_0 = self.state[[i, j, 0]].norm_sqr();
882                let prob_1 = self.state[[i, j, 1]].norm_sqr();
883                total_magnetization += prob_0 - prob_1; // Z expectation value
884            }
885        }
886
887        total_magnetization / (self.width * self.height) as f64
888    }
889}
890
891impl std::fmt::Debug for QuantumCellularAutomaton2D {
892    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
893        f.debug_struct("QuantumCellularAutomaton2D")
894            .field("width", &self.width)
895            .field("height", &self.height)
896            .field("boundary", &self.boundary)
897            .field("qca_type", &self.qca_type)
898            .field("time_step", &self.time_step)
899            .finish()
900    }
901}
902
903#[cfg(test)]
904mod tests {
905    use super::*;
906
907    #[test]
908    fn test_unitary_rule_creation() {
909        let hadamard = UnitaryRule::hadamard();
910        assert_eq!(hadamard.num_qubits, 1);
911        assert!(hadamard.is_reversible());
912
913        let cnot = UnitaryRule::cnot();
914        assert_eq!(cnot.num_qubits, 2);
915        assert!(cnot.is_reversible());
916    }
917
918    #[test]
919    fn test_1d_qca_initialization() {
920        let rule = Box::new(UnitaryRule::cnot());
921        let qca = QuantumCellularAutomaton1D::new(
922            10,
923            rule,
924            BoundaryCondition::Periodic,
925            QCAType::Partitioned,
926        );
927
928        assert_eq!(qca.num_sites, 10);
929        assert_eq!(qca.time_step, 0);
930
931        // Check initial state (all |0⟩)
932        for i in 0..10 {
933            let state = qca.get_site_state(i).unwrap();
934            assert!((state[0] - Complex64::new(1.0, 0.0)).norm() < 1e-10);
935            assert!((state[1] - Complex64::new(0.0, 0.0)).norm() < 1e-10);
936        }
937    }
938
939    #[test]
940    fn test_1d_qca_evolution() {
941        let rule = Box::new(UnitaryRule::cnot());
942        let mut qca = QuantumCellularAutomaton1D::new(
943            4,
944            rule,
945            BoundaryCondition::Periodic,
946            QCAType::Partitioned,
947        );
948
949        // Set middle site to |1⟩
950        qca.set_site_state(1, [Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)])
951            .unwrap();
952
953        // Evolve one step
954        qca.step().unwrap();
955        assert_eq!(qca.time_step, 1);
956
957        // Check that evolution occurred
958        let probs = qca.site_probabilities();
959        assert!(probs.len() == 4);
960    }
961
962    #[test]
963    fn test_2d_qca_initialization() {
964        let rule = Box::new(UnitaryRule::cnot());
965        let qca = QuantumCellularAutomaton2D::new(
966            5,
967            5,
968            rule,
969            BoundaryCondition::Periodic,
970            QCAType::Margolus,
971        );
972
973        assert_eq!(qca.width, 5);
974        assert_eq!(qca.height, 5);
975        assert_eq!(qca.time_step, 0);
976
977        // Check initial state
978        for i in 0..5 {
979            for j in 0..5 {
980                let state = qca.get_site_state(i, j).unwrap();
981                assert!((state[0] - Complex64::new(1.0, 0.0)).norm() < 1e-10);
982                assert!((state[1] - Complex64::new(0.0, 0.0)).norm() < 1e-10);
983            }
984        }
985    }
986
987    #[test]
988    fn test_entanglement_entropy() {
989        let rule = Box::new(UnitaryRule::cnot());
990        let mut qca = QuantumCellularAutomaton1D::new(
991            4,
992            rule,
993            BoundaryCondition::Periodic,
994            QCAType::Partitioned,
995        );
996
997        // Create some mixed state
998        let sqrt2_inv = 1.0 / 2.0_f64.sqrt();
999        qca.set_site_state(
1000            0,
1001            [
1002                Complex64::new(sqrt2_inv, 0.0),
1003                Complex64::new(sqrt2_inv, 0.0),
1004            ],
1005        )
1006        .unwrap();
1007
1008        let entropy = qca.entanglement_entropy(0, 2).unwrap();
1009        assert!(entropy >= 0.0);
1010    }
1011
1012    #[test]
1013    fn test_correlation_function() {
1014        let rule = Box::new(UnitaryRule::cnot());
1015        let mut qca = QuantumCellularAutomaton1D::new(
1016            4,
1017            rule,
1018            BoundaryCondition::Periodic,
1019            QCAType::Partitioned,
1020        );
1021
1022        // Set sites to definite states
1023        qca.set_site_state(0, [Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)])
1024            .unwrap(); // |0⟩
1025        qca.set_site_state(1, [Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)])
1026            .unwrap(); // |1⟩
1027
1028        let correlation = qca.correlation(0, 1).unwrap();
1029        assert!((correlation - Complex64::new(-1.0, 0.0)).norm() < 1e-10);
1030    }
1031
1032    #[test]
1033    fn test_2d_magnetization() {
1034        let rule = Box::new(UnitaryRule::cnot());
1035        let mut qca = QuantumCellularAutomaton2D::new(
1036            3,
1037            3,
1038            rule,
1039            BoundaryCondition::Periodic,
1040            QCAType::Partitioned,
1041        );
1042
1043        // Set all sites to |0⟩ (already the default)
1044        let magnetization = qca.magnetization();
1045        assert!((magnetization - 1.0).abs() < 1e-10);
1046
1047        // Set all sites to |1⟩
1048        for i in 0..3 {
1049            for j in 0..3 {
1050                qca.set_site_state(i, j, [Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)])
1051                    .unwrap();
1052            }
1053        }
1054
1055        let magnetization = qca.magnetization();
1056        assert!((magnetization - (-1.0)).abs() < 1e-10);
1057    }
1058
1059    #[test]
1060    fn test_toffoli_rule() {
1061        let toffoli = UnitaryRule::toffoli();
1062        assert_eq!(toffoli.num_qubits, 3);
1063        assert_eq!(toffoli.neighborhood_size(), 8);
1064
1065        // Test that |110⟩ → |111⟩
1066        let input = vec![
1067            Complex64::new(0.0, 0.0), // |000⟩
1068            Complex64::new(0.0, 0.0), // |001⟩
1069            Complex64::new(0.0, 0.0), // |010⟩
1070            Complex64::new(0.0, 0.0), // |011⟩
1071            Complex64::new(0.0, 0.0), // |100⟩
1072            Complex64::new(0.0, 0.0), // |101⟩
1073            Complex64::new(1.0, 0.0), // |110⟩
1074            Complex64::new(0.0, 0.0), // |111⟩
1075        ];
1076
1077        let output = toffoli.apply(&input).unwrap();
1078
1079        // Should flip to |111⟩
1080        assert!((output[6] - Complex64::new(0.0, 0.0)).norm() < 1e-10);
1081        assert!((output[7] - Complex64::new(1.0, 0.0)).norm() < 1e-10);
1082    }
1083}