1use crate::error::{QuantRS2Error, QuantRS2Result};
7use scirs2_core::ndarray::Array1;
8use scirs2_core::Complex64;
9use std::collections::HashMap;
10
11#[derive(Debug, Clone, Copy, PartialEq)]
13pub enum IonSpecies {
14 Be9,
16 Ca40,
18 Ca43,
20 Yb171,
22 Ba137,
24 Sr88,
26}
27
28impl IonSpecies {
29 pub fn atomic_mass(&self) -> f64 {
31 match self {
32 IonSpecies::Be9 => 9.012,
33 IonSpecies::Ca40 => 39.963,
34 IonSpecies::Ca43 => 42.959,
35 IonSpecies::Yb171 => 170.936,
36 IonSpecies::Ba137 => 136.906,
37 IonSpecies::Sr88 => 87.906,
38 }
39 }
40
41 pub fn typical_trap_frequency(&self) -> f64 {
43 match self {
44 IonSpecies::Be9 => 2.0e6, IonSpecies::Ca40 => 1.5e6, IonSpecies::Ca43 => 1.5e6, IonSpecies::Yb171 => 1.0e6, IonSpecies::Ba137 => 0.8e6, IonSpecies::Sr88 => 1.2e6, }
51 }
52
53 pub fn qubit_wavelength(&self) -> f64 {
55 match self {
56 IonSpecies::Be9 => 313.0, IonSpecies::Ca40 => 729.0, IonSpecies::Ca43 => 729.0, IonSpecies::Yb171 => 435.5, IonSpecies::Ba137 => 455.4, IonSpecies::Sr88 => 674.0, }
63 }
64}
65
66#[derive(Debug, Clone, Copy, PartialEq)]
68pub enum IonLevel {
69 Ground,
71 Excited,
73 Auxiliary,
75}
76
77#[derive(Debug, Clone)]
79pub struct MotionalMode {
80 pub mode_id: usize,
82 pub frequency: f64,
84 pub direction: String,
86 pub mode_type: MotionalModeType,
88 pub lamb_dicke_parameter: f64,
90 pub phonon_state: Array1<Complex64>,
92 pub max_phonons: usize,
94}
95
96#[derive(Debug, Clone, Copy, PartialEq)]
97pub enum MotionalModeType {
98 CenterOfMass,
100 Breathing,
102 Rocking,
104 HigherOrder,
106}
107
108impl MotionalMode {
109 pub fn ground_state(
111 mode_id: usize,
112 frequency: f64,
113 direction: String,
114 mode_type: MotionalModeType,
115 lamb_dicke_parameter: f64,
116 max_phonons: usize,
117 ) -> Self {
118 let mut phonon_state = Array1::zeros(max_phonons + 1);
119 phonon_state[0] = Complex64::new(1.0, 0.0); Self {
122 mode_id,
123 frequency,
124 direction,
125 mode_type,
126 lamb_dicke_parameter,
127 phonon_state,
128 max_phonons,
129 }
130 }
131
132 pub fn thermal_state(
134 mode_id: usize,
135 frequency: f64,
136 direction: String,
137 mode_type: MotionalModeType,
138 lamb_dicke_parameter: f64,
139 max_phonons: usize,
140 mean_phonons: f64,
141 ) -> Self {
142 let mut phonon_state = Array1::zeros(max_phonons + 1);
143
144 let nbar = mean_phonons;
146 for n in 0..=max_phonons {
147 let prob = nbar.powi(n as i32) / (1.0 + nbar).powi(n as i32 + 1);
148 phonon_state[n] = Complex64::new(prob.sqrt(), 0.0);
149 }
150
151 Self {
152 mode_id,
153 frequency,
154 direction,
155 mode_type,
156 lamb_dicke_parameter,
157 phonon_state,
158 max_phonons,
159 }
160 }
161
162 pub fn mean_phonon_number(&self) -> f64 {
164 let mut mean = 0.0;
165 for n in 0..=self.max_phonons {
166 mean += (n as f64) * self.phonon_state[n].norm_sqr();
167 }
168 mean
169 }
170
171 pub fn displace(&mut self, alpha: Complex64) -> QuantRS2Result<()> {
173 let mut new_state = Array1::zeros(self.max_phonons + 1);
174
175 for n in 0..=self.max_phonons {
178 new_state[n] = self.phonon_state[n] * (-alpha.norm_sqr() / 2.0).exp();
179
180 if n > 0 {
182 new_state[n] += alpha * (n as f64).sqrt() * self.phonon_state[n - 1];
183 }
184 }
185
186 let norm = new_state
188 .iter()
189 .map(|x: &Complex64| x.norm_sqr())
190 .sum::<f64>()
191 .sqrt();
192 for amp in new_state.iter_mut() {
193 *amp /= norm;
194 }
195
196 self.phonon_state = new_state;
197 Ok(())
198 }
199}
200
201#[derive(Debug, Clone)]
203pub struct TrappedIon {
204 pub ion_id: usize,
206 pub species: IonSpecies,
208 pub position: [f64; 3],
210 pub electronic_state: Array1<Complex64>,
212 pub motional_coupling: HashMap<usize, f64>,
214}
215
216impl TrappedIon {
217 pub fn new(ion_id: usize, species: IonSpecies, position: [f64; 3]) -> Self {
219 let mut electronic_state = Array1::zeros(2);
220 electronic_state[0] = Complex64::new(1.0, 0.0); Self {
223 ion_id,
224 species,
225 position,
226 electronic_state,
227 motional_coupling: HashMap::new(),
228 }
229 }
230
231 pub fn set_state(&mut self, amplitudes: [Complex64; 2]) -> QuantRS2Result<()> {
233 let norm = (amplitudes[0].norm_sqr() + amplitudes[1].norm_sqr()).sqrt();
235 if norm < 1e-10 {
236 return Err(QuantRS2Error::InvalidInput(
237 "State cannot have zero norm".to_string(),
238 ));
239 }
240
241 self.electronic_state[0] = amplitudes[0] / norm;
242 self.electronic_state[1] = amplitudes[1] / norm;
243
244 Ok(())
245 }
246
247 pub fn get_state(&self) -> [Complex64; 2] {
249 [self.electronic_state[0], self.electronic_state[1]]
250 }
251}
252
253#[derive(Debug, Clone)]
255pub struct LaserPulse {
256 pub frequency: f64,
258 pub rabi_frequency: f64,
260 pub duration: f64,
262 pub phase: f64,
264 pub detuning: f64,
266 pub target_ions: Vec<usize>,
268 pub addressing_efficiency: f64,
270}
271
272impl LaserPulse {
273 pub fn carrier_pulse(
275 rabi_frequency: f64,
276 duration: f64,
277 phase: f64,
278 target_ions: Vec<usize>,
279 ) -> Self {
280 Self {
281 frequency: 0.0, rabi_frequency,
283 duration,
284 phase,
285 detuning: 0.0, target_ions,
287 addressing_efficiency: 1.0,
288 }
289 }
290
291 pub fn red_sideband_pulse(
293 rabi_frequency: f64,
294 duration: f64,
295 phase: f64,
296 target_ions: Vec<usize>,
297 motional_frequency: f64,
298 ) -> Self {
299 Self {
300 frequency: 0.0,
301 rabi_frequency,
302 duration,
303 phase,
304 detuning: -motional_frequency, target_ions,
306 addressing_efficiency: 1.0,
307 }
308 }
309
310 pub fn blue_sideband_pulse(
312 rabi_frequency: f64,
313 duration: f64,
314 phase: f64,
315 target_ions: Vec<usize>,
316 motional_frequency: f64,
317 ) -> Self {
318 Self {
319 frequency: 0.0,
320 rabi_frequency,
321 duration,
322 phase,
323 detuning: motional_frequency, target_ions,
325 addressing_efficiency: 1.0,
326 }
327 }
328}
329
330#[derive(Debug, Clone)]
332pub struct TrappedIonSystem {
333 pub num_ions: usize,
335 pub ions: Vec<TrappedIon>,
337 pub motional_modes: Vec<MotionalMode>,
339 pub global_state: Option<Array1<Complex64>>,
341 pub temperature: f64,
343 pub magnetic_field: f64,
345}
346
347impl TrappedIonSystem {
348 pub fn new(ion_species: Vec<IonSpecies>, positions: Vec<[f64; 3]>) -> QuantRS2Result<Self> {
350 if ion_species.len() != positions.len() {
351 return Err(QuantRS2Error::InvalidInput(
352 "Number of species and positions must match".to_string(),
353 ));
354 }
355
356 let num_ions = ion_species.len();
357 let ions: Vec<TrappedIon> = ion_species
358 .into_iter()
359 .zip(positions)
360 .enumerate()
361 .map(|(id, (species, pos))| TrappedIon::new(id, species, pos))
362 .collect();
363
364 let mut motional_modes = Vec::new();
366 for i in 0..3 {
367 let direction = match i {
368 0 => "x".to_string(),
369 1 => "y".to_string(),
370 2 => "z".to_string(),
371 _ => unreachable!(),
372 };
373
374 let mode = MotionalMode::ground_state(
375 i,
376 1.0e6, direction,
378 MotionalModeType::CenterOfMass,
379 0.1, 20, );
382 motional_modes.push(mode);
383 }
384
385 Ok(Self {
386 num_ions,
387 ions,
388 motional_modes,
389 global_state: None,
390 temperature: 1e-6, magnetic_field: 0.01, })
393 }
394
395 pub fn apply_laser_pulse(&mut self, pulse: &LaserPulse) -> QuantRS2Result<()> {
397 for &ion_id in &pulse.target_ions {
398 if ion_id >= self.num_ions {
399 return Err(QuantRS2Error::InvalidInput(
400 "Target ion ID out of bounds".to_string(),
401 ));
402 }
403
404 self.apply_pulse_to_ion(ion_id, pulse)?;
405 }
406
407 Ok(())
408 }
409
410 fn apply_pulse_to_ion(&mut self, ion_id: usize, pulse: &LaserPulse) -> QuantRS2Result<()> {
412 let ion = &mut self.ions[ion_id];
413
414 let theta = pulse.rabi_frequency * pulse.duration * pulse.addressing_efficiency;
416
417 let cos_half = (theta / 2.0).cos();
419 let sin_half = (theta / 2.0).sin();
420 let phase_factor = Complex64::new(0.0, pulse.phase).exp();
421
422 let old_state = ion.electronic_state.clone();
424 ion.electronic_state[0] = cos_half * old_state[0]
425 - Complex64::new(0.0, 1.0) * sin_half * phase_factor * old_state[1];
426 ion.electronic_state[1] = cos_half * old_state[1]
427 - Complex64::new(0.0, 1.0) * sin_half * phase_factor.conj() * old_state[0];
428
429 if pulse.detuning.abs() > 1e3 {
431 self.apply_motional_coupling(ion_id, pulse)?;
432 }
433
434 Ok(())
435 }
436
437 fn apply_motional_coupling(
439 &mut self,
440 _ion_id: usize,
441 pulse: &LaserPulse,
442 ) -> QuantRS2Result<()> {
443 let mut mode_idx = None;
445 let mut eta = 0.0;
446
447 for (i, mode) in self.motional_modes.iter().enumerate() {
448 if (mode.frequency - pulse.detuning.abs()).abs() < 1e3 {
449 mode_idx = Some(i);
450 eta = mode.lamb_dicke_parameter;
451 break;
452 }
453 }
454
455 if let Some(idx) = mode_idx {
456 let mode = &mut self.motional_modes[idx];
457 if pulse.detuning < 0.0 {
458 let mut new_state = Array1::zeros(mode.max_phonons + 1);
460
461 for n in 1..=mode.max_phonons {
462 let coupling = eta * (n as f64).sqrt();
464 new_state[n - 1] += coupling * mode.phonon_state[n];
465 }
466
467 let norm = new_state
469 .iter()
470 .map(|x: &Complex64| x.norm_sqr())
471 .sum::<f64>()
472 .sqrt();
473 if norm > 1e-10 {
474 for amp in new_state.iter_mut() {
475 *amp /= norm;
476 }
477 mode.phonon_state = new_state;
478 }
479 } else {
480 let mut new_state = Array1::zeros(mode.max_phonons + 1);
482
483 for n in 0..mode.max_phonons {
484 let coupling = eta * ((n + 1) as f64).sqrt();
486 new_state[n + 1] += coupling * mode.phonon_state[n];
487 }
488
489 let norm = new_state
491 .iter()
492 .map(|x: &Complex64| x.norm_sqr())
493 .sum::<f64>()
494 .sqrt();
495 if norm > 1e-10 {
496 for amp in new_state.iter_mut() {
497 *amp /= norm;
498 }
499 mode.phonon_state = new_state;
500 }
501 }
502 }
503
504 Ok(())
505 }
506
507 fn apply_red_sideband(&self, mode: &mut MotionalMode, eta: f64) -> QuantRS2Result<()> {
509 let mut new_state = Array1::zeros(mode.max_phonons + 1);
510
511 for n in 1..=mode.max_phonons {
512 let coupling = eta * (n as f64).sqrt();
514 new_state[n - 1] += coupling * mode.phonon_state[n];
515 }
516
517 let norm = new_state
519 .iter()
520 .map(|x: &Complex64| x.norm_sqr())
521 .sum::<f64>()
522 .sqrt();
523 if norm > 1e-10 {
524 for amp in new_state.iter_mut() {
525 *amp /= norm;
526 }
527 mode.phonon_state = new_state;
528 }
529
530 Ok(())
531 }
532
533 fn apply_blue_sideband(&self, mode: &mut MotionalMode, eta: f64) -> QuantRS2Result<()> {
535 let mut new_state = Array1::zeros(mode.max_phonons + 1);
536
537 for n in 0..mode.max_phonons {
538 let coupling = eta * ((n + 1) as f64).sqrt();
540 new_state[n + 1] += coupling * mode.phonon_state[n];
541 }
542
543 let norm = new_state
545 .iter()
546 .map(|x: &Complex64| x.norm_sqr())
547 .sum::<f64>()
548 .sqrt();
549 if norm > 1e-10 {
550 for amp in new_state.iter_mut() {
551 *amp /= norm;
552 }
553 mode.phonon_state = new_state;
554 }
555
556 Ok(())
557 }
558
559 pub fn molmer_sorensen_gate(
561 &mut self,
562 ion1: usize,
563 ion2: usize,
564 phase: f64,
565 ) -> QuantRS2Result<()> {
566 if ion1 >= self.num_ions || ion2 >= self.num_ions {
567 return Err(QuantRS2Error::InvalidInput(
568 "Ion index out of bounds".to_string(),
569 ));
570 }
571
572 if ion1 == ion2 {
573 return Err(QuantRS2Error::InvalidInput(
574 "Cannot apply MS gate to same ion".to_string(),
575 ));
576 }
577
578 let state1 = self.ions[ion1].get_state();
583 let state2 = self.ions[ion2].get_state();
584
585 if state1[0].norm() > 0.9 && state2[0].norm() > 0.9 {
587 let sqrt2_inv = 1.0 / std::f64::consts::SQRT_2;
589 let phase_factor = Complex64::new(0.0, phase);
590
591 self.ions[ion1].set_state([
592 sqrt2_inv * Complex64::new(1.0, 0.0),
593 sqrt2_inv * phase_factor,
594 ])?;
595 self.ions[ion2].set_state([
596 sqrt2_inv * Complex64::new(1.0, 0.0),
597 sqrt2_inv * phase_factor,
598 ])?;
599 } else {
600 let sqrt2_inv = 1.0 / std::f64::consts::SQRT_2;
602
603 let new_state1_0 = sqrt2_inv * (state1[0] + state2[1]);
605 let new_state1_1 = sqrt2_inv * (state1[1] + state2[0]);
606 let new_state2_0 = sqrt2_inv * (state2[0] + state1[1]);
607 let new_state2_1 = sqrt2_inv * (state2[1] + state1[0]);
608
609 self.ions[ion1].set_state([new_state1_0, new_state1_1])?;
610 self.ions[ion2].set_state([new_state2_0, new_state2_1])?;
611 }
612
613 Ok(())
614 }
615
616 pub fn state_dependent_force(
618 &mut self,
619 target_ions: &[usize],
620 mode_id: usize,
621 force_strength: f64,
622 ) -> QuantRS2Result<()> {
623 if mode_id >= self.motional_modes.len() {
624 return Err(QuantRS2Error::InvalidInput(
625 "Mode ID out of bounds".to_string(),
626 ));
627 }
628
629 for &ion_id in target_ions {
631 if ion_id >= self.num_ions {
632 return Err(QuantRS2Error::InvalidInput(
633 "Ion ID out of bounds".to_string(),
634 ));
635 }
636
637 let ion_state = self.ions[ion_id].get_state();
638 let excited_population = ion_state[1].norm_sqr();
639
640 let alpha = Complex64::new(force_strength * excited_population, 0.0);
642 self.motional_modes[mode_id].displace(alpha)?;
643 }
644
645 Ok(())
646 }
647
648 pub fn measure_ion(&mut self, ion_id: usize) -> QuantRS2Result<bool> {
650 if ion_id >= self.num_ions {
651 return Err(QuantRS2Error::InvalidInput(
652 "Ion ID out of bounds".to_string(),
653 ));
654 }
655
656 let ion = &mut self.ions[ion_id];
657 let prob_excited = ion.electronic_state[1].norm_sqr();
658
659 use scirs2_core::random::prelude::*;
661 let mut rng = thread_rng();
662 let random_value: f64 = rng.gen();
663
664 let result = random_value < prob_excited;
665
666 if result {
668 ion.set_state([Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)])?;
669 } else {
671 ion.set_state([Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)])?;
672 }
674
675 Ok(result)
676 }
677
678 pub fn cool_motional_mode(&mut self, mode_id: usize) -> QuantRS2Result<()> {
680 if mode_id >= self.motional_modes.len() {
681 return Err(QuantRS2Error::InvalidInput(
682 "Mode ID out of bounds".to_string(),
683 ));
684 }
685
686 let mode = &mut self.motional_modes[mode_id];
687 mode.phonon_state.fill(Complex64::new(0.0, 0.0));
688 mode.phonon_state[0] = Complex64::new(1.0, 0.0);
689
690 Ok(())
691 }
692
693 pub fn get_motional_temperature(&self) -> f64 {
695 let total_energy: f64 = self
696 .motional_modes
697 .iter()
698 .map(|mode| mode.mean_phonon_number() * mode.frequency)
699 .sum();
700
701 let k_b = 1.381e-23; let avg_frequency = 1e6; total_energy / (k_b * avg_frequency * self.motional_modes.len() as f64)
706 }
707}
708
709pub struct TrappedIonGates;
711
712impl TrappedIonGates {
713 pub fn rotation_gate(
715 system: &mut TrappedIonSystem,
716 ion_id: usize,
717 axis: &str,
718 angle: f64,
719 ) -> QuantRS2Result<()> {
720 let rabi_freq = 1e6; let duration = angle / rabi_freq; let phase = match axis {
724 "x" => 0.0,
725 "y" => std::f64::consts::PI / 2.0,
726 "z" => 0.0, _ => {
728 return Err(QuantRS2Error::InvalidInput(
729 "Invalid rotation axis".to_string(),
730 ))
731 }
732 };
733
734 let pulse = LaserPulse::carrier_pulse(rabi_freq, duration, phase, vec![ion_id]);
735 system.apply_laser_pulse(&pulse)
736 }
737
738 pub fn hadamard(system: &mut TrappedIonSystem, ion_id: usize) -> QuantRS2Result<()> {
740 if ion_id >= system.num_ions {
741 return Err(QuantRS2Error::InvalidInput(
742 "Ion ID out of bounds".to_string(),
743 ));
744 }
745
746 let ion = &mut system.ions[ion_id];
748 let old_state = ion.electronic_state.clone();
749 let inv_sqrt2 = 1.0 / std::f64::consts::SQRT_2;
750
751 ion.electronic_state[0] = inv_sqrt2 * (old_state[0] + old_state[1]);
752 ion.electronic_state[1] = inv_sqrt2 * (old_state[0] - old_state[1]);
753
754 Ok(())
755 }
756
757 pub fn pauli_x(system: &mut TrappedIonSystem, ion_id: usize) -> QuantRS2Result<()> {
759 Self::rotation_gate(system, ion_id, "x", std::f64::consts::PI)
760 }
761
762 pub fn pauli_y(system: &mut TrappedIonSystem, ion_id: usize) -> QuantRS2Result<()> {
764 Self::rotation_gate(system, ion_id, "y", std::f64::consts::PI)
765 }
766
767 pub fn pauli_z(system: &mut TrappedIonSystem, ion_id: usize) -> QuantRS2Result<()> {
769 Self::rotation_gate(system, ion_id, "z", std::f64::consts::PI)
770 }
771
772 pub fn cnot(
774 system: &mut TrappedIonSystem,
775 control: usize,
776 target: usize,
777 ) -> QuantRS2Result<()> {
778 Self::rotation_gate(system, target, "y", -std::f64::consts::PI / 2.0)?;
780 system.molmer_sorensen_gate(control, target, std::f64::consts::PI / 2.0)?;
781 Self::rotation_gate(system, control, "x", -std::f64::consts::PI / 2.0)?;
782 Self::rotation_gate(system, target, "x", -std::f64::consts::PI / 2.0)?;
783 Ok(())
784 }
785
786 pub fn cz(system: &mut TrappedIonSystem, control: usize, target: usize) -> QuantRS2Result<()> {
788 Self::hadamard(system, target)?;
790 Self::cnot(system, control, target)?;
791 Self::hadamard(system, target)
792 }
793
794 pub fn toffoli(
796 system: &mut TrappedIonSystem,
797 control1: usize,
798 control2: usize,
799 target: usize,
800 ) -> QuantRS2Result<()> {
801 Self::hadamard(system, target)?;
803 Self::cnot(system, control2, target)?;
804 Self::rotation_gate(system, target, "z", -std::f64::consts::PI / 4.0)?; Self::cnot(system, control1, target)?;
806 Self::rotation_gate(system, target, "z", std::f64::consts::PI / 4.0)?; Self::cnot(system, control2, target)?;
808 Self::rotation_gate(system, target, "z", -std::f64::consts::PI / 4.0)?; Self::cnot(system, control1, target)?;
810 Self::rotation_gate(system, control1, "z", std::f64::consts::PI / 4.0)?; Self::rotation_gate(system, control2, "z", std::f64::consts::PI / 4.0)?; Self::rotation_gate(system, target, "z", std::f64::consts::PI / 4.0)?; Self::hadamard(system, target)?;
814 Self::cnot(system, control1, control2)?;
815 Self::rotation_gate(system, control1, "z", std::f64::consts::PI / 4.0)?; Self::rotation_gate(system, control2, "z", -std::f64::consts::PI / 4.0)?; Self::cnot(system, control1, control2)
818 }
819}
820
821#[cfg(test)]
822mod tests {
823 use super::*;
824
825 #[test]
826 fn test_ion_species_properties() {
827 let be9 = IonSpecies::Be9;
828 assert!((be9.atomic_mass() - 9.012).abs() < 0.001);
829 assert!(be9.typical_trap_frequency() > 1e6);
830 assert!(be9.qubit_wavelength() > 300.0);
831 }
832
833 #[test]
834 fn test_motional_mode_creation() {
835 let mode = MotionalMode::ground_state(
836 0,
837 1e6,
838 "x".to_string(),
839 MotionalModeType::CenterOfMass,
840 0.1,
841 10,
842 );
843
844 assert_eq!(mode.mode_id, 0);
845 assert!((mode.mean_phonon_number() - 0.0).abs() < 1e-10);
846 assert!((mode.phonon_state[0] - Complex64::new(1.0, 0.0)).norm() < 1e-10);
847 }
848
849 #[test]
850 fn test_thermal_state() {
851 let mode = MotionalMode::thermal_state(
852 0,
853 1e6,
854 "x".to_string(),
855 MotionalModeType::CenterOfMass,
856 0.1,
857 10,
858 2.0, );
860
861 let mean_phonons = mode.mean_phonon_number();
862 assert!((mean_phonons - 2.0).abs() < 0.5); }
864
865 #[test]
866 fn test_trapped_ion_creation() {
867 let ion = TrappedIon::new(0, IonSpecies::Ca40, [0.0, 0.0, 0.0]);
868 assert_eq!(ion.ion_id, 0);
869 assert_eq!(ion.species, IonSpecies::Ca40);
870
871 let state = ion.get_state();
872 assert!((state[0] - Complex64::new(1.0, 0.0)).norm() < 1e-10);
873 assert!((state[1] - Complex64::new(0.0, 0.0)).norm() < 1e-10);
874 }
875
876 #[test]
877 fn test_trapped_ion_system() {
878 let species = vec![IonSpecies::Ca40, IonSpecies::Ca40];
879 let positions = vec![[0.0, 0.0, 0.0], [10.0, 0.0, 0.0]];
880
881 let system = TrappedIonSystem::new(species, positions).unwrap();
882 assert_eq!(system.num_ions, 2);
883 assert_eq!(system.ions.len(), 2);
884 assert!(system.motional_modes.len() >= 3);
885 }
886
887 #[test]
888 fn test_laser_pulse_application() {
889 let species = vec![IonSpecies::Ca40];
890 let positions = vec![[0.0, 0.0, 0.0]];
891 let mut system = TrappedIonSystem::new(species, positions).unwrap();
892
893 let rabi_freq = 1e6; let pulse = LaserPulse::carrier_pulse(
896 rabi_freq, std::f64::consts::PI / rabi_freq, 0.0, vec![0], );
901
902 system.apply_laser_pulse(&pulse).unwrap();
903
904 let state = system.ions[0].get_state();
906 assert!(state[1].norm() > 0.9); }
908
909 #[test]
910 fn test_motional_displacement() {
911 let mut mode = MotionalMode::ground_state(
912 0,
913 1e6,
914 "x".to_string(),
915 MotionalModeType::CenterOfMass,
916 0.1,
917 10,
918 );
919
920 let alpha = Complex64::new(1.0, 0.0);
921 mode.displace(alpha).unwrap();
922
923 let mean_phonons = mode.mean_phonon_number();
924 assert!(mean_phonons > 0.5); }
926
927 #[test]
928 fn test_molmer_sorensen_gate() {
929 let species = vec![IonSpecies::Ca40, IonSpecies::Ca40];
930 let positions = vec![[0.0, 0.0, 0.0], [10.0, 0.0, 0.0]];
931 let mut system = TrappedIonSystem::new(species, positions).unwrap();
932
933 system
935 .molmer_sorensen_gate(0, 1, std::f64::consts::PI / 2.0)
936 .unwrap();
937
938 let state1 = system.ions[0].get_state();
940 let state2 = system.ions[1].get_state();
941
942 assert!(state1[0].norm() < 1.0);
944 assert!(state2[0].norm() < 1.0);
945 }
946
947 #[test]
948 fn test_ion_measurement() {
949 let species = vec![IonSpecies::Ca40];
950 let positions = vec![[0.0, 0.0, 0.0]];
951 let mut system = TrappedIonSystem::new(species, positions).unwrap();
952
953 system.ions[0]
955 .set_state([
956 Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0),
957 Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0),
958 ])
959 .unwrap();
960
961 let result = system.measure_ion(0).unwrap();
962
963 let _ = result;
966
967 let state = system.ions[0].get_state();
969 assert!(state[0].norm() == 1.0 || state[1].norm() == 1.0);
970 }
971
972 #[test]
973 fn test_trapped_ion_gates() {
974 let species = vec![IonSpecies::Ca40, IonSpecies::Ca40];
975 let positions = vec![[0.0, 0.0, 0.0], [10.0, 0.0, 0.0]];
976 let mut system = TrappedIonSystem::new(species, positions).unwrap();
977
978 TrappedIonGates::pauli_x(&mut system, 0).unwrap();
980 let state = system.ions[0].get_state();
981 assert!(state[1].norm() > 0.9); TrappedIonGates::hadamard(&mut system, 0).unwrap();
985 let state = system.ions[0].get_state();
986 assert!(state[0].norm() > 0.05 && state[0].norm() < 0.95); }
988
989 #[test]
990 fn test_cnot_gate() {
991 let species = vec![IonSpecies::Ca40, IonSpecies::Ca40];
992 let positions = vec![[0.0, 0.0, 0.0], [10.0, 0.0, 0.0]];
993 let mut system = TrappedIonSystem::new(species, positions).unwrap();
994
995 TrappedIonGates::pauli_x(&mut system, 0).unwrap();
997
998 TrappedIonGates::cnot(&mut system, 0, 1).unwrap();
1000
1001 let target_state = system.ions[1].get_state();
1003 assert!(target_state[1].norm() > 0.5);
1004 }
1005
1006 #[test]
1007 fn test_motional_temperature() {
1008 let species = vec![IonSpecies::Ca40];
1009 let positions = vec![[0.0, 0.0, 0.0]];
1010 let system = TrappedIonSystem::new(species, positions).unwrap();
1011
1012 let temp = system.get_motional_temperature();
1013 assert!(temp >= 0.0);
1014 assert!(temp.is_finite());
1015 }
1016}