1use scirs2_core::ndarray::{Array1, Array2};
9use scirs2_core::Complex64;
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12
13use crate::error::{Result, SimulatorError};
14use crate::scirs2_integration::SciRS2Backend;
15
16#[derive(Debug, Clone)]
18pub struct QCAConfig {
19 pub dimensions: Vec<usize>,
21 pub boundary_conditions: BoundaryConditions,
23 pub neighborhood: NeighborhoodType,
25 pub rule_type: QCARuleType,
27 pub evolution_steps: usize,
29 pub parallel_evolution: bool,
31 pub measurement_strategy: MeasurementStrategy,
33}
34
35impl Default for QCAConfig {
36 fn default() -> Self {
37 Self {
38 dimensions: vec![16], boundary_conditions: BoundaryConditions::Periodic,
40 neighborhood: NeighborhoodType::Moore,
41 rule_type: QCARuleType::Partitioned,
42 evolution_steps: 100,
43 parallel_evolution: true,
44 measurement_strategy: MeasurementStrategy::Probabilistic,
45 }
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum BoundaryConditions {
52 Periodic,
54 Fixed,
56 Open,
58 Reflective,
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum NeighborhoodType {
65 VonNeumann,
67 Moore,
69 Custom,
71 Extended,
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum QCARuleType {
78 Partitioned,
80 Global,
82 Sequential,
84 Margolus,
86 Custom,
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub enum MeasurementStrategy {
93 Probabilistic,
95 Deterministic,
97 Partial,
99 Continuous,
101}
102
103pub struct QuantumCellularAutomaton {
105 config: QCAConfig,
107 state: Array1<Complex64>,
109 evolution_rules: Vec<QCARule>,
111 cell_mapping: CellMapping,
113 backend: Option<SciRS2Backend>,
115 evolution_history: Vec<QCASnapshot>,
117 stats: QCAStats,
119}
120
121#[derive(Debug, Clone)]
123pub struct QCARule {
124 pub id: String,
126 pub unitary: Array2<Complex64>,
128 pub affected_cells: Vec<usize>,
130 pub neighborhood_pattern: Vec<(i32, i32, i32)>, pub parameters: HashMap<String, f64>,
134}
135
136#[derive(Debug, Clone)]
138pub struct CellMapping {
139 pub total_cells: usize,
141 pub dimensions: Vec<usize>,
143 pub coord_to_index: HashMap<Vec<usize>, usize>,
145 pub index_to_coord: HashMap<usize, Vec<usize>>,
147 pub neighbors: HashMap<usize, Vec<usize>>,
149}
150
151#[derive(Debug, Clone)]
153pub struct QCASnapshot {
154 pub time_step: usize,
156 pub state: Array1<Complex64>,
158 pub measurements: Option<Vec<f64>>,
160 pub entanglement_entropy: Option<f64>,
162 pub local_observables: HashMap<String, f64>,
164}
165
166#[derive(Debug, Clone, Default, Serialize, Deserialize)]
168pub struct QCAStats {
169 pub evolution_steps: usize,
171 pub total_evolution_time_ms: f64,
173 pub avg_step_time_ms: f64,
175 pub measurements_performed: usize,
177 pub entropy_stats: EntropyStats,
179 pub rule_applications: HashMap<String, usize>,
181}
182
183#[derive(Debug, Clone, Default, Serialize, Deserialize)]
185pub struct EntropyStats {
186 pub max_entropy: f64,
188 pub min_entropy: f64,
190 pub avg_entropy: f64,
192 pub entropy_variance: f64,
194}
195
196impl QuantumCellularAutomaton {
197 pub fn new(config: QCAConfig) -> Result<Self> {
199 let cell_mapping = Self::create_cell_mapping(&config)?;
200 let total_cells = cell_mapping.total_cells;
201
202 let state_size = 1 << total_cells;
204 let mut state = Array1::zeros(state_size);
205 state[0] = Complex64::new(1.0, 0.0);
206
207 let evolution_rules = Self::create_default_rules(&config, &cell_mapping)?;
208
209 Ok(Self {
210 config,
211 state,
212 evolution_rules,
213 cell_mapping,
214 backend: None,
215 evolution_history: Vec::new(),
216 stats: QCAStats::default(),
217 })
218 }
219
220 pub fn with_backend(mut self) -> Result<Self> {
222 self.backend = Some(SciRS2Backend::new());
223 Ok(self)
224 }
225
226 pub fn set_initial_state(&mut self, initial_state: Array1<Complex64>) -> Result<()> {
228 if initial_state.len() != self.state.len() {
229 return Err(SimulatorError::InvalidInput(format!(
230 "Initial state size {} doesn't match expected size {}",
231 initial_state.len(),
232 self.state.len()
233 )));
234 }
235
236 self.state = initial_state;
237 self.evolution_history.clear();
238 self.stats = QCAStats::default();
239
240 Ok(())
241 }
242
243 pub fn add_rule(&mut self, rule: QCARule) -> Result<()> {
245 if !Self::is_unitary(&rule.unitary) {
247 return Err(SimulatorError::InvalidInput(
248 "Rule matrix is not unitary".to_string(),
249 ));
250 }
251
252 self.evolution_rules.push(rule);
253 Ok(())
254 }
255
256 pub fn evolve(&mut self, steps: usize) -> Result<QCAEvolutionResult> {
258 let start_time = std::time::Instant::now();
259
260 for step in 0..steps {
261 let step_start = std::time::Instant::now();
262
263 match self.config.rule_type {
265 QCARuleType::Partitioned => self.evolve_partitioned()?,
266 QCARuleType::Global => self.evolve_global()?,
267 QCARuleType::Sequential => self.evolve_sequential()?,
268 QCARuleType::Margolus => self.evolve_margolus()?,
269 QCARuleType::Custom => self.evolve_custom()?,
270 }
271
272 if step % 10 == 0 || step == steps - 1 {
274 let snapshot = self.take_snapshot(step)?;
275 self.evolution_history.push(snapshot);
276 }
277
278 if self.config.measurement_strategy == MeasurementStrategy::Continuous {
280 self.apply_weak_measurements()?;
281 }
282
283 let step_time = step_start.elapsed().as_secs_f64() * 1000.0;
284 self.stats.avg_step_time_ms = self
285 .stats
286 .avg_step_time_ms
287 .mul_add(self.stats.evolution_steps as f64, step_time)
288 / (self.stats.evolution_steps + 1) as f64;
289 self.stats.evolution_steps += 1;
290 }
291
292 let total_time = start_time.elapsed().as_secs_f64() * 1000.0;
293 self.stats.total_evolution_time_ms += total_time;
294
295 Ok(QCAEvolutionResult {
296 final_state: self.state.clone(),
297 evolution_history: self.evolution_history.clone(),
298 total_steps: steps,
299 total_time_ms: total_time,
300 final_entropy: self.calculate_entanglement_entropy()?,
301 })
302 }
303
304 fn evolve_partitioned(&mut self) -> Result<()> {
306 match self.config.dimensions.len() {
307 1 => self.evolve_1d_partitioned(),
308 2 => self.evolve_2d_partitioned(),
309 3 => self.evolve_3d_partitioned(),
310 _ => Err(SimulatorError::UnsupportedOperation(
311 "QCA supports only 1D, 2D, and 3D lattices".to_string(),
312 )),
313 }
314 }
315
316 fn evolve_1d_partitioned(&mut self) -> Result<()> {
318 let lattice_size = self.config.dimensions[0];
319
320 let mut partitions = Vec::new();
322 let partition_size = 2; for start in (0..lattice_size).step_by(partition_size) {
325 let end = (start + partition_size).min(lattice_size);
326 partitions.push((start, end));
327 }
328
329 for (start, end) in partitions {
331 for cell in start..end - 1 {
332 let neighbor = match self.config.boundary_conditions {
333 BoundaryConditions::Periodic => (cell + 1) % lattice_size,
334 BoundaryConditions::Fixed | BoundaryConditions::Open => {
335 if cell + 1 < lattice_size {
336 cell + 1
337 } else {
338 continue;
339 }
340 }
341 BoundaryConditions::Reflective => {
342 if cell + 1 < lattice_size {
343 cell + 1
344 } else {
345 lattice_size - 1
346 }
347 }
348 };
349
350 self.apply_two_cell_rule(cell, neighbor)?;
351 }
352 }
353
354 Ok(())
355 }
356
357 fn evolve_2d_partitioned(&mut self) -> Result<()> {
359 let (width, height) = (self.config.dimensions[0], self.config.dimensions[1]);
360
361 for parity in 0..2 {
363 for y in 0..height {
364 for x in (parity..width).step_by(2) {
365 let cell = y * width + x;
366 let neighbors = self.get_neighbors_2d(x, y, width, height);
367
368 if !neighbors.is_empty() {
369 self.apply_neighborhood_rule(cell, &neighbors)?;
370 }
371 }
372 }
373 }
374
375 Ok(())
376 }
377
378 fn evolve_3d_partitioned(&mut self) -> Result<()> {
380 let (width, height, depth) = (
381 self.config.dimensions[0],
382 self.config.dimensions[1],
383 self.config.dimensions[2],
384 );
385
386 for parity in 0..2 {
388 for z in 0..depth {
389 for y in 0..height {
390 for x in (parity..width).step_by(2) {
391 let cell = z * width * height + y * width + x;
392 let neighbors = self.get_neighbors_3d(x, y, z, width, height, depth);
393
394 if !neighbors.is_empty() {
395 self.apply_neighborhood_rule(cell, &neighbors)?;
396 }
397 }
398 }
399 }
400 }
401
402 Ok(())
403 }
404
405 fn evolve_global(&mut self) -> Result<()> {
407 if let Some(global_rule) = self.evolution_rules.first() {
408 let new_state = global_rule.unitary.dot(&self.state);
410 self.state = new_state;
411
412 let rule_id = global_rule.id.clone();
414 *self.stats.rule_applications.entry(rule_id).or_insert(0) += 1;
415 }
416
417 Ok(())
418 }
419
420 fn evolve_sequential(&mut self) -> Result<()> {
422 let total_cells = self.cell_mapping.total_cells;
423 for cell in 0..total_cells {
424 let neighbors = self
425 .cell_mapping
426 .neighbors
427 .get(&cell)
428 .cloned()
429 .unwrap_or_default();
430 if !neighbors.is_empty() {
431 self.apply_neighborhood_rule(cell, &neighbors)?;
432 }
433 }
434 Ok(())
435 }
436
437 fn evolve_margolus(&mut self) -> Result<()> {
439 if self.config.dimensions.len() != 2 {
441 return Err(SimulatorError::UnsupportedOperation(
442 "Margolus QCA only supports 2D lattices".to_string(),
443 ));
444 }
445
446 let (width, height) = (self.config.dimensions[0], self.config.dimensions[1]);
447 let offset = self.stats.evolution_steps % 2; for y in (offset..height - 1).step_by(2) {
450 for x in (offset..width - 1).step_by(2) {
451 let cells = vec![
453 y * width + x,
454 y * width + (x + 1),
455 (y + 1) * width + x,
456 (y + 1) * width + (x + 1),
457 ];
458
459 self.apply_margolus_rule(&cells)?;
460 }
461 }
462
463 Ok(())
464 }
465
466 fn evolve_custom(&mut self) -> Result<()> {
468 for rule in &self.evolution_rules.clone() {
470 if rule.affected_cells.len() == 1 {
471 self.apply_single_cell_rule(rule.affected_cells[0], &rule.unitary)?;
472 } else if rule.affected_cells.len() == 2 {
473 self.apply_two_cell_rule(rule.affected_cells[0], rule.affected_cells[1])?;
474 } else {
475 self.apply_neighborhood_rule(rule.affected_cells[0], &rule.affected_cells[1..])?;
476 }
477
478 *self
480 .stats
481 .rule_applications
482 .entry(rule.id.clone())
483 .or_insert(0) += 1;
484 }
485
486 Ok(())
487 }
488
489 fn apply_two_cell_rule(&mut self, cell1: usize, cell2: usize) -> Result<()> {
491 let rule_data = self
493 .evolution_rules
494 .iter()
495 .find(|r| r.unitary.dim() == (4, 4))
496 .map(|r| (r.unitary.clone(), r.id.clone()))
497 .ok_or_else(|| {
498 SimulatorError::UnsupportedOperation("No two-cell rule available".to_string())
499 })?;
500
501 self.apply_two_qubit_unitary(cell1, cell2, &rule_data.0)?;
503
504 *self.stats.rule_applications.entry(rule_data.1).or_insert(0) += 1;
506
507 Ok(())
508 }
509
510 fn apply_neighborhood_rule(&mut self, center_cell: usize, neighbors: &[usize]) -> Result<()> {
512 for &neighbor in neighbors {
514 self.apply_two_cell_rule(center_cell, neighbor)?;
515 }
516 Ok(())
517 }
518
519 fn apply_margolus_rule(&mut self, cells: &[usize]) -> Result<()> {
521 if cells.len() != 4 {
522 return Err(SimulatorError::InvalidInput(
523 "Margolus rule requires exactly 4 cells".to_string(),
524 ));
525 }
526
527 let rule_unitary = self
529 .evolution_rules
530 .iter()
531 .find(|r| r.unitary.dim() == (16, 16))
532 .map_or_else(Self::create_margolus_rotation_unitary, |r| {
533 r.unitary.clone()
534 });
535
536 self.apply_four_qubit_unitary(cells, &rule_unitary)?;
538
539 Ok(())
540 }
541
542 fn apply_single_cell_rule(&mut self, cell: usize, unitary: &Array2<Complex64>) -> Result<()> {
544 self.apply_single_qubit_unitary(cell, unitary)
545 }
546
547 fn apply_single_qubit_unitary(
549 &mut self,
550 qubit: usize,
551 unitary: &Array2<Complex64>,
552 ) -> Result<()> {
553 let state_size = self.state.len();
554 let qubit_mask = 1 << qubit;
555
556 let mut new_state = self.state.clone();
557
558 for i in 0..state_size {
559 if i & qubit_mask == 0 {
560 let j = i | qubit_mask;
561 if j < state_size {
562 let amp_0 = self.state[i];
563 let amp_1 = self.state[j];
564
565 new_state[i] = unitary[[0, 0]] * amp_0 + unitary[[0, 1]] * amp_1;
566 new_state[j] = unitary[[1, 0]] * amp_0 + unitary[[1, 1]] * amp_1;
567 }
568 }
569 }
570
571 self.state = new_state;
572 Ok(())
573 }
574
575 fn apply_two_qubit_unitary(
577 &mut self,
578 qubit1: usize,
579 qubit2: usize,
580 unitary: &Array2<Complex64>,
581 ) -> Result<()> {
582 let state_size = self.state.len();
583 let mask1 = 1 << qubit1;
584 let mask2 = 1 << qubit2;
585
586 let mut new_state = self.state.clone();
587
588 for i in 0..state_size {
589 let i00 = i & !(mask1 | mask2);
590 let i01 = i00 | mask2;
591 let i10 = i00 | mask1;
592 let i11 = i00 | mask1 | mask2;
593
594 if i == i00 {
595 let amp_00 = self.state[i00];
596 let amp_01 = self.state[i01];
597 let amp_10 = self.state[i10];
598 let amp_11 = self.state[i11];
599
600 new_state[i00] = unitary[[0, 0]] * amp_00
601 + unitary[[0, 1]] * amp_01
602 + unitary[[0, 2]] * amp_10
603 + unitary[[0, 3]] * amp_11;
604 new_state[i01] = unitary[[1, 0]] * amp_00
605 + unitary[[1, 1]] * amp_01
606 + unitary[[1, 2]] * amp_10
607 + unitary[[1, 3]] * amp_11;
608 new_state[i10] = unitary[[2, 0]] * amp_00
609 + unitary[[2, 1]] * amp_01
610 + unitary[[2, 2]] * amp_10
611 + unitary[[2, 3]] * amp_11;
612 new_state[i11] = unitary[[3, 0]] * amp_00
613 + unitary[[3, 1]] * amp_01
614 + unitary[[3, 2]] * amp_10
615 + unitary[[3, 3]] * amp_11;
616 }
617 }
618
619 self.state = new_state;
620 Ok(())
621 }
622
623 fn apply_four_qubit_unitary(
625 &mut self,
626 qubits: &[usize],
627 unitary: &Array2<Complex64>,
628 ) -> Result<()> {
629 if qubits.len() != 4 {
630 return Err(SimulatorError::InvalidInput(
631 "Four qubits required".to_string(),
632 ));
633 }
634
635 let state_size = self.state.len();
636 let mut new_state = self.state.clone();
637
638 let masks: Vec<usize> = qubits.iter().map(|&q| 1 << q).collect();
640
641 for i in 0..state_size {
642 let base = i & !(masks[0] | masks[1] | masks[2] | masks[3]);
644
645 if i == base {
647 let mut amplitudes = Vec::new();
649 for state_idx in 0..16 {
650 let full_idx = base
651 | (if state_idx & 1 != 0 { masks[0] } else { 0 })
652 | (if state_idx & 2 != 0 { masks[1] } else { 0 })
653 | (if state_idx & 4 != 0 { masks[2] } else { 0 })
654 | (if state_idx & 8 != 0 { masks[3] } else { 0 });
655 amplitudes.push(self.state[full_idx]);
656 }
657
658 for out_state in 0..16 {
660 let mut new_amplitude = Complex64::new(0.0, 0.0);
661 for (in_state, &litude) in amplitudes.iter().enumerate() {
662 new_amplitude += unitary[[out_state, in_state]] * amplitude;
663 }
664
665 let full_idx = base
666 | (if out_state & 1 != 0 { masks[0] } else { 0 })
667 | (if out_state & 2 != 0 { masks[1] } else { 0 })
668 | (if out_state & 4 != 0 { masks[2] } else { 0 })
669 | (if out_state & 8 != 0 { masks[3] } else { 0 });
670 new_state[full_idx] = new_amplitude;
671 }
672 }
673 }
674
675 self.state = new_state;
676 Ok(())
677 }
678
679 fn get_neighbors_2d(&self, x: usize, y: usize, width: usize, height: usize) -> Vec<usize> {
681 let mut neighbors = Vec::new();
682
683 let deltas: &[(i32, i32)] = match self.config.neighborhood {
684 NeighborhoodType::VonNeumann => &[(-1, 0), (1, 0), (0, -1), (0, 1)],
685 NeighborhoodType::Moore => &[
686 (-1, -1),
687 (-1, 0),
688 (-1, 1),
689 (0, -1),
690 (0, 1),
691 (1, -1),
692 (1, 0),
693 (1, 1),
694 ],
695 _ => &[(-1, 0), (1, 0), (0, -1), (0, 1)], };
697
698 for (dx, dy) in deltas {
699 let nx = x as i32 + dx;
700 let ny = y as i32 + dy;
701
702 let (nx, ny) = match self.config.boundary_conditions {
703 BoundaryConditions::Periodic => {
704 let nx = ((nx % width as i32) + width as i32) % width as i32;
705 let ny = ((ny % height as i32) + height as i32) % height as i32;
706 (nx as usize, ny as usize)
707 }
708 BoundaryConditions::Fixed | BoundaryConditions::Open => {
709 if nx >= 0 && nx < width as i32 && ny >= 0 && ny < height as i32 {
710 (nx as usize, ny as usize)
711 } else {
712 continue;
713 }
714 }
715 BoundaryConditions::Reflective => {
716 let nx = if nx < 0 {
717 0
718 } else if nx >= width as i32 {
719 width - 1
720 } else {
721 nx as usize
722 };
723 let ny = if ny < 0 {
724 0
725 } else if ny >= height as i32 {
726 height - 1
727 } else {
728 ny as usize
729 };
730 (nx, ny)
731 }
732 };
733
734 neighbors.push(ny * width + nx);
735 }
736
737 neighbors
738 }
739
740 fn get_neighbors_3d(
742 &self,
743 x: usize,
744 y: usize,
745 z: usize,
746 width: usize,
747 height: usize,
748 depth: usize,
749 ) -> Vec<usize> {
750 let mut neighbors = Vec::new();
751
752 let deltas = [
754 (-1, 0, 0),
755 (1, 0, 0),
756 (0, -1, 0),
757 (0, 1, 0),
758 (0, 0, -1),
759 (0, 0, 1),
760 ];
761
762 for (dx, dy, dz) in deltas {
763 let nx = x as i32 + dx;
764 let ny = y as i32 + dy;
765 let nz = z as i32 + dz;
766
767 let (nx, ny, nz) = match self.config.boundary_conditions {
768 BoundaryConditions::Periodic => {
769 let nx = ((nx % width as i32) + width as i32) % width as i32;
770 let ny = ((ny % height as i32) + height as i32) % height as i32;
771 let nz = ((nz % depth as i32) + depth as i32) % depth as i32;
772 (nx as usize, ny as usize, nz as usize)
773 }
774 BoundaryConditions::Fixed | BoundaryConditions::Open => {
775 if nx >= 0
776 && nx < width as i32
777 && ny >= 0
778 && ny < height as i32
779 && nz >= 0
780 && nz < depth as i32
781 {
782 (nx as usize, ny as usize, nz as usize)
783 } else {
784 continue;
785 }
786 }
787 BoundaryConditions::Reflective => {
788 let nx = if nx < 0 {
789 0
790 } else if nx >= width as i32 {
791 width - 1
792 } else {
793 nx as usize
794 };
795 let ny = if ny < 0 {
796 0
797 } else if ny >= height as i32 {
798 height - 1
799 } else {
800 ny as usize
801 };
802 let nz = if nz < 0 {
803 0
804 } else if nz >= depth as i32 {
805 depth - 1
806 } else {
807 nz as usize
808 };
809 (nx, ny, nz)
810 }
811 };
812
813 neighbors.push(nz * width * height + ny * width + nx);
814 }
815
816 neighbors
817 }
818
819 fn apply_weak_measurements(&mut self) -> Result<()> {
821 let measurement_strength = 0.01; let cells_to_measure = (0..self.cell_mapping.total_cells)
824 .step_by(4)
825 .collect::<Vec<_>>();
826
827 for &cell in &cells_to_measure {
828 let measurement_operator =
830 self.create_weak_measurement_operator(cell, measurement_strength);
831 self.state = measurement_operator.dot(&self.state);
832
833 let norm: f64 = self
835 .state
836 .iter()
837 .map(scirs2_core::Complex::norm_sqr)
838 .sum::<f64>()
839 .sqrt();
840 if norm > 1e-15 {
841 self.state.mapv_inplace(|x| x / norm);
842 }
843 }
844
845 self.stats.measurements_performed += cells_to_measure.len();
846 Ok(())
847 }
848
849 fn create_weak_measurement_operator(&self, cell: usize, strength: f64) -> Array2<Complex64> {
851 let state_size = self.state.len();
852 let mut operator = Array2::eye(state_size);
853 let cell_mask = 1 << cell;
854
855 for i in 0..state_size {
856 if i & cell_mask != 0 {
857 let factor = Complex64::new((1.0 - strength).sqrt(), 0.0);
859 operator[[i, i]] = factor;
860 }
861 }
862
863 operator
864 }
865
866 fn take_snapshot(&mut self, time_step: usize) -> Result<QCASnapshot> {
868 let entropy = self.calculate_entanglement_entropy()?;
869
870 if self.stats.entropy_stats.max_entropy == 0.0 {
872 self.stats.entropy_stats.max_entropy = entropy;
873 self.stats.entropy_stats.min_entropy = entropy;
874 } else {
875 self.stats.entropy_stats.max_entropy =
876 self.stats.entropy_stats.max_entropy.max(entropy);
877 self.stats.entropy_stats.min_entropy =
878 self.stats.entropy_stats.min_entropy.min(entropy);
879 }
880
881 let snapshot_count = self.evolution_history.len() as f64;
882 self.stats.entropy_stats.avg_entropy = self
883 .stats
884 .entropy_stats
885 .avg_entropy
886 .mul_add(snapshot_count, entropy)
887 / (snapshot_count + 1.0);
888
889 Ok(QCASnapshot {
890 time_step,
891 state: self.state.clone(),
892 measurements: None,
893 entanglement_entropy: Some(entropy),
894 local_observables: self.calculate_local_observables()?,
895 })
896 }
897
898 fn calculate_entanglement_entropy(&self) -> Result<f64> {
900 let half_size = self.cell_mapping.total_cells / 2;
902 if half_size == 0 {
903 return Ok(0.0);
904 }
905
906 let reduced_dm = self.compute_reduced_density_matrix(half_size)?;
907 let eigenvalues = self.compute_eigenvalues(&reduced_dm)?;
908
909 let entropy = eigenvalues
910 .iter()
911 .filter(|&&lambda| lambda > 1e-15)
912 .map(|&lambda| -lambda * lambda.ln())
913 .sum();
914
915 Ok(entropy)
916 }
917
918 fn compute_reduced_density_matrix(&self, n_qubits: usize) -> Result<Array2<f64>> {
920 let reduced_size = 1 << n_qubits;
921 let mut reduced_dm = Array2::zeros((reduced_size, reduced_size));
922
923 let total_qubits = self.cell_mapping.total_cells;
924 let env_size = 1 << (total_qubits - n_qubits);
925
926 for i in 0..reduced_size {
927 for j in 0..reduced_size {
928 let mut element = 0.0;
929
930 for k in 0..env_size {
931 let full_i = i | (k << n_qubits);
932 let full_j = j | (k << n_qubits);
933
934 if full_i < self.state.len() && full_j < self.state.len() {
935 element += (self.state[full_i] * self.state[full_j].conj()).re;
936 }
937 }
938
939 reduced_dm[[i, j]] = element;
940 }
941 }
942
943 Ok(reduced_dm)
944 }
945
946 fn compute_eigenvalues(&self, matrix: &Array2<f64>) -> Result<Vec<f64>> {
948 let mut eigenvalues = Vec::new();
950
951 if matrix.dim().0 <= 4 {
953 for i in 0..matrix.dim().0 {
955 eigenvalues.push(matrix[[i, i]]); }
957 } else {
958 for i in 0..matrix.dim().0 {
960 eigenvalues.push(matrix[[i, i]]);
961 }
962 }
963
964 Ok(eigenvalues)
965 }
966
967 fn calculate_local_observables(&self) -> Result<HashMap<String, f64>> {
969 let mut observables = HashMap::new();
970
971 for cell in 0..self.cell_mapping.total_cells {
973 let magnetization = self.calculate_local_magnetization(cell)?;
974 observables.insert(format!("magnetization_{cell}"), magnetization);
975 }
976
977 for cell in 0..self.cell_mapping.total_cells {
979 if let Some(neighbors) = self.cell_mapping.neighbors.get(&cell) {
980 for &neighbor in neighbors {
981 if neighbor > cell {
982 let correlation = self.calculate_correlation(cell, neighbor)?;
984 observables.insert(format!("correlation_{cell}_{neighbor}"), correlation);
985 }
986 }
987 }
988 }
989
990 Ok(observables)
991 }
992
993 fn calculate_local_magnetization(&self, cell: usize) -> Result<f64> {
995 let cell_mask = 1 << cell;
996 let mut expectation = 0.0;
997
998 for (i, &litude) in self.state.iter().enumerate() {
999 let prob = amplitude.norm_sqr();
1000 let z_value = if i & cell_mask != 0 { -1.0 } else { 1.0 };
1001 expectation += prob * z_value;
1002 }
1003
1004 Ok(expectation)
1005 }
1006
1007 fn calculate_correlation(&self, cell1: usize, cell2: usize) -> Result<f64> {
1009 let mask1 = 1 << cell1;
1010 let mask2 = 1 << cell2;
1011 let mut correlation = 0.0;
1012
1013 for (i, &litude) in self.state.iter().enumerate() {
1014 let prob = amplitude.norm_sqr();
1015 let z1 = if i & mask1 != 0 { -1.0 } else { 1.0 };
1016 let z2 = if i & mask2 != 0 { -1.0 } else { 1.0 };
1017 correlation += prob * z1 * z2;
1018 }
1019
1020 Ok(correlation)
1021 }
1022
1023 pub fn measure_cells(&mut self, cells: &[usize]) -> Result<Vec<bool>> {
1025 let mut results = Vec::new();
1026
1027 for &cell in cells {
1028 let prob_one = self.calculate_measurement_probability(cell)?;
1029 let result = fastrand::f64() < prob_one;
1030 results.push(result);
1031
1032 self.collapse_state_after_measurement(cell, result)?;
1034 }
1035
1036 self.stats.measurements_performed += cells.len();
1037 Ok(results)
1038 }
1039
1040 fn calculate_measurement_probability(&self, cell: usize) -> Result<f64> {
1042 let cell_mask = 1 << cell;
1043 let mut prob_one = 0.0;
1044
1045 for (i, &litude) in self.state.iter().enumerate() {
1046 if i & cell_mask != 0 {
1047 prob_one += amplitude.norm_sqr();
1048 }
1049 }
1050
1051 Ok(prob_one)
1052 }
1053
1054 fn collapse_state_after_measurement(&mut self, cell: usize, result: bool) -> Result<()> {
1056 let cell_mask = 1 << cell;
1057 let mut norm = 0.0;
1058
1059 for (i, amplitude) in self.state.iter_mut().enumerate() {
1061 let cell_value = (i & cell_mask) != 0;
1062 if cell_value == result {
1063 norm += amplitude.norm_sqr();
1064 } else {
1065 *amplitude = Complex64::new(0.0, 0.0);
1066 }
1067 }
1068
1069 if norm > 1e-15 {
1071 let norm_factor = 1.0 / norm.sqrt();
1072 self.state.mapv_inplace(|x| x * norm_factor);
1073 }
1074
1075 Ok(())
1076 }
1077
1078 #[must_use]
1080 pub const fn get_state(&self) -> &Array1<Complex64> {
1081 &self.state
1082 }
1083
1084 #[must_use]
1086 pub fn get_evolution_history(&self) -> &[QCASnapshot] {
1087 &self.evolution_history
1088 }
1089
1090 #[must_use]
1092 pub const fn get_stats(&self) -> &QCAStats {
1093 &self.stats
1094 }
1095
1096 pub fn reset(&mut self) -> Result<()> {
1098 let state_size = self.state.len();
1099 self.state = Array1::zeros(state_size);
1100 self.state[0] = Complex64::new(1.0, 0.0);
1101 self.evolution_history.clear();
1102 self.stats = QCAStats::default();
1103 Ok(())
1104 }
1105
1106 fn create_cell_mapping(config: &QCAConfig) -> Result<CellMapping> {
1108 let total_cells = config.dimensions.iter().product();
1109 let mut coord_to_index = HashMap::new();
1110 let mut index_to_coord = HashMap::new();
1111 let mut neighbors = HashMap::new();
1112
1113 match config.dimensions.len() {
1114 1 => {
1115 let size = config.dimensions[0];
1116 for i in 0..size {
1117 coord_to_index.insert(vec![i], i);
1118 index_to_coord.insert(i, vec![i]);
1119
1120 let mut cell_neighbors = Vec::new();
1121 if config.boundary_conditions == BoundaryConditions::Periodic {
1122 cell_neighbors.push((i + size - 1) % size);
1123 cell_neighbors.push((i + 1) % size);
1124 } else {
1125 if i > 0 {
1126 cell_neighbors.push(i - 1);
1127 }
1128 if i < size - 1 {
1129 cell_neighbors.push(i + 1);
1130 }
1131 }
1132 neighbors.insert(i, cell_neighbors);
1133 }
1134 }
1135 2 => {
1136 let (width, height) = (config.dimensions[0], config.dimensions[1]);
1137 for y in 0..height {
1138 for x in 0..width {
1139 let index = y * width + x;
1140 coord_to_index.insert(vec![x, y], index);
1141 index_to_coord.insert(index, vec![x, y]);
1142 neighbors.insert(index, Vec::new());
1144 }
1145 }
1146 }
1147 3 => {
1148 let (width, height, depth) = (
1149 config.dimensions[0],
1150 config.dimensions[1],
1151 config.dimensions[2],
1152 );
1153 for z in 0..depth {
1154 for y in 0..height {
1155 for x in 0..width {
1156 let index = z * width * height + y * width + x;
1157 coord_to_index.insert(vec![x, y, z], index);
1158 index_to_coord.insert(index, vec![x, y, z]);
1159 neighbors.insert(index, Vec::new());
1160 }
1161 }
1162 }
1163 }
1164 _ => {
1165 return Err(SimulatorError::UnsupportedOperation(
1166 "QCA supports only 1D, 2D, and 3D lattices".to_string(),
1167 ))
1168 }
1169 }
1170
1171 Ok(CellMapping {
1172 total_cells,
1173 dimensions: config.dimensions.clone(),
1174 coord_to_index,
1175 index_to_coord,
1176 neighbors,
1177 })
1178 }
1179
1180 fn create_default_rules(
1181 config: &QCAConfig,
1182 cell_mapping: &CellMapping,
1183 ) -> Result<Vec<QCARule>> {
1184 let mut rules = Vec::new();
1185
1186 if config.rule_type == QCARuleType::Global {
1187 let state_size = 1 << cell_mapping.total_cells.min(10); let unitary = Self::create_random_unitary(state_size);
1190 rules.push(QCARule {
1191 id: "global_evolution".to_string(),
1192 unitary,
1193 affected_cells: (0..cell_mapping.total_cells).collect(),
1194 neighborhood_pattern: Vec::new(),
1195 parameters: HashMap::new(),
1196 });
1197 } else {
1198 rules.push(QCARule {
1200 id: "two_cell_interaction".to_string(),
1201 unitary: Self::create_cnot_unitary(),
1202 affected_cells: Vec::new(),
1203 neighborhood_pattern: vec![(0, 0, 0), (1, 0, 0)],
1204 parameters: HashMap::new(),
1205 });
1206
1207 rules.push(QCARule {
1208 id: "single_cell_rotation".to_string(),
1209 unitary: Self::create_rotation_unitary(std::f64::consts::PI / 4.0),
1210 affected_cells: Vec::new(),
1211 neighborhood_pattern: vec![(0, 0, 0)],
1212 parameters: HashMap::new(),
1213 });
1214 }
1215
1216 Ok(rules)
1217 }
1218
1219 fn create_cnot_unitary() -> Array2<Complex64> {
1220 Array2::from_shape_vec(
1221 (4, 4),
1222 vec![
1223 Complex64::new(1.0, 0.0),
1224 Complex64::new(0.0, 0.0),
1225 Complex64::new(0.0, 0.0),
1226 Complex64::new(0.0, 0.0),
1227 Complex64::new(0.0, 0.0),
1228 Complex64::new(1.0, 0.0),
1229 Complex64::new(0.0, 0.0),
1230 Complex64::new(0.0, 0.0),
1231 Complex64::new(0.0, 0.0),
1232 Complex64::new(0.0, 0.0),
1233 Complex64::new(0.0, 0.0),
1234 Complex64::new(1.0, 0.0),
1235 Complex64::new(0.0, 0.0),
1236 Complex64::new(0.0, 0.0),
1237 Complex64::new(1.0, 0.0),
1238 Complex64::new(0.0, 0.0),
1239 ],
1240 )
1241 .expect("CNOT unitary shape is always valid")
1242 }
1243
1244 fn create_rotation_unitary(angle: f64) -> Array2<Complex64> {
1245 let cos_half = (angle / 2.0).cos();
1246 let sin_half = (angle / 2.0).sin();
1247 Array2::from_shape_vec(
1248 (2, 2),
1249 vec![
1250 Complex64::new(cos_half, 0.0),
1251 Complex64::new(0.0, -sin_half),
1252 Complex64::new(0.0, -sin_half),
1253 Complex64::new(cos_half, 0.0),
1254 ],
1255 )
1256 .expect("Rotation unitary shape is always valid")
1257 }
1258
1259 fn create_margolus_rotation_unitary() -> Array2<Complex64> {
1260 let mut unitary = Array2::zeros((16, 16));
1262
1263 for i in 0..16 {
1265 let a = (i >> 3) & 1;
1267 let b = (i >> 2) & 1;
1268 let c = (i >> 1) & 1;
1269 let d = i & 1;
1270
1271 let rotated = (d << 3) | (a << 2) | (b << 1) | c;
1272 unitary[[rotated, i]] = Complex64::new(1.0, 0.0);
1273 }
1274
1275 unitary
1276 }
1277
1278 fn create_random_unitary(size: usize) -> Array2<Complex64> {
1279 let mut matrix = Array2::zeros((size, size));
1281
1282 for i in 0..size {
1284 for j in 0..size {
1285 matrix[[i, j]] = Complex64::new(fastrand::f64() - 0.5, fastrand::f64() - 0.5);
1286 }
1287 }
1288
1289 for j in 0..size {
1291 let mut norm_sq = 0.0;
1293 for i in 0..size {
1294 norm_sq += matrix[[i, j]].norm_sqr();
1295 }
1296 let norm = norm_sq.sqrt();
1297
1298 if norm > 1e-15 {
1299 for i in 0..size {
1300 matrix[[i, j]] /= norm;
1301 }
1302 }
1303
1304 for k in j + 1..size {
1306 let mut inner_product = Complex64::new(0.0, 0.0);
1307 for i in 0..size {
1308 inner_product += matrix[[i, j]].conj() * matrix[[i, k]];
1309 }
1310
1311 for i in 0..size {
1312 let correction = inner_product * matrix[[i, j]];
1313 matrix[[i, k]] -= correction;
1314 }
1315 }
1316 }
1317
1318 matrix
1319 }
1320
1321 fn is_unitary(matrix: &Array2<Complex64>) -> bool {
1322 let (rows, cols) = matrix.dim();
1323 if rows != cols {
1324 return false;
1325 }
1326
1327 let mut identity_check = Array2::zeros((rows, cols));
1329
1330 for i in 0..rows {
1331 for j in 0..cols {
1332 let mut sum = Complex64::new(0.0, 0.0);
1333 for k in 0..rows {
1334 sum += matrix[[k, i]].conj() * matrix[[k, j]];
1335 }
1336 identity_check[[i, j]] = sum;
1337 }
1338 }
1339
1340 for i in 0..rows {
1342 for j in 0..cols {
1343 let expected = if i == j {
1344 Complex64::new(1.0, 0.0)
1345 } else {
1346 Complex64::new(0.0, 0.0)
1347 };
1348 let diff = (identity_check[[i, j]] - expected).norm();
1349 if diff > 1e-10 {
1350 return false;
1351 }
1352 }
1353 }
1354
1355 true
1356 }
1357}
1358
1359#[derive(Debug, Clone)]
1361pub struct QCAEvolutionResult {
1362 pub final_state: Array1<Complex64>,
1364 pub evolution_history: Vec<QCASnapshot>,
1366 pub total_steps: usize,
1368 pub total_time_ms: f64,
1370 pub final_entropy: f64,
1372}
1373
1374pub struct QCAUtils;
1376
1377impl QCAUtils {
1378 #[must_use]
1380 pub fn create_predefined_config(config_type: &str, size: usize) -> QCAConfig {
1381 match config_type {
1382 "game_of_life" => QCAConfig {
1383 dimensions: vec![size, size],
1384 boundary_conditions: BoundaryConditions::Periodic,
1385 neighborhood: NeighborhoodType::Moore,
1386 rule_type: QCARuleType::Partitioned,
1387 evolution_steps: 50,
1388 parallel_evolution: true,
1389 measurement_strategy: MeasurementStrategy::Probabilistic,
1390 },
1391 "elementary_ca" => QCAConfig {
1392 dimensions: vec![size],
1393 boundary_conditions: BoundaryConditions::Periodic,
1394 neighborhood: NeighborhoodType::VonNeumann,
1395 rule_type: QCARuleType::Sequential,
1396 evolution_steps: 100,
1397 parallel_evolution: false,
1398 measurement_strategy: MeasurementStrategy::Deterministic,
1399 },
1400 "margolus_ca" => QCAConfig {
1401 dimensions: vec![size, size],
1402 boundary_conditions: BoundaryConditions::Periodic,
1403 neighborhood: NeighborhoodType::Custom,
1404 rule_type: QCARuleType::Margolus,
1405 evolution_steps: 25,
1406 parallel_evolution: true,
1407 measurement_strategy: MeasurementStrategy::Partial,
1408 },
1409 _ => QCAConfig::default(),
1410 }
1411 }
1412
1413 #[must_use]
1415 pub fn create_initial_pattern(pattern_type: &str, dimensions: &[usize]) -> Array1<Complex64> {
1416 let total_cells = dimensions.iter().product::<usize>();
1417 let state_size = 1 << total_cells;
1418 let mut state = Array1::zeros(state_size);
1419
1420 match pattern_type {
1421 "random" => {
1422 for i in 0..state_size {
1424 state[i] = Complex64::new(fastrand::f64() - 0.5, fastrand::f64() - 0.5);
1425 }
1426 let norm: f64 = state
1428 .iter()
1429 .map(scirs2_core::Complex::norm_sqr)
1430 .sum::<f64>()
1431 .sqrt();
1432 state.mapv_inplace(|x| x / norm);
1433 }
1434 "glider" if dimensions.len() == 2 => {
1435 let width = dimensions[0];
1437 let height = dimensions[1];
1438
1439 if width >= 3 && height >= 3 {
1440 let glider_positions = [
1442 width,
1443 2 * width + 1,
1444 2, width + 2,
1446 2 * width + 2,
1447 ];
1448
1449 let mut glider_state = 0;
1450 for &pos in &glider_positions {
1451 if pos < total_cells {
1452 glider_state |= 1 << pos;
1453 }
1454 }
1455
1456 if glider_state < state_size {
1457 state[glider_state] = Complex64::new(1.0, 0.0);
1458 }
1459 }
1460 }
1461 "uniform" => {
1462 let amplitude = 1.0 / (state_size as f64).sqrt();
1464 state.fill(Complex64::new(amplitude, 0.0));
1465 }
1466 _ => {
1467 state[0] = Complex64::new(1.0, 0.0);
1469 }
1470 }
1471
1472 state
1473 }
1474
1475 pub fn benchmark_qca() -> Result<QCABenchmarkResults> {
1477 let mut results = QCABenchmarkResults::default();
1478
1479 let configs = vec![
1480 (
1481 "1d_elementary",
1482 Self::create_predefined_config("elementary_ca", 8),
1483 ),
1484 (
1485 "2d_game_of_life",
1486 Self::create_predefined_config("game_of_life", 4),
1487 ),
1488 (
1489 "2d_margolus",
1490 Self::create_predefined_config("margolus_ca", 4),
1491 ),
1492 ];
1493
1494 for (name, mut config) in configs {
1495 config.evolution_steps = 20; let mut qca = QuantumCellularAutomaton::new(config)?;
1498 let initial_state = Self::create_initial_pattern("random", &qca.config.dimensions);
1499 qca.set_initial_state(initial_state)?;
1500
1501 let start = std::time::Instant::now();
1502 let _result = qca.evolve(20)?;
1503 let time = start.elapsed().as_secs_f64() * 1000.0;
1504
1505 results.benchmark_times.push((name.to_string(), time));
1506 results
1507 .qca_stats
1508 .insert(name.to_string(), qca.get_stats().clone());
1509 }
1510
1511 Ok(results)
1512 }
1513}
1514
1515#[derive(Debug, Clone, Default)]
1517pub struct QCABenchmarkResults {
1518 pub benchmark_times: Vec<(String, f64)>,
1520 pub qca_stats: HashMap<String, QCAStats>,
1522}
1523
1524#[cfg(test)]
1525mod tests {
1526 use super::*;
1527 use approx::assert_abs_diff_eq;
1528
1529 #[test]
1530 fn test_qca_creation() {
1531 let config = QCAConfig::default();
1532 let qca = QuantumCellularAutomaton::new(config);
1533 assert!(qca.is_ok());
1534 }
1535
1536 #[test]
1537 fn test_qca_1d_evolution() {
1538 let config = QCAConfig {
1539 dimensions: vec![4],
1540 evolution_steps: 5,
1541 ..Default::default()
1542 };
1543
1544 let mut qca =
1545 QuantumCellularAutomaton::new(config).expect("QCA creation should succeed in test");
1546 let result = qca.evolve(5);
1547 assert!(result.is_ok());
1548
1549 let evolution_result = result.expect("Evolution should succeed in test");
1550 assert_eq!(evolution_result.total_steps, 5);
1551 assert!(evolution_result.total_time_ms > 0.0);
1552 }
1553
1554 #[test]
1555 fn test_qca_2d_evolution() {
1556 let config = QCAConfig {
1557 dimensions: vec![3, 3],
1558 evolution_steps: 3,
1559 rule_type: QCARuleType::Partitioned,
1560 ..Default::default()
1561 };
1562
1563 let mut qca =
1564 QuantumCellularAutomaton::new(config).expect("QCA creation should succeed in test");
1565 let result = qca.evolve(3);
1566 assert!(result.is_ok());
1567 }
1568
1569 #[test]
1570 fn test_qca_measurement() {
1571 let config = QCAConfig {
1572 dimensions: vec![3],
1573 ..Default::default()
1574 };
1575
1576 let mut qca =
1577 QuantumCellularAutomaton::new(config).expect("QCA creation should succeed in test");
1578
1579 let results = qca.measure_cells(&[0, 1, 2]);
1581 assert!(results.is_ok());
1582 assert_eq!(
1583 results.expect("Measurement should succeed in test").len(),
1584 3
1585 );
1586 }
1587
1588 #[test]
1589 fn test_boundary_conditions() {
1590 let configs = vec![
1591 BoundaryConditions::Periodic,
1592 BoundaryConditions::Fixed,
1593 BoundaryConditions::Open,
1594 BoundaryConditions::Reflective,
1595 ];
1596
1597 for boundary in configs {
1598 let config = QCAConfig {
1599 dimensions: vec![4],
1600 boundary_conditions: boundary,
1601 evolution_steps: 2,
1602 ..Default::default()
1603 };
1604
1605 let mut qca =
1606 QuantumCellularAutomaton::new(config).expect("QCA creation should succeed in test");
1607 let result = qca.evolve(2);
1608 assert!(result.is_ok());
1609 }
1610 }
1611
1612 #[test]
1613 fn test_neighborhood_types() {
1614 let neighborhoods = vec![NeighborhoodType::VonNeumann, NeighborhoodType::Moore];
1615
1616 for neighborhood in neighborhoods {
1617 let config = QCAConfig {
1618 dimensions: vec![3, 3],
1619 neighborhood,
1620 evolution_steps: 2,
1621 ..Default::default()
1622 };
1623
1624 let mut qca =
1625 QuantumCellularAutomaton::new(config).expect("QCA creation should succeed in test");
1626 let result = qca.evolve(2);
1627 assert!(result.is_ok());
1628 }
1629 }
1630
1631 #[test]
1632 fn test_predefined_configs() {
1633 let config_types = vec!["game_of_life", "elementary_ca", "margolus_ca"];
1634
1635 for config_type in config_types {
1636 let config = QCAUtils::create_predefined_config(config_type, 4);
1637 let qca = QuantumCellularAutomaton::new(config);
1638 assert!(qca.is_ok());
1639 }
1640 }
1641
1642 #[test]
1643 fn test_initial_patterns() {
1644 let dimensions = vec![4, 4];
1645 let patterns = vec!["random", "glider", "uniform"];
1646
1647 for pattern in patterns {
1648 let state = QCAUtils::create_initial_pattern(pattern, &dimensions);
1649 assert_eq!(state.len(), 1 << 16); let norm: f64 = state.iter().map(|x| x.norm_sqr()).sum();
1653 assert_abs_diff_eq!(norm, 1.0, epsilon = 1e-10);
1654 }
1655 }
1656
1657 #[test]
1658 fn test_entanglement_entropy_calculation() {
1659 let config = QCAConfig {
1660 dimensions: vec![4],
1661 ..Default::default()
1662 };
1663
1664 let mut qca =
1665 QuantumCellularAutomaton::new(config).expect("QCA creation should succeed in test");
1666
1667 let state_size = qca.state.len();
1669 qca.state = Array1::zeros(state_size);
1670 qca.state[0] = Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0); qca.state[15] = Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0); let entropy = qca
1674 .calculate_entanglement_entropy()
1675 .expect("Entropy calculation should succeed in test");
1676 assert!(entropy >= 0.0);
1677 }
1678
1679 #[test]
1680 fn test_local_observables() {
1681 let config = QCAConfig {
1682 dimensions: vec![3],
1683 ..Default::default()
1684 };
1685
1686 let qca =
1687 QuantumCellularAutomaton::new(config).expect("QCA creation should succeed in test");
1688 let observables = qca
1689 .calculate_local_observables()
1690 .expect("Observable calculation should succeed in test");
1691
1692 assert!(observables.contains_key("magnetization_0"));
1694 assert!(observables.contains_key("magnetization_1"));
1695 assert!(observables.contains_key("magnetization_2"));
1696 }
1697
1698 #[test]
1699 fn test_unitary_check() {
1700 let cnot = QuantumCellularAutomaton::create_cnot_unitary();
1701 assert!(QuantumCellularAutomaton::is_unitary(&cnot));
1702
1703 let rotation =
1704 QuantumCellularAutomaton::create_rotation_unitary(std::f64::consts::PI / 4.0);
1705 assert!(QuantumCellularAutomaton::is_unitary(&rotation));
1706 }
1707}