Skip to content

Commit 3b92bab

Browse files
authored
Merge pull request #268 from lcpp-org/tracked_bca_py
Parallelized RustBCA python function for 0D compounds with tracking
2 parents 0bf4d68 + faadaf1 commit 3b92bab

File tree

2 files changed

+209
-2
lines changed

2 files changed

+209
-2
lines changed

examples/test_rustbca.py

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
def main():
1414

15+
1516
#test rotation to and from RustBCA coordinates
1617

1718
#nx, ny, nz is the normal vector (out of surface)
@@ -153,7 +154,7 @@ def main():
153154
print('Data processing complete.')
154155
print(f'RustBCA Y: {len(sputtered[:, 0])/number_ions} Yamamura Y: {yamamura_yield}')
155156
print(f'RustBCA R: {len(reflected[:, 0])/number_ions} Thomas R: {thomas}')
156-
print(f'Time per ion: {delta_time/number_ions*1e3} us/{ion["symbol"]}')
157+
print(f'Time per ion: {delta_time/number_ions} s/{ion["symbol"]}')
157158

158159
#Next up is the layered target version. I'll add a 50 Angstrom layer of W-H to the top of the target.
159160

@@ -170,14 +171,15 @@ def main():
170171

171172
print(f'Running RustBCA for {number_ions} {ion["symbol"]} ions on {target["symbol"]} with hydrogenated layer at {energies_eV[0]/1000.} keV...')
172173
print(f'This may take several minutes.')
173-
#Not the different argument order; when a breaking change is due, this will
174+
#Note the different argument order; when a breaking change is due, this will
174175
#be back-ported to the other bindings as well for consistency.
175176
output, incident, stopped = compound_bca_list_1D_py(
176177
ux, uy, uz, energies_eV, [ion['Z']]*number_ions,
177178
[ion['m']]*number_ions, [ion['Ec']]*number_ions, [ion['Es']]*number_ions, [target['Z'], 1.0], [target['m'], 1.008],
178179
[target['Ec'], 1.0], [target['Es'], 1.5], [target['Eb'], 0.0], [[target['n']/10**30, target['n']/10**30], [target['n']/10**30, 0.0]], [50.0, 1e6]
179180
)
180181

182+
181183
output = np.array(output)
182184

183185
Z = output[:, 0]
@@ -198,6 +200,80 @@ def main():
198200
plt.plot([50.0, 50.0], [0.0, np.max(heights)*1.1])
199201
plt.gca().set_ylim([0.0, np.max(heights)*1.1])
200202

203+
number_ions = 10000
204+
205+
#1 keV is above the He on W sputtering threshold of ~150 eV
206+
energies_eV = 1000.0*np.ones(number_ions)
207+
208+
#Working with angles of exactly 0 is problematic due to gimbal lock
209+
angle = 0.0001
210+
211+
#In RustBCA's 0D geometry, +x -> into the surface
212+
ux = np.cos(angle*np.pi/180.)*np.ones(number_ions)
213+
uy = np.sin(angle*np.pi/180.)*np.ones(number_ions)
214+
uz = np.zeros(number_ions)
215+
216+
Z1 = np.array([ion['Z']]*number_ions)
217+
m1 = np.array([ion['m']]*number_ions)
218+
Ec1 = np.array([ion['Ec']]*number_ions)
219+
Es1 = np.array([ion['Es']]*number_ions)
220+
221+
print(f'Running tracked RustBCA for {number_ions} {ion["symbol"]} ions on {target["symbol"]} with 1% {ion["symbol"]} at {energies_eV[0]/1000.} keV...')
222+
print(f'This may take several minutes.')
223+
224+
start = time.time()
225+
# Note that simple_bca_list_py expects number densities in 1/Angstrom^3
226+
output, incident, incident_index = compound_bca_list_tracked_py(
227+
energies_eV, ux, uy, uz, Z1, m1, Ec1, Es1,
228+
[target['Z'], ion['Z']], [target['m'], ion['m']],
229+
[target['Ec'], ion['Ec']], [target['Es'], ion['Es']], [target['n']/10**30, 0.01*target['n']/10**30],
230+
[target['Eb'], 0.0])
231+
stop = time.time()
232+
delta_time = stop - start
233+
234+
output = np.array(output)
235+
Z = output[:, 0]
236+
m = output[:, 1]
237+
E = output[:, 2]
238+
x = output[:, 3]
239+
y = output[:, 4]
240+
z = output[:, 5]
241+
ux = output[:, 6]
242+
uy = output[:, 7]
243+
uz = output[:, 8]
244+
245+
#For the python bindings, these conditionals can be used to distinguish
246+
#between sputtered, reflected, and implanted particles in the output list
247+
sputtered = output[np.logical_and(Z == target['Z'], E > 0), :]
248+
reflected = output[np.logical_and(Z == ion['Z'], x < 0), :]
249+
implanted = output[np.logical_and(Z == ion['Z'], x > 0), :]
250+
251+
plt.figure(1)
252+
plt.title(f'Sputtered {target["symbol"]} Energy Distribution')
253+
plt.xlabel('E [eV]')
254+
plt.ylabel('f(E) [A.U.]')
255+
plt.hist(sputtered[:, 2], bins=100, density=True, histtype='step')
256+
257+
plt.figure(2)
258+
plt.title(f'Implanted {ion["symbol"]} Depth Distribution')
259+
plt.xlabel('x [A]')
260+
plt.ylabel('f(x) [A.U.]')
261+
plt.hist(implanted[:, 3], bins=100, density=True, histtype='step')
262+
263+
thomas = thomas_reflection(ion, target, energies_eV[0])
264+
yamamura_yield = yamamura(ion, target, energies_eV[0])
265+
266+
print('Data processing complete.')
267+
print(f'RustBCA Y: {len(sputtered[:, 0])/number_ions} Yamamura Y: {yamamura_yield}')
268+
print(f'RustBCA R: {len(reflected[:, 0])/number_ions} Thomas R: {thomas}')
269+
print(f'Time per ion: {delta_time/number_ions} s/{ion["symbol"]}')
270+
271+
plt.figure()
272+
plt.plot(incident_index)
273+
plt.xlabel('Particle number')
274+
plt.ylabel('Particle index')
275+
plt.legend(['Incident', 'Indicies'])
276+
201277
plt.show()
202278

