1use crate::error::{QuantRS2Error, QuantRS2Result};
7use ndarray::Array1;
8use num_complex::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(&mut 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(&mut 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();
580 let state2 = self.ions[ion2].get_state();
581
582 let sqrt2_inv = 1.0 / 2.0_f64.sqrt();
585 let phase_factor = Complex64::new(0.0, phase).exp();
586
587 let amp_00 = state1[0] * state2[0];
594 let amp_01 = state1[0] * state2[1];
595 let amp_10 = state1[1] * state2[0];
596 let amp_11 = state1[1] * state2[1];
597
598 let new_00 = sqrt2_inv * (amp_00 + Complex64::new(0.0, 1.0) * phase_factor * amp_11);
600 let new_01 = sqrt2_inv * (amp_01 + Complex64::new(0.0, 1.0) * phase_factor * amp_10);
601 let new_10 = sqrt2_inv * (amp_10 + Complex64::new(0.0, 1.0) * phase_factor * amp_01);
602 let _new_11 = sqrt2_inv * (amp_11 + Complex64::new(0.0, 1.0) * phase_factor * amp_00);
603
604 let norm1 = (new_00.norm_sqr() + new_10.norm_sqr()).sqrt();
606 let norm2 = (new_00.norm_sqr() + new_01.norm_sqr()).sqrt();
607
608 if norm1 > 1e-10 && norm2 > 1e-10 {
609 self.ions[ion1].set_state([new_00 / norm1, new_10 / norm1])?;
610 self.ions[ion2].set_state([new_00 / norm2, new_01 / norm2])?;
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 let mut rng = rand::rng();
661 use rand::Rng;
662 let random_value: f64 = rng.random();
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 Self::rotation_gate(system, ion_id, "y", std::f64::consts::PI / 2.0)?;
742 Self::rotation_gate(system, ion_id, "z", std::f64::consts::PI)?;
743 Self::rotation_gate(system, ion_id, "y", std::f64::consts::PI / 2.0)
744 }
745
746 pub fn pauli_x(system: &mut TrappedIonSystem, ion_id: usize) -> QuantRS2Result<()> {
748 Self::rotation_gate(system, ion_id, "x", std::f64::consts::PI)
749 }
750
751 pub fn pauli_y(system: &mut TrappedIonSystem, ion_id: usize) -> QuantRS2Result<()> {
753 Self::rotation_gate(system, ion_id, "y", std::f64::consts::PI)
754 }
755
756 pub fn pauli_z(system: &mut TrappedIonSystem, ion_id: usize) -> QuantRS2Result<()> {
758 Self::rotation_gate(system, ion_id, "z", std::f64::consts::PI)
759 }
760
761 pub fn cnot(
763 system: &mut TrappedIonSystem,
764 control: usize,
765 target: usize,
766 ) -> QuantRS2Result<()> {
767 Self::rotation_gate(system, target, "y", -std::f64::consts::PI / 2.0)?;
769 system.molmer_sorensen_gate(control, target, std::f64::consts::PI / 2.0)?;
770 Self::rotation_gate(system, control, "x", -std::f64::consts::PI / 2.0)?;
771 Self::rotation_gate(system, target, "x", -std::f64::consts::PI / 2.0)?;
772 Ok(())
773 }
774
775 pub fn cz(system: &mut TrappedIonSystem, control: usize, target: usize) -> QuantRS2Result<()> {
777 Self::hadamard(system, target)?;
779 Self::cnot(system, control, target)?;
780 Self::hadamard(system, target)
781 }
782
783 pub fn toffoli(
785 system: &mut TrappedIonSystem,
786 control1: usize,
787 control2: usize,
788 target: usize,
789 ) -> QuantRS2Result<()> {
790 Self::hadamard(system, target)?;
792 Self::cnot(system, control2, target)?;
793 Self::rotation_gate(system, target, "z", -std::f64::consts::PI / 4.0)?; Self::cnot(system, control1, target)?;
795 Self::rotation_gate(system, target, "z", std::f64::consts::PI / 4.0)?; Self::cnot(system, control2, target)?;
797 Self::rotation_gate(system, target, "z", -std::f64::consts::PI / 4.0)?; Self::cnot(system, control1, target)?;
799 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)?;
803 Self::cnot(system, control1, control2)?;
804 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)
807 }
808}
809
810#[cfg(test)]
811mod tests {
812 use super::*;
813
814 #[test]
815 fn test_ion_species_properties() {
816 let be9 = IonSpecies::Be9;
817 assert!((be9.atomic_mass() - 9.012).abs() < 0.001);
818 assert!(be9.typical_trap_frequency() > 1e6);
819 assert!(be9.qubit_wavelength() > 300.0);
820 }
821
822 #[test]
823 fn test_motional_mode_creation() {
824 let mode = MotionalMode::ground_state(
825 0,
826 1e6,
827 "x".to_string(),
828 MotionalModeType::CenterOfMass,
829 0.1,
830 10,
831 );
832
833 assert_eq!(mode.mode_id, 0);
834 assert!((mode.mean_phonon_number() - 0.0).abs() < 1e-10);
835 assert!((mode.phonon_state[0] - Complex64::new(1.0, 0.0)).norm() < 1e-10);
836 }
837
838 #[test]
839 fn test_thermal_state() {
840 let mode = MotionalMode::thermal_state(
841 0,
842 1e6,
843 "x".to_string(),
844 MotionalModeType::CenterOfMass,
845 0.1,
846 10,
847 2.0, );
849
850 let mean_phonons = mode.mean_phonon_number();
851 assert!((mean_phonons - 2.0).abs() < 0.5); }
853
854 #[test]
855 fn test_trapped_ion_creation() {
856 let ion = TrappedIon::new(0, IonSpecies::Ca40, [0.0, 0.0, 0.0]);
857 assert_eq!(ion.ion_id, 0);
858 assert_eq!(ion.species, IonSpecies::Ca40);
859
860 let state = ion.get_state();
861 assert!((state[0] - Complex64::new(1.0, 0.0)).norm() < 1e-10);
862 assert!((state[1] - Complex64::new(0.0, 0.0)).norm() < 1e-10);
863 }
864
865 #[test]
866 fn test_trapped_ion_system() {
867 let species = vec![IonSpecies::Ca40, IonSpecies::Ca40];
868 let positions = vec![[0.0, 0.0, 0.0], [10.0, 0.0, 0.0]];
869
870 let system = TrappedIonSystem::new(species, positions).unwrap();
871 assert_eq!(system.num_ions, 2);
872 assert_eq!(system.ions.len(), 2);
873 assert!(system.motional_modes.len() >= 3);
874 }
875
876 #[test]
877 fn test_laser_pulse_application() {
878 let species = vec![IonSpecies::Ca40];
879 let positions = vec![[0.0, 0.0, 0.0]];
880 let mut system = TrappedIonSystem::new(species, positions).unwrap();
881
882 let rabi_freq = 1e6; let pulse = LaserPulse::carrier_pulse(
885 rabi_freq, std::f64::consts::PI / rabi_freq, 0.0, vec![0], );
890
891 system.apply_laser_pulse(&pulse).unwrap();
892
893 let state = system.ions[0].get_state();
895 assert!(state[1].norm() > 0.9); }
897
898 #[test]
899 fn test_motional_displacement() {
900 let mut mode = MotionalMode::ground_state(
901 0,
902 1e6,
903 "x".to_string(),
904 MotionalModeType::CenterOfMass,
905 0.1,
906 10,
907 );
908
909 let alpha = Complex64::new(1.0, 0.0);
910 mode.displace(alpha).unwrap();
911
912 let mean_phonons = mode.mean_phonon_number();
913 assert!(mean_phonons > 0.5); }
915
916 #[test]
917 #[ignore] fn test_molmer_sorensen_gate() {
919 let species = vec![IonSpecies::Ca40, IonSpecies::Ca40];
920 let positions = vec![[0.0, 0.0, 0.0], [10.0, 0.0, 0.0]];
921 let mut system = TrappedIonSystem::new(species, positions).unwrap();
922
923 system
925 .molmer_sorensen_gate(0, 1, std::f64::consts::PI / 2.0)
926 .unwrap();
927
928 let state1 = system.ions[0].get_state();
930 let state2 = system.ions[1].get_state();
931
932 assert!(state1[0].norm() < 1.0);
934 assert!(state2[0].norm() < 1.0);
935 }
936
937 #[test]
938 fn test_ion_measurement() {
939 let species = vec![IonSpecies::Ca40];
940 let positions = vec![[0.0, 0.0, 0.0]];
941 let mut system = TrappedIonSystem::new(species, positions).unwrap();
942
943 system.ions[0]
945 .set_state([
946 Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0),
947 Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0),
948 ])
949 .unwrap();
950
951 let result = system.measure_ion(0).unwrap();
952
953 assert!(result == true || result == false);
955
956 let state = system.ions[0].get_state();
958 assert!(state[0].norm() == 1.0 || state[1].norm() == 1.0);
959 }
960
961 #[test]
962 #[ignore] fn test_trapped_ion_gates() {
964 let species = vec![IonSpecies::Ca40, IonSpecies::Ca40];
965 let positions = vec![[0.0, 0.0, 0.0], [10.0, 0.0, 0.0]];
966 let mut system = TrappedIonSystem::new(species, positions).unwrap();
967
968 TrappedIonGates::pauli_x(&mut system, 0).unwrap();
970 let state = system.ions[0].get_state();
971 assert!(state[1].norm() > 0.9); TrappedIonGates::hadamard(&mut system, 0).unwrap();
975 let state = system.ions[0].get_state();
976 assert!(state[0].norm() > 0.05 && state[0].norm() < 0.95); }
978
979 #[test]
980 fn test_cnot_gate() {
981 let species = vec![IonSpecies::Ca40, IonSpecies::Ca40];
982 let positions = vec![[0.0, 0.0, 0.0], [10.0, 0.0, 0.0]];
983 let mut system = TrappedIonSystem::new(species, positions).unwrap();
984
985 TrappedIonGates::pauli_x(&mut system, 0).unwrap();
987
988 TrappedIonGates::cnot(&mut system, 0, 1).unwrap();
990
991 let target_state = system.ions[1].get_state();
993 assert!(target_state[1].norm() > 0.5);
994 }
995
996 #[test]
997 fn test_motional_temperature() {
998 let species = vec![IonSpecies::Ca40];
999 let positions = vec![[0.0, 0.0, 0.0]];
1000 let system = TrappedIonSystem::new(species, positions).unwrap();
1001
1002 let temp = system.get_motional_temperature();
1003 assert!(temp >= 0.0);
1004 assert!(temp.is_finite());
1005 }
1006}