Skip to content

Commit 74a8ff8

Browse files
committed
Move particle advance and rotate into Particle.
1 parent 543bb9a commit 74a8ff8

File tree

5 files changed

+109
-71
lines changed

5 files changed

+109
-71
lines changed

src/bca.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,10 @@ pub fn single_ion_bca<T: Geometry>(particle: particle::Particle, material: &mate
134134
total_asymptotic_deflection += binary_collision_result.asymptotic_deflection;
135135

136136
//Rotate particle 1, 2 by lab frame scattering angles
137-
particle::rotate_particle(&mut particle_1, binary_collision_result.psi,
137+
particle_1.rotate(binary_collision_result.psi,
138138
binary_collision_geometry.phi_azimuthal);
139139

140-
particle::rotate_particle(&mut particle_2, -binary_collision_result.psi_recoil,
140+
particle_2.rotate(-binary_collision_result.psi_recoil,
141141
binary_collision_geometry.phi_azimuthal);
142142

143143
particle_2.dir_old.x = particle_2.dir.x;
@@ -184,11 +184,11 @@ pub fn single_ion_bca<T: Geometry>(particle: particle::Particle, material: &mate
184184

185185
//Advance particle in space and track total distance traveled
186186
#[cfg(not(feature = "accelerated_ions"))]
187-
let distance_traveled = particle::particle_advance(&mut particle_1,
187+
let distance_traveled = particle_1.advance(
188188
binary_collision_geometries[0].mfp, total_asymptotic_deflection);
189189

190190
#[cfg(feature = "accelerated_ions")]
191-
let distance_traveled = particle::particle_advance(&mut particle_1,
191+
let distance_traveled = particle_1.advance(
192192
binary_collision_geometries[0].mfp + distance_to_target - material.geometry.get_energy_barrier_thickness(), total_asymptotic_deflection);
193193

194194
//Subtract total energy from all simultaneous collisions and electronic stopping

src/lib.rs

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -984,9 +984,46 @@ pub extern "C" fn simple_bca_c(x: f64, y: f64, z: f64, ux: f64, uy: f64, uz: f64
984984
#[cfg(feature = "python")]
985985
///compound_tagged_bca_list_py(ux, uy, uz, energy, Z1, m1, Ec1, Es1, Z2, m2, Ec2, Es2, n2, Eb2)
986986
/// runs a BCA simulation for a list of particles and outputs a list of sputtered, reflected, and implanted particles.
987+
/// Args:
988+
/// energies (list(f64)): initial ion energies in eV.
989+
/// ux (list(f64)): initial ion directions x. ux != 0.0 to avoid gimbal lock
990+
/// uy (list(f64)): initial ion directions y.
991+
/// uz (list(f64)): initial ion directions z.
992+
/// Z1 (list(f64)): initial ion atomic numbers.
993+
/// m1 (list(f64)): initial ion masses in amu.
994+
/// Ec1 (list(f64)): ion cutoff energies in eV. If ion energy < Ec1, it stops in the material.
995+
/// Es1 (list(f64)): ion surface binding energies. Assumed planar.
996+
/// Z2 (list(f64)): target material species atomic numbers.
997+
/// m2 (list(f64)): target material species masses in amu.
998+
/// Ec2 (list(f64)): target material species cutoff energies in eV. If recoil energy < Ec2, it stops in the material.
999+
/// Es2 (list(f64)): target species surface binding energies. Assumed planar.
1000+
/// n2 (list(f64)): target material species atomic number densities in inverse cubic Angstroms.
1001+
/// Eb2 (list(f64)): target material species bulk binding energies in eV.
1002+
/// Returns:
1003+
/// output (NX9 list of f64): each row in the list represents an output particle (implanted,
1004+
/// sputtered, or reflected). Each row consists of:
1005+
/// [Z, m (amu), E (eV), x, y, z, (angstrom), ux, uy, uz]
1006+
/// incident (list(bool)): whether each row of output was an incident ion or originated in the target
9871007
#[pyfunction]
988-
pub fn compound_bca_list_py(num_species_target: usize, energies: Vec<f64>, ux: Vec<f64>, uy: Vec<f64>, uz: Vec<f64>, Z1: Vec<f64>, m1: Vec<f64>, Ec1: Vec<f64>, Es1: Vec<f64>, Z2: Vec<f64>, m2: Vec<f64>, Ec2: Vec<f64>, Es2: Vec<f64>, n2: Vec<f64>, Eb2: Vec<f64>) -> Vec<[f64; 6]> {
1008+
pub fn compound_bca_list_py(energies: Vec<f64>, ux: Vec<f64>, uy: Vec<f64>, uz: Vec<f64>, Z1: Vec<f64>, m1: Vec<f64>, Ec1: Vec<f64>, Es1: Vec<f64>, Z2: Vec<f64>, m2: Vec<f64>, Ec2: Vec<f64>, Es2: Vec<f64>, n2: Vec<f64>, Eb2: Vec<f64>) -> (Vec<[f64; 9]>, Vec<bool>) {
9891009
let mut total_output = vec![];
1010+
let mut incident = vec![];
1011+
let num_species_target = Z2.len();
1012+
let num_incident_ions = energies.len();
1013+
1014+
assert_eq!(ux.len(), num_incident_ions, "Input error: list of x-directions is not the same length as list of incident energies.");
1015+
assert_eq!(uy.len(), num_incident_ions, "Input error: list of y-directions is not the same length as list of incident energies.");
1016+
assert_eq!(uz.len(), num_incident_ions, "Input error: list of z-directions is not the same length as list of incident energies.");
1017+
assert_eq!(Z1.len(), num_incident_ions, "Input error: list of incident atomic numbers is not the same length as list of incident energies.");
1018+
assert_eq!(m1.len(), num_incident_ions, "Input error: list of incident atomic masses is not the same length as list of incident energies.");
1019+
assert_eq!(Es1.len(), num_incident_ions, "Input error: list of incident surface binding energies is not the same length as list of incident energies.");
1020+
assert_eq!(Ec1.len(), num_incident_ions, "Input error: list of incident cutoff energies is not the same length as list of incident energies.");
1021+
1022+
assert_eq!(m2.len(), num_species_target, "Input error: list of target atomic masses is not the same length as atomic numbers.");
1023+
assert_eq!(Ec2.len(), num_species_target, "Input error: list of target cutoff energies is not the same length as atomic numbers.");
1024+
assert_eq!(Es2.len(), num_species_target, "Input error: list of target surface binding energies is not the same length as atomic numbers.");
1025+
assert_eq!(Eb2.len(), num_species_target, "Input error: list of target bulk binding energies is not the same length as atomic numbers.");
1026+
assert_eq!(n2.len(), num_species_target, "Input error: list of target number densities is not the same length as atomic numbers.");
9901027

9911028
#[cfg(feature = "distributions")]
9921029
let options = Options {
@@ -1107,8 +1144,10 @@ pub fn compound_bca_list_py(num_species_target: usize, energies: Vec<f64>, ux: V
11071144
let output = bca::single_ion_bca(p, &m, &options);
11081145

11091146
for particle in output {
1110-
11111147
if (particle.left) | (particle.incident) {
1148+
1149+
incident.push(particle.incident);
1150+
11121151
if particle.stopped {
11131152
energy_out = 0.
11141153
} else {
@@ -1119,6 +1158,9 @@ pub fn compound_bca_list_py(num_species_target: usize, energies: Vec<f64>, ux: V
11191158
particle.Z,
11201159
particle.m/AMU,
11211160
energy_out,
1161+
particle.pos.x,
1162+
particle.pos.y,
1163+
particle.pos.z,
11221164
particle.dir.x,
11231165
particle.dir.y,
11241166
particle.dir.z,
@@ -1128,7 +1170,7 @@ pub fn compound_bca_list_py(num_species_target: usize, energies: Vec<f64>, ux: V
11281170
}
11291171
index += 1;
11301172
}
1131-
total_output
1173+
(total_output, incident)
11321174
}
11331175

11341176
#[cfg(feature = "python")]

src/particle.rs

Lines changed: 48 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ pub struct Particle {
8383
pub left: bool,
8484
pub incident: bool,
8585
pub first_step: bool,
86-
pub trajectory: Vec<Vector4>,
86+
pub trajectory: Vec<TrajectoryElement>,
8787
pub energies: Vec<EnergyLoss>,
8888
pub track_trajectories: bool,
8989
pub number_collision_events: usize,
@@ -122,7 +122,7 @@ impl Particle {
122122
left: false,
123123
incident: true,
124124
first_step: true,
125-
trajectory: vec![Vector4::new(input.E, input.x, input.y, input.z)],
125+
trajectory: vec![TrajectoryElement::new(input.E, input.x, input.y, input.z)],
126126
energies: vec![],
127127
track_trajectories: options.track_trajectories,
128128
number_collision_events: 0,
@@ -170,7 +170,7 @@ impl Particle {
170170
/// If `track_trajectories`, add the current (E, x, y, z) to the trajectory.
171171
pub fn add_trajectory(&mut self) {
172172
if self.track_trajectories {
173-
self.trajectory.push(Vector4 {E: self.E, x: self.pos.x, y: self.pos.y, z: self.pos.z});
173+
self.trajectory.push(TrajectoryElement {E: self.E, x: self.pos.x, y: self.pos.y, z: self.pos.z});
174174
}
175175
}
176176

@@ -190,60 +190,62 @@ impl Particle {
190190
self.m*speed*self.dir.z,
191191
)
192192
}
193-
}
194193

195-
/// Rotate a particle by a deflection psi at an azimuthal angle phi.
196-
pub fn rotate_particle(particle_1: &mut particle::Particle, psi: f64, phi: f64) {
197-
let cosx: f64 = particle_1.dir.x;
198-
let cosy: f64 = particle_1.dir.y;
199-
let cosz: f64 = particle_1.dir.z;
200-
let cphi: f64 = phi.cos();
201-
let sphi: f64 = phi.sin();
202-
let sa = (1. - cosx*cosx).sqrt();
203-
204-
//Particle direction update formulas from original TRIDYN paper, see Moeller and Eckstein 1988
205-
let cpsi: f64 = psi.cos();
206-
let spsi: f64 = psi.sin();
207-
let cosx_new: f64 = cpsi*cosx + spsi*cphi*sa;
208-
let cosy_new: f64 = cpsi*cosy - spsi/sa*(cphi*cosx*cosy - sphi*cosz);
209-
let cosz_new: f64 = cpsi*cosz - spsi/sa*(cphi*cosx*cosz + sphi*cosy);
210-
211-
let dir_new = Vector {x: cosx_new, y: cosy_new, z: cosz_new};
212-
213-
particle_1.dir.assign(&dir_new);
214-
particle_1.dir.normalize();
215-
}
194+
/// Rotate a particle by a deflection psi at an azimuthal angle phi.
195+
pub fn rotate(&mut self, psi: f64, phi: f64) {
196+
let cosx: f64 = self.dir.x;
197+
let cosy: f64 = self.dir.y;
198+
let cosz: f64 = self.dir.z;
199+
let cphi: f64 = phi.cos();
200+
let sphi: f64 = phi.sin();
201+
let sa = (1. - cosx*cosx).sqrt();
202+
203+
//Particle direction update formulas from original TRIDYN paper, see Moeller and Eckstein 1988
204+
let cpsi: f64 = psi.cos();
205+
let spsi: f64 = psi.sin();
206+
let cosx_new: f64 = cpsi*cosx + spsi*cphi*sa;
207+
let cosy_new: f64 = cpsi*cosy - spsi/sa*(cphi*cosx*cosy - sphi*cosz);
208+
let cosz_new: f64 = cpsi*cosz - spsi/sa*(cphi*cosx*cosz + sphi*cosy);
216209

217-
/// Push particle in space according to previous direction and return the distance traveled.
218-
pub fn particle_advance(particle_1: &mut particle::Particle, mfp: f64, asymptotic_deflection: f64) -> f64 {
210+
let dir_new = Vector {x: cosx_new, y: cosy_new, z: cosz_new};
219211

220-
if particle_1.E > particle_1.Ec {
221-
particle_1.add_trajectory();
212+
self.dir.assign(&dir_new);
213+
self.dir.normalize();
222214
}
223215

224-
//Update previous position
225-
particle_1.pos_old.x = particle_1.pos.x;
226-
particle_1.pos_old.y = particle_1.pos.y;
227-
particle_1.pos_old.z = particle_1.pos.z;
216+
/// Push particle in space according to previous direction and return the distance traveled.
217+
pub fn advance(&mut self, mfp: f64, asymptotic_deflection: f64) -> f64 {
218+
219+
if self.E > self.Ec {
220+
self.add_trajectory();
221+
}
228222

229-
//In order to keep average denisty constant, must add back previous asymptotic deflection
230-
let distance_traveled = mfp + particle_1.asymptotic_deflection - asymptotic_deflection;
231-
//let distance_traveled = mfp - asymptotic_deflection;
223+
//Update previous position
224+
self.pos_old.x = self.pos.x;
225+
self.pos_old.y = self.pos.y;
226+
self.pos_old.z = self.pos.z;
232227

233-
//dir has been updated, so use previous direction to advance in space
234-
particle_1.pos.x += particle_1.dir_old.x*distance_traveled;
235-
particle_1.pos.y += particle_1.dir_old.y*distance_traveled;
236-
particle_1.pos.z += particle_1.dir_old.z*distance_traveled;
237-
particle_1.asymptotic_deflection = asymptotic_deflection;
228+
//In order to keep average denisty constant, must add back previous asymptotic deflection
229+
let distance_traveled = mfp + self.asymptotic_deflection - asymptotic_deflection;
230+
//let distance_traveled = mfp - asymptotic_deflection;
238231

239-
//Update previous direction
240-
particle_1.dir_old.x = particle_1.dir.x;
241-
particle_1.dir_old.y = particle_1.dir.y;
242-
particle_1.dir_old.z = particle_1.dir.z;
232+
//dir has been updated, so use previous direction to advance in space
233+
self.pos.x += self.dir_old.x*distance_traveled;
234+
self.pos.y += self.dir_old.y*distance_traveled;
235+
self.pos.z += self.dir_old.z*distance_traveled;
236+
self.asymptotic_deflection = asymptotic_deflection;
243237

244-
return distance_traveled;
238+
//Update previous direction
239+
self.dir_old.x = self.dir.x;
240+
self.dir_old.y = self.dir.y;
241+
self.dir_old.z = self.dir.z;
242+
243+
return distance_traveled;
244+
}
245245
}
246246

247+
248+
247249
pub fn surface_refraction(particle: &mut Particle, normal: Vector, Es: f64) {
248250
let E = particle.E;
249251

src/structs.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,18 @@ impl Vector {
4444
}
4545
}
4646

47-
/// Vector4 is a trajectory-tracking object that includes x, y, z, and the current energy.
47+
/// TrajectoryElement is a trajectory-tracking object that includes x, y, z, and the current energy.
4848
#[derive(Clone)]
49-
pub struct Vector4 {
49+
pub struct TrajectoryElement {
5050
pub E: f64,
5151
pub x: f64,
5252
pub y: f64,
5353
pub z: f64,
5454
}
5555

56-
impl Vector4 {
57-
pub fn new(E: f64, x: f64, y: f64, z: f64) -> Vector4 {
58-
Vector4 {
56+
impl TrajectoryElement {
57+
pub fn new(E: f64, x: f64, y: f64, z: f64) -> TrajectoryElement {
58+
TrajectoryElement {
5959
E,
6060
x,
6161
y,

src/tests.rs

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -660,9 +660,6 @@ fn test_surface_refraction() {
660660
let cosy_new = cosy_new/dir_mag;
661661
let cosz_new = cosz_new/dir_mag;
662662

663-
//let delta_theta = particle::refraction_angle(cosx, E, E + Es);
664-
//particle::rotate_particle(&mut particle_1, delta_theta, 0.);
665-
666663
let normal = Vector::new(1.0, 0.0, 0.0);
667664
particle::surface_refraction(&mut particle_1, normal, Es);
668665

@@ -690,9 +687,6 @@ fn test_surface_refraction() {
690687
println!("{} {} {}", particle_1.dir.x, particle_1.dir.y, particle_1.dir.z);
691688
}
692689

693-
//let delta_theta = particle::refraction_angle(particle_1.dir.x, particle_1.E, particle_1.E - Es);
694-
//particle::rotate_particle(&mut particle_1, delta_theta, 0.);
695-
696690
let normal = Vector::new(1.0, 0.0, 0.0);
697691
particle::surface_refraction(&mut particle_1, normal, -Es);
698692

@@ -856,10 +850,10 @@ fn test_momentum_conservation() {
856850
particle_2.E = binary_collision_result.recoil_energy - material_1.average_bulk_binding_energy(particle_2.pos.x, particle_2.pos.y, particle_2.pos.z);
857851

858852
//Rotate particle 1, 2 by lab frame scattering angles
859-
particle::rotate_particle(&mut particle_1, binary_collision_result.psi,
853+
particle_1.rotate(binary_collision_result.psi,
860854
binary_collision_geometries[0].phi_azimuthal);
861855

862-
particle::rotate_particle(&mut particle_2, -binary_collision_result.psi_recoil,
856+
particle_2.rotate(-binary_collision_result.psi_recoil,
863857
binary_collision_geometries[0].phi_azimuthal);
864858

865859
//Subtract total energy from all simultaneous collisions and electronic stopping
@@ -894,7 +888,7 @@ fn test_momentum_conservation() {
894888
}
895889

896890
#[test]
897-
fn test_rotate_particle() {
891+
fn test_rotate() {
898892
let mass = 1.;
899893
let Z = 1.;
900894
let E = 1.;
@@ -912,18 +906,18 @@ fn test_rotate_particle() {
912906
let mut particle = particle::Particle::new(mass, Z, E, Ec, Es, x, y, z, cosx, cosy, cosz, false, false, 0);
913907

914908
//Check that rotation in 2D works
915-
particle::rotate_particle(&mut particle, psi, phi);
909+
particle.rotate(psi, phi);
916910
assert!(approx_eq!(f64, particle.dir.x, 0., epsilon = 1E-12), "particle.dir.x: {} Should be ~0.", particle.dir.x);
917911
assert!(approx_eq!(f64, particle.dir.y, 1., epsilon = 1E-12), "particle.dir.y: {} Should be ~1.", particle.dir.y);
918912

919913
//Check that rotating back by negative psi returns to the previous values
920-
particle::rotate_particle(&mut particle, -psi, phi);
914+
particle.rotate(-psi, phi);
921915
assert!(approx_eq!(f64, particle.dir.x, cosx, epsilon = 1E-12), "particle.dir.x: {} Should be ~{}", particle.dir.x, cosx);
922916
assert!(approx_eq!(f64, particle.dir.y, cosy, epsilon = 1E-12), "particle.dir.y: {} Should be ~{}", particle.dir.y, cosy);
923917

924918
//Check that azimuthal rotation by 180 degrees works correctly
925919
let phi = PI;
926-
particle::rotate_particle(&mut particle, psi, phi);
920+
particle.rotate(psi, phi);
927921
assert!(approx_eq!(f64, particle.dir.x, 1., epsilon = 1E-12), "particle.dir.x: {} Should be ~1.", particle.dir.x);
928922
assert!(approx_eq!(f64, particle.dir.y, 0., epsilon = 1E-12), "particle.dir.y: {} Should be ~0.", particle.dir.y);
929923

@@ -950,7 +944,7 @@ fn test_particle_advance() {
950944

951945
let mut particle = particle::Particle::new(mass, Z, E, Ec, Es, x, y, z, cosx, cosy, cosz, false, false, 0);
952946

953-
let distance_traveled = particle::particle_advance(&mut particle, mfp, asymptotic_deflection);
947+
let distance_traveled = particle.advance(mfp, asymptotic_deflection);
954948

955949
assert_eq!(particle.pos.x, (1. - 0.5)*cosx);
956950
assert_eq!(particle.pos.y, (1. - 0.5)*cosy);

0 commit comments

Comments
 (0)