1use crate::{
12 error::{QuantRS2Error, QuantRS2Result},
13 qubit::QubitId,
14};
15use rustc_hash::FxHashMap;
16use scirs2_core::ndarray::{Array1, Array2};
17use scirs2_core::Complex64;
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 const fn mass(&self) -> f64 {
43 match self {
44 Self::Rb87 => 86.909_183,
45 Self::Cs133 => 132.905_447,
46 Self::Sr88 => 87.905_614,
47 Self::Yb171 => 170.936_426,
48 Self::Custom { mass, .. } => *mass,
49 }
50 }
51
52 pub const fn typical_trap_depth(&self) -> f64 {
54 match self {
55 Self::Rb87 | Self::Custom { .. } => 1000.0,
56 Self::Cs133 => 800.0,
57 Self::Sr88 => 1200.0,
58 Self::Yb171 => 900.0,
59 }
60 }
61
62 pub const fn rydberg_energy(&self) -> f64 {
64 match self {
65 Self::Rb87 | Self::Custom { .. } => 100.0, Self::Cs133 => 95.0,
67 Self::Sr88 => 110.0,
68 Self::Yb171 => 105.0,
69 }
70 }
71}
72
73#[derive(Debug, Clone, Copy, PartialEq)]
75pub struct Position3D {
76 pub x: f64,
77 pub y: f64,
78 pub z: f64,
79}
80
81impl Position3D {
82 pub const fn new(x: f64, y: f64, z: f64) -> Self {
83 Self { x, y, z }
84 }
85
86 pub fn distance_to(&self, other: &Self) -> f64 {
88 (self.z - other.z)
89 .mul_add(
90 self.z - other.z,
91 (self.y - other.y).mul_add(self.y - other.y, (self.x - other.x).powi(2)),
92 )
93 .sqrt()
94 }
95
96 pub fn rydberg_interaction(&self, other: &Self, c6: f64) -> f64 {
98 let distance = self.distance_to(other);
99 if distance == 0.0 {
100 f64::INFINITY
101 } else {
102 c6 / distance.powi(6) }
104 }
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109pub enum AtomState {
110 Ground,
112 Rydberg,
114 Intermediate,
116 Missing,
118}
119
120#[derive(Debug, Clone)]
122pub struct NeutralAtom {
123 pub species: AtomSpecies,
125 pub position: Position3D,
127 pub state: AtomState,
129 pub loading_probability: f64,
131 pub lifetime: f64,
133 pub coherence_time: f64,
135}
136
137impl NeutralAtom {
138 pub const fn new(species: AtomSpecies, position: Position3D) -> Self {
140 Self {
141 species,
142 position,
143 state: AtomState::Ground,
144 loading_probability: 0.9, lifetime: 100.0, coherence_time: 1.0, }
148 }
149
150 pub fn is_loaded(&self) -> bool {
152 self.state != AtomState::Missing
153 }
154
155 pub fn interaction_with(&self, other: &Self) -> f64 {
157 let c6 = match (&self.species, &other.species) {
159 (AtomSpecies::Cs133, AtomSpecies::Cs133) => 6000.0,
160 (AtomSpecies::Sr88, AtomSpecies::Sr88) => 4500.0,
161 (AtomSpecies::Yb171, AtomSpecies::Yb171) => 4800.0,
162 (AtomSpecies::Rb87, AtomSpecies::Rb87) | _ => 5000.0, };
164
165 self.position.rydberg_interaction(&other.position, c6)
166 }
167}
168
169#[derive(Debug, Clone)]
171pub struct OpticalTweezer {
172 pub position: Position3D,
174 pub power: f64,
176 pub wavelength: f64,
178 pub numerical_aperture: f64,
180 pub active: bool,
182 pub atom: Option<NeutralAtom>,
184}
185
186impl OpticalTweezer {
187 pub const fn new(
189 position: Position3D,
190 power: f64,
191 wavelength: f64,
192 numerical_aperture: f64,
193 ) -> Self {
194 Self {
195 position,
196 power,
197 wavelength,
198 numerical_aperture,
199 active: true,
200 atom: None,
201 }
202 }
203
204 pub fn trap_depth(&self) -> f64 {
206 if !self.active {
207 return 0.0;
208 }
209
210 let beam_waist = self.wavelength / (PI * self.numerical_aperture); (self.power * 1000.0) / (self.wavelength * beam_waist.powi(2))
213 }
214
215 pub fn load_atom(&mut self, mut atom: NeutralAtom) -> bool {
217 if !self.active || self.atom.is_some() {
218 return false;
219 }
220
221 use scirs2_core::random::prelude::*;
223 let mut rng = thread_rng();
224 let success = rng.gen::<f64>() < atom.loading_probability;
225
226 if success {
227 atom.position = self.position;
228 self.atom = Some(atom);
229 true
230 } else {
231 false
232 }
233 }
234
235 pub const fn remove_atom(&mut self) -> Option<NeutralAtom> {
237 self.atom.take()
238 }
239
240 pub const fn has_atom(&self) -> bool {
242 self.atom.is_some()
243 }
244}
245
246#[derive(Debug, Clone)]
248pub struct LaserSystem {
249 pub wavelength: f64,
251 pub power: f64,
253 pub beam_waist: f64,
255 pub detuning: f64,
257 pub linewidth: f64,
259 pub active: bool,
261}
262
263impl LaserSystem {
264 pub const fn new(wavelength: f64, power: f64, beam_waist: f64, detuning: f64) -> Self {
266 Self {
267 wavelength,
268 power,
269 beam_waist,
270 detuning,
271 linewidth: 1.0, active: false,
273 }
274 }
275
276 pub fn rabi_frequency(&self) -> f64 {
278 if !self.active {
279 return 0.0;
280 }
281
282 (self.power.sqrt() * 10.0) / self.beam_waist
284 }
285
286 pub const fn set_detuning(&mut self, detuning: f64) {
288 self.detuning = detuning;
289 }
290
291 pub const fn set_active(&mut self, active: bool) {
293 self.active = active;
294 }
295}
296
297#[derive(Debug)]
299pub struct NeutralAtomQC {
300 pub tweezers: Vec<OpticalTweezer>,
302 pub qubit_map: FxHashMap<QubitId, usize>,
304 pub lasers: HashMap<String, LaserSystem>,
306 pub state: Array1<Complex64>,
308 pub num_qubits: usize,
310 pub global_phase: f64,
312 pub error_model: NeutralAtomErrorModel,
314}
315
316impl NeutralAtomQC {
317 pub fn new(num_qubits: usize) -> Self {
319 let mut tweezers = Vec::new();
320 let mut qubit_map = FxHashMap::default();
321
322 for i in 0..num_qubits {
324 let position = Position3D::new(i as f64 * 5.0, 0.0, 0.0); let tweezer = OpticalTweezer::new(
326 position, 1.0, 1064.0, 0.75, );
330 tweezers.push(tweezer);
331 qubit_map.insert(QubitId(i as u32), i);
332 }
333
334 let mut lasers = HashMap::new();
336
337 lasers.insert(
339 "cooling".to_string(),
340 LaserSystem::new(780.0, 10.0, 100.0, -10.0),
341 );
342
343 lasers.insert(
345 "rydberg".to_string(),
346 LaserSystem::new(480.0, 1.0, 2.0, 0.0),
347 );
348
349 lasers.insert("raman".to_string(), LaserSystem::new(795.0, 5.0, 10.0, 0.0));
351
352 let dim = 1 << num_qubits;
354 let mut state = Array1::zeros(dim);
355 state[0] = Complex64::new(1.0, 0.0);
356
357 Self {
358 tweezers,
359 qubit_map,
360 lasers,
361 state,
362 num_qubits,
363 global_phase: 0.0,
364 error_model: NeutralAtomErrorModel::default(),
365 }
366 }
367
368 pub fn load_atoms(&mut self, species: AtomSpecies) -> QuantRS2Result<usize> {
370 let mut loaded_count = 0;
371
372 for tweezer in &mut self.tweezers {
373 let atom = NeutralAtom::new(species.clone(), tweezer.position);
374 if tweezer.load_atom(atom) {
375 loaded_count += 1;
376 }
377 }
378
379 Ok(loaded_count)
380 }
381
382 pub fn rearrange_atoms(&mut self) -> QuantRS2Result<()> {
384 let mut atoms = Vec::new();
386 for tweezer in &mut self.tweezers {
387 if let Some(atom) = tweezer.remove_atom() {
388 atoms.push(atom);
389 }
390 }
391
392 for (i, atom) in atoms.into_iter().enumerate() {
394 if i < self.tweezers.len() {
395 self.tweezers[i].load_atom(atom);
396 }
397 }
398
399 Ok(())
400 }
401
402 pub fn apply_single_qubit_gate(
404 &mut self,
405 qubit: QubitId,
406 gate_matrix: &Array2<Complex64>,
407 ) -> QuantRS2Result<()> {
408 let tweezer_idx = *self
409 .qubit_map
410 .get(&qubit)
411 .ok_or_else(|| QuantRS2Error::InvalidInput(format!("Qubit {qubit:?} not found")))?;
412
413 if !self.tweezers[tweezer_idx].has_atom() {
415 return Err(QuantRS2Error::InvalidOperation(
416 "No atom in tweezer".to_string(),
417 ));
418 }
419
420 let qubit_index = qubit.0 as usize;
422 let new_state = self.apply_single_qubit_tensor(qubit_index, gate_matrix)?;
423 self.state = new_state;
424
425 self.apply_single_qubit_errors(qubit_index)?;
427
428 Ok(())
429 }
430
431 pub fn apply_rydberg_gate(&mut self, control: QubitId, target: QubitId) -> QuantRS2Result<()> {
433 let control_idx = *self.qubit_map.get(&control).ok_or_else(|| {
434 QuantRS2Error::InvalidInput(format!("Control qubit {control:?} not found"))
435 })?;
436 let target_idx = *self.qubit_map.get(&target).ok_or_else(|| {
437 QuantRS2Error::InvalidInput(format!("Target qubit {target:?} not found"))
438 })?;
439
440 if !self.tweezers[control_idx].has_atom() || !self.tweezers[target_idx].has_atom() {
442 return Err(QuantRS2Error::InvalidOperation(
443 "Missing atoms for two-qubit gate".to_string(),
444 ));
445 }
446
447 let control_atom = self.tweezers[control_idx].atom.as_ref().ok_or_else(|| {
449 QuantRS2Error::InvalidOperation("Control atom unexpectedly missing".to_string())
450 })?;
451 let target_atom = self.tweezers[target_idx].atom.as_ref().ok_or_else(|| {
452 QuantRS2Error::InvalidOperation("Target atom unexpectedly missing".to_string())
453 })?;
454 let interaction = control_atom.interaction_with(target_atom);
455
456 if interaction < 1.0 {
457 return Err(QuantRS2Error::InvalidOperation(
459 "Insufficient Rydberg interaction".to_string(),
460 ));
461 }
462
463 let cz_matrix = self.create_rydberg_cz_matrix()?;
465 let new_state =
466 self.apply_two_qubit_tensor(control.0 as usize, target.0 as usize, &cz_matrix)?;
467 self.state = new_state;
468
469 self.apply_two_qubit_errors(control.0 as usize, target.0 as usize)?;
471
472 Ok(())
473 }
474
475 fn create_rydberg_cz_matrix(&self) -> QuantRS2Result<Array2<Complex64>> {
477 let mut cz = Array2::eye(4);
478 cz[[3, 3]] = Complex64::new(-1.0, 0.0); Ok(cz)
480 }
481
482 fn apply_single_qubit_tensor(
484 &self,
485 qubit_idx: usize,
486 gate: &Array2<Complex64>,
487 ) -> QuantRS2Result<Array1<Complex64>> {
488 let dim = 1 << self.num_qubits;
489 let mut new_state = Array1::zeros(dim);
490
491 for state in 0..dim {
492 let qubit_bit = (state >> qubit_idx) & 1;
493 let other_bits = state & !(1 << qubit_idx);
494
495 for new_qubit_bit in 0..2 {
496 let new_state_idx = other_bits | (new_qubit_bit << qubit_idx);
497 let gate_element = gate[[new_qubit_bit, qubit_bit]];
498 new_state[new_state_idx] += gate_element * self.state[state];
499 }
500 }
501
502 Ok(new_state)
503 }
504
505 fn apply_two_qubit_tensor(
507 &self,
508 qubit1: usize,
509 qubit2: usize,
510 gate: &Array2<Complex64>,
511 ) -> QuantRS2Result<Array1<Complex64>> {
512 let dim = 1 << self.num_qubits;
513 let mut new_state = Array1::zeros(dim);
514
515 for state in 0..dim {
516 let bit1 = (state >> qubit1) & 1;
517 let bit2 = (state >> qubit2) & 1;
518 let other_bits = state & !((1 << qubit1) | (1 << qubit2));
519 let two_qubit_state = (bit1 << 1) | bit2;
520
521 for new_two_qubit_state in 0..4 {
522 let new_bit1 = (new_two_qubit_state >> 1) & 1;
523 let new_bit2 = new_two_qubit_state & 1;
524 let new_state_idx = other_bits | (new_bit1 << qubit1) | (new_bit2 << qubit2);
525
526 let gate_element = gate[[new_two_qubit_state, two_qubit_state]];
527 new_state[new_state_idx] += gate_element * self.state[state];
528 }
529 }
530
531 Ok(new_state)
532 }
533
534 fn apply_single_qubit_errors(&mut self, _qubit: usize) -> QuantRS2Result<()> {
536 use scirs2_core::random::prelude::*;
543 let mut rng = thread_rng();
544
545 let phase_error = rng.gen_range(-0.01..0.01);
547 self.global_phase += phase_error;
548
549 Ok(())
550 }
551
552 fn apply_two_qubit_errors(&mut self, _qubit1: usize, _qubit2: usize) -> QuantRS2Result<()> {
554 use scirs2_core::random::prelude::*;
556 let mut rng = thread_rng();
557
558 let phase_error = rng.gen_range(-0.05..0.05);
560 self.global_phase += phase_error;
561
562 Ok(())
563 }
564
565 pub fn measure_qubit(&mut self, qubit: QubitId) -> QuantRS2Result<u8> {
567 let qubit_idx = qubit.0 as usize;
568 if qubit_idx >= self.num_qubits {
569 return Err(QuantRS2Error::InvalidInput(
570 "Qubit index out of range".to_string(),
571 ));
572 }
573
574 let mut prob_0 = 0.0;
576 let mut prob_1 = 0.0;
577
578 for state in 0..(1 << self.num_qubits) {
579 let amplitude_sq = self.state[state].norm_sqr();
580 if (state >> qubit_idx) & 1 == 0 {
581 prob_0 += amplitude_sq;
582 } else {
583 prob_1 += amplitude_sq;
584 }
585 }
586
587 use scirs2_core::random::prelude::*;
589 let mut rng = thread_rng();
590 let result: usize = usize::from(rng.gen::<f64>() >= prob_0 / (prob_0 + prob_1));
591
592 let mut new_state = Array1::zeros(1 << self.num_qubits);
594 let normalization = if result == 0 {
595 prob_0.sqrt()
596 } else {
597 prob_1.sqrt()
598 };
599
600 for state in 0..(1 << self.num_qubits) {
601 if ((state >> qubit_idx) & 1) == result {
602 new_state[state] = self.state[state] / normalization;
603 }
604 }
605
606 self.state = new_state;
607 Ok(result as u8)
608 }
609
610 pub fn loaded_atom_count(&self) -> usize {
612 self.tweezers.iter().filter(|t| t.has_atom()).count()
613 }
614
615 pub fn get_probabilities(&self) -> Vec<f64> {
617 self.state.iter().map(|c| c.norm_sqr()).collect()
618 }
619
620 pub fn reset(&mut self) {
622 let dim = 1 << self.num_qubits;
623 self.state = Array1::zeros(dim);
624 self.state[0] = Complex64::new(1.0, 0.0);
625 self.global_phase = 0.0;
626 }
627
628 pub fn get_atom_positions(&self) -> Vec<(QubitId, Position3D, bool)> {
630 self.tweezers
631 .iter()
632 .enumerate()
633 .filter_map(|(i, tweezer)| {
634 self.qubit_map
635 .iter()
636 .find(|(_, &idx)| idx == i)
637 .map(|(&qubit_id, _)| (qubit_id, tweezer.position, tweezer.has_atom()))
638 })
639 .collect()
640 }
641}
642
643#[derive(Debug, Clone)]
645pub struct NeutralAtomErrorModel {
646 pub loading_fidelity: f64,
648 pub single_qubit_fidelity: f64,
650 pub two_qubit_fidelity: f64,
652 pub measurement_fidelity: f64,
654 pub coherence_time: f64,
656 pub blockade_radius: f64,
658}
659
660impl Default for NeutralAtomErrorModel {
661 fn default() -> Self {
662 Self {
663 loading_fidelity: 0.95,
664 single_qubit_fidelity: 0.999,
665 two_qubit_fidelity: 0.98,
666 measurement_fidelity: 0.99,
667 coherence_time: 1.0,
668 blockade_radius: 10.0,
669 }
670 }
671}
672
673pub struct NeutralAtomGates;
675
676impl NeutralAtomGates {
677 pub fn x_gate() -> Array2<Complex64> {
679 let mut x = Array2::zeros((2, 2));
680 x[[0, 1]] = Complex64::new(1.0, 0.0);
681 x[[1, 0]] = Complex64::new(1.0, 0.0);
682 x
683 }
684
685 pub fn y_gate() -> Array2<Complex64> {
687 let mut y = Array2::zeros((2, 2));
688 y[[0, 1]] = Complex64::new(0.0, -1.0);
689 y[[1, 0]] = Complex64::new(0.0, 1.0);
690 y
691 }
692
693 pub fn z_gate() -> Array2<Complex64> {
695 let mut z = Array2::zeros((2, 2));
696 z[[0, 0]] = Complex64::new(1.0, 0.0);
697 z[[1, 1]] = Complex64::new(-1.0, 0.0);
698 z
699 }
700
701 pub fn h_gate() -> Array2<Complex64> {
703 let h_val = 1.0 / 2.0_f64.sqrt();
704 let mut h = Array2::zeros((2, 2));
705 h[[0, 0]] = Complex64::new(h_val, 0.0);
706 h[[0, 1]] = Complex64::new(h_val, 0.0);
707 h[[1, 0]] = Complex64::new(h_val, 0.0);
708 h[[1, 1]] = Complex64::new(-h_val, 0.0);
709 h
710 }
711
712 pub fn rx_gate(theta: f64) -> Array2<Complex64> {
714 let cos_half = (theta / 2.0).cos();
715 let sin_half = (theta / 2.0).sin();
716
717 let mut rx = Array2::zeros((2, 2));
718 rx[[0, 0]] = Complex64::new(cos_half, 0.0);
719 rx[[0, 1]] = Complex64::new(0.0, -sin_half);
720 rx[[1, 0]] = Complex64::new(0.0, -sin_half);
721 rx[[1, 1]] = Complex64::new(cos_half, 0.0);
722 rx
723 }
724
725 pub fn ry_gate(theta: f64) -> Array2<Complex64> {
727 let cos_half = (theta / 2.0).cos();
728 let sin_half = (theta / 2.0).sin();
729
730 let mut ry = Array2::zeros((2, 2));
731 ry[[0, 0]] = Complex64::new(cos_half, 0.0);
732 ry[[0, 1]] = Complex64::new(-sin_half, 0.0);
733 ry[[1, 0]] = Complex64::new(sin_half, 0.0);
734 ry[[1, 1]] = Complex64::new(cos_half, 0.0);
735 ry
736 }
737
738 pub fn rz_gate(theta: f64) -> Array2<Complex64> {
740 let exp_neg = Complex64::new(0.0, -theta / 2.0).exp();
741 let exp_pos = Complex64::new(0.0, theta / 2.0).exp();
742
743 let mut rz = Array2::zeros((2, 2));
744 rz[[0, 0]] = exp_neg;
745 rz[[1, 1]] = exp_pos;
746 rz
747 }
748}
749
750#[cfg(test)]
751mod tests {
752 use super::*;
753
754 #[test]
755 fn test_atom_species_properties() {
756 let rb87 = AtomSpecies::Rb87;
757 assert!((rb87.mass() - 86.909183).abs() < 1e-6);
758 assert!(rb87.typical_trap_depth() > 0.0);
759 assert!(rb87.rydberg_energy() > 0.0);
760
761 let custom = AtomSpecies::Custom {
762 mass: 100.0,
763 ground_state: "5s".to_string(),
764 rydberg_state: "50s".to_string(),
765 };
766 assert_eq!(custom.mass(), 100.0);
767 }
768
769 #[test]
770 fn test_position_calculations() {
771 let pos1 = Position3D::new(0.0, 0.0, 0.0);
772 let pos2 = Position3D::new(3.0, 4.0, 0.0);
773
774 assert_eq!(pos1.distance_to(&pos2), 5.0);
775
776 let interaction = pos1.rydberg_interaction(&pos2, 1000.0);
777 assert!(interaction > 0.0);
778 assert!(interaction.is_finite());
779 }
780
781 #[test]
782 fn test_optical_tweezer() {
783 let position = Position3D::new(0.0, 0.0, 0.0);
784 let mut tweezer = OpticalTweezer::new(position, 1.0, 1064.0, 0.75);
785
786 assert!(tweezer.active);
787 assert!(!tweezer.has_atom());
788 assert!(tweezer.trap_depth() > 0.0);
789
790 let atom = NeutralAtom::new(AtomSpecies::Rb87, position);
791 let loaded = tweezer.load_atom(atom);
792 let _result = loaded; }
796
797 #[test]
798 fn test_laser_system() {
799 let mut laser = LaserSystem::new(780.0, 10.0, 100.0, -10.0);
800
801 assert!(!laser.active);
802 assert_eq!(laser.rabi_frequency(), 0.0);
803
804 laser.set_active(true);
805 assert!(laser.rabi_frequency() > 0.0);
806
807 laser.set_detuning(5.0);
808 assert_eq!(laser.detuning, 5.0);
809 }
810
811 #[test]
812 fn test_neutral_atom_creation() {
813 let position = Position3D::new(1.0, 2.0, 3.0);
814 let atom = NeutralAtom::new(AtomSpecies::Rb87, position);
815
816 assert_eq!(atom.species, AtomSpecies::Rb87);
817 assert_eq!(atom.position, position);
818 assert_eq!(atom.state, AtomState::Ground);
819 assert!(atom.is_loaded());
820 }
821
822 #[test]
823 fn test_neutral_atom_qc_initialization() {
824 let qc = NeutralAtomQC::new(3);
825
826 assert_eq!(qc.num_qubits, 3);
827 assert_eq!(qc.tweezers.len(), 3);
828 assert_eq!(qc.qubit_map.len(), 3);
829 assert_eq!(qc.state.len(), 8); assert!((qc.state[0].norm_sqr() - 1.0).abs() < 1e-10);
833 for i in 1..8 {
834 assert!(qc.state[i].norm_sqr() < 1e-10);
835 }
836 }
837
838 #[test]
839 fn test_atom_loading() {
840 let mut qc = NeutralAtomQC::new(2);
841 let loaded = qc
842 .load_atoms(AtomSpecies::Rb87)
843 .expect("Failed to load atoms");
844
845 assert!(loaded <= 2);
847 assert!(qc.loaded_atom_count() <= 2);
848 }
849
850 #[test]
851 fn test_single_qubit_gates() {
852 let mut qc = NeutralAtomQC::new(1);
853 qc.load_atoms(AtomSpecies::Rb87)
854 .expect("Failed to load atoms");
855
856 if qc.loaded_atom_count() > 0 {
857 let x_gate = NeutralAtomGates::x_gate();
858 let result = qc.apply_single_qubit_gate(QubitId(0), &x_gate);
859
860 assert!(result.is_ok());
862 }
863 }
864
865 #[test]
866 fn test_two_qubit_rydberg_gate() {
867 let mut qc = NeutralAtomQC::new(2);
868 qc.load_atoms(AtomSpecies::Rb87)
869 .expect("Failed to load atoms");
870
871 if qc.loaded_atom_count() == 2 {
872 let result = qc.apply_rydberg_gate(QubitId(0), QubitId(1));
873 assert!(result.is_ok() || result.is_err()); }
876 }
877
878 #[test]
879 fn test_measurement() {
880 let mut qc = NeutralAtomQC::new(1);
881 qc.load_atoms(AtomSpecies::Rb87)
882 .expect("Failed to load atoms");
883
884 if qc.loaded_atom_count() > 0 {
885 let result = qc.measure_qubit(QubitId(0));
886 if let Ok(measurement) = result {
887 assert!(measurement == 0 || measurement == 1);
888 }
889 }
890 }
891
892 #[test]
893 fn test_gate_matrices() {
894 let x = NeutralAtomGates::x_gate();
895 let y = NeutralAtomGates::y_gate();
896 let z = NeutralAtomGates::z_gate();
897 let h = NeutralAtomGates::h_gate();
898
899 assert_eq!(x.dim(), (2, 2));
901 assert_eq!(y.dim(), (2, 2));
902 assert_eq!(z.dim(), (2, 2));
903 assert_eq!(h.dim(), (2, 2));
904
905 assert_eq!(x[[0, 1]], Complex64::new(1.0, 0.0));
907 assert_eq!(x[[1, 0]], Complex64::new(1.0, 0.0));
908 assert_eq!(x[[0, 0]], Complex64::new(0.0, 0.0));
909 assert_eq!(x[[1, 1]], Complex64::new(0.0, 0.0));
910 }
911
912 #[test]
913 fn test_rotation_gates() {
914 let rx_pi = NeutralAtomGates::rx_gate(PI);
915 let _ry_pi = NeutralAtomGates::ry_gate(PI);
916 let _rz_pi = NeutralAtomGates::rz_gate(PI);
917
918 let x = NeutralAtomGates::x_gate();
920 let expected_rx = x.mapv(|x| Complex64::new(0.0, -1.0) * x);
921
922 for i in 0..2 {
923 for j in 0..2 {
924 assert!((rx_pi[[i, j]] - expected_rx[[i, j]]).norm() < 1e-10);
925 }
926 }
927 }
928
929 #[test]
930 fn test_atom_rearrangement() {
931 let mut qc = NeutralAtomQC::new(3);
932
933 let mut atom1 = NeutralAtom::new(AtomSpecies::Rb87, qc.tweezers[0].position);
935 let mut atom2 = NeutralAtom::new(AtomSpecies::Rb87, qc.tweezers[2].position);
936 atom1.loading_probability = 1.0; atom2.loading_probability = 1.0; qc.tweezers[0].atom = Some(atom1);
939 qc.tweezers[2].atom = Some(atom2);
940
941 assert_eq!(qc.loaded_atom_count(), 2);
942
943 assert!(qc.rearrange_atoms().is_ok());
945
946 assert!(qc.tweezers[0].has_atom());
948 assert!(qc.tweezers[1].has_atom());
949 assert!(!qc.tweezers[2].has_atom());
950 }
951
952 #[test]
953 fn test_error_model_defaults() {
954 let error_model = NeutralAtomErrorModel::default();
955
956 assert!(error_model.loading_fidelity > 0.0 && error_model.loading_fidelity <= 1.0);
957 assert!(
958 error_model.single_qubit_fidelity > 0.0 && error_model.single_qubit_fidelity <= 1.0
959 );
960 assert!(error_model.two_qubit_fidelity > 0.0 && error_model.two_qubit_fidelity <= 1.0);
961 assert!(error_model.measurement_fidelity > 0.0 && error_model.measurement_fidelity <= 1.0);
962 assert!(error_model.coherence_time > 0.0);
963 assert!(error_model.blockade_radius > 0.0);
964 }
965
966 #[test]
967 fn test_quantum_state_probabilities() {
968 let qc = NeutralAtomQC::new(2);
969 let probs = qc.get_probabilities();
970
971 assert_eq!(probs.len(), 4); let total: f64 = probs.iter().sum();
975 assert!((total - 1.0).abs() < 1e-10);
976
977 assert!((probs[0] - 1.0).abs() < 1e-10);
979 }
980
981 #[test]
982 fn test_atom_position_retrieval() {
983 let qc = NeutralAtomQC::new(2);
984 let positions = qc.get_atom_positions();
985
986 assert_eq!(positions.len(), 2);
987
988 for (qubit_id, position, has_atom) in positions {
990 assert!(qubit_id.0 < 2);
991 assert!(position.x >= 0.0); let _ = has_atom;
997 }
998 }
999}