1use crate::{
12 error::{QuantRS2Error, QuantRS2Result},
13 qubit::QubitId,
14};
15use ndarray::{Array1, Array2};
16use num_complex::Complex64;
17use rustc_hash::FxHashMap;
18use std::collections::HashMap;
19use std::f64::consts::PI;
20
21#[derive(Debug, Clone, PartialEq)]
23pub enum AtomSpecies {
24 Rb87,
26 Cs133,
28 Sr88,
30 Yb171,
32 Custom {
34 mass: f64, ground_state: String, rydberg_state: String, },
38}
39
40impl AtomSpecies {
41 pub fn mass(&self) -> f64 {
43 match self {
44 AtomSpecies::Rb87 => 86.909183,
45 AtomSpecies::Cs133 => 132.905447,
46 AtomSpecies::Sr88 => 87.905614,
47 AtomSpecies::Yb171 => 170.936426,
48 AtomSpecies::Custom { mass, .. } => *mass,
49 }
50 }
51
52 pub fn typical_trap_depth(&self) -> f64 {
54 match self {
55 AtomSpecies::Rb87 => 1000.0,
56 AtomSpecies::Cs133 => 800.0,
57 AtomSpecies::Sr88 => 1200.0,
58 AtomSpecies::Yb171 => 900.0,
59 AtomSpecies::Custom { .. } => 1000.0,
60 }
61 }
62
63 pub fn rydberg_energy(&self) -> f64 {
65 match self {
66 AtomSpecies::Rb87 => 100.0, AtomSpecies::Cs133 => 95.0,
68 AtomSpecies::Sr88 => 110.0,
69 AtomSpecies::Yb171 => 105.0,
70 AtomSpecies::Custom { .. } => 100.0,
71 }
72 }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq)]
77pub struct Position3D {
78 pub x: f64,
79 pub y: f64,
80 pub z: f64,
81}
82
83impl Position3D {
84 pub fn new(x: f64, y: f64, z: f64) -> Self {
85 Self { x, y, z }
86 }
87
88 pub fn distance_to(&self, other: &Position3D) -> f64 {
90 ((self.x - other.x).powi(2) + (self.y - other.y).powi(2) + (self.z - other.z).powi(2))
91 .sqrt()
92 }
93
94 pub fn rydberg_interaction(&self, other: &Position3D, c6: f64) -> f64 {
96 let distance = self.distance_to(other);
97 if distance == 0.0 {
98 f64::INFINITY
99 } else {
100 c6 / distance.powi(6) }
102 }
103}
104
105#[derive(Debug, Clone, Copy, PartialEq)]
107pub enum AtomState {
108 Ground,
110 Rydberg,
112 Intermediate,
114 Missing,
116}
117
118#[derive(Debug, Clone)]
120pub struct NeutralAtom {
121 pub species: AtomSpecies,
123 pub position: Position3D,
125 pub state: AtomState,
127 pub loading_probability: f64,
129 pub lifetime: f64,
131 pub coherence_time: f64,
133}
134
135impl NeutralAtom {
136 pub fn new(species: AtomSpecies, position: Position3D) -> Self {
138 Self {
139 species,
140 position,
141 state: AtomState::Ground,
142 loading_probability: 0.9, lifetime: 100.0, coherence_time: 1.0, }
146 }
147
148 pub fn is_loaded(&self) -> bool {
150 self.state != AtomState::Missing
151 }
152
153 pub fn interaction_with(&self, other: &NeutralAtom) -> f64 {
155 let c6 = match (&self.species, &other.species) {
157 (AtomSpecies::Rb87, AtomSpecies::Rb87) => 5000.0,
158 (AtomSpecies::Cs133, AtomSpecies::Cs133) => 6000.0,
159 (AtomSpecies::Sr88, AtomSpecies::Sr88) => 4500.0,
160 (AtomSpecies::Yb171, AtomSpecies::Yb171) => 4800.0,
161 _ => 5000.0, };
163
164 self.position.rydberg_interaction(&other.position, c6)
165 }
166}
167
168#[derive(Debug, Clone)]
170pub struct OpticalTweezer {
171 pub position: Position3D,
173 pub power: f64,
175 pub wavelength: f64,
177 pub numerical_aperture: f64,
179 pub active: bool,
181 pub atom: Option<NeutralAtom>,
183}
184
185impl OpticalTweezer {
186 pub fn new(position: Position3D, power: f64, wavelength: f64, numerical_aperture: f64) -> Self {
188 Self {
189 position,
190 power,
191 wavelength,
192 numerical_aperture,
193 active: true,
194 atom: None,
195 }
196 }
197
198 pub fn trap_depth(&self) -> f64 {
200 if !self.active {
201 return 0.0;
202 }
203
204 let beam_waist = self.wavelength / (PI * self.numerical_aperture); (self.power * 1000.0) / (self.wavelength * beam_waist.powi(2))
207 }
208
209 pub fn load_atom(&mut self, mut atom: NeutralAtom) -> bool {
211 if !self.active || self.atom.is_some() {
212 return false;
213 }
214
215 use rand::Rng;
217 let mut rng = rand::rng();
218 let success = rng.random::<f64>() < atom.loading_probability;
219
220 if success {
221 atom.position = self.position;
222 self.atom = Some(atom);
223 true
224 } else {
225 false
226 }
227 }
228
229 pub fn remove_atom(&mut self) -> Option<NeutralAtom> {
231 self.atom.take()
232 }
233
234 pub fn has_atom(&self) -> bool {
236 self.atom.is_some()
237 }
238}
239
240#[derive(Debug, Clone)]
242pub struct LaserSystem {
243 pub wavelength: f64,
245 pub power: f64,
247 pub beam_waist: f64,
249 pub detuning: f64,
251 pub linewidth: f64,
253 pub active: bool,
255}
256
257impl LaserSystem {
258 pub fn new(wavelength: f64, power: f64, beam_waist: f64, detuning: f64) -> Self {
260 Self {
261 wavelength,
262 power,
263 beam_waist,
264 detuning,
265 linewidth: 1.0, active: false,
267 }
268 }
269
270 pub fn rabi_frequency(&self) -> f64 {
272 if !self.active {
273 return 0.0;
274 }
275
276 (self.power.sqrt() * 10.0) / self.beam_waist
278 }
279
280 pub fn set_detuning(&mut self, detuning: f64) {
282 self.detuning = detuning;
283 }
284
285 pub fn set_active(&mut self, active: bool) {
287 self.active = active;
288 }
289}
290
291#[derive(Debug)]
293pub struct NeutralAtomQC {
294 pub tweezers: Vec<OpticalTweezer>,
296 pub qubit_map: FxHashMap<QubitId, usize>,
298 pub lasers: HashMap<String, LaserSystem>,
300 pub state: Array1<Complex64>,
302 pub num_qubits: usize,
304 pub global_phase: f64,
306 pub error_model: NeutralAtomErrorModel,
308}
309
310impl NeutralAtomQC {
311 pub fn new(num_qubits: usize) -> Self {
313 let mut tweezers = Vec::new();
314 let mut qubit_map = FxHashMap::default();
315
316 for i in 0..num_qubits {
318 let position = Position3D::new(i as f64 * 5.0, 0.0, 0.0); let tweezer = OpticalTweezer::new(
320 position, 1.0, 1064.0, 0.75, );
324 tweezers.push(tweezer);
325 qubit_map.insert(QubitId(i as u32), i);
326 }
327
328 let mut lasers = HashMap::new();
330
331 lasers.insert(
333 "cooling".to_string(),
334 LaserSystem::new(780.0, 10.0, 100.0, -10.0),
335 );
336
337 lasers.insert(
339 "rydberg".to_string(),
340 LaserSystem::new(480.0, 1.0, 2.0, 0.0),
341 );
342
343 lasers.insert("raman".to_string(), LaserSystem::new(795.0, 5.0, 10.0, 0.0));
345
346 let dim = 1 << num_qubits;
348 let mut state = Array1::zeros(dim);
349 state[0] = Complex64::new(1.0, 0.0);
350
351 Self {
352 tweezers,
353 qubit_map,
354 lasers,
355 state,
356 num_qubits,
357 global_phase: 0.0,
358 error_model: NeutralAtomErrorModel::default(),
359 }
360 }
361
362 pub fn load_atoms(&mut self, species: AtomSpecies) -> QuantRS2Result<usize> {
364 let mut loaded_count = 0;
365
366 for tweezer in &mut self.tweezers {
367 let atom = NeutralAtom::new(species.clone(), tweezer.position);
368 if tweezer.load_atom(atom) {
369 loaded_count += 1;
370 }
371 }
372
373 Ok(loaded_count)
374 }
375
376 pub fn rearrange_atoms(&mut self) -> QuantRS2Result<()> {
378 let mut atoms = Vec::new();
380 for tweezer in &mut self.tweezers {
381 if let Some(atom) = tweezer.remove_atom() {
382 atoms.push(atom);
383 }
384 }
385
386 for (i, atom) in atoms.into_iter().enumerate() {
388 if i < self.tweezers.len() {
389 self.tweezers[i].load_atom(atom);
390 }
391 }
392
393 Ok(())
394 }
395
396 pub fn apply_single_qubit_gate(
398 &mut self,
399 qubit: QubitId,
400 gate_matrix: &Array2<Complex64>,
401 ) -> QuantRS2Result<()> {
402 let tweezer_idx = *self
403 .qubit_map
404 .get(&qubit)
405 .ok_or_else(|| QuantRS2Error::InvalidInput(format!("Qubit {:?} not found", qubit)))?;
406
407 if !self.tweezers[tweezer_idx].has_atom() {
409 return Err(QuantRS2Error::InvalidOperation(
410 "No atom in tweezer".to_string(),
411 ));
412 }
413
414 let qubit_index = qubit.0 as usize;
416 let new_state = self.apply_single_qubit_tensor(qubit_index, gate_matrix)?;
417 self.state = new_state;
418
419 self.apply_single_qubit_errors(qubit_index)?;
421
422 Ok(())
423 }
424
425 pub fn apply_rydberg_gate(&mut self, control: QubitId, target: QubitId) -> QuantRS2Result<()> {
427 let control_idx = *self.qubit_map.get(&control).ok_or_else(|| {
428 QuantRS2Error::InvalidInput(format!("Control qubit {:?} not found", control))
429 })?;
430 let target_idx = *self.qubit_map.get(&target).ok_or_else(|| {
431 QuantRS2Error::InvalidInput(format!("Target qubit {:?} not found", target))
432 })?;
433
434 if !self.tweezers[control_idx].has_atom() || !self.tweezers[target_idx].has_atom() {
436 return Err(QuantRS2Error::InvalidOperation(
437 "Missing atoms for two-qubit gate".to_string(),
438 ));
439 }
440
441 let control_atom = self.tweezers[control_idx].atom.as_ref().unwrap();
443 let target_atom = self.tweezers[target_idx].atom.as_ref().unwrap();
444 let interaction = control_atom.interaction_with(target_atom);
445
446 if interaction < 1.0 {
447 return Err(QuantRS2Error::InvalidOperation(
449 "Insufficient Rydberg interaction".to_string(),
450 ));
451 }
452
453 let cz_matrix = self.create_rydberg_cz_matrix()?;
455 let new_state =
456 self.apply_two_qubit_tensor(control.0 as usize, target.0 as usize, &cz_matrix)?;
457 self.state = new_state;
458
459 self.apply_two_qubit_errors(control.0 as usize, target.0 as usize)?;
461
462 Ok(())
463 }
464
465 fn create_rydberg_cz_matrix(&self) -> QuantRS2Result<Array2<Complex64>> {
467 let mut cz = Array2::eye(4);
468 cz[[3, 3]] = Complex64::new(-1.0, 0.0); Ok(cz)
470 }
471
472 fn apply_single_qubit_tensor(
474 &self,
475 qubit_idx: usize,
476 gate: &Array2<Complex64>,
477 ) -> QuantRS2Result<Array1<Complex64>> {
478 let dim = 1 << self.num_qubits;
479 let mut new_state = Array1::zeros(dim);
480
481 for state in 0..dim {
482 let qubit_bit = (state >> qubit_idx) & 1;
483 let other_bits = state & !(1 << qubit_idx);
484
485 for new_qubit_bit in 0..2 {
486 let new_state_idx = other_bits | (new_qubit_bit << qubit_idx);
487 let gate_element = gate[[new_qubit_bit, qubit_bit]];
488 new_state[new_state_idx] += gate_element * self.state[state];
489 }
490 }
491
492 Ok(new_state)
493 }
494
495 fn apply_two_qubit_tensor(
497 &self,
498 qubit1: usize,
499 qubit2: usize,
500 gate: &Array2<Complex64>,
501 ) -> QuantRS2Result<Array1<Complex64>> {
502 let dim = 1 << self.num_qubits;
503 let mut new_state = Array1::zeros(dim);
504
505 for state in 0..dim {
506 let bit1 = (state >> qubit1) & 1;
507 let bit2 = (state >> qubit2) & 1;
508 let other_bits = state & !((1 << qubit1) | (1 << qubit2));
509 let two_qubit_state = (bit1 << 1) | bit2;
510
511 for new_two_qubit_state in 0..4 {
512 let new_bit1 = (new_two_qubit_state >> 1) & 1;
513 let new_bit2 = new_two_qubit_state & 1;
514 let new_state_idx = other_bits | (new_bit1 << qubit1) | (new_bit2 << qubit2);
515
516 let gate_element = gate[[new_two_qubit_state, two_qubit_state]];
517 new_state[new_state_idx] += gate_element * self.state[state];
518 }
519 }
520
521 Ok(new_state)
522 }
523
524 fn apply_single_qubit_errors(&mut self, _qubit: usize) -> QuantRS2Result<()> {
526 use rand::Rng;
533 let mut rng = rand::rng();
534
535 let phase_error = rng.random_range(-0.01..0.01);
537 self.global_phase += phase_error;
538
539 Ok(())
540 }
541
542 fn apply_two_qubit_errors(&mut self, _qubit1: usize, _qubit2: usize) -> QuantRS2Result<()> {
544 use rand::Rng;
546 let mut rng = rand::rng();
547
548 let phase_error = rng.random_range(-0.05..0.05);
550 self.global_phase += phase_error;
551
552 Ok(())
553 }
554
555 pub fn measure_qubit(&mut self, qubit: QubitId) -> QuantRS2Result<u8> {
557 let qubit_idx = qubit.0 as usize;
558 if qubit_idx >= self.num_qubits {
559 return Err(QuantRS2Error::InvalidInput(
560 "Qubit index out of range".to_string(),
561 ));
562 }
563
564 let mut prob_0 = 0.0;
566 let mut prob_1 = 0.0;
567
568 for state in 0..(1 << self.num_qubits) {
569 let amplitude_sq = self.state[state].norm_sqr();
570 if (state >> qubit_idx) & 1 == 0 {
571 prob_0 += amplitude_sq;
572 } else {
573 prob_1 += amplitude_sq;
574 }
575 }
576
577 use rand::Rng;
579 let mut rng = rand::rng();
580 let result: usize = if rng.random::<f64>() < prob_0 / (prob_0 + prob_1) {
581 0
582 } else {
583 1
584 };
585
586 let mut new_state = Array1::zeros(1 << self.num_qubits);
588 let normalization = if result == 0 {
589 prob_0.sqrt()
590 } else {
591 prob_1.sqrt()
592 };
593
594 for state in 0..(1 << self.num_qubits) {
595 if ((state >> qubit_idx) & 1) == result {
596 new_state[state] = self.state[state] / normalization;
597 }
598 }
599
600 self.state = new_state;
601 Ok(result as u8)
602 }
603
604 pub fn loaded_atom_count(&self) -> usize {
606 self.tweezers.iter().filter(|t| t.has_atom()).count()
607 }
608
609 pub fn get_probabilities(&self) -> Vec<f64> {
611 self.state.iter().map(|c| c.norm_sqr()).collect()
612 }
613
614 pub fn reset(&mut self) {
616 let dim = 1 << self.num_qubits;
617 self.state = Array1::zeros(dim);
618 self.state[0] = Complex64::new(1.0, 0.0);
619 self.global_phase = 0.0;
620 }
621
622 pub fn get_atom_positions(&self) -> Vec<(QubitId, Position3D, bool)> {
624 self.tweezers
625 .iter()
626 .enumerate()
627 .filter_map(|(i, tweezer)| {
628 self.qubit_map
629 .iter()
630 .find(|(_, &idx)| idx == i)
631 .map(|(&qubit_id, _)| (qubit_id, tweezer.position, tweezer.has_atom()))
632 })
633 .collect()
634 }
635}
636
637#[derive(Debug, Clone)]
639pub struct NeutralAtomErrorModel {
640 pub loading_fidelity: f64,
642 pub single_qubit_fidelity: f64,
644 pub two_qubit_fidelity: f64,
646 pub measurement_fidelity: f64,
648 pub coherence_time: f64,
650 pub blockade_radius: f64,
652}
653
654impl Default for NeutralAtomErrorModel {
655 fn default() -> Self {
656 Self {
657 loading_fidelity: 0.95,
658 single_qubit_fidelity: 0.999,
659 two_qubit_fidelity: 0.98,
660 measurement_fidelity: 0.99,
661 coherence_time: 1.0,
662 blockade_radius: 10.0,
663 }
664 }
665}
666
667pub struct NeutralAtomGates;
669
670impl NeutralAtomGates {
671 pub fn x_gate() -> Array2<Complex64> {
673 let mut x = Array2::zeros((2, 2));
674 x[[0, 1]] = Complex64::new(1.0, 0.0);
675 x[[1, 0]] = Complex64::new(1.0, 0.0);
676 x
677 }
678
679 pub fn y_gate() -> Array2<Complex64> {
681 let mut y = Array2::zeros((2, 2));
682 y[[0, 1]] = Complex64::new(0.0, -1.0);
683 y[[1, 0]] = Complex64::new(0.0, 1.0);
684 y
685 }
686
687 pub fn z_gate() -> Array2<Complex64> {
689 let mut z = Array2::zeros((2, 2));
690 z[[0, 0]] = Complex64::new(1.0, 0.0);
691 z[[1, 1]] = Complex64::new(-1.0, 0.0);
692 z
693 }
694
695 pub fn h_gate() -> Array2<Complex64> {
697 let h_val = 1.0 / 2.0_f64.sqrt();
698 let mut h = Array2::zeros((2, 2));
699 h[[0, 0]] = Complex64::new(h_val, 0.0);
700 h[[0, 1]] = Complex64::new(h_val, 0.0);
701 h[[1, 0]] = Complex64::new(h_val, 0.0);
702 h[[1, 1]] = Complex64::new(-h_val, 0.0);
703 h
704 }
705
706 pub fn rx_gate(theta: f64) -> Array2<Complex64> {
708 let cos_half = (theta / 2.0).cos();
709 let sin_half = (theta / 2.0).sin();
710
711 let mut rx = Array2::zeros((2, 2));
712 rx[[0, 0]] = Complex64::new(cos_half, 0.0);
713 rx[[0, 1]] = Complex64::new(0.0, -sin_half);
714 rx[[1, 0]] = Complex64::new(0.0, -sin_half);
715 rx[[1, 1]] = Complex64::new(cos_half, 0.0);
716 rx
717 }
718
719 pub fn ry_gate(theta: f64) -> Array2<Complex64> {
721 let cos_half = (theta / 2.0).cos();
722 let sin_half = (theta / 2.0).sin();
723
724 let mut ry = Array2::zeros((2, 2));
725 ry[[0, 0]] = Complex64::new(cos_half, 0.0);
726 ry[[0, 1]] = Complex64::new(-sin_half, 0.0);
727 ry[[1, 0]] = Complex64::new(sin_half, 0.0);
728 ry[[1, 1]] = Complex64::new(cos_half, 0.0);
729 ry
730 }
731
732 pub fn rz_gate(theta: f64) -> Array2<Complex64> {
734 let exp_neg = Complex64::new(0.0, -theta / 2.0).exp();
735 let exp_pos = Complex64::new(0.0, theta / 2.0).exp();
736
737 let mut rz = Array2::zeros((2, 2));
738 rz[[0, 0]] = exp_neg;
739 rz[[1, 1]] = exp_pos;
740 rz
741 }
742}
743
744#[cfg(test)]
745mod tests {
746 use super::*;
747
748 #[test]
749 fn test_atom_species_properties() {
750 let rb87 = AtomSpecies::Rb87;
751 assert!((rb87.mass() - 86.909183).abs() < 1e-6);
752 assert!(rb87.typical_trap_depth() > 0.0);
753 assert!(rb87.rydberg_energy() > 0.0);
754
755 let custom = AtomSpecies::Custom {
756 mass: 100.0,
757 ground_state: "5s".to_string(),
758 rydberg_state: "50s".to_string(),
759 };
760 assert_eq!(custom.mass(), 100.0);
761 }
762
763 #[test]
764 fn test_position_calculations() {
765 let pos1 = Position3D::new(0.0, 0.0, 0.0);
766 let pos2 = Position3D::new(3.0, 4.0, 0.0);
767
768 assert_eq!(pos1.distance_to(&pos2), 5.0);
769
770 let interaction = pos1.rydberg_interaction(&pos2, 1000.0);
771 assert!(interaction > 0.0);
772 assert!(interaction.is_finite());
773 }
774
775 #[test]
776 fn test_optical_tweezer() {
777 let position = Position3D::new(0.0, 0.0, 0.0);
778 let mut tweezer = OpticalTweezer::new(position, 1.0, 1064.0, 0.75);
779
780 assert!(tweezer.active);
781 assert!(!tweezer.has_atom());
782 assert!(tweezer.trap_depth() > 0.0);
783
784 let atom = NeutralAtom::new(AtomSpecies::Rb87, position);
785 let loaded = tweezer.load_atom(atom);
786 assert!(loaded == true || loaded == false);
789 }
790
791 #[test]
792 fn test_laser_system() {
793 let mut laser = LaserSystem::new(780.0, 10.0, 100.0, -10.0);
794
795 assert!(!laser.active);
796 assert_eq!(laser.rabi_frequency(), 0.0);
797
798 laser.set_active(true);
799 assert!(laser.rabi_frequency() > 0.0);
800
801 laser.set_detuning(5.0);
802 assert_eq!(laser.detuning, 5.0);
803 }
804
805 #[test]
806 fn test_neutral_atom_creation() {
807 let position = Position3D::new(1.0, 2.0, 3.0);
808 let atom = NeutralAtom::new(AtomSpecies::Rb87, position);
809
810 assert_eq!(atom.species, AtomSpecies::Rb87);
811 assert_eq!(atom.position, position);
812 assert_eq!(atom.state, AtomState::Ground);
813 assert!(atom.is_loaded());
814 }
815
816 #[test]
817 fn test_neutral_atom_qc_initialization() {
818 let qc = NeutralAtomQC::new(3);
819
820 assert_eq!(qc.num_qubits, 3);
821 assert_eq!(qc.tweezers.len(), 3);
822 assert_eq!(qc.qubit_map.len(), 3);
823 assert_eq!(qc.state.len(), 8); assert!((qc.state[0].norm_sqr() - 1.0).abs() < 1e-10);
827 for i in 1..8 {
828 assert!(qc.state[i].norm_sqr() < 1e-10);
829 }
830 }
831
832 #[test]
833 fn test_atom_loading() {
834 let mut qc = NeutralAtomQC::new(2);
835 let loaded = qc.load_atoms(AtomSpecies::Rb87).unwrap();
836
837 assert!(loaded <= 2);
839 assert!(qc.loaded_atom_count() <= 2);
840 }
841
842 #[test]
843 fn test_single_qubit_gates() {
844 let mut qc = NeutralAtomQC::new(1);
845 qc.load_atoms(AtomSpecies::Rb87).unwrap();
846
847 if qc.loaded_atom_count() > 0 {
848 let x_gate = NeutralAtomGates::x_gate();
849 let result = qc.apply_single_qubit_gate(QubitId(0), &x_gate);
850
851 assert!(result.is_ok());
853 }
854 }
855
856 #[test]
857 fn test_two_qubit_rydberg_gate() {
858 let mut qc = NeutralAtomQC::new(2);
859 qc.load_atoms(AtomSpecies::Rb87).unwrap();
860
861 if qc.loaded_atom_count() == 2 {
862 let result = qc.apply_rydberg_gate(QubitId(0), QubitId(1));
863 assert!(result.is_ok() || result.is_err()); }
866 }
867
868 #[test]
869 fn test_measurement() {
870 let mut qc = NeutralAtomQC::new(1);
871 qc.load_atoms(AtomSpecies::Rb87).unwrap();
872
873 if qc.loaded_atom_count() > 0 {
874 let result = qc.measure_qubit(QubitId(0));
875 if result.is_ok() {
876 let measurement = result.unwrap();
877 assert!(measurement == 0 || measurement == 1);
878 }
879 }
880 }
881
882 #[test]
883 fn test_gate_matrices() {
884 let x = NeutralAtomGates::x_gate();
885 let y = NeutralAtomGates::y_gate();
886 let z = NeutralAtomGates::z_gate();
887 let h = NeutralAtomGates::h_gate();
888
889 assert_eq!(x.dim(), (2, 2));
891 assert_eq!(y.dim(), (2, 2));
892 assert_eq!(z.dim(), (2, 2));
893 assert_eq!(h.dim(), (2, 2));
894
895 assert_eq!(x[[0, 1]], Complex64::new(1.0, 0.0));
897 assert_eq!(x[[1, 0]], Complex64::new(1.0, 0.0));
898 assert_eq!(x[[0, 0]], Complex64::new(0.0, 0.0));
899 assert_eq!(x[[1, 1]], Complex64::new(0.0, 0.0));
900 }
901
902 #[test]
903 fn test_rotation_gates() {
904 let rx_pi = NeutralAtomGates::rx_gate(PI);
905 let _ry_pi = NeutralAtomGates::ry_gate(PI);
906 let _rz_pi = NeutralAtomGates::rz_gate(PI);
907
908 let x = NeutralAtomGates::x_gate();
910 let expected_rx = x.mapv(|x| Complex64::new(0.0, -1.0) * x);
911
912 for i in 0..2 {
913 for j in 0..2 {
914 assert!((rx_pi[[i, j]] - expected_rx[[i, j]]).norm() < 1e-10);
915 }
916 }
917 }
918
919 #[test]
920 fn test_atom_rearrangement() {
921 let mut qc = NeutralAtomQC::new(3);
922
923 let mut atom1 = NeutralAtom::new(AtomSpecies::Rb87, qc.tweezers[0].position);
925 let mut atom2 = NeutralAtom::new(AtomSpecies::Rb87, qc.tweezers[2].position);
926 atom1.loading_probability = 1.0; atom2.loading_probability = 1.0; qc.tweezers[0].atom = Some(atom1);
929 qc.tweezers[2].atom = Some(atom2);
930
931 assert_eq!(qc.loaded_atom_count(), 2);
932
933 assert!(qc.rearrange_atoms().is_ok());
935
936 assert!(qc.tweezers[0].has_atom());
938 assert!(qc.tweezers[1].has_atom());
939 assert!(!qc.tweezers[2].has_atom());
940 }
941
942 #[test]
943 fn test_error_model_defaults() {
944 let error_model = NeutralAtomErrorModel::default();
945
946 assert!(error_model.loading_fidelity > 0.0 && error_model.loading_fidelity <= 1.0);
947 assert!(
948 error_model.single_qubit_fidelity > 0.0 && error_model.single_qubit_fidelity <= 1.0
949 );
950 assert!(error_model.two_qubit_fidelity > 0.0 && error_model.two_qubit_fidelity <= 1.0);
951 assert!(error_model.measurement_fidelity > 0.0 && error_model.measurement_fidelity <= 1.0);
952 assert!(error_model.coherence_time > 0.0);
953 assert!(error_model.blockade_radius > 0.0);
954 }
955
956 #[test]
957 fn test_quantum_state_probabilities() {
958 let qc = NeutralAtomQC::new(2);
959 let probs = qc.get_probabilities();
960
961 assert_eq!(probs.len(), 4); let total: f64 = probs.iter().sum();
965 assert!((total - 1.0).abs() < 1e-10);
966
967 assert!((probs[0] - 1.0).abs() < 1e-10);
969 }
970
971 #[test]
972 fn test_atom_position_retrieval() {
973 let qc = NeutralAtomQC::new(2);
974 let positions = qc.get_atom_positions();
975
976 assert_eq!(positions.len(), 2);
977
978 for (qubit_id, position, has_atom) in positions {
980 assert!(qubit_id.0 < 2);
981 assert!(position.x >= 0.0); assert!(has_atom == true || has_atom == false);
985 }
986 }
987}