use crate::error::{QuantRS2Error, QuantRS2Result};
use scirs2_core::ndarray::Array1;
use scirs2_core::Complex64;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IonSpecies {
Be9,
Ca40,
Ca43,
Yb171,
Ba137,
Sr88,
}
impl IonSpecies {
pub const fn atomic_mass(&self) -> f64 {
match self {
Self::Be9 => 9.012,
Self::Ca40 => 39.963,
Self::Ca43 => 42.959,
Self::Yb171 => 170.936,
Self::Ba137 => 136.906,
Self::Sr88 => 87.906,
}
}
pub const fn typical_trap_frequency(&self) -> f64 {
match self {
Self::Be9 => 2.0e6, Self::Ca40 | Self::Ca43 => 1.5e6, Self::Yb171 => 1.0e6, Self::Ba137 => 0.8e6, Self::Sr88 => 1.2e6, }
}
pub const fn qubit_wavelength(&self) -> f64 {
match self {
Self::Be9 => 313.0, Self::Ca40 | Self::Ca43 => 729.0, Self::Yb171 => 435.5, Self::Ba137 => 455.4, Self::Sr88 => 674.0, }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IonLevel {
Ground,
Excited,
Auxiliary,
}
#[derive(Debug, Clone)]
pub struct MotionalMode {
pub mode_id: usize,
pub frequency: f64,
pub direction: String,
pub mode_type: MotionalModeType,
pub lamb_dicke_parameter: f64,
pub phonon_state: Array1<Complex64>,
pub max_phonons: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MotionalModeType {
CenterOfMass,
Breathing,
Rocking,
HigherOrder,
}
impl MotionalMode {
pub fn ground_state(
mode_id: usize,
frequency: f64,
direction: String,
mode_type: MotionalModeType,
lamb_dicke_parameter: f64,
max_phonons: usize,
) -> Self {
let mut phonon_state = Array1::zeros(max_phonons + 1);
phonon_state[0] = Complex64::new(1.0, 0.0);
Self {
mode_id,
frequency,
direction,
mode_type,
lamb_dicke_parameter,
phonon_state,
max_phonons,
}
}
pub fn thermal_state(
mode_id: usize,
frequency: f64,
direction: String,
mode_type: MotionalModeType,
lamb_dicke_parameter: f64,
max_phonons: usize,
mean_phonons: f64,
) -> Self {
let mut phonon_state = Array1::zeros(max_phonons + 1);
let nbar = mean_phonons;
for n in 0..=max_phonons {
let prob = nbar.powi(n as i32) / (1.0 + nbar).powi(n as i32 + 1);
phonon_state[n] = Complex64::new(prob.sqrt(), 0.0);
}
Self {
mode_id,
frequency,
direction,
mode_type,
lamb_dicke_parameter,
phonon_state,
max_phonons,
}
}
pub fn mean_phonon_number(&self) -> f64 {
let mut mean = 0.0;
for n in 0..=self.max_phonons {
mean += (n as f64) * self.phonon_state[n].norm_sqr();
}
mean
}
pub fn displace(&mut self, alpha: Complex64) -> QuantRS2Result<()> {
let mut new_state = Array1::zeros(self.max_phonons + 1);
for n in 0..=self.max_phonons {
new_state[n] = self.phonon_state[n] * (-alpha.norm_sqr() / 2.0).exp();
if n > 0 {
new_state[n] += alpha * (n as f64).sqrt() * self.phonon_state[n - 1];
}
}
let norm = new_state
.iter()
.map(|x: &Complex64| x.norm_sqr())
.sum::<f64>()
.sqrt();
for amp in &mut new_state {
*amp /= norm;
}
self.phonon_state = new_state;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct TrappedIon {
pub ion_id: usize,
pub species: IonSpecies,
pub position: [f64; 3],
pub electronic_state: Array1<Complex64>,
pub motional_coupling: HashMap<usize, f64>,
}
impl TrappedIon {
pub fn new(ion_id: usize, species: IonSpecies, position: [f64; 3]) -> Self {
let mut electronic_state = Array1::zeros(2);
electronic_state[0] = Complex64::new(1.0, 0.0);
Self {
ion_id,
species,
position,
electronic_state,
motional_coupling: HashMap::new(),
}
}
pub fn set_state(&mut self, amplitudes: [Complex64; 2]) -> QuantRS2Result<()> {
let norm = (amplitudes[0].norm_sqr() + amplitudes[1].norm_sqr()).sqrt();
if norm < 1e-10 {
return Err(QuantRS2Error::InvalidInput(
"State cannot have zero norm".to_string(),
));
}
self.electronic_state[0] = amplitudes[0] / norm;
self.electronic_state[1] = amplitudes[1] / norm;
Ok(())
}
pub fn get_state(&self) -> [Complex64; 2] {
[self.electronic_state[0], self.electronic_state[1]]
}
}
#[derive(Debug, Clone)]
pub struct LaserPulse {
pub frequency: f64,
pub rabi_frequency: f64,
pub duration: f64,
pub phase: f64,
pub detuning: f64,
pub target_ions: Vec<usize>,
pub addressing_efficiency: f64,
}
impl LaserPulse {
pub const fn carrier_pulse(
rabi_frequency: f64,
duration: f64,
phase: f64,
target_ions: Vec<usize>,
) -> Self {
Self {
frequency: 0.0, rabi_frequency,
duration,
phase,
detuning: 0.0, target_ions,
addressing_efficiency: 1.0,
}
}
pub fn red_sideband_pulse(
rabi_frequency: f64,
duration: f64,
phase: f64,
target_ions: Vec<usize>,
motional_frequency: f64,
) -> Self {
Self {
frequency: 0.0,
rabi_frequency,
duration,
phase,
detuning: -motional_frequency, target_ions,
addressing_efficiency: 1.0,
}
}
pub const fn blue_sideband_pulse(
rabi_frequency: f64,
duration: f64,
phase: f64,
target_ions: Vec<usize>,
motional_frequency: f64,
) -> Self {
Self {
frequency: 0.0,
rabi_frequency,
duration,
phase,
detuning: motional_frequency, target_ions,
addressing_efficiency: 1.0,
}
}
}
#[derive(Debug, Clone)]
pub struct TrappedIonSystem {
pub num_ions: usize,
pub ions: Vec<TrappedIon>,
pub motional_modes: Vec<MotionalMode>,
pub global_state: Option<Array1<Complex64>>,
pub temperature: f64,
pub magnetic_field: f64,
}
impl TrappedIonSystem {
pub fn new(ion_species: Vec<IonSpecies>, positions: Vec<[f64; 3]>) -> QuantRS2Result<Self> {
if ion_species.len() != positions.len() {
return Err(QuantRS2Error::InvalidInput(
"Number of species and positions must match".to_string(),
));
}
let num_ions = ion_species.len();
let ions: Vec<TrappedIon> = ion_species
.into_iter()
.zip(positions)
.enumerate()
.map(|(id, (species, pos))| TrappedIon::new(id, species, pos))
.collect();
let mut motional_modes = Vec::new();
for i in 0..3 {
let direction = match i {
0 => "x".to_string(),
1 => "y".to_string(),
2 => "z".to_string(),
_ => unreachable!(),
};
let mode = MotionalMode::ground_state(
i,
1.0e6, direction,
MotionalModeType::CenterOfMass,
0.1, 20, );
motional_modes.push(mode);
}
Ok(Self {
num_ions,
ions,
motional_modes,
global_state: None,
temperature: 1e-6, magnetic_field: 0.01, })
}
pub fn apply_laser_pulse(&mut self, pulse: &LaserPulse) -> QuantRS2Result<()> {
for &ion_id in &pulse.target_ions {
if ion_id >= self.num_ions {
return Err(QuantRS2Error::InvalidInput(
"Target ion ID out of bounds".to_string(),
));
}
self.apply_pulse_to_ion(ion_id, pulse)?;
}
Ok(())
}
fn apply_pulse_to_ion(&mut self, ion_id: usize, pulse: &LaserPulse) -> QuantRS2Result<()> {
let ion = &mut self.ions[ion_id];
let theta = pulse.rabi_frequency * pulse.duration * pulse.addressing_efficiency;
let cos_half = (theta / 2.0).cos();
let sin_half = (theta / 2.0).sin();
let phase_factor = Complex64::new(0.0, pulse.phase).exp();
let old_state = ion.electronic_state.clone();
ion.electronic_state[0] = cos_half * old_state[0]
- Complex64::new(0.0, 1.0) * sin_half * phase_factor * old_state[1];
ion.electronic_state[1] = cos_half * old_state[1]
- Complex64::new(0.0, 1.0) * sin_half * phase_factor.conj() * old_state[0];
if pulse.detuning.abs() > 1e3 {
self.apply_motional_coupling(ion_id, pulse)?;
}
Ok(())
}
fn apply_motional_coupling(
&mut self,
_ion_id: usize,
pulse: &LaserPulse,
) -> QuantRS2Result<()> {
let mut mode_idx = None;
let mut eta = 0.0;
for (i, mode) in self.motional_modes.iter().enumerate() {
if (mode.frequency - pulse.detuning.abs()).abs() < 1e3 {
mode_idx = Some(i);
eta = mode.lamb_dicke_parameter;
break;
}
}
if let Some(idx) = mode_idx {
let mode = &mut self.motional_modes[idx];
if pulse.detuning < 0.0 {
let mut new_state = Array1::zeros(mode.max_phonons + 1);
for n in 1..=mode.max_phonons {
let coupling = eta * (n as f64).sqrt();
new_state[n - 1] += coupling * mode.phonon_state[n];
}
let norm = new_state
.iter()
.map(|x: &Complex64| x.norm_sqr())
.sum::<f64>()
.sqrt();
if norm > 1e-10 {
for amp in &mut new_state {
*amp /= norm;
}
mode.phonon_state = new_state;
}
} else {
let mut new_state = Array1::zeros(mode.max_phonons + 1);
for n in 0..mode.max_phonons {
let coupling = eta * ((n + 1) as f64).sqrt();
new_state[n + 1] += coupling * mode.phonon_state[n];
}
let norm = new_state
.iter()
.map(|x: &Complex64| x.norm_sqr())
.sum::<f64>()
.sqrt();
if norm > 1e-10 {
for amp in &mut new_state {
*amp /= norm;
}
mode.phonon_state = new_state;
}
}
}
Ok(())
}
fn apply_red_sideband(&self, mode: &mut MotionalMode, eta: f64) -> QuantRS2Result<()> {
let mut new_state = Array1::zeros(mode.max_phonons + 1);
for n in 1..=mode.max_phonons {
let coupling = eta * (n as f64).sqrt();
new_state[n - 1] += coupling * mode.phonon_state[n];
}
let norm = new_state
.iter()
.map(|x: &Complex64| x.norm_sqr())
.sum::<f64>()
.sqrt();
if norm > 1e-10 {
for amp in &mut new_state {
*amp /= norm;
}
mode.phonon_state = new_state;
}
Ok(())
}
fn apply_blue_sideband(&self, mode: &mut MotionalMode, eta: f64) -> QuantRS2Result<()> {
let mut new_state = Array1::zeros(mode.max_phonons + 1);
for n in 0..mode.max_phonons {
let coupling = eta * ((n + 1) as f64).sqrt();
new_state[n + 1] += coupling * mode.phonon_state[n];
}
let norm = new_state
.iter()
.map(|x: &Complex64| x.norm_sqr())
.sum::<f64>()
.sqrt();
if norm > 1e-10 {
for amp in &mut new_state {
*amp /= norm;
}
mode.phonon_state = new_state;
}
Ok(())
}
pub fn molmer_sorensen_gate(
&mut self,
ion1: usize,
ion2: usize,
phase: f64,
) -> QuantRS2Result<()> {
if ion1 >= self.num_ions || ion2 >= self.num_ions {
return Err(QuantRS2Error::InvalidInput(
"Ion index out of bounds".to_string(),
));
}
if ion1 == ion2 {
return Err(QuantRS2Error::InvalidInput(
"Cannot apply MS gate to same ion".to_string(),
));
}
let state1 = self.ions[ion1].get_state();
let state2 = self.ions[ion2].get_state();
if state1[0].norm() > 0.9 && state2[0].norm() > 0.9 {
let sqrt2_inv = 1.0 / std::f64::consts::SQRT_2;
let phase_factor = Complex64::new(0.0, phase);
self.ions[ion1].set_state([
sqrt2_inv * Complex64::new(1.0, 0.0),
sqrt2_inv * phase_factor,
])?;
self.ions[ion2].set_state([
sqrt2_inv * Complex64::new(1.0, 0.0),
sqrt2_inv * phase_factor,
])?;
} else {
let sqrt2_inv = 1.0 / std::f64::consts::SQRT_2;
let new_state1_0 = sqrt2_inv * (state1[0] + state2[1]);
let new_state1_1 = sqrt2_inv * (state1[1] + state2[0]);
let new_state2_0 = sqrt2_inv * (state2[0] + state1[1]);
let new_state2_1 = sqrt2_inv * (state2[1] + state1[0]);
self.ions[ion1].set_state([new_state1_0, new_state1_1])?;
self.ions[ion2].set_state([new_state2_0, new_state2_1])?;
}
Ok(())
}
pub fn state_dependent_force(
&mut self,
target_ions: &[usize],
mode_id: usize,
force_strength: f64,
) -> QuantRS2Result<()> {
if mode_id >= self.motional_modes.len() {
return Err(QuantRS2Error::InvalidInput(
"Mode ID out of bounds".to_string(),
));
}
for &ion_id in target_ions {
if ion_id >= self.num_ions {
return Err(QuantRS2Error::InvalidInput(
"Ion ID out of bounds".to_string(),
));
}
let ion_state = self.ions[ion_id].get_state();
let excited_population = ion_state[1].norm_sqr();
let alpha = Complex64::new(force_strength * excited_population, 0.0);
self.motional_modes[mode_id].displace(alpha)?;
}
Ok(())
}
pub fn measure_ion(&mut self, ion_id: usize) -> QuantRS2Result<bool> {
if ion_id >= self.num_ions {
return Err(QuantRS2Error::InvalidInput(
"Ion ID out of bounds".to_string(),
));
}
let ion = &mut self.ions[ion_id];
let prob_excited = ion.electronic_state[1].norm_sqr();
use scirs2_core::random::prelude::*;
let mut rng = thread_rng();
let random_value: f64 = rng.random();
let result = random_value < prob_excited;
if result {
ion.set_state([Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)])?;
} else {
ion.set_state([Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)])?;
}
Ok(result)
}
pub fn cool_motional_mode(&mut self, mode_id: usize) -> QuantRS2Result<()> {
if mode_id >= self.motional_modes.len() {
return Err(QuantRS2Error::InvalidInput(
"Mode ID out of bounds".to_string(),
));
}
let mode = &mut self.motional_modes[mode_id];
mode.phonon_state.fill(Complex64::new(0.0, 0.0));
mode.phonon_state[0] = Complex64::new(1.0, 0.0);
Ok(())
}
pub fn get_motional_temperature(&self) -> f64 {
let total_energy: f64 = self
.motional_modes
.iter()
.map(|mode| mode.mean_phonon_number() * mode.frequency)
.sum();
let k_b = 1.381e-23; let avg_frequency = 1e6;
total_energy / (k_b * avg_frequency * self.motional_modes.len() as f64)
}
}
pub struct TrappedIonGates;
impl TrappedIonGates {
pub fn rotation_gate(
system: &mut TrappedIonSystem,
ion_id: usize,
axis: &str,
angle: f64,
) -> QuantRS2Result<()> {
let rabi_freq = 1e6; let duration = angle / rabi_freq;
let phase = match axis {
"x" | "z" => 0.0, "y" => std::f64::consts::PI / 2.0,
_ => {
return Err(QuantRS2Error::InvalidInput(
"Invalid rotation axis".to_string(),
))
}
};
let pulse = LaserPulse::carrier_pulse(rabi_freq, duration, phase, vec![ion_id]);
system.apply_laser_pulse(&pulse)
}
pub fn hadamard(system: &mut TrappedIonSystem, ion_id: usize) -> QuantRS2Result<()> {
if ion_id >= system.num_ions {
return Err(QuantRS2Error::InvalidInput(
"Ion ID out of bounds".to_string(),
));
}
let ion = &mut system.ions[ion_id];
let old_state = ion.electronic_state.clone();
let inv_sqrt2 = 1.0 / std::f64::consts::SQRT_2;
ion.electronic_state[0] = inv_sqrt2 * (old_state[0] + old_state[1]);
ion.electronic_state[1] = inv_sqrt2 * (old_state[0] - old_state[1]);
Ok(())
}
pub fn pauli_x(system: &mut TrappedIonSystem, ion_id: usize) -> QuantRS2Result<()> {
Self::rotation_gate(system, ion_id, "x", std::f64::consts::PI)
}
pub fn pauli_y(system: &mut TrappedIonSystem, ion_id: usize) -> QuantRS2Result<()> {
Self::rotation_gate(system, ion_id, "y", std::f64::consts::PI)
}
pub fn pauli_z(system: &mut TrappedIonSystem, ion_id: usize) -> QuantRS2Result<()> {
Self::rotation_gate(system, ion_id, "z", std::f64::consts::PI)
}
pub fn cnot(
system: &mut TrappedIonSystem,
control: usize,
target: usize,
) -> QuantRS2Result<()> {
Self::rotation_gate(system, target, "y", -std::f64::consts::PI / 2.0)?;
system.molmer_sorensen_gate(control, target, std::f64::consts::PI / 2.0)?;
Self::rotation_gate(system, control, "x", -std::f64::consts::PI / 2.0)?;
Self::rotation_gate(system, target, "x", -std::f64::consts::PI / 2.0)?;
Ok(())
}
pub fn cz(system: &mut TrappedIonSystem, control: usize, target: usize) -> QuantRS2Result<()> {
Self::hadamard(system, target)?;
Self::cnot(system, control, target)?;
Self::hadamard(system, target)
}
pub fn toffoli(
system: &mut TrappedIonSystem,
control1: usize,
control2: usize,
target: usize,
) -> QuantRS2Result<()> {
Self::hadamard(system, target)?;
Self::cnot(system, control2, target)?;
Self::rotation_gate(system, target, "z", -std::f64::consts::PI / 4.0)?; Self::cnot(system, control1, target)?;
Self::rotation_gate(system, target, "z", std::f64::consts::PI / 4.0)?; Self::cnot(system, control2, target)?;
Self::rotation_gate(system, target, "z", -std::f64::consts::PI / 4.0)?; Self::cnot(system, control1, target)?;
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)?;
Self::cnot(system, control1, control2)?;
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)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ion_species_properties() {
let be9 = IonSpecies::Be9;
assert!((be9.atomic_mass() - 9.012).abs() < 0.001);
assert!(be9.typical_trap_frequency() > 1e6);
assert!(be9.qubit_wavelength() > 300.0);
}
#[test]
fn test_motional_mode_creation() {
let mode = MotionalMode::ground_state(
0,
1e6,
"x".to_string(),
MotionalModeType::CenterOfMass,
0.1,
10,
);
assert_eq!(mode.mode_id, 0);
assert!((mode.mean_phonon_number() - 0.0).abs() < 1e-10);
assert!((mode.phonon_state[0] - Complex64::new(1.0, 0.0)).norm() < 1e-10);
}
#[test]
fn test_thermal_state() {
let mode = MotionalMode::thermal_state(
0,
1e6,
"x".to_string(),
MotionalModeType::CenterOfMass,
0.1,
10,
2.0, );
let mean_phonons = mode.mean_phonon_number();
assert!((mean_phonons - 2.0).abs() < 0.5); }
#[test]
fn test_trapped_ion_creation() {
let ion = TrappedIon::new(0, IonSpecies::Ca40, [0.0, 0.0, 0.0]);
assert_eq!(ion.ion_id, 0);
assert_eq!(ion.species, IonSpecies::Ca40);
let state = ion.get_state();
assert!((state[0] - Complex64::new(1.0, 0.0)).norm() < 1e-10);
assert!((state[1] - Complex64::new(0.0, 0.0)).norm() < 1e-10);
}
#[test]
fn test_trapped_ion_system() {
let species = vec![IonSpecies::Ca40, IonSpecies::Ca40];
let positions = vec![[0.0, 0.0, 0.0], [10.0, 0.0, 0.0]];
let system = TrappedIonSystem::new(species, positions)
.expect("Trapped ion system should be created successfully");
assert_eq!(system.num_ions, 2);
assert_eq!(system.ions.len(), 2);
assert!(system.motional_modes.len() >= 3);
}
#[test]
fn test_laser_pulse_application() {
let species = vec![IonSpecies::Ca40];
let positions = vec![[0.0, 0.0, 0.0]];
let mut system = TrappedIonSystem::new(species, positions)
.expect("Trapped ion system should be created successfully");
let rabi_freq = 1e6; let pulse = LaserPulse::carrier_pulse(
rabi_freq, std::f64::consts::PI / rabi_freq, 0.0, vec![0], );
system
.apply_laser_pulse(&pulse)
.expect("Laser pulse should be applied successfully");
let state = system.ions[0].get_state();
assert!(state[1].norm() > 0.9); }
#[test]
fn test_motional_displacement() {
let mut mode = MotionalMode::ground_state(
0,
1e6,
"x".to_string(),
MotionalModeType::CenterOfMass,
0.1,
10,
);
let alpha = Complex64::new(1.0, 0.0);
mode.displace(alpha)
.expect("Displacement operation should succeed");
let mean_phonons = mode.mean_phonon_number();
assert!(mean_phonons > 0.5); }
#[test]
fn test_molmer_sorensen_gate() {
let species = vec![IonSpecies::Ca40, IonSpecies::Ca40];
let positions = vec![[0.0, 0.0, 0.0], [10.0, 0.0, 0.0]];
let mut system = TrappedIonSystem::new(species, positions)
.expect("Trapped ion system should be created successfully");
system
.molmer_sorensen_gate(0, 1, std::f64::consts::PI / 2.0)
.expect("Molmer-Sorensen gate should be applied successfully");
let state1 = system.ions[0].get_state();
let state2 = system.ions[1].get_state();
assert!(state1[0].norm() < 1.0);
assert!(state2[0].norm() < 1.0);
}
#[test]
fn test_ion_measurement() {
let species = vec![IonSpecies::Ca40];
let positions = vec![[0.0, 0.0, 0.0]];
let mut system = TrappedIonSystem::new(species, positions)
.expect("Trapped ion system should be created successfully");
system.ions[0]
.set_state([
Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0),
Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0),
])
.expect("Ion state should be set successfully");
let result = system
.measure_ion(0)
.expect("Ion measurement should succeed");
let _ = result;
let state = system.ions[0].get_state();
assert!(state[0].norm() == 1.0 || state[1].norm() == 1.0);
}
#[test]
fn test_trapped_ion_gates() {
let species = vec![IonSpecies::Ca40, IonSpecies::Ca40];
let positions = vec![[0.0, 0.0, 0.0], [10.0, 0.0, 0.0]];
let mut system = TrappedIonSystem::new(species, positions)
.expect("Trapped ion system should be created successfully");
TrappedIonGates::pauli_x(&mut system, 0)
.expect("Pauli-X gate should be applied successfully");
let state = system.ions[0].get_state();
assert!(state[1].norm() > 0.9);
TrappedIonGates::hadamard(&mut system, 0)
.expect("Hadamard gate should be applied successfully");
let state = system.ions[0].get_state();
assert!(state[0].norm() > 0.05 && state[0].norm() < 0.95); }
#[test]
fn test_cnot_gate() {
let species = vec![IonSpecies::Ca40, IonSpecies::Ca40];
let positions = vec![[0.0, 0.0, 0.0], [10.0, 0.0, 0.0]];
let mut system = TrappedIonSystem::new(species, positions)
.expect("Trapped ion system should be created successfully");
TrappedIonGates::pauli_x(&mut system, 0)
.expect("Pauli-X gate should be applied successfully");
TrappedIonGates::cnot(&mut system, 0, 1).expect("CNOT gate should be applied successfully");
let target_state = system.ions[1].get_state();
assert!(target_state[1].norm() > 0.5);
}
#[test]
fn test_motional_temperature() {
let species = vec![IonSpecies::Ca40];
let positions = vec![[0.0, 0.0, 0.0]];
let system = TrappedIonSystem::new(species, positions)
.expect("Trapped ion system should be created successfully");
let temp = system.get_motional_temperature();
assert!(temp >= 0.0);
assert!(temp.is_finite());
}
}