203279
if __name__ == '__main__':

src/lib.rs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ pub fn libRustBCA(py: Python, m: &PyModule) -> PyResult<()> {
8989
m.add_function(wrap_pyfunction!(simple_bca_py, m)?)?;
9090
m.add_function(wrap_pyfunction!(simple_bca_list_py, m)?)?;
9191
m.add_function(wrap_pyfunction!(compound_bca_list_py, m)?)?;
92+
m.add_function(wrap_pyfunction!(compound_bca_list_tracked_py, m)?)?;
9293
m.add_function(wrap_pyfunction!(compound_bca_list_1D_py, m)?)?;
9394
m.add_function(wrap_pyfunction!(sputtering_yield, m)?)?;
9495
m.add_function(wrap_pyfunction!(reflection_coefficient, m)?)?;
@@ -918,6 +919,136 @@ pub fn compound_bca_list_py(energies: Vec<f64>, ux: Vec<f64>, uy: Vec<f64>, uz:
918919
(total_output, incident)
919920
}
920921

922+
#[cfg(feature = "python")]
923+
///compound_tagged_bca_list_py(ux, uy, uz, energy, Z1, m1, Ec1, Es1, Z2, m2, Ec2, Es2, n2, Eb2)
924+
/// runs a BCA simulation for a list of particles and outputs a list of sputtered, reflected, and implanted particles.
925+
/// Args:
926+
/// energies (list(f64)): initial ion energies in eV.
927+
/// ux (list(f64)): initial ion directions x. ux != 0.0 to avoid gimbal lock
928+
/// uy (list(f64)): initial ion directions y.
929+
/// uz (list(f64)): initial ion directions z.
930+
/// Z1 (list(f64)): initial ion atomic numbers.
931+
/// m1 (list(f64)): initial ion masses in amu.
932+
/// Ec1 (list(f64)): ion cutoff energies in eV. If ion energy < Ec1, it stops in the material.
933+
/// Es1 (list(f64)): ion surface binding energies. Assumed planar.
934+
/// Z2 (list(f64)): target material species atomic numbers.
935+
/// m2 (list(f64)): target material species masses in amu.
936+
/// Ec2 (list(f64)): target material species cutoff energies in eV. If recoil energy < Ec2, it stops in the material.
937+
/// Es2 (list(f64)): target species surface binding energies. Assumed planar.
938+
/// n2 (list(f64)): target material species atomic number densities in inverse cubic Angstroms.
939+
/// Eb2 (list(f64)): target material species bulk binding energies in eV.
940+
/// Returns:
941+
/// output (NX9 list of f64): each row in the list represents an output particle (implanted,
942+
/// sputtered, or reflected). Each row consists of:
943+
/// [Z, m (amu), E (eV), x, y, z, (angstrom), ux, uy, uz]
944+
/// incident (list(bool)): whether each row of output was an incident ion or originated in the target
945+
/// incident_index (list(usize)): index of incident particle that caused this particle to be emitted
946+
#[pyfunction]
947+
pub fn compound_bca_list_tracked_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>, Vec<usize>) {
948+
let mut total_output = vec![];
949+
let mut incident = vec![];
950+
let mut incident_index = vec![];
951+
let num_species_target = Z2.len();
952+
let num_incident_ions = energies.len();
953+
954+
assert_eq!(ux.len(), num_incident_ions, "Input error: list of x-directions is not the same length as list of incident energies.");
955+
assert_eq!(uy.len(), num_incident_ions, "Input error: list of y-directions is not the same length as list of incident energies.");
956+
assert_eq!(uz.len(), num_incident_ions, "Input error: list of z-directions is not the same length as list of incident energies.");
957+
assert_eq!(Z1.len(), num_incident_ions, "Input error: list of incident atomic numbers is not the same length as list of incident energies.");
958+
assert_eq!(m1.len(), num_incident_ions, "Input error: list of incident atomic masses is not the same length as list of incident energies.");
959+
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.");
960+
assert_eq!(Ec1.len(), num_incident_ions, "Input error: list of incident cutoff energies is not the same length as list of incident energies.");
961+
962+
assert_eq!(m2.len(), num_species_target, "Input error: list of target atomic masses is not the same length as atomic numbers.");
963+
assert_eq!(Ec2.len(), num_species_target, "Input error: list of target cutoff energies is not the same length as atomic numbers.");
964+
assert_eq!(Es2.len(), num_species_target, "Input error: list of target surface binding energies is not the same length as atomic numbers.");
965+
assert_eq!(Eb2.len(), num_species_target, "Input error: list of target bulk binding energies is not the same length as atomic numbers.");
966+
assert_eq!(n2.len(), num_species_target, "Input error: list of target number densities is not the same length as atomic numbers.");
967+
968+
let options = Options::default_options(true);
969+
//options.high_energy_free_flight_paths = true;
970+
971+
let x = -2.*(n2.iter().sum::<f64>()*10E30).powf(-1./3.);
972+
let y = 0.0;
973+
let z = 0.0;
974+
975+
let material_parameters = material::MaterialParameters {
976+
energy_unit: "EV".to_string(),
977+
mass_unit: "AMU".to_string(),
978+
Eb: Eb2,
979+
Es: Es2,
980+
Ec: Ec2,
981+
Ed: vec![0.0; num_species_target],
982+
Z: Z2,
983+
m: m2,
984+
interaction_index: vec![0; num_species_target],
985+
surface_binding_model: SurfaceBindingModel::INDIVIDUAL,
986+
bulk_binding_model: BulkBindingModel::INDIVIDUAL,
987+
};
988+
989+
let geometry_input = geometry::Mesh0DInput {
990+
length_unit: "ANGSTROM".to_string(),
991+
densities: n2,
992+
electronic_stopping_correction_factor: 1.0
993+
};
994+
995+
let m = material::Material::<Mesh0D>::new(&material_parameters, &geometry_input);
996+
997+
let mut finished_particles: Vec<particle::Particle> = Vec::new();
998+
999+
let incident_particles: Vec<particle::Particle> = izip!(energies, ux, uy, uz, Z1, Ec1, Es1, m1)
1000+
.enumerate()
1001+
.map(|(index, (energy, ux_, uy_, uz_, Z1_, Ec1_, Es1_, m1_))| {
1002+
let mut p = particle::Particle::default_incident(
1003+
m1_,
1004+
Z1_,
1005+
energy,
1006+
Ec1_,
1007+
Es1_,
1008+
x,
1009+
ux_,
1010+
uy_,
1011+
uz_
1012+
);
1013+
p.tag = index as i32;
1014+
p
1015+
}).collect();
1016+
1017+
finished_particles.par_extend(
1018+
incident_particles.into_par_iter()
1019+
.map(|particle| bca::single_ion_bca(particle, &m, &options))
1020+
.flatten()
1021+
);
1022+
1023+
for particle in finished_particles {
1024+
if (particle.left) | (particle.incident) {
1025+
incident.push(particle.incident);
1026+
incident_index.push(particle.tag as usize);
1027+
let mut energy_out;
1028+
if particle.stopped {
1029+
energy_out = 0.;
1030+
} else {
1031+
energy_out = particle.E/EV;
1032+
}
1033+
total_output.push(
1034+
[
1035+
particle.Z,
1036+
particle.m/AMU,
1037+
energy_out,
1038+
particle.pos.x/ANGSTROM,
1039+
particle.pos.y/ANGSTROM,
1040+
particle.pos.z/ANGSTROM,
1041+
particle.dir.x,
1042+
particle.dir.y,
1043+
particle.dir.z,
1044+
]
1045+
)
1046+
}
1047+
}
1048+
1049+
(total_output, incident, incident_index)
1050+
}
1051+
9211052
#[cfg(feature = "python")]
9221053
///reflect_single_ion_py(ion, target, vx, vy, vz)
9231054
///Performs a single BCA ion trajectory in target material with specified incident velocity.

0 commit comments

Comments
 (0)