From 7ddd574bce82cbd313ac6b7fd7ed7a5b5db1d64c Mon Sep 17 00:00:00 2001 From: Jon Drobny Date: Mon, 15 Mar 2021 11:59:40 -0500 Subject: [PATCH 01/33] Working parry3d::Ball example - runs with same input file as RustBCA's Sphere. --- Cargo.toml | 2 + src/enums.rs | 4 ++ src/main.rs | 13 +++++ src/parry.rs | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 154 insertions(+) create mode 100644 src/parry.rs diff --git a/Cargo.toml b/Cargo.toml index 258a56e..38df1c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ netlib-src = {version = "0.8", optional = true} intel-mkl-src = {version = "0.6.0", optional = true} rcpr = { git = "https://github.com/drobnyjt/rcpr", optional = true} ndarray = {version = "0.14.0", features = ["serde"], optional = true} +parry3d = {version = "0.2.0", optional = true} [dev-dependencies] float-cmp = "0.8.0" @@ -39,3 +40,4 @@ cpr_rootfinder_netlib = ["rcpr", "netlib-src"] cpr_rootfinder_intel_mkl = ["rcpr", "intel-mkl-src"] distributions = ["ndarray"] no_list_output = [] +parry = ["parry3d"] diff --git a/src/enums.rs b/src/enums.rs index 012b5a2..cc457ff 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -5,6 +5,8 @@ pub enum MaterialType { MESH1D(material::Material), MESH2D(material::Material), SPHERE(material::Material), + #[cfg(feature = "parry")] + BALL(material::Material), } #[derive(Deserialize)] @@ -13,6 +15,8 @@ pub enum GeometryType { MESH1D, MESH2D, SPHERE, + #[cfg(feature = "parry")] + BALL, } /// Mode of electronic stopping to use. diff --git a/src/main.rs b/src/main.rs index 9e08cd2..6c6d25c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -60,6 +60,9 @@ pub mod consts; pub mod structs; pub mod sphere; +#[cfg(feature = "parry")] +pub mod parry; + pub use crate::enums::*; pub use crate::consts::*; pub use crate::structs::*; @@ -68,6 +71,9 @@ pub use crate::output::{OutputUnits}; pub use crate::geometry::{Geometry, GeometryElement, Mesh0D, Mesh1D, Mesh2D}; pub use crate::sphere::{Sphere, SphereInput, InputSphere}; +#[cfg(feature = "parry")] +pub use crate::parry::{ParryBall, ParryBallInput, InputParryBall}; + fn physics_loop(particle_input_array: Vec, material: material::Material, options: Options, output_units: OutputUnits) { println!("Processing {} ions...", particle_input_array.len()); @@ -158,6 +164,8 @@ fn main() { "1D" => GeometryType::MESH1D, "2D" => GeometryType::MESH2D, "SPHERE" => GeometryType::SPHERE, + #[cfg(feature = "parry")] + "BALL" => GeometryType::BALL, _ => panic!("Unimplemented geometry {}.", args[1].clone()) }), _ => panic!("Too many command line arguments. RustBCA accepts 0 (use 'input.toml') 1 () or 2 ( )"), @@ -179,6 +187,11 @@ fn main() { GeometryType::SPHERE => { let (particle_input_array, material, options, output_units) = input::input::(input_file); physics_loop::(particle_input_array, material, options, output_units); + }, + #[cfg(feature = "parry")] + GeometryType::BALL => { + let (particle_input_array, material, options, output_units) = input::input::(input_file); + physics_loop::(particle_input_array, material, options, output_units); } } } diff --git a/src/parry.rs b/src/parry.rs new file mode 100644 index 0000000..9be1b49 --- /dev/null +++ b/src/parry.rs @@ -0,0 +1,135 @@ +use super::*; +use parry3d::shape::{Ball}; +use parry3d::query::PointQuery; +use parry3d::math::{Isometry, Point}; + +#[derive(Deserialize, Clone)] +pub struct InputParryBall { + pub options: Options, + pub material_parameters: material::MaterialParameters, + pub particle_parameters: particle::ParticleParameters, + pub geometry_input: ParryBallInput, +} + +impl GeometryInput for InputParryBall { + type GeometryInput = ParryBallInput; +} + +impl InputFile for InputParryBall { + + fn new(string: &str) -> InputParryBall { + toml::from_str(string).expect("Could not parse TOML file.") + } + + fn get_options(&self) -> &Options{ + &self.options + } + fn get_material_parameters(&self) -> &material::MaterialParameters{ + &self.material_parameters + } + fn get_particle_parameters(&self) ->& particle::ParticleParameters{ + &self.particle_parameters + } + fn get_geometry_input(&self) -> &Self::GeometryInput{ + &self.geometry_input + } +} + +#[derive(Deserialize, Clone)] +pub struct ParryBallInput { + pub length_unit: String, + pub radius: f64, + pub densities: Vec, + pub electronic_stopping_correction_factor: f64, +} + +#[derive(Clone)] +pub struct ParryBall { + pub densities: Vec, + pub concentrations: Vec, + pub radius: f64, + pub electronic_stopping_correction_factor: f64, + pub energy_barrier_thickness: f64, + pub ball: Ball, +} + +impl GeometryInput for ParryBall { + type GeometryInput = ParryBallInput; +} + +impl Geometry for ParryBall { + + type InputFileFormat = InputParryBall; + + fn new(input: &::GeometryInput) -> ParryBall { + + let length_unit: f64 = match input.length_unit.as_str() { + "MICRON" => MICRON, + "CM" => CM, + "ANGSTROM" => ANGSTROM, + "NM" => NM, + "M" => 1., + _ => input.length_unit.parse() + .expect(format!( + "Input errror: could nor parse length unit {}. Use a valid float or one of ANGSTROM, NM, MICRON, CM, MM, M", + &input.length_unit.as_str() + ).as_str()), + }; + + let electronic_stopping_correction_factor = input.electronic_stopping_correction_factor; + let densities: Vec = input.densities.iter().map(|element| element/(length_unit).powi(3)).collect(); + let total_density: f64 = densities.iter().sum(); + let energy_barrier_thickness = total_density.powf(-1./3.)/SQRTPI*2.; + let concentrations: Vec = densities.iter().map(|&density| density/total_density).collect::>(); + let radius = input.radius*length_unit; + + ParryBall { + densities, + concentrations, + radius, + electronic_stopping_correction_factor, + energy_barrier_thickness, + ball: Ball::new(radius as f32) + } + } + + fn get_densities(&self, x: f64, y: f64, z: f64) -> &Vec { + &self.densities + } + fn get_ck(&self, x: f64, y: f64, z: f64) -> f64 { + self.electronic_stopping_correction_factor + } + fn get_total_density(&self, x: f64, y: f64, z: f64) -> f64{ + self.densities.iter().sum() + } + fn get_concentrations(&self, x: f64, y: f64, z: f64) -> &Vec { + &self.concentrations + } + fn get_densities_nearest_to(&self, x: f64, y: f64, z: f64) -> &Vec { + &self.densities + } + fn get_ck_nearest_to(&self, x: f64, y: f64, z: f64) -> f64 { + self.electronic_stopping_correction_factor + } + fn inside(&self, x: f64, y: f64, z: f64) -> bool { + let p = Point::new(x as f32, y as f32, z as f32); + self.ball.contains_local_point(&p) + } + + fn inside_simulation_boundary(&self, x: f64, y: f64, z: f64) -> bool { + let p = Point::new(x as f32, y as f32, z as f32); + (self.ball.distance_to_local_point(&p, true) as f64) < 10.*self.energy_barrier_thickness + } + + fn inside_energy_barrier(&self, x: f64, y: f64, z: f64) -> bool { + let p = Point::new(x as f32, y as f32, z as f32); + (self.ball.distance_to_local_point(&p, true) as f64) < self.energy_barrier_thickness + } + + fn closest_point(&self, x: f64, y: f64, z: f64) -> (f64, f64, f64) { + let p = Point::new(x as f32, y as f32, z as f32); + let point_projection = self.ball.project_point(&Isometry::identity(), &p, false); + let (x_, y_, z_) = (point_projection.point.x, point_projection.point.y, point_projection.point.z); + (x_ as f64, y_ as f64, z_ as f64) + } +} From d64e19f488b74aa322172e234c51cef2ebf10ef0 Mon Sep 17 00:00:00 2001 From: Jon Drobny Date: Mon, 15 Mar 2021 17:47:16 -0500 Subject: [PATCH 02/33] Working TriMesh input. --- Cargo.toml | 4 +- examples/boron_nitride_trimesh.toml | 61 +++++++++ src/enums.rs | 4 + src/main.rs | 9 +- src/parry.rs | 189 ++++++++++++++++++++++++++-- 5 files changed, 256 insertions(+), 11 deletions(-) create mode 100644 examples/boron_nitride_trimesh.toml diff --git a/Cargo.toml b/Cargo.toml index 38df1c6..7dcf149 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ netlib-src = {version = "0.8", optional = true} intel-mkl-src = {version = "0.6.0", optional = true} rcpr = { git = "https://github.com/drobnyjt/rcpr", optional = true} ndarray = {version = "0.14.0", features = ["serde"], optional = true} -parry3d = {version = "0.2.0", optional = true} +parry3d-f64 = {version = "0.2.0", optional = true} [dev-dependencies] float-cmp = "0.8.0" @@ -40,4 +40,4 @@ cpr_rootfinder_netlib = ["rcpr", "netlib-src"] cpr_rootfinder_intel_mkl = ["rcpr", "intel-mkl-src"] distributions = ["ndarray"] no_list_output = [] -parry = ["parry3d"] +parry = ["parry3d-f64"] diff --git a/examples/boron_nitride_trimesh.toml b/examples/boron_nitride_trimesh.toml new file mode 100644 index 0000000..352da04 --- /dev/null +++ b/examples/boron_nitride_trimesh.toml @@ -0,0 +1,61 @@ +[options] +name = "boron_nitride_" +track_trajectories = true +track_recoils = true +track_recoil_trajectories = true +write_buffer_size = 8000 +weak_collision_order = 0 +suppress_deep_recoils = false +high_energy_free_flight_paths = false +num_threads = 1 +num_chunks = 1 +use_hdf5 = false +electronic_stopping_mode = "LOW_ENERGY_NONLOCAL" +mean_free_path_model = "LIQUID" +interaction_potential = [["KR_C"]] +scattering_integral = [["MENDENHALL_WELLER"]] +root_finder = [[{"NEWTON"={max_iterations=100, tolerance=1E-3}}]] +track_displacements = false +track_energy_losses = false + +[material_parameters] +energy_unit = "EV" +mass_unit = "AMU" +Eb = [ 0.0, 0.0,] +Es = [ 5.76, 0.0,] +Ec = [ 1.0, 1.0,] +Z = [ 5, 7,] +m = [ 10.811, 14,] +interaction_index = [0, 0] +surface_binding_model = {"PLANAR"={calculation="TARGET"}} +bulk_binding_model = "AVERAGE" + +[particle_parameters] +length_unit = "1E-8" +energy_unit = "EV" +mass_unit = "AMU" +N = [ 5 ] +m = [ 1.008 ] +Z = [ 1 ] +E = [ 500.0 ] +Ec = [ 1.0 ] +Es = [ 10.0 ] +pos = [ [ -0.5, 0.0, 0.0,] ] +dir = [ [ 0.9999999999984769, 1.7453292519934434e-6, 0.0,] ] +interaction_index = [ 0 ] + +[geometry_input] +length_unit = "1E-8" +electronic_stopping_correction_factor = 1.0 +densities = [65000, 65000] +vertices = [ + [-0.5, -0.5, 0.5], + [0.5, -0.5, 0.5], + [-0.5, 0.5, 0.5], + [0.5, 0.5, 0.5], + [-0.5, 0.5, -0.5], + [0.5, 0.5, -0.5], + [-0.5, -0.5, -0.5], + [0.5, -0.5, -0.5], +] +indices =[[0, 1, 3], [0, 3, 2], [2, 3, 5], [2, 5, 4], [4, 5, 7], [4, 7, 6], [6, 7, 1], [6, 1, 0], [1, 7, 5], [1, 5, 3], [6, 0, 2], [6, 2, 4]] diff --git a/src/enums.rs b/src/enums.rs index cc457ff..d02ffbd 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -7,6 +7,8 @@ pub enum MaterialType { SPHERE(material::Material), #[cfg(feature = "parry")] BALL(material::Material), + #[cfg(feature = "parry")] + TRIMESH(material::Material) } #[derive(Deserialize)] @@ -17,6 +19,8 @@ pub enum GeometryType { SPHERE, #[cfg(feature = "parry")] BALL, + #[cfg(feature = "parry")] + TRIMESH, } /// Mode of electronic stopping to use. diff --git a/src/main.rs b/src/main.rs index 6c6d25c..c765da2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -72,7 +72,7 @@ pub use crate::geometry::{Geometry, GeometryElement, Mesh0D, Mesh1D, Mesh2D}; pub use crate::sphere::{Sphere, SphereInput, InputSphere}; #[cfg(feature = "parry")] -pub use crate::parry::{ParryBall, ParryBallInput, InputParryBall}; +pub use crate::parry::{ParryBall, ParryBallInput, InputParryBall, ParryTriMesh, ParryTriMeshInput, InputParryTriMesh}; fn physics_loop(particle_input_array: Vec, material: material::Material, options: Options, output_units: OutputUnits) { @@ -166,6 +166,8 @@ fn main() { "SPHERE" => GeometryType::SPHERE, #[cfg(feature = "parry")] "BALL" => GeometryType::BALL, + #[cfg(feature = "parry")] + "TRIMESH" => GeometryType::TRIMESH, _ => panic!("Unimplemented geometry {}.", args[1].clone()) }), _ => panic!("Too many command line arguments. RustBCA accepts 0 (use 'input.toml') 1 () or 2 ( )"), @@ -193,5 +195,10 @@ fn main() { let (particle_input_array, material, options, output_units) = input::input::(input_file); physics_loop::(particle_input_array, material, options, output_units); } + #[cfg(feature = "parry")] + GeometryType::TRIMESH => { + let (particle_input_array, material, options, output_units) = input::input::(input_file); + physics_loop::(particle_input_array, material, options, output_units); + } } } diff --git a/src/parry.rs b/src/parry.rs index 9be1b49..ed649ed 100644 --- a/src/parry.rs +++ b/src/parry.rs @@ -1,7 +1,7 @@ use super::*; -use parry3d::shape::{Ball}; -use parry3d::query::PointQuery; -use parry3d::math::{Isometry, Point}; +use parry3d_f64::shape::{Ball, TriMesh}; +use parry3d_f64::query::{PointQuery, Ray, RayCast}; +use parry3d_f64::math::{Isometry, Point, Vector}; #[derive(Deserialize, Clone)] pub struct InputParryBall { @@ -89,7 +89,7 @@ impl Geometry for ParryBall { radius, electronic_stopping_correction_factor, energy_barrier_thickness, - ball: Ball::new(radius as f32) + ball: Ball::new(radius) } } @@ -112,24 +112,197 @@ impl Geometry for ParryBall { self.electronic_stopping_correction_factor } fn inside(&self, x: f64, y: f64, z: f64) -> bool { - let p = Point::new(x as f32, y as f32, z as f32); + let p = Point::new(x , y , z ); self.ball.contains_local_point(&p) } fn inside_simulation_boundary(&self, x: f64, y: f64, z: f64) -> bool { - let p = Point::new(x as f32, y as f32, z as f32); + let p = Point::new(x , y , z ); (self.ball.distance_to_local_point(&p, true) as f64) < 10.*self.energy_barrier_thickness } fn inside_energy_barrier(&self, x: f64, y: f64, z: f64) -> bool { - let p = Point::new(x as f32, y as f32, z as f32); + let p = Point::new(x , y , z ); (self.ball.distance_to_local_point(&p, true) as f64) < self.energy_barrier_thickness } fn closest_point(&self, x: f64, y: f64, z: f64) -> (f64, f64, f64) { - let p = Point::new(x as f32, y as f32, z as f32); + let p = Point::new(x , y , z ); let point_projection = self.ball.project_point(&Isometry::identity(), &p, false); let (x_, y_, z_) = (point_projection.point.x, point_projection.point.y, point_projection.point.z); (x_ as f64, y_ as f64, z_ as f64) } } + + +#[derive(Deserialize, Clone)] +pub struct InputParryTriMesh { + pub options: Options, + pub material_parameters: material::MaterialParameters, + pub particle_parameters: particle::ParticleParameters, + pub geometry_input: ParryTriMeshInput, +} + +impl GeometryInput for InputParryTriMesh { + type GeometryInput = ParryTriMeshInput; +} + +impl InputFile for InputParryTriMesh { + + fn new(string: &str) -> InputParryTriMesh { + toml::from_str(string).expect("Could not parse TOML file.") + } + + fn get_options(&self) -> &Options{ + &self.options + } + fn get_material_parameters(&self) -> &material::MaterialParameters{ + &self.material_parameters + } + fn get_particle_parameters(&self) ->& particle::ParticleParameters{ + &self.particle_parameters + } + fn get_geometry_input(&self) -> &Self::GeometryInput{ + &self.geometry_input + } +} + +#[derive(Deserialize, Clone)] +pub struct ParryTriMeshInput { + pub length_unit: String, + pub densities: Vec, + pub electronic_stopping_correction_factor: f64, + pub vertices: Vec<[f64; 3]>, + pub indices: Vec<[u32; 3]>, +} + +#[derive(Clone)] +pub struct ParryTriMesh { + pub densities: Vec, + pub concentrations: Vec, + pub electronic_stopping_correction_factor: f64, + pub energy_barrier_thickness: f64, + pub trimesh: TriMesh, +} + +impl GeometryInput for ParryTriMesh { + type GeometryInput = ParryTriMeshInput; +} + +impl Geometry for ParryTriMesh { + + type InputFileFormat = InputParryTriMesh; + + fn new(input: &::GeometryInput) -> ParryTriMesh { + + let length_unit: f64 = match input.length_unit.as_str() { + "MICRON" => MICRON, + "CM" => CM, + "ANGSTROM" => ANGSTROM, + "NM" => NM, + "M" => 1., + _ => input.length_unit.parse() + .expect(format!( + "Input errror: could nor parse length unit {}. Use a valid float or one of ANGSTROM, NM, MICRON, CM, MM, M", + &input.length_unit.as_str() + ).as_str()), + }; + + let electronic_stopping_correction_factor = input.electronic_stopping_correction_factor; + let densities: Vec = input.densities.iter().map(|element| element/(length_unit).powi(3)).collect(); + let total_density: f64 = densities.iter().sum(); + let energy_barrier_thickness = total_density.powf(-1./3.)/SQRTPI*2.; + let concentrations: Vec = densities.iter().map(|&density| density/total_density).collect::>(); + let points = input.vertices.iter().map(|p| Point::new(p[0]*length_unit , p[1]*length_unit , p[2]*length_unit )).collect(); + let trimesh = TriMesh::new(points, input.indices.clone()); + + ParryTriMesh { + densities, + concentrations, + electronic_stopping_correction_factor, + energy_barrier_thickness, + trimesh, + } + } + + fn get_densities(&self, x: f64, y: f64, z: f64) -> &Vec { + &self.densities + } + fn get_ck(&self, x: f64, y: f64, z: f64) -> f64 { + self.electronic_stopping_correction_factor + } + fn get_total_density(&self, x: f64, y: f64, z: f64) -> f64{ + self.densities.iter().sum() + } + fn get_concentrations(&self, x: f64, y: f64, z: f64) -> &Vec { + &self.concentrations + } + fn get_densities_nearest_to(&self, x: f64, y: f64, z: f64) -> &Vec { + &self.densities + } + fn get_ck_nearest_to(&self, x: f64, y: f64, z: f64) -> f64 { + self.electronic_stopping_correction_factor + } + fn inside(&self, x: f64, y: f64, z: f64) -> bool { + inside_trimesh(&self.trimesh, x, y, z) + } + + fn inside_simulation_boundary(&self, x: f64, y: f64, z: f64) -> bool { + if self.inside(x, y, z) { + true + } else { + let p = Point::new(x , y , z ); + let distance = self.trimesh.distance_to_local_point(&p, true); + //dbg!(distance); + distance < 10.*self.energy_barrier_thickness + } + } + + fn inside_energy_barrier(&self, x: f64, y: f64, z: f64) -> bool { + if self.inside(x, y, z) { + true + } else { + let p = Point::new(x , y , z ); + let distance = self.trimesh.distance_to_local_point(&p, true); + //dbg!(distance/ANGSTROM); + //dbg!(self.energy_barrier_thickness/ANGSTROM); + //dbg!(distance < self.energy_barrier_thickness); + distance < self.energy_barrier_thickness + } + } + + fn closest_point(&self, x: f64, y: f64, z: f64) -> (f64, f64, f64) { + let p = Point::new(x , y , z ); + let point_projection = self.trimesh.project_local_point(&p, false); + let (x_, y_, z_) = (point_projection.point.x, point_projection.point.y, point_projection.point.z); + (x_ as f64, y_ as f64, z_ as f64) + } +} + +fn inside_trimesh(trimesh: &TriMesh, x: f64, y: f64, z: f64) -> bool { + let p = Point::new(x , y , z ); + + if !trimesh.aabb(&Isometry::identity()) + .contains_local_point(&p) { + return false; + } + + let dir = Vector::new(1.0, 0.0, 0.0); + + let result = trimesh.cast_ray_and_get_normal( + &Isometry::identity(), + &Ray::new(p, dir), + 1.0, + false + ); + + //dbg!(result); + + match result { + Some(intersection) => { + //dbg!(intersection.feature); + trimesh.is_backface(intersection.feature) + }, + None => false, + } +} From 79b5882063d974a88fdf920842cab931d0f44154 Mon Sep 17 00:00:00 2001 From: Jon Drobny Date: Mon, 15 Mar 2021 17:51:43 -0500 Subject: [PATCH 03/33] Small fix to boron_nitride_trimesh.toml; updates to plotting routines. --- examples/boron_nitride_trimesh.toml | 15 +++++++- scripts/rustbca.py | 55 ++++++++++++++++++++++++----- 2 files changed, 61 insertions(+), 9 deletions(-) diff --git a/examples/boron_nitride_trimesh.toml b/examples/boron_nitride_trimesh.toml index 352da04..6241642 100644 --- a/examples/boron_nitride_trimesh.toml +++ b/examples/boron_nitride_trimesh.toml @@ -58,4 +58,17 @@ vertices = [ [-0.5, -0.5, -0.5], [0.5, -0.5, -0.5], ] -indices =[[0, 1, 3], [0, 3, 2], [2, 3, 5], [2, 5, 4], [4, 5, 7], [4, 7, 6], [6, 7, 1], [6, 1, 0], [1, 7, 5], [1, 5, 3], [6, 0, 2], [6, 2, 4]] +indices = [ + [0, 1, 3], + [0, 3, 2], + [2, 3, 5], + [2, 5, 4], + [4, 5, 7], + [4, 7, 6], + [6, 7, 1], + [6, 1, 0], + [1, 7, 5], + [1, 5, 3], + [6, 0, 2], + [6, 2, 4] + ] diff --git a/scripts/rustbca.py b/scripts/rustbca.py index ac6afc1..ab22fc2 100644 --- a/scripts/rustbca.py +++ b/scripts/rustbca.py @@ -136,7 +136,7 @@ def do_trajectory_plot(name, thickness=None, depth=None, boundary=None, plot_fin plt.savefig(name+'trajectories_.png') plt.close() -def do_trajectory_plot_3d(name, thickness=None, depth=None, boundary=None, plot_final_positions=True, plot_origins=True, radius=None): +def do_trajectory_plot_3d(name, thickness=None, depth=None, boundary=None, plot_final_positions=True, plot_origins=True, radius=None, cube_length=None): ''' Plots trajectories of ions and recoils from [name]trajectories.output. Optionally marks final positions/origins and draws material geometry. @@ -169,16 +169,17 @@ def do_trajectory_plot_3d(name, thickness=None, depth=None, boundary=None, plot_ index = 0 x_max = 0 - min_length = 3 + min_length = 1 + scale_factor = 100.0 if np.size(trajectories) > 0: for trajectory_length in trajectory_data: M = trajectories[index, 0] Z = trajectories[index, 1] E = trajectories[index:(trajectory_length + index), 2] - x = trajectories[index:(trajectory_length + index), 3] - y = trajectories[index:(trajectory_length + index), 4] - z = trajectories[index:(trajectory_length + index), 5] + x = trajectories[index:(trajectory_length + index), 3]*scale_factor + y = trajectories[index:(trajectory_length + index), 4]*scale_factor + z = trajectories[index:(trajectory_length + index), 5]*scale_factor if np.max(x) > x_max: x_max = np.max(x) @@ -193,17 +194,17 @@ def do_trajectory_plot_3d(name, thickness=None, depth=None, boundary=None, plot_ if np.size(sputtered) > 0: sputtered_colors = [colormap.to_rgba(Z)[:3] for Z in sputtered[:,1]] for x, y, z, c in zip(sputtered[:,3], sputtered[:,4], sputtered[:,5], sputtered_colors): - points3d(x, y, z, color=c, scale_factor=2) + points3d(x*scale_factor, y*scale_factor, z*scale_factor, color=c, scale_factor=2) if np.size(reflected) > 0: reflected_colors = [colormap.to_rgba(Z)[:3] for Z in reflected[:,1]] for x, y, z, c in zip(reflected[:,3], reflected[:,4], reflected[:,5], reflected_colors): - points3d(x, y, z, color=c, scale_factor=2) + points3d(x*scale_factor, y*scale_factor, z*scale_factor, color=c, scale_factor=2) if np.size(deposited) > 0: deposited_colors = [colormap.to_rgba(Z)[:3] for Z in deposited[:,1]] for x, y, z, c in zip(deposited[:,2], deposited[:,3], deposited[:,4], deposited_colors): - points3d(x, y, z, color=c, scale_factor=2) + points3d(x*scale_factor, y*scale_factor, z*scale_factor, color=c, scale_factor=2) if boundary: x = [x_ for (x_, y_) in boundary] @@ -221,6 +222,44 @@ def do_trajectory_plot_3d(name, thickness=None, depth=None, boundary=None, plot_ z = np.cos(theta) mesh(radius*x, radius*y, radius*z, color=(0.1,0.7,0.3), opacity=0.2) + if cube_length: + faces = [] + + xmin = -cube_length/2.*scale_factor + xmax = cube_length/2.*scale_factor + ymin = -cube_length/2.*scale_factor + ymax = cube_length/2.*scale_factor + zmin = -cube_length/2.*scale_factor + zmax = cube_length/2.*scale_factor + + x,y = np.mgrid[xmin:xmax:3j,ymin:ymax:3j] + z = np.ones(y.shape)*zmin + faces.append((x,y,z)) + + x,y = np.mgrid[xmin:xmax:3j,ymin:ymax:3j] + z = np.ones(y.shape)*zmax + faces.append((x,y,z)) + + x,z = np.mgrid[xmin:xmax:3j,zmin:zmax:3j] + y = np.ones(z.shape)*ymin + faces.append((x,y,z)) + + x,z = np.mgrid[xmin:xmax:3j,zmin:zmax:3j] + y = np.ones(z.shape)*ymax + faces.append((x,y,z)) + + y,z = np.mgrid[ymin:ymax:3j,zmin:zmax:3j] + x = np.ones(z.shape)*xmin + faces.append((x,y,z)) + + y,z = np.mgrid[ymin:ymax:3j,zmin:zmax:3j] + x = np.ones(z.shape)*xmax + faces.append((x,y,z)) + + for grid in faces: + x,y,z = grid + mesh(x, y, z, opacity=0.4, color=(0.1,0.7,0.3)) + def generate_rustbca_input(Zb, Mb, n, Eca, Ecb, Esa, Esb, Eb, Ma, Za, E0, N, N_, theta, thickness, depth, track_trajectories=True, track_recoils=True, From 84929706ea2f2b05dd0a64ff388b731ba008bab0 Mon Sep 17 00:00:00 2001 From: Jon Drobny Date: Mon, 15 Mar 2021 20:52:30 -0500 Subject: [PATCH 04/33] Updates to boundary calculation - now uses bounding box. --- src/parry.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/parry.rs b/src/parry.rs index ed649ed..2e7f03e 100644 --- a/src/parry.rs +++ b/src/parry.rs @@ -2,6 +2,7 @@ use super::*; use parry3d_f64::shape::{Ball, TriMesh}; use parry3d_f64::query::{PointQuery, Ray, RayCast}; use parry3d_f64::math::{Isometry, Point, Vector}; +use parry3d_f64::bounding_volume::AABB; #[derive(Deserialize, Clone)] pub struct InputParryBall { @@ -183,6 +184,7 @@ pub struct ParryTriMesh { pub electronic_stopping_correction_factor: f64, pub energy_barrier_thickness: f64, pub trimesh: TriMesh, + pub boundary: AABB, } impl GeometryInput for ParryTriMesh { @@ -215,6 +217,7 @@ impl Geometry for ParryTriMesh { let concentrations: Vec = densities.iter().map(|&density| density/total_density).collect::>(); let points = input.vertices.iter().map(|p| Point::new(p[0]*length_unit , p[1]*length_unit , p[2]*length_unit )).collect(); let trimesh = TriMesh::new(points, input.indices.clone()); + let boundary = trimesh.aabb(&Isometry::identity()); ParryTriMesh { densities, @@ -222,6 +225,7 @@ impl Geometry for ParryTriMesh { electronic_stopping_correction_factor, energy_barrier_thickness, trimesh, + boundary, } } @@ -252,7 +256,7 @@ impl Geometry for ParryTriMesh { true } else { let p = Point::new(x , y , z ); - let distance = self.trimesh.distance_to_local_point(&p, true); + let distance = self.boundary.distance_to_local_point(&p, true); //dbg!(distance); distance < 10.*self.energy_barrier_thickness } From cf70397d135bc5a64279ca38ebb0a1baaba47deb Mon Sep 17 00:00:00 2001 From: Jon Drobny Date: Mon, 15 Mar 2021 23:53:56 -0500 Subject: [PATCH 05/33] Final changes to core geometry with parry3d. --- examples/boron_nitride_trimesh.toml | 74 ------------------- examples/tungsten_tiles_trimesh.toml | 105 +++++++++++++++++++++++++++ examples/tungsten_twist_trimesh.toml | 52 +++++++++++++ scripts/rustbca.py | 17 ++++- src/bca.rs | 7 ++ src/parry.rs | 2 +- 6 files changed, 178 insertions(+), 79 deletions(-) delete mode 100644 examples/boron_nitride_trimesh.toml create mode 100644 examples/tungsten_tiles_trimesh.toml create mode 100644 examples/tungsten_twist_trimesh.toml diff --git a/examples/boron_nitride_trimesh.toml b/examples/boron_nitride_trimesh.toml deleted file mode 100644 index 6241642..0000000 --- a/examples/boron_nitride_trimesh.toml +++ /dev/null @@ -1,74 +0,0 @@ -[options] -name = "boron_nitride_" -track_trajectories = true -track_recoils = true -track_recoil_trajectories = true -write_buffer_size = 8000 -weak_collision_order = 0 -suppress_deep_recoils = false -high_energy_free_flight_paths = false -num_threads = 1 -num_chunks = 1 -use_hdf5 = false -electronic_stopping_mode = "LOW_ENERGY_NONLOCAL" -mean_free_path_model = "LIQUID" -interaction_potential = [["KR_C"]] -scattering_integral = [["MENDENHALL_WELLER"]] -root_finder = [[{"NEWTON"={max_iterations=100, tolerance=1E-3}}]] -track_displacements = false -track_energy_losses = false - -[material_parameters] -energy_unit = "EV" -mass_unit = "AMU" -Eb = [ 0.0, 0.0,] -Es = [ 5.76, 0.0,] -Ec = [ 1.0, 1.0,] -Z = [ 5, 7,] -m = [ 10.811, 14,] -interaction_index = [0, 0] -surface_binding_model = {"PLANAR"={calculation="TARGET"}} -bulk_binding_model = "AVERAGE" - -[particle_parameters] -length_unit = "1E-8" -energy_unit = "EV" -mass_unit = "AMU" -N = [ 5 ] -m = [ 1.008 ] -Z = [ 1 ] -E = [ 500.0 ] -Ec = [ 1.0 ] -Es = [ 10.0 ] -pos = [ [ -0.5, 0.0, 0.0,] ] -dir = [ [ 0.9999999999984769, 1.7453292519934434e-6, 0.0,] ] -interaction_index = [ 0 ] - -[geometry_input] -length_unit = "1E-8" -electronic_stopping_correction_factor = 1.0 -densities = [65000, 65000] -vertices = [ - [-0.5, -0.5, 0.5], - [0.5, -0.5, 0.5], - [-0.5, 0.5, 0.5], - [0.5, 0.5, 0.5], - [-0.5, 0.5, -0.5], - [0.5, 0.5, -0.5], - [-0.5, -0.5, -0.5], - [0.5, -0.5, -0.5], -] -indices = [ - [0, 1, 3], - [0, 3, 2], - [2, 3, 5], - [2, 5, 4], - [4, 5, 7], - [4, 7, 6], - [6, 7, 1], - [6, 1, 0], - [1, 7, 5], - [1, 5, 3], - [6, 0, 2], - [6, 2, 4] - ] diff --git a/examples/tungsten_tiles_trimesh.toml b/examples/tungsten_tiles_trimesh.toml new file mode 100644 index 0000000..43e5644 --- /dev/null +++ b/examples/tungsten_tiles_trimesh.toml @@ -0,0 +1,105 @@ +[options] +name = "tungsten_tiles_" +track_trajectories = true +track_recoils = true +track_recoil_trajectories = true +write_buffer_size = 8000 +weak_collision_order = 0 +suppress_deep_recoils = false +high_energy_free_flight_paths = false +num_threads = 4 +num_chunks = 5 +use_hdf5 = false +electronic_stopping_mode = "LOW_ENERGY_NONLOCAL" +mean_free_path_model = "LIQUID" +interaction_potential = [["KR_C"]] +scattering_integral = [["MENDENHALL_WELLER"]] +root_finder = [[{"NEWTON"={max_iterations=100, tolerance=1E-3}}]] +track_displacements = false +track_energy_losses = false + +[material_parameters] +energy_unit = "EV" +mass_unit = "AMU" +Eb = [ 3.0,] +Es = [ 8.79,] +Ec = [ 1.0,] +Z = [ 74 ] +m = [ 183.84 ] +interaction_index = [0] +surface_binding_model = {"PLANAR"={calculation="TARGET"}} +bulk_binding_model = "AVERAGE" + +[particle_parameters] +length_unit = "1e-8" +energy_unit = "EV" +mass_unit = "AMU" +N = [ 10 ] +m = [ 4.008 ] +Z = [ 2 ] +E = [ 250.0 ] +Ec = [ 1.0 ] +Es = [ 0.0 ] +pos = [ [ 0.35, 1.0, 0.5,] ] +dir = [ [ 0.98, -0.3, 0.0,] ] +interaction_index = [ 0 ] + +[geometry_input] +length_unit = "1e-8" +electronic_stopping_correction_factor = 1.0 +densities = [60000] +vertices = [ + [0.00000000, 0.00000000, 0.00000000], + [0.00000000, 0.00000000, 0.20000000], + [0.00000000, 0.00000000, 0.40000000], + [0.00000000, 0.00000000, 0.60000000], + [0.00000000, 0.00000000, 0.80000000], + [0.00000000, 0.00000000, 1.00000000], + [0.00000000, 1.00000000, 0.00000000], + [0.00000000, 1.00000000, 0.20000000], + [0.00000000, 1.00000000, 0.40000000], + [0.00000000, 1.00000000, 0.60000000], + [0.00000000, 1.00000000, 0.80000000], + [0.00000000, 1.00000000, 1.00000000], + [0.20000000, 0.55000000, 0.00000000], + [0.20000000, 0.55000000, 1.00000000], + [0.40000000, 0.20000000, 0.00000000], + [0.40000000, 0.20000000, 0.20000000], + [0.40000000, 0.20000000, 0.40000000], + [0.40000000, 0.20000000, 0.60000000], + [0.40000000, 0.20000000, 0.80000000], + [0.40000000, 0.20000000, 1.00000000], + [0.40000000, 1.00000000, 0.00000000], + [0.40000000, 1.00000000, 0.20000000], + [0.40000000, 1.00000000, 0.40000000], + [0.40000000, 1.00000000, 0.60000000], + [0.40000000, 1.00000000, 0.80000000], + [0.40000000, 1.00000000, 1.00000000], + [0.60000000, 0.20000000, 0.00000000], + [0.60000000, 0.20000000, 0.20000000], + [0.60000000, 0.20000000, 0.40000000], + [0.60000000, 0.20000000, 0.60000000], + [0.60000000, 0.20000000, 0.80000000], + [0.60000000, 0.20000000, 1.00000000], + [0.60000000, 1.00000000, 0.00000000], + [0.60000000, 1.00000000, 0.20000000], + [0.60000000, 1.00000000, 0.40000000], + [0.60000000, 1.00000000, 0.60000000], + [0.60000000, 1.00000000, 0.80000000], + [0.60000000, 1.00000000, 1.00000000], + [0.80000000, 0.55000000, 0.00000000], + [0.80000000, 0.55000000, 1.00000000], + [1.00000000, 0.00000000, 0.00000000], + [1.00000000, 0.00000000, 0.20000000], + [1.00000000, 0.00000000, 0.40000000], + [1.00000000, 0.00000000, 0.60000000], + [1.00000000, 0.00000000, 0.80000000], + [1.00000000, 0.00000000, 1.00000000], + [1.00000000, 1.00000000, 0.00000000], + [1.00000000, 1.00000000, 0.20000000], + [1.00000000, 1.00000000, 0.40000000], + [1.00000000, 1.00000000, 0.60000000], + [1.00000000, 1.00000000, 0.80000000], + [1.00000000, 1.00000000, 1.00000000], +] +indices = [[0, 14, 12], [26, 40, 38], [46, 32, 38], [20, 6, 12], [0, 40, 14], [6, 0, 12], [40, 46, 38], [40, 26, 14], [32, 26, 38], [14, 20, 12], [1, 0, 40], [1, 40, 41], [2, 1, 41], [2, 41, 42], [3, 2, 42], [3, 42, 43], [4, 3, 43], [4, 43, 44], [5, 4, 44], [5, 44, 45], [41, 40, 46], [41, 46, 47], [42, 41, 47], [42, 47, 48], [43, 42, 48], [43, 48, 49], [44, 43, 49], [44, 49, 50], [45, 44, 50], [45, 50, 51], [47, 46, 32], [47, 32, 33], [48, 47, 33], [48, 33, 34], [49, 48, 34], [49, 34, 35], [50, 49, 35], [50, 35, 36], [51, 50, 36], [51, 36, 37], [33, 32, 26], [33, 26, 27], [34, 33, 27], [34, 27, 28], [35, 34, 28], [35, 28, 29], [36, 35, 29], [36, 29, 30], [37, 36, 30], [37, 30, 31], [27, 26, 15], [26, 14, 15], [28, 27, 16], [27, 15, 16], [29, 28, 17], [28, 16, 17], [30, 29, 18], [29, 17, 18], [31, 30, 19], [30, 18, 19], [15, 14, 20], [15, 20, 21], [16, 15, 21], [16, 21, 22], [17, 16, 22], [17, 22, 23], [18, 17, 23], [18, 23, 24], [19, 18, 24], [19, 24, 25], [21, 20, 6], [21, 6, 7], [22, 21, 7], [22, 7, 8], [23, 22, 8], [23, 8, 9], [24, 23, 9], [24, 9, 10], [25, 24, 10], [25, 10, 11], [7, 6, 0], [7, 0, 1], [8, 7, 1], [8, 1, 2], [9, 8, 2], [9, 2, 3], [10, 9, 3], [10, 3, 4], [11, 10, 4], [11, 4, 5], [5, 19, 13], [31, 45, 39], [51, 37, 39], [25, 11, 13], [5, 45, 19], [11, 5, 13], [45, 51, 39], [45, 31, 19], [37, 31, 39], [19, 25, 13]] diff --git a/examples/tungsten_twist_trimesh.toml b/examples/tungsten_twist_trimesh.toml new file mode 100644 index 0000000..01051d9 --- /dev/null +++ b/examples/tungsten_twist_trimesh.toml @@ -0,0 +1,52 @@ +[options] +name = "tungsten_twist_" +track_trajectories = true +track_recoils = true +track_recoil_trajectories = true +write_buffer_size = 8000 +weak_collision_order = 0 +suppress_deep_recoils = false +high_energy_free_flight_paths = false +num_threads = 4 +num_chunks = 5 +use_hdf5 = false +electronic_stopping_mode = "LOW_ENERGY_NONLOCAL" +mean_free_path_model = "LIQUID" +interaction_potential = [["KR_C"]] +scattering_integral = [["MENDENHALL_WELLER"]] +root_finder = [[{"NEWTON"={max_iterations=100, tolerance=1E-3}}]] +track_displacements = false +track_energy_losses = false + +[material_parameters] +energy_unit = "EV" +mass_unit = "AMU" +Eb = [ 3.0,] +Es = [ 8.79,] +Ec = [ 1.0,] +Z = [ 74 ] +m = [ 183.84 ] +interaction_index = [0] +surface_binding_model = {"ISOTROPIC"={calculation="TARGET"}} +bulk_binding_model = "AVERAGE" + +[particle_parameters] +length_unit = "5e-8" +energy_unit = "EV" +mass_unit = "AMU" +N = [ 10 ] +m = [ 4.008 ] +Z = [ 2 ] +E = [ 2000.0 ] +Ec = [ 1.0 ] +Es = [ 0.0 ] +pos = [ [ 0.0, 0.0, 1.0,] ] +dir = [ [ 0.0, 0.0001, -0.9999,] ] +interaction_index = [ 0 ] + +[geometry_input] +length_unit = "5e-8" +electronic_stopping_correction_factor = 1.0 +densities = [7.8e6] +vertices = [ [ 4.572626999266305e-13, -0.3725000000004647, 0.0,], [ 0.0, -0.5, 0.0,], [ 0.0, 0.5, 0.0,], [ -4.572522915857746e-13, 0.37250000000046457, 0.0,], [ 0.039999999999889777, -0.3400000000004409, 0.0,], [ 0.040000000000105985, 0.3399999999995761, 0.0,], [ 0.07999999999994536, -0.18000000000021854, 0.0,], [ 0.08000000000004716, 0.1799999999998114, 0.0,], [ -0.08000000000004716, -0.1799999999998114, 0.0,], [ -0.07999999999994536, 0.18000000000021854, 0.0,], [ -0.040000000000105985, -0.3399999999995761, 0.0,], [ -0.039999999999889777, 0.3400000000004409, 0.0,], [ 0.3460761710126883, 0.2383022164555415, 0.5302073804909463,], [ 0.1282022509828264, 0.04280900393084118, 0.0,], [ -0.43299324531432326, -0.2499887675338708, 0.500000001309108,], [ 0.3830425941522091, 0.3213419137658031, 0.6666744211770179,], [ 0.03319300091782479, -0.261138630970263, 0.34397232458352234,], [ 0.1905075625186843, 0.2752945886840013, 0.8019241769361253,], [ 0.40559107737231626, 0.11085127653671033, 0.3002129631402097,], [ 0.29879556015359177, 0.295826539010757, 0.6997870384432034,], [ 0.11035141320596037, 0.23272151617106032, 0.8429745430544462,], [ -0.14439163046688128, 0.39236667705674766, 0.28964912334810244,], [ -0.10547450024114946, 0.16880936970747587, 0.1416705253853082,], [ 0.20163077877213748, 0.0029129171295438736, 0.0,], [ -0.11283194123344073, -0.16008067191774167, 0.5100226041806184,], [ -0.22937079846756286, -0.3750829832922387, 0.9429751416480727,], [ -0.3213106369533618, -0.3827575233600255, 0.8333496137776426,], [ -0.25038069608229085, -0.08273282305366661, 0.5271339135345402,], [ -0.02071796769739012, -0.19588457268134912, 1.0,], [ 0.3535130717427297, 0.35357582625438827, 0.7499473440345463,], [ 0.35373066958927196, -0.2273205080756338, 1.0,], [ -0.2614910947890534, -0.006880508491315662, 0.25150521267141857,], [ -0.19516660498354385, 0.181961524226949, 1.0,], [ 0.3709159571262127, 0.22043239723385152, 0.5501605407292253,], [ 0.12411149131306203, -0.06778494104304725, 0.27273135859688163,], [ 0.2960820548324385, 0.15641449633222393, 0.34417989389567694,], [ -0.2752945886840013, 0.1905075625186843, 0.8019241769361253,], [ -0.05866480459113656, 0.3308844032338997, 0.2861861058936483,], [ 0.03004175741525734, 0.1381895881369778, 0.545451303246514,], [ -0.23272176204453937, 0.22078329046522133, 0.9113919236888627,], [ 0.3010996919956904, 0.15159605858926453, 0.5628566923390071,], [ 0.24286481344394953, -0.3432420605913338, 0.6334689430732575,], [ 0.14137617490461166, -0.003352264536796283, 0.72726864263183,], [ -0.3432420605913338, -0.24286481344394953, 0.6334689430732575,], [ 0.2273205080756338, 0.35373066958927196, 1.0,], [ -0.12787948411898845, 0.3108406104852018, 0.25416242928053256,], [ 0.1087490092177436, -0.08974623900537222, 0.09090575668150692,], [ 0.13670424174988352, 0.22301767654486787, 0.7484947884303376,], [ -0.18624999999986297, -0.32259446291024796, 1.0,], [ -0.12811751323706685, 0.23011555754610388, 0.7080989021609679,], [ -0.036175109197266464, 0.41539876138478526, 0.13080822198027475,], [ 0.1699177047710262, -0.09665987855212022, 0.6010258456660696,], [ -0.20464101615125777, -0.27444863728629026, 1.0,], [ 0.48296218339593283, 0.1293633882265425, 0.2500526572589161,], [ 0.24651802646263324, 0.22200893429460314, 0.8234997710274429,], [ 0.32551962977214255, 0.08896668353784273, 0.37184078929925374,], [ -0.18862051535932128, 0.03858336894564142, 0.22406819693318994,], [ 0.13522716241926555, -0.2260108006336161, 0.29190109927669305,], [ -0.23011555752253832, -0.12811751319348708, 0.708098902143065,], [ -0.20955618780481172, 0.15931529634386715, 0.6560276772075244,], [ 0.2374249272571373, -0.23980722172969998, 0.6281592127404793,], [ 0.3108406104624231, 0.12787948417138995, 0.25416242944159334,], [ 0.11558684565855919, -0.3963506814153268, 0.22022101032899222,], [ 0.18055964838790206, -0.3794138581587593, 0.46979262196489335,], [ -0.01006819038779251, 0.1410389588765214, 0.8181816111750334,], [ 0.15027997906385604, -0.21920406776930185, 0.7924145492839705,], [ 0.05789725911480256, -0.257440370300532, 0.4333004886929356,], [ 0.22322973536694926, 0.0843115695710863, 0.06971816333933709,], [ 0.13965854583061185, 0.24306086394145443, 1.0,], [ -0.11964640244198879, 0.31297468504697246, 0.46838952889172525,], [ 0.016809283713724213, 0.14041656779181083, 0.6363653307983118,], [ 0.08900127910614036, -0.07727166349257321, 1.0,], [ 0.08974623900537222, 0.1087490092177436, 0.09090575668150692,], [ -0.1562492738841116, 0.2118330558531523, 0.3836095383159287,], [ -0.09581023169714559, 0.25523159087166986, 0.13666343649704626,], [ -0.0012489414339721533, -0.19548298841643938, 0.39897415629239585,], [ -0.03660254037844385, 0.13660254037844388, 1.0,], [ 0.01990434120847588, 0.19379044745542848, 0.3099640249851412,], [ 0.21122082708205525, -0.26010416453506197, 0.5316104738645642,], [ -0.2499887675338708, 0.43299324531432326, 0.500000001309108,], [ -0.0854754928913008, 0.1596702181964385, 0.9359984715878263,], [ 0.2818358839968427, 0.18496195406024268, 0.4371433105784621,], [ 0.4153987613811732, 0.03617510920507221, 0.13080822200083506,], [ 0.46981155738148817, 0.17105366125509808, 0.3333255805881006,], [ 0.2112771694579553, -0.4531664806011405, 0.41669435422853884,], [ -0.37635803387282524, -0.2110064439789368, 0.4498394619076902,], [ -0.12177881919208919, 0.15247695769075534, 0.23713344789406382,], [ 0.2868151005492483, -0.4095546356373149, 0.5833056479573179,], [ -0.1346965726697516, 0.04307786866862426, 0.4545486989047308,], [ 0.4082435857975277, -0.2859631405720417, 0.9166961793728162,], [ -0.17607383516628242, 0.09829272915276606, 1.0,], [ 0.4531664806011405, 0.2112771694579553, 0.41669435422853884,], [ -0.23272151617106032, 0.11035141320596037, 0.8429745430544462,], [ -0.25523159090841296, -0.09581023169261282, 0.13666343649402357,], [ 0.1688093698172481, 0.10547450022816708, 0.1416705253712006,], [ 0.17777962111750606, -0.07965755805049436, 0.6900359766182131,], [ -0.17105366125509808, 0.46981155738148817, 0.3333255805881006,], [ 0.2834999527553662, 0.17820733151557788, 0.655820108458231,], [ 0.20525612252234304, -0.26616718601562606, 0.7458375725885337,], [ -0.17546963249217945, 0.19683907432227515, 0.47286608902670724,], [ -0.1324309081388907, 0.027027440576591358, 1.0,], [ 0.1596702181964385, 0.0854754928913008, 0.9359984715878263,], [ -0.32430800579064517, -0.26646629080978856, 0.6108572497083525,], [ -0.10532817077836558, 0.24123236767263964, 0.6163904640753997,], [ 0.3213419137658031, -0.3830425941522091, 0.6666744211770179,], [ 0.3275678261876392, -0.1812594487445165, 0.9363009295050629,], [ -0.3725000000004646, -4.572522915857746e-13, 0.0,], [ 0.26010416441144346, 0.2112208270303067, 0.531610473829892,], [ 0.03999280726046196, 0.18143090635394693, 0.8476301642343661,], [ -0.23830221644295485, 0.3460761709759009, 0.5302073804891553,], [ 0.15385905715184453, 0.09554071879135333, 0.0640015285287694,], [ 0.32265267464874814, -0.27428991476805276, 0.7838020564428306,], [ -0.41657037006101477, -0.07621569761639421, 0.2161979453086568,], [ -0.11710922616794303, 0.07923878801083857, 0.18181838943427378,], [ 0.084311569574439, -0.2232297353773739, 0.06971816336297523,], [ -0.18793929950246036, -0.05401477923550248, 0.6720908582586579,], [ 0.15159605858926453, -0.3010996919956904, 0.5628566923390071,], [ -0.3750829832922387, 0.22937079846756286, 0.9429751416480727,], [ -0.0780384757732574, -0.2551666049842888, 1.0,], [ 0.14041656779181083, -0.016809283713724213, 0.6363653307983118,], [ 0.0868844127991979, -0.49213305714063227, 0.16665038694353176,], [ 0.17582393811226835, -0.3819481276563166, 0.3665310588577555,], [ 0.09345595207411436, -0.1757482812467558, 0.8583294751625014,], [ -0.3801773319632138, -0.08550076925677855, 0.1429869277252464,], [ -0.09665987855212022, -0.1699177047710262, 0.6010258456660696,], [ 0.07965755805049436, 0.17777962111750606, 0.6900359766182131,], [ 0.3923666770180675, 0.1443916304528351, 0.28964912332001924,], [ 0.20566587649344237, -0.05126815202734319, 0.919003579087715,], [ 0.33366588879888825, 0.027337094910558295, 0.19807582414808178,], [ 0.2868844483634396, -0.16467071281208057, 0.877902428113227,], [ 0.22200893429460314, -0.24651802646263324, 0.8234997710274429,], [ -0.18496195406024266, 0.28183588399684256, 0.4371433105784621,], [ 0.17820733151557788, -0.2834999527553662, 0.655820108458231,], [ 0.11469630200353009, -0.23974831403367877, 0.20758545172877368,], [ 0.295826539010757, -0.29879556015359177, 0.6997870384432034,], [ -0.07621569761639414, 0.4165703700610147, 0.21619794530865677,], [ 0.07727166349256004, 0.08900127910614056, 1.0,], [ -0.10248640655529279, 0.3155243904999129, 0.17650022993985037,], [ 0.43951679070003674, 0.01109944656979172, 0.05702485844436234,], [ -0.03100865480332172, -0.2929523584797162, 0.07050258724554687,], [ 0.29827648464678935, 0.2854563356907578, 0.7797789907804049,], [ 0.25000000000000006, 0.4330127018922193, 1.0,], [ -0.1300089567816845, 0.05565101693579122, 0.36363467100030356,], [ -0.19400127130953645, 0.17886068133656935, 0.5666995135909063,], [ 0.12713281853260583, 0.14850215890206264, 0.418815368519463,], [ 0.1968390743222751, 0.17546963249217945, 0.47286608902670724,], [ -0.3129746850508658, -0.11964640244771335, 0.46838952889671664,], [ -0.08550076923451833, 0.38017733199895054, 0.14298692767686907,], [ 0.20374594984238775, -0.058433416135664565, 0.08099642100680052,], [ 0.2864928434919832, -0.2641345041765258, 0.8570130730791857,], [ 0.04280900393084118, -0.1282022509828264, 0.0,], [ -0.04352955150691354, 0.4965308865097226, 0.08330382086471845,], [ -0.330784575153691, 0.0008332040654743036, 0.12209757229950059,], [ -0.011099446572985262, 0.43951679068520993, 0.05702485846129726,], [ 0.26113863093263984, 0.03319300090094479, 0.343972324614267,], [ 0.4056301486161195, 0.054547229799177394, 0.07433309329000042,], [ -0.33088440334778824, -0.05866480464249037, 0.28618610584767795,], [ 0.11085127653671033, -0.40559107737231626, 0.3002129631402097,], [ 0.22301767654486787, -0.13670424174988352, 0.7484947884303376,], [ -0.181961524226949, -0.19516660498354385, 1.0,], [ 0.4095546356373149, 0.2868151005492483, 0.5833056479573179,], [ 0.34165832587637174, -0.23902794414550274, 0.8691917789440509,], [ -0.31552439046269243, -0.1024864065390655, 0.17650022988283992,], [ 0.21183305585315232, 0.15624927388411164, 0.38360953831592876,], [ 0.23902794414550274, 0.34165832587637174, 0.8691917789440509,], [ 0.04307786866862426, 0.1346965726697516, 0.4545486989047308,], [ 0.01767499190728805, -0.19504989901600386, 0.48997739820370295,], [ -0.006880508595382258, 0.26149109494923667, 0.2515052127247301,], [ -0.23980722167376378, -0.23742492724531375, 0.6281592127756889,], [ 0.257221896874944, -0.21624741204257814, 0.7138138957504989,], [ 0.11962189625900777, 0.2692085120118234, 0.9294974133710218,], [ -0.26760369432653197, 0.32123015750876655, 0.710350878355716,], [ 0.1381895881369778, -0.03004175741525734, 0.545451303246514,], [ 0.15247695769075534, 0.12177881919208919, 0.23713344789406382,], [ -0.23974831398128404, -0.1146963020270878, 0.20758545181802338,], [ -0.07115950897192602, 0.1817020295604646, 0.7628665530933992,], [ -0.05565101693579122, -0.1300089567816845, 0.36363467100030356,], [ 0.02334801827980766, -0.13905252411862765, 0.9090942434276862,], [ -0.00044204692535520785, 0.2804079665921417, 0.0,], [ -0.2105898896120768, -0.1731319251222549, 0.8633365640709225,], [ -0.1757482812467558, -0.09345595207411436, 0.8583294751625014,], [ 0.14762582728527868, -0.3929205799237823, 0.38914275232054196,], [ -0.17313192511541192, 0.21058988960682495, 0.8633365640787076,], [ 0.1410389588765214, 0.01006819038779251, 0.8181816111750334,], [ -0.14850215888411922, 0.12713281842348526, 0.41881536854055246,], [ 0.3240123985313803, -0.25005436085245025, 0.9256669072548005,], [ -0.3827575233600255, 0.3213106369533618, 0.8333496137776426,], [ -0.07923878801083857, -0.11710922616794303, 0.18181838943427378,], [ -0.01307301892783809, 0.05805612402181283, 1.0,], [ -0.2430608639414823, 0.13965854583062795, 1.0,], [ 0.2859631405720417, 0.4082435857975277, 0.9166961793728162,], [ -0.2162474120374094, -0.2572218968731536, 0.7138138957533035,], [ -0.257440370300532, -0.05789725911480256, 0.4333004886929356,], [ 0.1817020295604646, 0.07115950897192602, 0.7628665530933992,], [ -0.256718451919196, 0.020793630656624283, 0.1570254573956425,], [ -0.13982115942903242, -0.24306144595498963, 1.0,], [ 0.13660254037844388, 0.03660254037844385, 1.0,], [ 0.19504989901600386, 0.01767499190728805, 0.48997739820370295,], [ -0.03858336894564142, -0.18862051535932128, 0.22406819693318994,], [ -0.006808625571027564, -0.3743117830034124, 0.06369907135175619,], [ 0.4965308865097226, 0.04352955150691354, 0.08330382086471845,], [ -0.05126815202734319, -0.20566587649344237, 0.919003579087715,], [ -0.1407478210833046, -0.1357528184326633, 0.3279091434686116,], [ 0.2854563356907578, -0.29827648464678935, 0.7797789907804049,], [ 0.13905252411862765, 0.02334801827980766, 0.9090942434276862,], [ -0.0889666834825325, 0.3255196297121406, 0.37184078927199904,], [ 0.2110064439721351, -0.3763580338536665, 0.44983946190230817,], [ 0.24123236767263953, 0.10532817077836548, 0.6163904640753997,], [ 0.2664662908066769, -0.3243080057816075, 0.6108572497097852,], [ 0.19379044745542848, -0.01990434120847588, 0.3099640249851412,], [ -0.3929205799406444, -0.14762582730040072, 0.38914275232476603,], [ -0.13535898384890976, -0.3144486372870062, 1.0,], [ -0.192173051021227, -0.03584917116861537, 0.5811846336959405,], [ -0.09554071879135331, 0.15385905715184459, 0.0640015285287694,], [ 0.22043239723385152, -0.3709159571262127, 0.5501605407292253,], [ 0.2803261557544342, 0.0005825834258224174, 0.0,], [ -0.027027440576591358, -0.1324309081388907, 1.0,], [ 0.05845352640182483, 0.011562442225933231, 1.0,], [ -0.21920406776930185, -0.15027997906385604, 0.7924145492839705,], [ 0.11588457268102555, -0.15928203230270552, 1.0,], [ 0.19588457268134912, -0.02071796769739012, 1.0,], [ -0.4330127018922193, 0.25000000000000006, 1.0,], [ 0.15641449632757393, -0.2960820548709696, 0.3441798938679665,], [ -0.035849171209017025, 0.192173051051974, 0.5811846337170081,], [ 0.18463082872875658, 0.15116683692070118, 0.9302818368160787,], [ 0.1926794919244359, 0.3737306695895931, 1.0,], [ -0.2692085120118234, 0.11962189625900777, 0.9294974133710218,], [ -0.1293633882265425, 0.48296218339593283, 0.2500526572589161,], [ -0.17712017748552306, 0.05608066608938753, 0.15236983617188293,], [ -0.07484305753576045, 0.31193460312490323, 0.08860807718863248,], [ -0.35357582625438827, 0.3535130717427297, 0.7499473440345463,], [ -0.26616718601562606, -0.20525612252234304, 0.7458375725885337,], [ 0.13575281841339892, -0.14074782099317457, 0.327909143404395,], [ -0.05843341609373548, -0.20374594996532658, 0.08099642100600661,], [ -0.02733709481324227, 0.33366588860446383, 0.1980758241403164,], [ 0.18143090635394693, -0.03999280726046196, 0.8476301642343661,], [ -0.15116683692070118, 0.18463082872875658, 0.9302818368160787,], [ 0.2551666049842888, -0.0780384757732574, 1.0,], [ 0.17886068133656935, 0.19400127130953645, 0.5666995135909063,], [ 0.31193460305271925, 0.07484305755995456, 0.08860807723034066,], [ -0.19548298841643938, 0.0012489414339721533, 0.39897415629239585,], [ -0.3737306695895931, 0.1926794919244359, 1.0,], [ -0.15928203230270552, -0.11588457268102555, 1.0,], [ -0.3794138581587593, -0.18055964838790206, 0.46979262196489335,], [ 0.3225944629095305, -0.18624999999944872, 1.0,], [ -0.37431178312384833, 0.006808625529926414, 0.06369907135765435,], [ -0.15931529634386715, -0.20955618780481172, 0.6560276772075244,], [ -0.2641345041765258, -0.2864928434919832, 0.8570130730791857,], [ -0.06089607975244819, -0.18264184245282067, 0.7759318039691542,], [ 0.0008332040654743036, 0.33078457515369103, 0.12209757229950056,], [ 0.11571320042150712, -0.022418584024176152, 0.0,], [ 0.18264184245282067, -0.06089607975244819, 0.7759318039691542,], [ -0.32123015741992306, -0.26760369419654917, 0.7103508783623392,], [ 0.22601080062813186, 0.13522716242389238, 0.2919010992969149,], [ 0.1812594487445165, 0.3275678261876392, 0.9363009295050629,], [ 0.22078329046522133, 0.23272176204453937, 0.9113919236888627,], [ 0.003352264536796283, 0.14137617490461166, 0.72726864263183,], [ -0.054014779227268196, 0.18793929950112703, 0.6720908582355372,], [ 0.49213305714063227, 0.0868844127991979, 0.16665038694353176,], [ 0.25005436085245025, 0.3240123985313803, 0.9256669072548005,], [ 0.3963506815068328, 0.11558684569349488, 0.22022101041669728,], [ -0.2929523584693378, 0.031008654810814267, 0.07050258723180984,], [ -0.02079363066184354, -0.25671845192747356, 0.15702545736167312,], [ 0.02315901413613517, 0.11678553464223565, 0.0,], [ -0.056080666071232854, -0.17712017752614487, 0.15236983617663716,], [ -0.16008067190168807, 0.11283194115031796, 0.5100226042322266,], [ -0.054547229779268395, 0.4056301486386689, 0.07433309325120684,], [ 0.3819481276563166, 0.17582393811226835, 0.3665310588577555,], [ 0.06778494104304725, 0.12411149131306203, 0.27273135859688163,], [ -0.08273282305366661, 0.25038069608229085, 0.5271339135345402,], [ 0.16467071281208057, 0.2868844483634396, 0.877902428113227,], [ 0.27428991476805276, 0.32265267464874814, 0.7838020564428306,], [ -0.5, 0.0, 0.0,], [ -0.4200000000002186, -0.01999999999994535, 0.0,], [ -0.4199999999997881, 0.020000000000052975, 0.0,], [ -0.3144486372870062, 0.13535898384890976, 1.0,], [ -0.3399999999995761, 0.040000000000105985, 0.0,], [ -0.3400000000004409, -0.039999999999889777, 0.0,], [ -0.27444863728629026, 0.20464101615125777, 1.0,], [ -0.2600000000004393, -0.059999999999890176, 0.0,], [ -0.259999999999577, 0.06000000000010575, 0.0,], [ -0.1799999999998114, 0.08000000000004716, 0.0,], [ -0.18000000000021854, -0.07999999999994536, 0.0,], [ -0.1, -0.1, 0.0,], [ -0.1, 0.1, 0.0,], [ -0.06000000000010575, -0.259999999999577, 0.0,], [ -0.059999999999890176, 0.2600000000004393, 0.0,], [ -0.020000000000052975, -0.4199999999997881, 0.0,], [ -0.01999999999994535, 0.4200000000002186, 0.0,], [ 0.01999999999994535, -0.4200000000002186, 0.0,], [ 0.020000000000052975, 0.4199999999997881, 0.0,], [ 0.059999999999890176, -0.2600000000004393, 0.0,], [ 0.06000000000010575, 0.259999999999577, 0.0,], [ 0.1, -0.1, 0.0,], [ 0.1, 0.1, 0.0,], [ 0.18000000000021854, 0.07999999999994536, 0.0,], [ 0.1799999999998114, -0.08000000000004716, 0.0,], [ 0.259999999999577, -0.06000000000010575, 0.0,], [ 0.2600000000004393, 0.059999999999890176, 0.0,], [ 0.27444863728629026, -0.20464101615125777, 1.0,], [ 0.3400000000004409, 0.039999999999889777, 0.0,], [ 0.3399999999995761, -0.040000000000105985, 0.0,], [ 0.3144486372870062, -0.13535898384890976, 1.0,], [ 0.4199999999997881, -0.020000000000052975, 0.0,], [ 0.4200000000002186, 0.01999999999994535, 0.0,], [ 0.5, 0.0, 0.0,], [ -0.27428991476805276, -0.32265267464874814, 0.7838020564428306,], [ -0.16467071281208057, -0.2868844483634396, 0.877902428113227,], [ 0.08273282305366661, -0.25038069608229085, 0.5271339135345402,], [ -0.06778494104304725, -0.12411149131306203, 0.27273135859688163,], [ -0.3819481276563166, -0.17582393811226835, 0.3665310588577555,], [ 0.054547229779268395, -0.4056301486386689, 0.07433309325120684,], [ 0.16008067190168807, -0.11283194115031796, 0.5100226042322266,], [ 0.056080666071232854, 0.17712017752614487, 0.15236983617663716,], [ 0.02079363066184354, 0.25671845192747356, 0.15702545736167312,], [ 0.2929523584693378, -0.031008654810814267, 0.07050258723180984,], [ -0.3963506815068328, -0.11558684569349488, 0.22022101041669728,], [ -0.25005436085245025, -0.3240123985313803, 0.9256669072548005,], [ -0.49213305714063227, -0.0868844127991979, 0.16665038694353176,], [ 0.05401477923550248, -0.18793929950246036, 0.6720908582586579,], [ -0.003352264536796283, -0.14137617490461166, 0.72726864263183,], [ -0.22078329046522133, -0.23272176204453937, 0.9113919236888627,], [ -0.1812594487445165, -0.3275678261876392, 0.9363009295050629,], [ -0.22601080062813186, -0.13522716242389238, 0.2919010992969149,], [ 0.32123015741992306, 0.26760369419654917, 0.7103508783623392,], [ -0.18264184245282067, 0.06089607975244819, 0.7759318039691542,], [ -0.11571320042151381, 0.02241858402418742, 0.0,], [ -0.0008332040654743036, -0.33078457515369103, 0.12209757229950056,], [ 0.06089607975244819, 0.18264184245282067, 0.7759318039691542,], [ 0.2641345041765258, 0.2864928434919832, 0.8570130730791857,], [ 0.15931529634386715, 0.20955618780481172, 0.6560276772075244,], [ 0.37431178312384833, -0.006808625529926414, 0.06369907135765435,], [ -0.322594462910248, 0.18624999999986297, 1.0,], [ 0.3794138581587593, 0.18055964838790206, 0.46979262196489335,], [ 0.15928203230270552, 0.11588457268102555, 1.0,], [ 0.3737306695895931, -0.1926794919244359, 1.0,], [ 0.19548298841643938, -0.0012489414339721533, 0.39897415629239585,], [ -0.31193460305271925, -0.07484305755995456, 0.08860807723034066,], [ -0.17886068133656935, -0.19400127130953645, 0.5666995135909063,], [ -0.2551666049842888, 0.0780384757732574, 1.0,], [ 0.15116683692070118, -0.18463082872875658, 0.9302818368160787,], [ -0.18143090635394693, 0.03999280726046196, 0.8476301642343661,], [ 0.02733709481324227, -0.33366588860446383, 0.1980758241403164,], [ 0.05843341609373548, 0.20374594996532658, 0.08099642100600661,], [ -0.13575281841339892, 0.14074782099317457, 0.327909143404395,], [ 0.26616718601562606, 0.20525612252234304, 0.7458375725885337,], [ 0.35357582625438827, -0.3535130717427297, 0.7499473440345463,], [ 0.07484305753576045, -0.31193460312490323, 0.08860807718863248,], [ 0.17712017748552306, -0.05608066608938753, 0.15236983617188293,], [ -0.05979502403710553, -0.009863657322112196, 1.0,], [ 0.000582583425822173, -0.28032615575440123, 0.0,], [ 0.1293633882265425, -0.48296218339593283, 0.2500526572589161,], [ 0.04034962850653133, -0.043741568784226206, 0.0,], [ 0.2692085120118234, -0.11962189625900777, 0.9294974133710218,], [ -0.1926794919244359, -0.3737306695895931, 1.0,], [ -0.18463082872875658, -0.15116683692070118, 0.9302818368160787,], [ 0.03584917116861537, -0.192173051021227, 0.5811846336959405,], [ -0.15641449632757393, 0.2960820548709696, 0.3441798938679665,], [ 0.4330127018922193, -0.25000000000000006, 1.0,], [ -0.19588457268134912, 0.02071796769739012, 1.0,], [ -0.11588457268102555, 0.15928203230270552, 1.0,], [ 0.21920406776930185, 0.15027997906385604, 0.7924145492839705,], [ 0.027027440576591358, 0.13243090813889066, 1.0,], [ -0.2803261557544015, -0.0005825834258220982, 0.0,], [ -0.22043239712810567, 0.37091595700126195, 0.5501605407739526,], [ 0.09554071879135331, -0.15385905715184459, 0.0640015285287694,], [ 0.192173051021227, 0.03584917116861537, 0.5811846336959405,], [ 0.09829272915268539, 0.17607383516614336, 1.0,], [ 0.13535898384890976, 0.3144486372870062, 1.0,], [ 0.3929205799406444, 0.14762582730040072, 0.38914275232476603,], [ -0.19379044745542848, 0.01990434120847588, 0.3099640249851412,], [ -0.2664662908066769, 0.3243080057816075, 0.6108572497097852,], [ -0.24123236767263953, -0.10532817077836548, 0.6163904640753997,], [ -0.2110064439721351, 0.3763580338536665, 0.44983946190230817,], [ 0.0889666834825325, -0.3255196297121406, 0.37184078927199904,], [ -0.13905252411862765, -0.02334801827980766, 0.9090942434276862,], [ -0.2854563356907578, 0.29827648464678935, 0.7797789907804049,], [ 0.1407478210833046, 0.1357528184326633, 0.3279091434686116,], [ 0.05126815202734319, 0.20566587649344237, 0.919003579087715,], [ -0.4965308865097226, -0.04352955150691354, 0.08330382086471845,], [ 0.006808625571027564, 0.3743117830034124, 0.06369907135175619,], [ -0.0022102346272078258, 0.20203983296067513, 0.0,], [ 0.03858336894564142, 0.18862051535932128, 0.22406819693318994,], [ -0.19504989901600386, -0.01767499190728805, 0.48997739820370295,], [ 0.04685218117497973, 0.03843968983372776, 0.0,], [ -0.13660254037844388, -0.03660254037844385, 1.0,], [ 0.256718451919196, -0.020793630656624283, 0.1570254573956425,], [ -0.1817020295604646, -0.07115950897192602, 0.7628665530933992,], [ 0.257440370300532, 0.05789725911480256, 0.4333004886929356,], [ 0.2162474120374094, 0.2572218968731536, 0.7138138957533035,], [ -0.2859631405720417, -0.4082435857975277, 0.9166961793728162,], [ 0.24306086394145443, -0.13965854583061224, 1.0,], [ 0.07923878801083857, 0.11710922616794303, 0.18181838943427378,], [ 0.3827575233600255, -0.3213106369533618, 0.8333496137776426,], [ -0.3240123985313803, 0.25005436085245025, 0.9256669072548005,], [ 0.14850215888411922, -0.12713281842348526, 0.41881536854055246,], [ -0.1410389588765214, -0.01006819038779251, 0.8181816111750334,], [ 0.0007364072744616131, 0.00046606467373267634, 0.0,], [ 0.1731319251222549, -0.2105898896120768, 0.8633365640709225,], [ -0.1476258272776477, 0.39292057992223023, 0.3891427523083724,], [ 0.1757482812467558, 0.09345595207411436, 0.8583294751625014,], [ 0.2105898896120768, 0.1731319251222549, 0.8633365640709225,], [ -0.02334801827980766, 0.13905252411862765, 0.9090942434276862,], [ 0.05565101693579122, 0.1300089567816845, 0.36363467100030356,], [ 0.07115950897192602, -0.1817020295604646, 0.7628665530933992,], [ 0.23974831398128404, 0.1146963020270878, 0.20758545181802338,], [ -0.15247695769075534, -0.12177881919208919, 0.23713344789406382,], [ -0.1381895881369778, 0.03004175741525734, 0.545451303246514,], [ 0.26760369419654917, -0.32123015741992306, 0.7103508783623392,], [ -0.11962189625900777, -0.2692085120118234, 0.9294974133710218,], [ -0.257221896874944, 0.21624741204257814, 0.7138138957504989,], [ 0.23980722167376378, 0.23742492724531375, 0.6281592127756889,], [ 0.006880508595382258, -0.26149109494923667, 0.2515052127247301,], [ -0.017674991916865515, 0.1950498990250472, 0.48997739820828046,], [ -0.04307786866862426, -0.1346965726697516, 0.4545486989047308,], [ -0.038236911252714396, 0.04715197302170622, 0.0,], [ -0.23902794414550274, -0.34165832587637174, 0.8691917789440509,], [ -0.21183305585315232, -0.15624927388411164, 0.38360953831592876,], [ 0.31552439046269243, 0.1024864065390655, 0.17650022988283992,], [ -0.34165832587637174, 0.23902794414550274, 0.8691917789440509,], [ -0.4095546356373149, -0.2868151005492483, 0.5833056479573179,], [ 0.181961524226949, 0.19516660498354385, 1.0,], [ -0.22301767654486787, 0.13670424174988352, 0.7484947884303376,], [ -0.11085127653671033, 0.40559107737231626, 0.3002129631402097,], [ 0.33088440334778824, 0.05866480464249037, 0.28618610584767795,], [ -0.4056301486161195, -0.054547229799177394, 0.07433309329000042,], [ -0.26113863093263984, -0.03319300090094479, 0.343972324614267,], [ 0.011099446572985262, -0.43951679068520993, 0.05702485846129726,], [ 0.330784575153691, -0.0008332040654743036, 0.12209757229950059,], [ 0.04352955150691354, -0.4965308865097226, 0.08330382086471845,], [ -0.04280900393084118, 0.1282022509828264, 0.0,], [ -0.2864928434919832, 0.2641345041765258, 0.8570130730791857,], [ -0.20374594984238775, 0.058433416135664565, 0.08099642100680052,], [ 0.08550076923451833, -0.38017733199895054, 0.14298692767686907,], [ 0.3129746850508658, 0.11964640244771335, 0.46838952889671664,], [ -0.1968390743222751, -0.17546963249217945, 0.47286608902670724,], [ -0.12713281853260583, -0.14850215890206264, 0.418815368519463,], [ -0.022418584024184802, -0.11571320042150464, 0.0,], [ 0.19400127130953645, -0.17886068133656935, 0.5666995135909063,], [ 0.1300089567816845, -0.05565101693579122, 0.36363467100030356,], [ -0.25000000000000006, -0.4330127018922193, 1.0,], [ -0.29827648464678935, -0.2854563356907578, 0.7797789907804049,], [ 0.03100865480332172, 0.2929523584797162, 0.07050258724554687,], [ -0.43951679070003674, -0.01109944656979172, 0.05702485844436234,], [ 0.10248640655529279, -0.3155243904999129, 0.17650022993985037,], [ 0.07621569761639414, -0.4165703700610147, 0.21619794530865677,], [ -0.295826539010757, 0.29879556015359177, 0.6997870384432034,], [ -0.11469630200353009, 0.23974831403367877, 0.20758545172877368,], [ -0.17820733280432927, 0.2834999538094197, 0.6558201082214182,], [ 0.18496195406024266, -0.28183588399684256, 0.4371433105784621,], [ -0.22200893445325406, 0.2465180265414618, 0.8234997710287095,], [ -0.2868844483634396, 0.16467071281208057, 0.877902428113227,], [ -0.33366588879888825, -0.027337094910558295, 0.19807582414808178,], [ -0.20566587649344237, 0.05126815202734319, 0.919003579087715,], [ -0.3923666770180675, -0.1443916304528351, 0.28964912332001924,], [ -0.07965755805049436, -0.17777962111750606, 0.6900359766182131,], [ 0.09665987855212022, 0.1699177047710262, 0.6010258456660696,], [ 0.3801773319632138, 0.08550076925677855, 0.1429869277252464,], [ -0.09345595207411436, 0.1757482812467558, 0.8583294751625014,], [ -0.17582393811226835, 0.3819481276563166, 0.3665310588577555,], [ -0.0868844127991979, 0.49213305714063227, 0.16665038694353176,], [ -0.14041656779181083, 0.016809283713724213, 0.6363653307983118,], [ 0.0780384757732574, 0.2551666049842888, 1.0,], [ 0.3750829832922387, -0.22937079846756286, 0.9429751416480727,], [ -0.15159605858926462, 0.3010996919956906, 0.562856692339007,], [ 0.18793929950246036, 0.05401477923550248, 0.6720908582586579,], [ -0.084311569574439, 0.2232297353773739, 0.06971816336297523,], [ 0.11710922616794303, -0.07923878801083857, 0.18181838943427378,], [ -0.0991057971447882, -0.1760767452338191, 1.0,], [ 0.41657037006101477, 0.07621569761639421, 0.2161979453086568,], [ -0.32265267464874814, 0.27428991476805276, 0.7838020564428306,], [ -0.15385905715184453, -0.09554071879135333, 0.0640015285287694,], [ 0.23830221644295485, -0.3460761709759009, 0.5302073804891553,], [ -0.03999280726046196, -0.18143090635394693, 0.8476301642343661,], [ -0.26010416441144346, -0.2112208270303067, 0.531610473829892,], [ 0.3725000000004647, 4.572626999266305e-13, 0.0,], [ -0.3275678261876392, 0.1812594487445165, 0.9363009295050629,], [ -0.3213419137658031, 0.3830425941522091, 0.6666744211770179,], [ 0.10532817077836548, -0.24123236767263953, 0.6163904640753997,], [ 0.32430800579064517, 0.26646629080978856, 0.6108572497083525,], [ -0.1596702181964385, -0.0854754928913008, 0.9359984715878263,], [ 0.1324309081388907, -0.027027440576591358, 1.0,], [ 0.17546963249217945, -0.19683907432227515, 0.47286608902670724,], [ -0.20525612305547106, 0.2661671863656952, 0.7458375725181301,], [ -0.2834999527553662, -0.17820733151557788, 0.655820108458231,], [ 0.17105366125509808, -0.46981155738148817, 0.3333255805881006,], [ -0.07844906188960828, -0.08955973272666004, 1.0,], [ -0.17777962111750606, 0.07965755805049436, 0.6900359766182131,], [ -0.1688093698172481, -0.10547450022816708, 0.1416705253712006,], [ 0.25523159090841296, 0.09581023169261282, 0.13666343649402357,], [ 0.009538149996248965, -0.0599532621017078, 1.0,], [ 0.23272151617106032, -0.11035141320596037, 0.8429745430544462,], [ -0.4531664806011405, -0.2112771694579553, 0.41669435422853884,], [ 0.17607383516614336, -0.09829272915268725, 1.0,], [ -0.4082435857975277, 0.2859631405720417, 0.9166961793728162,], [ 0.1346965726697516, -0.04307786866862426, 0.4545486989047308,], [ -0.2868151005492483, 0.4095546356373149, 0.5833056479573179,], [ -0.0008707797440886906, -3.542021002197375e-5, 1.0,], [ 0.12177881919208919, -0.15247695769075534, 0.23713344789406382,], [ 0.37635803387282524, 0.2110064439789368, 0.4498394619076902,], [ -0.2112771694579553, 0.4531664806011405, 0.41669435422853884,], [ -0.46981155738148817, -0.17105366125509808, 0.3333255805881006,], [ -0.4153987613811732, -0.03617510920507221, 0.13080822200083506,], [ 0.002912917129542638, -0.20163077877197322, 0.0,], [ -0.2818358839968427, -0.18496195406024268, 0.4371433105784621,], [ 0.0854754928913008, -0.1596702181964385, 0.9359984715878263,], [ 0.2499887675338708, -0.43299324531432326, 0.500000001309108,], [ -0.21122082708205525, 0.26010416453506197, 0.5316104738645642,], [ -0.01990434120847588, -0.19379044745542848, 0.3099640249851412,], [ 0.03660254037844385, -0.13660254037844388, 1.0,], [ 0.0012489414339721533, 0.19548298841643938, 0.39897415629239585,], [ 0.09581023169714559, -0.25523159087166986, 0.13666343649704626,], [ 0.1562492738841116, -0.2118330558531523, 0.3836095383159287,], [ -0.08974623900537222, -0.1087490092177436, 0.09090575668150692,], [ -0.08900127910614704, 0.07727166349256673, 1.0,], [ -0.016809283713724213, -0.14041656779181083, 0.6363653307983118,], [ 0.11964640244229122, -0.31297468504755493, 0.4683895288912161,], [ -0.22322973536694926, -0.0843115695710863, 0.06971816333933709,], [ -0.05789725911480256, 0.257440370300532, 0.4333004886929356,], [ -0.15027997906385604, 0.21920406776930185, 0.7924145492839705,], [ 0.01006819038779251, -0.1410389588765214, 0.8181816111750334,], [ -0.180559648379687, 0.3794138581406193, 0.4697926219793894,], [ -0.11558684565855919, 0.3963506814153268, 0.22022101032899222,], [ -0.3108406104624231, -0.12787948417138995, 0.25416242944159334,], [ -0.2374249272571373, 0.23980722172969998, 0.6281592127404793,], [ 0.20955618780481172, -0.15931529634386715, 0.6560276772075244,], [ 0.23011555752253832, 0.12811751319348708, 0.708098902143065,], [ -0.13522716241926555, 0.2260108006336161, 0.29190109927669305,], [ 0.18862051535932128, -0.03858336894564142, 0.22406819693318994,], [ -0.32551962977214255, -0.08896668353784273, 0.37184078929925374,], [ -0.24651802646263324, -0.22200893429460314, 0.8234997710274429,], [ -0.48296218339593283, -0.1293633882265425, 0.2500526572589161,], [ 0.20464101615125777, 0.27444863728629026, 1.0,], [ -0.1699177047710262, 0.09665987855212022, 0.6010258456660696,], [ 0.036175109197266464, -0.41539876138478526, 0.13080822198027475,], [ 0.12811751319348708, -0.23011555752253832, 0.708098902143065,], [ 0.18624999999944872, 0.3225944629095305, 1.0,], [ -0.13670424174988352, -0.22301767654486787, 0.7484947884303376,], [ -0.1087490092177436, 0.08974623900537222, 0.09090575668150692,], [ 0.12787948411898845, -0.3108406104852018, 0.25416242928053256,], [ -0.2273205080756338, -0.35373066958927196, 1.0,], [ 0.3432420605913338, 0.24286481344394953, 0.6334689430732575,], [ -0.14137617490461166, 0.003352264536796283, 0.72726864263183,], [ -0.24286481344394953, 0.3432420605913338, 0.6334689430732575,], [ -0.3010996919956904, -0.15159605858926453, 0.5628566923390071,], [ 0.23272176204453937, -0.22078329046522133, 0.9113919236888627,], [ -0.03004175741525734, -0.1381895881369778, 0.545451303246514,], [ 0.05866480459113656, -0.3308844032338997, 0.2861861058936483,], [ 0.2752945886840013, -0.1905075625186843, 0.8019241769361253,], [ -0.2960820548324385, -0.15641449633222393, 0.34417989389567694,], [ -0.12411149131306203, 0.06778494104304725, 0.27273135859688163,], [ -0.3709159571262127, -0.22043239723385152, 0.5501605407292253,], [ 0.19516660498354385, -0.181961524226949, 1.0,], [ 0.2614910947890534, 0.006880508491315662, 0.25150521267141857,], [ -0.35373066958927196, 0.2273205080756338, 1.0,], [ -0.3535130717427297, -0.35357582625438827, 0.7499473440345463,], [ 0.02071796769739012, 0.19588457268134912, 1.0,], [ 0.25038069608229085, 0.08273282305366661, 0.5271339135345402,], [ 0.3213106369533618, 0.3827575233600255, 0.8333496137776426,], [ 0.22937079846756286, 0.3750829832922387, 0.9429751416480727,], [ 0.11283194123344073, 0.16008067191774167, 0.5100226041806184,], [ -0.20163077877197452, -0.0029129171295422637, 0.0,], [ -0.04484101769180693, -0.039240131898362104, 0.0,], [ 0.10547450024114946, -0.16880936970747587, 0.1416705253853082,], [ 0.14439163046688128, -0.39236667705674766, 0.28964912334810244,], [ -0.11035141320596037, -0.23272151617106032, 0.8429745430544462,], [ -0.29879556015359177, -0.295826539010757, 0.6997870384432034,], [ -0.40559107737231626, -0.11085127653671033, 0.3002129631402097,], [ -0.1905075625186843, -0.2752945886840013, 0.8019241769361253,], [ -0.03319300091782479, 0.261138630970263, 0.34397232458352234,], [ -0.3830425941522091, -0.3213419137658031, 0.6666744211770179,], [ 0.43299324531432326, 0.2499887675338708, 0.500000001309108,], [ -0.1282022509828264, -0.04280900393084118, 0.0,], [ -0.3460761710126883, -0.2383022164555415, 0.5302073804909463,],] +indices = [ [ 2, 288, 290,], [ 284, 430, 9,], [ 281, 326, 284,], [ 326, 415, 284,], [ 415, 430, 284,], [ 274, 272, 273,], [ 283, 572, 282,], [ 8, 437, 283,], [ 437, 562, 283,], [ 562, 572, 283,], [ 287, 1, 289,], [ 293, 150, 6,], [ 296, 250, 293,], [ 250, 352, 293,], [ 352, 150, 293,], [ 304, 303, 305,], [ 294, 13, 295,], [ 7, 263, 294,], [ 263, 384, 294,], [ 384, 13, 294,], [ 11, 3, 288,], [ 288, 3, 290,], [ 286, 178, 11,], [ 5, 3, 11,], [ 11, 178, 5,], [ 9, 381, 286,], [ 381, 178, 286,], [ 9, 430, 381,], [ 280, 561, 281,], [ 281, 561, 326,], [ 276, 363, 280,], [ 280, 363, 561,], [ 274, 106, 276,], [ 276, 106, 277,], [ 277, 363, 276,], [ 273, 106, 274,], [ 277, 106, 273,], [ 279, 363, 277,], [ 282, 561, 279,], [ 561, 363, 279,], [ 282, 572, 561,], [ 285, 503, 8,], [ 8, 503, 437,], [ 10, 350, 285,], [ 285, 350, 503,], [ 287, 0, 10,], [ 10, 0, 4,], [ 4, 350, 10,], [ 289, 0, 287,], [ 4, 0, 289,], [ 291, 350, 4,], [ 6, 503, 291,], [ 503, 350, 291,], [ 6, 150, 503,], [ 297, 23, 296,], [ 296, 23, 250,], [ 301, 215, 297,], [ 297, 215, 23,], [ 303, 475, 301,], [ 301, 475, 300,], [ 300, 215, 301,], [ 304, 475, 303,], [ 300, 475, 304,], [ 298, 215, 300,], [ 295, 23, 298,], [ 23, 215, 298,], [ 295, 13, 23,], [ 292, 381, 7,], [ 7, 381, 263,], [ 5, 178, 292,], [ 292, 178, 381,], [ 290, 3, 5,], [ 326, 562, 415,], [ 326, 572, 562,], [ 561, 572, 326,], [ 415, 384, 263,], [ 263, 430, 415,], [ 562, 397, 415,], [ 415, 397, 384,], [ 250, 384, 352,], [ 250, 13, 384,], [ 23, 13, 250,], [ 352, 562, 437,], [ 437, 150, 352,], [ 503, 150, 437,], [ 352, 397, 562,], [ 384, 397, 352,], [ 381, 430, 263,], [ 2, 288, 151,], [ 284, 213, 9,], [ 538, 213, 284,], [ 221, 117, 241,], [ 494, 117, 221,], [ 359, 375, 385,], [ 11, 266, 288,], [ 288, 266, 151,], [ 286, 229, 11,], [ 229, 266, 11,], [ 9, 466, 286,], [ 286, 466, 229,], [ 9, 213, 466,], [ 241, 476, 275,], [ 241, 117, 476,], [ 275, 226, 339,], [ 275, 476, 226,], [ 339, 453, 359,], [ 226, 453, 339,], [ 359, 453, 375,], [ 151, 266, 460,], [ 460, 522, 227,], [ 460, 147, 522,], [ 266, 147, 460,], [ 227, 21, 96,], [ 522, 21, 227,], [ 96, 459, 500,], [ 96, 21, 459,], [ 500, 373, 79,], [ 459, 373, 500,], [ 79, 109, 496,], [ 79, 373, 109,], [ 496, 371, 477,], [ 109, 371, 496,], [ 477, 446, 230,], [ 477, 371, 446,], [ 230, 470, 186,], [ 446, 470, 230,], [ 186, 419, 494,], [ 186, 470, 419,], [ 419, 117, 494,], [ 113, 22, 538,], [ 22, 213, 538,], [ 550, 86, 113,], [ 86, 22, 113,], [ 142, 344, 550,], [ 550, 344, 86,], [ 88, 184, 142,], [ 184, 344, 142,], [ 407, 265, 88,], [ 88, 265, 184,], [ 461, 533, 407,], [ 533, 265, 407,], [ 542, 487, 461,], [ 461, 487, 533,], [ 396, 325, 542,], [ 325, 487, 542,], [ 375, 341, 396,], [ 396, 341, 325,], [ 453, 341, 375,], [ 265, 99, 184,], [ 99, 73, 184,], [ 73, 344, 184,], [ 86, 447, 22,], [ 86, 527, 447,], [ 86, 344, 527,], [ 487, 59, 533,], [ 59, 143, 533,], [ 143, 265, 533,], [ 92, 422, 325,], [ 325, 341, 92,], [ 325, 422, 487,], [ 229, 147, 266,], [ 229, 74, 137,], [ 137, 147, 229,], [ 466, 74, 229,], [ 131, 507, 109,], [ 109, 373, 131,], [ 507, 371, 109,], [ 459, 357, 131,], [ 131, 373, 459,], [ 21, 357, 459,], [ 524, 410, 446,], [ 446, 371, 524,], [ 410, 470, 446,], [ 419, 470, 36,], [ 36, 451, 419,], [ 419, 451, 476,], [ 476, 117, 419,], [ 226, 92, 453,], [ 226, 451, 92,], [ 476, 451, 226,], [ 522, 137, 45,], [ 45, 21, 522,], [ 522, 147, 137,], [ 92, 341, 453,], [ 92, 36, 422,], [ 92, 451, 36,], [ 447, 74, 22,], [ 22, 74, 466,], [ 466, 213, 22,], [ 527, 45, 447,], [ 45, 137, 447,], [ 137, 74, 447,], [ 527, 357, 45,], [ 527, 344, 73,], [ 73, 357, 527,], [ 45, 357, 21,], [ 487, 422, 59,], [ 59, 524, 143,], [ 59, 410, 524,], [ 422, 410, 59,], [ 524, 507, 143,], [ 143, 99, 265,], [ 143, 507, 99,], [ 524, 371, 507,], [ 36, 410, 422,], [ 36, 470, 410,], [ 99, 131, 73,], [ 99, 507, 131,], [ 131, 357, 73,], [ 538, 284, 281,], [ 272, 443, 274,], [ 379, 443, 272,], [ 385, 480, 242,], [ 375, 480, 385,], [ 540, 390, 440,], [ 280, 432, 281,], [ 281, 432, 538,], [ 276, 261, 280,], [ 261, 432, 280,], [ 274, 245, 276,], [ 276, 245, 261,], [ 274, 443, 245,], [ 242, 355, 159,], [ 242, 480, 355,], [ 159, 321, 52,], [ 159, 355, 321,], [ 52, 317, 540,], [ 321, 317, 52,], [ 540, 317, 390,], [ 538, 228, 113,], [ 432, 228, 538,], [ 113, 56, 550,], [ 113, 228, 56,], [ 550, 370, 142,], [ 56, 370, 550,], [ 142, 240, 88,], [ 142, 370, 240,], [ 88, 383, 407,], [ 240, 383, 88,], [ 407, 212, 461,], [ 407, 383, 212,], [ 461, 115, 542,], [ 212, 115, 461,], [ 542, 387, 396,], [ 542, 115, 387,], [ 396, 180, 375,], [ 387, 180, 396,], [ 180, 480, 375,], [ 318, 502, 379,], [ 502, 443, 379,], [ 531, 112, 318,], [ 318, 112, 502,], [ 501, 567, 531,], [ 567, 112, 531,], [ 492, 210, 501,], [ 501, 210, 567,], [ 14, 243, 492,], [ 243, 210, 492,], [ 420, 551, 14,], [ 14, 551, 243,], [ 570, 43, 420,], [ 43, 551, 420,], [ 555, 252, 570,], [ 570, 252, 43,], [ 26, 441, 555,], [ 441, 252, 555,], [ 390, 317, 26,], [ 26, 247, 441,], [ 317, 247, 26,], [ 383, 27, 212,], [ 27, 372, 212,], [ 372, 115, 212,], [ 387, 218, 180,], [ 387, 58, 218,], [ 387, 115, 58,], [ 370, 426, 240,], [ 426, 192, 240,], [ 192, 383, 240,], [ 194, 31, 56,], [ 56, 228, 194,], [ 56, 31, 370,], [ 321, 247, 317,], [ 321, 179, 530,], [ 530, 247, 321,], [ 355, 179, 321,], [ 544, 146, 243,], [ 243, 551, 544,], [ 146, 210, 243,], [ 43, 484, 544,], [ 544, 551, 43,], [ 252, 484, 43,], [ 529, 156, 567,], [ 567, 210, 529,], [ 156, 112, 567,], [ 502, 112, 452,], [ 452, 152, 502,], [ 502, 152, 245,], [ 245, 443, 502,], [ 261, 194, 432,], [ 261, 152, 194,], [ 245, 152, 261,], [ 441, 530, 231,], [ 231, 252, 441,], [ 441, 247, 530,], [ 194, 228, 432,], [ 194, 452, 31,], [ 194, 152, 452,], [ 218, 179, 180,], [ 180, 179, 355,], [ 355, 480, 180,], [ 58, 231, 218,], [ 231, 530, 218,], [ 530, 179, 218,], [ 58, 484, 231,], [ 58, 115, 372,], [ 372, 484, 58,], [ 231, 484, 252,], [ 370, 31, 426,], [ 426, 529, 192,], [ 426, 156, 529,], [ 31, 156, 426,], [ 529, 146, 192,], [ 192, 27, 383,], [ 192, 146, 27,], [ 529, 210, 146,], [ 452, 156, 31,], [ 452, 112, 156,], [ 27, 544, 372,], [ 27, 146, 544,], [ 544, 484, 372,], [ 272, 273, 379,], [ 283, 471, 282,], [ 513, 471, 283,], [ 440, 25, 354,], [ 390, 25, 440,], [ 28, 177, 509,], [ 277, 425, 273,], [ 273, 425, 379,], [ 279, 337, 277,], [ 337, 425, 277,], [ 282, 517, 279,], [ 279, 517, 337,], [ 282, 471, 517,], [ 354, 322, 211,], [ 354, 25, 322,], [ 211, 409, 118,], [ 211, 322, 409,], [ 118, 201, 28,], [ 409, 201, 118,], [ 28, 201, 177,], [ 379, 425, 318,], [ 318, 316, 531,], [ 318, 123, 316,], [ 425, 123, 318,], [ 531, 454, 501,], [ 316, 454, 531,], [ 501, 310, 492,], [ 501, 454, 310,], [ 492, 85, 14,], [ 310, 85, 492,], [ 14, 573, 420,], [ 14, 85, 573,], [ 420, 102, 570,], [ 573, 102, 420,], [ 570, 566, 555,], [ 570, 102, 566,], [ 555, 306, 26,], [ 566, 306, 555,], [ 26, 416, 390,], [ 26, 306, 416,], [ 416, 25, 390,], [ 187, 488, 513,], [ 488, 471, 513,], [ 309, 406, 187,], [ 406, 488, 187,], [ 176, 202, 309,], [ 309, 202, 406,], [ 414, 436, 176,], [ 436, 202, 176,], [ 546, 24, 414,], [ 414, 24, 436,], [ 515, 124, 546,], [ 124, 24, 546,], [ 320, 455, 515,], [ 515, 455, 124,], [ 520, 248, 320,], [ 248, 455, 320,], [ 177, 473, 520,], [ 520, 473, 248,], [ 201, 473, 177,], [ 24, 435, 436,], [ 435, 417, 436,], [ 417, 202, 436,], [ 406, 174, 488,], [ 406, 323, 174,], [ 406, 202, 323,], [ 455, 246, 124,], [ 246, 338, 124,], [ 338, 24, 124,], [ 565, 537, 248,], [ 248, 473, 565,], [ 248, 537, 455,], [ 337, 123, 425,], [ 337, 93, 162,], [ 162, 123, 337,], [ 517, 93, 337,], [ 504, 474, 573,], [ 573, 85, 504,], [ 474, 102, 573,], [ 310, 549, 504,], [ 504, 85, 310,], [ 454, 549, 310,], [ 168, 191, 566,], [ 566, 102, 168,], [ 191, 306, 566,], [ 416, 306, 568,], [ 568, 307, 416,], [ 416, 307, 322,], [ 322, 25, 416,], [ 409, 565, 201,], [ 409, 307, 565,], [ 322, 307, 409,], [ 316, 162, 523,], [ 523, 454, 316,], [ 316, 123, 162,], [ 565, 473, 201,], [ 565, 568, 537,], [ 565, 307, 568,], [ 174, 93, 488,], [ 488, 93, 517,], [ 517, 471, 488,], [ 323, 523, 174,], [ 523, 162, 174,], [ 162, 93, 174,], [ 323, 549, 523,], [ 323, 202, 417,], [ 417, 549, 323,], [ 523, 549, 454,], [ 455, 537, 246,], [ 246, 168, 338,], [ 246, 191, 168,], [ 537, 191, 246,], [ 168, 474, 338,], [ 338, 435, 24,], [ 338, 474, 435,], [ 168, 102, 474,], [ 568, 191, 537,], [ 568, 306, 191,], [ 435, 504, 417,], [ 435, 474, 504,], [ 504, 549, 417,], [ 513, 283, 8,], [ 1, 427, 287,], [ 429, 427, 1,], [ 509, 505, 219,], [ 177, 505, 509,], [ 30, 89, 358,], [ 285, 233, 8,], [ 8, 233, 513,], [ 10, 139, 285,], [ 139, 233, 285,], [ 287, 199, 10,], [ 10, 199, 139,], [ 287, 427, 199,], [ 219, 340, 552,], [ 219, 505, 340,], [ 552, 545, 299,], [ 552, 340, 545,], [ 299, 185, 30,], [ 545, 185, 299,], [ 30, 185, 89,], [ 513, 264, 187,], [ 233, 264, 513,], [ 187, 198, 309,], [ 187, 264, 198,], [ 309, 508, 176,], [ 198, 508, 309,], [ 176, 75, 414,], [ 176, 508, 75,], [ 414, 166, 546,], [ 75, 166, 414,], [ 546, 356, 515,], [ 546, 166, 356,], [ 515, 319, 320,], [ 356, 319, 515,], [ 320, 404, 520,], [ 320, 319, 404,], [ 520, 122, 177,], [ 404, 122, 520,], [ 122, 505, 177,], [ 120, 534, 429,], [ 534, 427, 429,], [ 351, 445, 120,], [ 120, 445, 534,], [ 485, 157, 351,], [ 157, 445, 351,], [ 84, 181, 485,], [ 485, 181, 157,], [ 506, 63, 84,], [ 63, 181, 84,], [ 87, 214, 506,], [ 506, 214, 63,], [ 104, 41, 87,], [ 41, 214, 87,], [ 346, 408, 104,], [ 104, 408, 41,], [ 393, 203, 346,], [ 203, 408, 346,], [ 89, 185, 393,], [ 393, 149, 203,], [ 185, 149, 393,], [ 166, 308, 356,], [ 308, 478, 356,], [ 478, 319, 356,], [ 404, 65, 122,], [ 404, 535, 65,], [ 404, 319, 535,], [ 508, 16, 75,], [ 16, 66, 75,], [ 66, 166, 75,], [ 262, 412, 198,], [ 198, 264, 262,], [ 198, 412, 508,], [ 545, 149, 185,], [ 545, 398, 130,], [ 130, 149, 545,], [ 340, 398, 545,], [ 116, 516, 63,], [ 63, 214, 116,], [ 516, 181, 63,], [ 41, 132, 116,], [ 116, 214, 41,], [ 408, 132, 41,], [ 374, 547, 157,], [ 157, 181, 374,], [ 547, 445, 157,], [ 534, 445, 342,], [ 342, 327, 534,], [ 534, 327, 199,], [ 199, 427, 534,], [ 139, 262, 233,], [ 139, 327, 262,], [ 199, 327, 139,], [ 203, 130, 98,], [ 98, 408, 203,], [ 203, 149, 130,], [ 262, 264, 233,], [ 262, 342, 412,], [ 262, 327, 342,], [ 65, 398, 122,], [ 122, 398, 340,], [ 340, 505, 122,], [ 535, 98, 65,], [ 98, 130, 65,], [ 130, 398, 65,], [ 535, 132, 98,], [ 535, 319, 478,], [ 478, 132, 535,], [ 98, 132, 408,], [ 508, 412, 16,], [ 16, 374, 66,], [ 16, 547, 374,], [ 412, 547, 16,], [ 374, 516, 66,], [ 66, 308, 166,], [ 66, 516, 308,], [ 374, 181, 516,], [ 342, 547, 412,], [ 342, 445, 547,], [ 308, 116, 478,], [ 308, 516, 116,], [ 116, 132, 478,], [ 1, 289, 429,], [ 293, 365, 6,], [ 46, 365, 293,], [ 358, 463, 335,], [ 89, 463, 358,], [ 220, 204, 196,], [ 4, 311, 289,], [ 289, 311, 429,], [ 291, 347, 4,], [ 347, 311, 4,], [ 6, 114, 291,], [ 291, 114, 347,], [ 6, 365, 114,], [ 335, 105, 302,], [ 335, 463, 105,], [ 302, 353, 237,], [ 302, 105, 353,], [ 237, 127, 220,], [ 353, 127, 237,], [ 220, 127, 204,], [ 429, 311, 120,], [ 120, 62, 351,], [ 120, 433, 62,], [ 311, 433, 120,], [ 351, 564, 485,], [ 62, 564, 351,], [ 485, 121, 84,], [ 485, 564, 121,], [ 84, 206, 506,], [ 121, 206, 84,], [ 506, 472, 87,], [ 506, 206, 472,], [ 87, 208, 104,], [ 472, 208, 87,], [ 104, 134, 346,], [ 104, 208, 134,], [ 346, 111, 393,], [ 134, 111, 346,], [ 393, 161, 89,], [ 393, 111, 161,], [ 161, 463, 89,], [ 467, 563, 46,], [ 563, 365, 46,], [ 34, 498, 467,], [ 498, 563, 467,], [ 439, 232, 34,], [ 34, 232, 498,], [ 495, 395, 439,], [ 395, 232, 439,], [ 172, 312, 495,], [ 495, 312, 395,], [ 119, 51, 172,], [ 51, 312, 172,], [ 42, 95, 119,], [ 119, 95, 51,], [ 183, 251, 42,], [ 251, 95, 42,], [ 204, 235, 183,], [ 183, 235, 251,], [ 127, 235, 204,], [ 312, 482, 395,], [ 482, 512, 395,], [ 512, 232, 395,], [ 498, 133, 563,], [ 498, 57, 133,], [ 498, 232, 57,], [ 95, 525, 51,], [ 525, 438, 51,], [ 438, 312, 51,], [ 491, 158, 251,], [ 251, 235, 491,], [ 251, 158, 95,], [ 347, 433, 311,], [ 347, 511, 444,], [ 444, 433, 347,], [ 114, 511, 347,], [ 449, 78, 472,], [ 472, 206, 449,], [ 78, 208, 472,], [ 121, 222, 449,], [ 449, 206, 121,], [ 564, 222, 121,], [ 60, 169, 134,], [ 134, 208, 60,], [ 169, 111, 134,], [ 161, 111, 548,], [ 548, 129, 161,], [ 161, 129, 105,], [ 105, 463, 161,], [ 353, 491, 127,], [ 353, 129, 491,], [ 105, 129, 353,], [ 62, 444, 539,], [ 539, 564, 62,], [ 62, 433, 444,], [ 491, 235, 127,], [ 491, 548, 158,], [ 491, 129, 548,], [ 133, 511, 563,], [ 563, 511, 114,], [ 114, 365, 563,], [ 57, 539, 133,], [ 539, 444, 133,], [ 444, 511, 133,], [ 57, 222, 539,], [ 57, 232, 512,], [ 512, 222, 57,], [ 539, 222, 564,], [ 95, 158, 525,], [ 525, 60, 438,], [ 525, 169, 60,], [ 158, 169, 525,], [ 60, 78, 438,], [ 438, 482, 312,], [ 438, 78, 482,], [ 60, 208, 78,], [ 548, 169, 158,], [ 548, 111, 169,], [ 482, 449, 512,], [ 482, 78, 449,], [ 449, 222, 512,], [ 293, 296, 46,], [ 305, 138, 303,], [ 200, 138, 305,], [ 196, 101, 334,], [ 204, 101, 196,], [ 44, 190, 141,], [ 297, 148, 296,], [ 296, 148, 46,], [ 301, 315, 297,], [ 315, 148, 297,], [ 303, 331, 301,], [ 301, 331, 315,], [ 303, 138, 331,], [ 334, 224, 421,], [ 334, 101, 224,], [ 421, 255, 532,], [ 421, 224, 255,], [ 532, 259, 44,], [ 255, 259, 532,], [ 44, 259, 190,], [ 46, 348, 467,], [ 148, 348, 46,], [ 467, 528, 34,], [ 467, 348, 528,], [ 34, 209, 439,], [ 528, 209, 34,], [ 439, 336, 495,], [ 439, 209, 336,], [ 495, 197, 172,], [ 336, 197, 495,], [ 172, 366, 119,], [ 172, 197, 366,], [ 119, 465, 42,], [ 366, 465, 119,], [ 42, 193, 183,], [ 42, 465, 193,], [ 183, 400, 204,], [ 193, 400, 183,], [ 400, 101, 204,], [ 258, 82, 200,], [ 82, 138, 200,], [ 53, 469, 258,], [ 258, 469, 82,], [ 83, 18, 53,], [ 18, 469, 53,], [ 91, 369, 83,], [ 83, 369, 18,], [ 571, 333, 91,], [ 333, 369, 91,], [ 160, 33, 571,], [ 571, 33, 333,], [ 15, 541, 160,], [ 541, 33, 160,], [ 29, 324, 15,], [ 15, 324, 541,], [ 558, 140, 29,], [ 140, 324, 29,], [ 190, 259, 558,], [ 558, 329, 140,], [ 259, 329, 558,], [ 197, 557, 366,], [ 557, 207, 366,], [ 207, 465, 366,], [ 193, 361, 400,], [ 193, 526, 361,], [ 193, 465, 526,], [ 209, 154, 336,], [ 154, 388, 336,], [ 388, 197, 336,], [ 386, 553, 528,], [ 528, 348, 386,], [ 528, 553, 209,], [ 255, 329, 259,], [ 255, 401, 54,], [ 54, 329, 255,], [ 224, 401, 255,], [ 40, 434, 333,], [ 333, 33, 40,], [ 434, 369, 333,], [ 541, 97, 40,], [ 40, 33, 541,], [ 324, 97, 541,], [ 55, 424, 18,], [ 18, 369, 55,], [ 424, 469, 18,], [ 82, 469, 128,], [ 128, 428, 82,], [ 82, 428, 331,], [ 331, 138, 82,], [ 315, 386, 148,], [ 315, 428, 386,], [ 331, 428, 315,], [ 140, 54, 345,], [ 345, 324, 140,], [ 140, 329, 54,], [ 386, 348, 148,], [ 386, 128, 553,], [ 386, 428, 128,], [ 361, 401, 400,], [ 400, 401, 224,], [ 224, 101, 400,], [ 526, 345, 361,], [ 345, 54, 361,], [ 54, 401, 361,], [ 526, 97, 345,], [ 526, 465, 207,], [ 207, 97, 526,], [ 345, 97, 324,], [ 209, 553, 154,], [ 154, 55, 388,], [ 154, 424, 55,], [ 553, 424, 154,], [ 55, 434, 388,], [ 388, 557, 197,], [ 388, 434, 557,], [ 55, 369, 434,], [ 128, 424, 553,], [ 128, 469, 424,], [ 557, 40, 207,], [ 557, 434, 40,], [ 40, 97, 207,], [ 200, 305, 304,], [ 294, 110, 295,], [ 72, 110, 294,], [ 141, 559, 225,], [ 190, 559, 141,], [ 556, 402, 76,], [ 300, 155, 304,], [ 304, 155, 200,], [ 298, 239, 300,], [ 239, 155, 300,], [ 295, 67, 298,], [ 298, 67, 239,], [ 295, 110, 67,], [ 225, 254, 368,], [ 225, 559, 254,], [ 368, 170, 462,], [ 368, 254, 170,], [ 462, 378, 556,], [ 170, 378, 462,], [ 556, 378, 402,], [ 200, 155, 258,], [ 258, 260, 53,], [ 258, 457, 260,], [ 155, 457, 258,], [ 53, 126, 83,], [ 260, 126, 53,], [ 83, 267, 91,], [ 83, 126, 267,], [ 91, 499, 571,], [ 267, 499, 91,], [ 571, 12, 160,], [ 571, 499, 12,], [ 160, 479, 15,], [ 12, 479, 160,], [ 15, 19, 29,], [ 15, 479, 19,], [ 29, 271, 558,], [ 19, 271, 29,], [ 558, 164, 190,], [ 558, 271, 164,], [ 164, 559, 190,], [ 392, 94, 72,], [ 94, 110, 72,], [ 268, 173, 392,], [ 173, 94, 392,], [ 403, 377, 268,], [ 268, 377, 173,], [ 165, 144, 403,], [ 144, 377, 403,], [ 38, 560, 165,], [ 165, 560, 144,], [ 70, 456, 38,], [ 456, 560, 38,], [ 256, 125, 70,], [ 70, 125, 456,], [ 64, 328, 256,], [ 328, 125, 256,], [ 402, 108, 64,], [ 64, 108, 328,], [ 378, 108, 402,], [ 560, 145, 144,], [ 145, 163, 144,], [ 163, 377, 144,], [ 173, 405, 94,], [ 173, 253, 405,], [ 173, 377, 253,], [ 125, 330, 456,], [ 330, 238, 456,], [ 238, 560, 456,], [ 20, 47, 328,], [ 328, 108, 20,], [ 328, 47, 125,], [ 239, 457, 155,], [ 239, 489, 418,], [ 418, 457, 239,], [ 67, 489, 239,], [ 81, 107, 12,], [ 12, 499, 81,], [ 107, 479, 12,], [ 267, 35, 81,], [ 81, 499, 267,], [ 126, 35, 267,], [ 411, 389, 19,], [ 19, 479, 411,], [ 389, 271, 19,], [ 164, 271, 17,], [ 17, 270, 164,], [ 164, 270, 254,], [ 254, 559, 164,], [ 170, 20, 378,], [ 170, 270, 20,], [ 254, 270, 170,], [ 260, 418, 61,], [ 61, 126, 260,], [ 260, 457, 418,], [ 20, 108, 378,], [ 20, 17, 47,], [ 20, 270, 17,], [ 405, 489, 94,], [ 94, 489, 67,], [ 67, 110, 94,], [ 253, 61, 405,], [ 61, 418, 405,], [ 418, 489, 405,], [ 253, 35, 61,], [ 253, 377, 163,], [ 163, 35, 253,], [ 61, 35, 126,], [ 125, 47, 330,], [ 330, 411, 238,], [ 330, 389, 411,], [ 47, 389, 330,], [ 411, 107, 238,], [ 238, 145, 560,], [ 238, 107, 145,], [ 411, 479, 107,], [ 17, 389, 47,], [ 17, 271, 389,], [ 145, 81, 163,], [ 145, 107, 81,], [ 81, 35, 163,], [ 2, 153, 290,], [ 151, 153, 2,], [ 294, 7, 72,], [ 554, 494, 221,], [ 76, 80, 360,], [ 402, 80, 76,], [ 292, 343, 7,], [ 7, 343, 72,], [ 5, 442, 292,], [ 442, 343, 292,], [ 290, 380, 5,], [ 5, 380, 442,], [ 290, 153, 380,], [ 360, 236, 32,], [ 360, 80, 236,], [ 32, 39, 278,], [ 32, 236, 39,], [ 278, 394, 554,], [ 39, 394, 278,], [ 554, 394, 494,], [ 460, 50, 151,], [ 50, 153, 151,], [ 227, 135, 460,], [ 460, 135, 50,], [ 96, 423, 227,], [ 423, 135, 227,], [ 500, 399, 96,], [ 96, 399, 423,], [ 79, 521, 500,], [ 521, 399, 500,], [ 496, 364, 79,], [ 79, 364, 521,], [ 477, 543, 496,], [ 543, 364, 496,], [ 230, 171, 477,], [ 477, 171, 543,], [ 186, 376, 230,], [ 376, 171, 230,], [ 494, 394, 186,], [ 186, 431, 376,], [ 394, 431, 186,], [ 72, 313, 392,], [ 343, 313, 72,], [ 392, 382, 268,], [ 392, 313, 382,], [ 268, 77, 403,], [ 382, 77, 268,], [ 403, 510, 165,], [ 403, 77, 510,], [ 165, 413, 38,], [ 510, 413, 165,], [ 38, 223, 70,], [ 38, 413, 223,], [ 70, 257, 256,], [ 223, 257, 70,], [ 256, 175, 64,], [ 256, 257, 175,], [ 64, 458, 402,], [ 175, 458, 64,], [ 458, 80, 402,], [ 413, 269, 223,], [ 269, 103, 223,], [ 103, 257, 223,], [ 175, 519, 458,], [ 175, 49, 519,], [ 175, 257, 49,], [ 77, 569, 510,], [ 569, 518, 510,], [ 518, 413, 510,], [ 314, 167, 382,], [ 382, 313, 314,], [ 382, 167, 77,], [ 39, 431, 394,], [ 39, 182, 450,], [ 450, 431, 39,], [ 236, 182, 39,], [ 464, 69, 521,], [ 521, 364, 464,], [ 69, 399, 521,], [ 543, 448, 464,], [ 464, 364, 543,], [ 171, 448, 543,], [ 205, 37, 423,], [ 423, 399, 205,], [ 37, 135, 423,], [ 50, 135, 234,], [ 234, 249, 50,], [ 50, 249, 380,], [ 380, 153, 50,], [ 442, 314, 343,], [ 442, 249, 314,], [ 380, 249, 442,], [ 376, 450, 483,], [ 483, 171, 376,], [ 376, 431, 450,], [ 314, 313, 343,], [ 314, 234, 167,], [ 314, 249, 234,], [ 519, 182, 458,], [ 458, 182, 236,], [ 236, 80, 458,], [ 49, 483, 519,], [ 483, 450, 519,], [ 450, 182, 519,], [ 49, 448, 483,], [ 49, 257, 103,], [ 103, 448, 49,], [ 483, 448, 171,], [ 77, 167, 569,], [ 569, 205, 518,], [ 569, 37, 205,], [ 167, 37, 569,], [ 205, 69, 518,], [ 518, 269, 413,], [ 518, 69, 269,], [ 205, 399, 69,], [ 234, 37, 167,], [ 234, 135, 37,], [ 269, 464, 103,], [ 269, 69, 464,], [ 464, 448, 103,], [ 221, 241, 554,], [ 385, 100, 359,], [ 242, 486, 385,], [ 486, 349, 385,], [ 349, 100, 385,], [ 540, 440, 354,], [ 509, 216, 28,], [ 219, 71, 509,], [ 71, 490, 509,], [ 490, 216, 509,], [ 335, 30, 358,], [ 196, 481, 220,], [ 334, 136, 196,], [ 136, 217, 196,], [ 217, 481, 196,], [ 141, 225, 44,], [ 76, 362, 556,], [ 360, 514, 76,], [ 514, 188, 76,], [ 188, 362, 76,], [ 275, 332, 241,], [ 241, 332, 554,], [ 339, 189, 275,], [ 278, 332, 275,], [ 275, 189, 278,], [ 359, 90, 339,], [ 90, 189, 339,], [ 359, 100, 90,], [ 159, 468, 242,], [ 242, 468, 486,], [ 52, 195, 159,], [ 159, 195, 468,], [ 540, 48, 52,], [ 52, 48, 211,], [ 211, 195, 52,], [ 354, 48, 540,], [ 211, 48, 354,], [ 118, 195, 211,], [ 28, 468, 118,], [ 468, 195, 118,], [ 28, 216, 468,], [ 552, 493, 219,], [ 219, 493, 71,], [ 299, 391, 552,], [ 552, 391, 493,], [ 30, 244, 299,], [ 299, 244, 302,], [ 302, 391, 299,], [ 335, 244, 30,], [ 302, 244, 335,], [ 237, 391, 302,], [ 220, 493, 237,], [ 493, 391, 237,], [ 220, 481, 493,], [ 421, 367, 334,], [ 334, 367, 136,], [ 532, 68, 421,], [ 421, 68, 367,], [ 44, 536, 532,], [ 532, 536, 368,], [ 368, 68, 532,], [ 225, 536, 44,], [ 368, 536, 225,], [ 462, 68, 368,], [ 556, 367, 462,], [ 367, 68, 462,], [ 556, 362, 367,], [ 32, 90, 360,], [ 360, 90, 514,], [ 278, 189, 32,], [ 32, 189, 90,], [ 554, 332, 278,], [ 71, 217, 490,], [ 71, 481, 217,], [ 493, 481, 71,], [ 490, 349, 486,], [ 486, 216, 490,], [ 490, 497, 349,], [ 217, 497, 490,], [ 514, 349, 188,], [ 514, 100, 349,], [ 90, 100, 514,], [ 188, 217, 136,], [ 136, 362, 188,], [ 367, 362, 136,], [ 349, 497, 188,], [ 188, 497, 217,], [ 468, 216, 486,],] diff --git a/scripts/rustbca.py b/scripts/rustbca.py index ab22fc2..2654c97 100644 --- a/scripts/rustbca.py +++ b/scripts/rustbca.py @@ -136,7 +136,7 @@ def do_trajectory_plot(name, thickness=None, depth=None, boundary=None, plot_fin plt.savefig(name+'trajectories_.png') plt.close() -def do_trajectory_plot_3d(name, thickness=None, depth=None, boundary=None, plot_final_positions=True, plot_origins=True, radius=None, cube_length=None): +def do_trajectory_plot_3d(name, thickness=None, depth=None, boundary=None, plot_final_positions=True, plot_origins=True, radius=None, cube_length=None, input_file=None): ''' Plots trajectories of ions and recoils from [name]trajectories.output. Optionally marks final positions/origins and draws material geometry. @@ -154,7 +154,7 @@ def do_trajectory_plot_3d(name, thickness=None, depth=None, boundary=None, plot_ ''' - from mayavi.mlab import points3d, plot3d, mesh + from mayavi.mlab import points3d, plot3d, mesh, triangular_mesh reflected = np.atleast_2d(np.genfromtxt(name+'reflected.output', delimiter=',')) sputtered = np.atleast_2d(np.genfromtxt(name+'sputtered.output', delimiter=',')) @@ -170,7 +170,7 @@ def do_trajectory_plot_3d(name, thickness=None, depth=None, boundary=None, plot_ index = 0 x_max = 0 min_length = 1 - scale_factor = 100.0 + scale_factor = 200.0 if np.size(trajectories) > 0: for trajectory_length in trajectory_data: @@ -224,7 +224,7 @@ def do_trajectory_plot_3d(name, thickness=None, depth=None, boundary=None, plot_ if cube_length: faces = [] - + xmin = -cube_length/2.*scale_factor xmax = cube_length/2.*scale_factor ymin = -cube_length/2.*scale_factor @@ -260,6 +260,15 @@ def do_trajectory_plot_3d(name, thickness=None, depth=None, boundary=None, plot_ x,y,z = grid mesh(x, y, z, opacity=0.4, color=(0.1,0.7,0.3)) + if input_file: + input = toml.load(input_file) + vertices = input['geometry_input']['vertices'] + triangles = input['geometry_input']['indices'] + x = [vertex[0]*scale_factor for vertex in vertices] + y = [vertex[1]*scale_factor for vertex in vertices] + z = [vertex[2]*scale_factor for vertex in vertices] + triangular_mesh(x, y, z, triangles, opacity=0.3, color=(0.1, 0.7, 0.3), representation='surface') + def generate_rustbca_input(Zb, Mb, n, Eca, Ecb, Esa, Esb, Eb, Ma, Za, E0, N, N_, theta, thickness, depth, track_trajectories=True, track_recoils=True, diff --git a/src/bca.rs b/src/bca.rs index 771acb7..95e5545 100644 --- a/src/bca.rs +++ b/src/bca.rs @@ -330,9 +330,16 @@ pub fn determine_mfp_phi_impact_parameter(particle_1: &mut particle mfp *= -rand::random::().ln(); } + if !material.inside(particle_1.pos.x, particle_1.pos.y, particle_1.pos.z) { + let (x, y, z) = material.geometry.closest_point(particle_1.pos.x, particle_1.pos.y, particle_1.pos.z); + let distance_to = ((x - particle_1.pos.x).powi(2) + (y - particle_1.pos.y).powi(2) + (z - particle_1.pos.z).powi(2)).sqrt(); + mfp += distance_to; + } + for k in 0..(options.weak_collision_order + 1) { binary_collision_geometries.push(BinaryCollisionGeometry::new(phis_azimuthal[k], impact_parameters[k], mfp)) } + return binary_collision_geometries; } } diff --git a/src/parry.rs b/src/parry.rs index 2e7f03e..39748d7 100644 --- a/src/parry.rs +++ b/src/parry.rs @@ -215,7 +215,7 @@ impl Geometry for ParryTriMesh { let total_density: f64 = densities.iter().sum(); let energy_barrier_thickness = total_density.powf(-1./3.)/SQRTPI*2.; let concentrations: Vec = densities.iter().map(|&density| density/total_density).collect::>(); - let points = input.vertices.iter().map(|p| Point::new(p[0]*length_unit , p[1]*length_unit , p[2]*length_unit )).collect(); + let points = input.vertices.iter().map(|p| Point::new(p[0]*length_unit , p[1]*length_unit , p[2]*length_unit)).collect(); let trimesh = TriMesh::new(points, input.indices.clone()); let boundary = trimesh.aabb(&Isometry::identity()); From 9f51c5d6b72ed5ff98f5378fd45949114e09a679 Mon Sep 17 00:00:00 2001 From: Jon Drobny Date: Mon, 15 Mar 2021 23:55:14 -0500 Subject: [PATCH 06/33] Made sure to add new worfklow before i forget. --- .github/workflows/rustbca_compile_check.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rustbca_compile_check.yml b/.github/workflows/rustbca_compile_check.yml index 2bff114..ee02efa 100644 --- a/.github/workflows/rustbca_compile_check.yml +++ b/.github/workflows/rustbca_compile_check.yml @@ -37,7 +37,7 @@ jobs: sudo apt install libhdf5-dev - name: Test RustBCA run: | - cargo test --features cpr_rootfinder_netlib,hdf5_input,distributions + cargo test --features cpr_rootfinder_netlib,hdf5_input,distributions,parry - name: Run Examples run: | cargo run --release 0D examples/boron_nitride_0D.toml @@ -48,4 +48,4 @@ jobs: ./target/release/rustBCA examples/layered_geometry.toml cat 2000.0eV_0.0001deg_He_TiO2_Al_Sisummary.output ./target/release/rustBCA SPHERE examples/boron_nitride_sphere.toml - + cargo run --release --features parry TRIMESH examples/tungsten_twist_trimesh.toml From 353c5275e8dee752359266e6c3a2842864518d52 Mon Sep 17 00:00:00 2001 From: Jon Drobny Date: Tue, 16 Mar 2021 00:10:43 -0500 Subject: [PATCH 07/33] Acclerated vacuum transport was breaking the momentum conservation test because it was trying to find a single point of an empty material. I've moved it out into single_ion_bca() and also made it an optional feature. --- src/bca.rs | 12 +++++++----- src/geometry.rs | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/bca.rs b/src/bca.rs index 95e5545..d6b7f97 100644 --- a/src/bca.rs +++ b/src/bca.rs @@ -84,6 +84,13 @@ pub fn single_ion_bca(particle: particle::Particle, material: &mate //Choose impact parameters and azimuthal angles for all collisions, and determine mean free path let binary_collision_geometries = bca::determine_mfp_phi_impact_parameter(&mut particle_1, &material, &options); + #[cfg(feature = "accelerated_ions")] + if !material.inside(particle_1.pos.x, particle_1.pos.y, particle_1.pos.z) { + let (x, y, z) = material.geometry.closest_point(particle_1.pos.x, particle_1.pos.y, particle_1.pos.z); + let distance_to = ((x - particle_1.pos.x).powi(2) + (y - particle_1.pos.y).powi(2) + (z - particle_1.pos.z).powi(2)).sqrt(); + mfp += distance_to; + } + let mut total_energy_loss = 0.; let mut total_asymptotic_deflection = 0.; let mut normalized_distance_of_closest_approach = 0.; @@ -330,11 +337,6 @@ pub fn determine_mfp_phi_impact_parameter(particle_1: &mut particle mfp *= -rand::random::().ln(); } - if !material.inside(particle_1.pos.x, particle_1.pos.y, particle_1.pos.z) { - let (x, y, z) = material.geometry.closest_point(particle_1.pos.x, particle_1.pos.y, particle_1.pos.z); - let distance_to = ((x - particle_1.pos.x).powi(2) + (y - particle_1.pos.y).powi(2) + (z - particle_1.pos.z).powi(2)).sqrt(); - mfp += distance_to; - } for k in 0..(options.weak_collision_order + 1) { binary_collision_geometries.push(BinaryCollisionGeometry::new(phis_azimuthal[k], impact_parameters[k], mfp)) diff --git a/src/geometry.rs b/src/geometry.rs index 0d59108..0cce49e 100644 --- a/src/geometry.rs +++ b/src/geometry.rs @@ -427,7 +427,7 @@ impl Geometry for Mesh2D { fn get_densities_nearest_to(&self, x: f64, y: f64, z: f64) -> &Vec { self.nearest_to(x, y, z).get_densities() } - + fn inside_simulation_boundary(&self, x: f64, y: f64, z: f64) -> bool { self.simulation_boundary.contains(&point!(x: x, y: y)) } From 4653945acfd43b0b35897eb78cf0a8e3912a9de8 Mon Sep 17 00:00:00 2001 From: Jon Drobny Date: Tue, 16 Mar 2021 00:19:51 -0500 Subject: [PATCH 08/33] Cargo.toml update to keep up with semantic versioning. --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7dcf149..ae21690 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustBCA" -version = "0.1.0" +version = "1.1.0" authors = ["Jon Drobny "] edition = "2018" @@ -41,3 +41,4 @@ cpr_rootfinder_intel_mkl = ["rcpr", "intel-mkl-src"] distributions = ["ndarray"] no_list_output = [] parry = ["parry3d-f64"] +accelerated_ions = [] From 5e7852a69cbf7c6b27ebb3f2e092d364e6fba262 Mon Sep 17 00:00:00 2001 From: Jon Drobny Date: Tue, 16 Mar 2021 11:48:25 -0500 Subject: [PATCH 09/33] Added some tests of the parry3d geometry. --- Cargo.toml | 2 +- src/enums.rs | 8 +-- src/main.rs | 12 ++-- src/tests.rs | 187 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 198 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ae21690..06a44b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,5 +40,5 @@ cpr_rootfinder_netlib = ["rcpr", "netlib-src"] cpr_rootfinder_intel_mkl = ["rcpr", "intel-mkl-src"] distributions = ["ndarray"] no_list_output = [] -parry = ["parry3d-f64"] +parry3d = ["parry3d-f64"] accelerated_ions = [] diff --git a/src/enums.rs b/src/enums.rs index d02ffbd..c3aa849 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -5,9 +5,9 @@ pub enum MaterialType { MESH1D(material::Material), MESH2D(material::Material), SPHERE(material::Material), - #[cfg(feature = "parry")] + #[cfg(feature = "parry3d")] BALL(material::Material), - #[cfg(feature = "parry")] + #[cfg(feature = "parry3d")] TRIMESH(material::Material) } @@ -17,9 +17,9 @@ pub enum GeometryType { MESH1D, MESH2D, SPHERE, - #[cfg(feature = "parry")] + #[cfg(feature = "parry3d")] BALL, - #[cfg(feature = "parry")] + #[cfg(feature = "parry3d")] TRIMESH, } diff --git a/src/main.rs b/src/main.rs index c765da2..64b8bd0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -60,7 +60,7 @@ pub mod consts; pub mod structs; pub mod sphere; -#[cfg(feature = "parry")] +#[cfg(feature = "parry3d")] pub mod parry; pub use crate::enums::*; @@ -71,7 +71,7 @@ pub use crate::output::{OutputUnits}; pub use crate::geometry::{Geometry, GeometryElement, Mesh0D, Mesh1D, Mesh2D}; pub use crate::sphere::{Sphere, SphereInput, InputSphere}; -#[cfg(feature = "parry")] +#[cfg(feature = "parry3d")] pub use crate::parry::{ParryBall, ParryBallInput, InputParryBall, ParryTriMesh, ParryTriMeshInput, InputParryTriMesh}; fn physics_loop(particle_input_array: Vec, material: material::Material, options: Options, output_units: OutputUnits) { @@ -164,9 +164,9 @@ fn main() { "1D" => GeometryType::MESH1D, "2D" => GeometryType::MESH2D, "SPHERE" => GeometryType::SPHERE, - #[cfg(feature = "parry")] + #[cfg(feature = "parry3d")] "BALL" => GeometryType::BALL, - #[cfg(feature = "parry")] + #[cfg(feature = "parry3d")] "TRIMESH" => GeometryType::TRIMESH, _ => panic!("Unimplemented geometry {}.", args[1].clone()) }), @@ -190,12 +190,12 @@ fn main() { let (particle_input_array, material, options, output_units) = input::input::(input_file); physics_loop::(particle_input_array, material, options, output_units); }, - #[cfg(feature = "parry")] + #[cfg(feature = "parry3d")] GeometryType::BALL => { let (particle_input_array, material, options, output_units) = input::input::(input_file); physics_loop::(particle_input_array, material, options, output_units); } - #[cfg(feature = "parry")] + #[cfg(feature = "parry3d")] GeometryType::TRIMESH => { let (particle_input_array, material, options, output_units) = input::input::(input_file); physics_loop::(particle_input_array, material, options, output_units); diff --git a/src/tests.rs b/src/tests.rs index a3b327b..1a5bedc 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -3,6 +3,193 @@ use super::*; #[cfg(test)] use float_cmp::*; +#[test] +#[cfg(feature = "parry3d")] +fn test_parry_cuboid() { + let mass = 1.; + let Z = 1.; + let E = 10.*EV; + let Ec = 1.*EV; + let Es = 5.76*EV; + let x = 0.; + let y = 0.; + let z = 0.; + let cosx = 1./(2.0_f64).sqrt(); + let cosy = 1./(2.0_f64).sqrt(); + let cosz = 0.; + let particle_1 = particle::Particle::new(mass, Z, E, Ec, Es, x, y, z, cosx, cosy, cosz, false, false, 0); + + let material_parameters = material::MaterialParameters{ + energy_unit: "EV".to_string(), + mass_unit: "AMU".to_string(), + Eb: vec![0.0, 0.0], + Es: vec![2.0, 4.0], + Ec: vec![1.0, 1.0], + Z: vec![29., 1.], + m: vec![63.54, 1.0008], + interaction_index: vec![0, 0], + surface_binding_model: SurfaceBindingModel::PLANAR{calculation: SurfaceBindingCalculation::TARGET}, + bulk_binding_model: BulkBindingModel::INDIVIDUAL, + }; + + let radius = 1000.*ANGSTROM; + + //cuboid centered at (0, 0, 0) with l=w=h=1 Angstrom + let geometry_input = parry::ParryTriMeshInput { + length_unit: "ANGSTROM".to_string(), + densities: vec![0.03, 0.03], + electronic_stopping_correction_factor: 1.0, + vertices: vec![ + [-0.5, -0.5, 0.5], + [0.5, -0.5, 0.5], + [-0.5, 0.5, 0.5], + [0.5, 0.5, 0.5], + [-0.5, 0.5, -0.5], + [0.5, 0.5, -0.5], + [-0.5, -0.5, -0.5], + [0.5, -0.5, -0.5], + ], + indices: vec![ + [0, 1, 3], + [0, 3, 2], + [2, 3, 5], + [2, 5, 4], + [4, 5, 7], + [4, 7, 6], + [6, 7, 1], + [6, 1, 0], + [1, 7, 5], + [1, 5, 3], + [6, 0, 2], + [6, 2, 4] + ] + }; + + let mut material_cuboid: material::Material = material::Material::::new(&material_parameters, &geometry_input); + material_cuboid.geometry.energy_barrier_thickness = 10.*ANGSTROM; + + let surface_binding_energy = material_cuboid.actual_surface_binding_energy(&particle_1, particle_1.pos.x, particle_1.pos.y, particle_1.pos.z); + assert!(approx_eq!(f64, surface_binding_energy/EV, (2. + 4.)/2., epsilon=1E-24)); + + //Test origin is inside + assert!(material_cuboid.geometry.inside(0., 0., 0.)); + + //Test all outside areas for containment + assert!(!material_cuboid.geometry.inside(0., 1., 0.)); + assert!(!material_cuboid.geometry.inside(0., -1., 0.)); + assert!(!material_cuboid.geometry.inside(1., 0., 0.)); + assert!(!material_cuboid.geometry.inside(-1., 0., 0.)); + assert!(!material_cuboid.geometry.inside(0., 0., -1.)); + assert!(!material_cuboid.geometry.inside(0., 0., -1.)); + + //distance to origin + let (x, y, z) = material_cuboid.geometry.closest_point(0., 0., 0.); + assert!(approx_eq!(f64, (x.powi(2) + y.powi(2) + z.powi(2)).sqrt(), 0.5*ANGSTROM, epsilon=1E-24)); + + //distance to wall should be zero at wall + let (x, y, z) = material_cuboid.geometry.closest_point(0.5*ANGSTROM, 0., 0.); + assert!(approx_eq!(f64, ((x - 0.5*ANGSTROM).powi(2) + y.powi(2) + z.powi(2)).sqrt(), 0.0, epsilon=1E-24)); +} + +#[test] +#[cfg(feature = "parry3d")] +fn test_parry_sphere() { + let mass = 1.; + let Z = 1.; + let E = 10.*EV; + let Ec = 1.*EV; + let Es = 5.76*EV; + let x = 0.; + let y = 0.; + let z = 0.; + let cosx = 1./(2.0_f64).sqrt(); + let cosy = 1./(2.0_f64).sqrt(); + let cosz = 0.; + let mut particle_1 = particle::Particle::new(mass, Z, E, Ec, Es, x, y, z, cosx, cosy, cosz, false, false, 0); + + let material_parameters = material::MaterialParameters{ + energy_unit: "EV".to_string(), + mass_unit: "AMU".to_string(), + Eb: vec![0.0, 0.0], + Es: vec![2.0, 4.0], + Ec: vec![1.0, 1.0], + Z: vec![29., 1.], + m: vec![63.54, 1.0008], + interaction_index: vec![0, 0], + surface_binding_model: SurfaceBindingModel::PLANAR{calculation: SurfaceBindingCalculation::TARGET}, + bulk_binding_model: BulkBindingModel::INDIVIDUAL, + }; + + let radius = 1000.*ANGSTROM; + + let geometry_input_sphere = parry::ParryBallInput { + length_unit: "ANGSTROM".to_string(), + radius: 1000.0, + densities: vec![0.03, 0.03], + electronic_stopping_correction_factor: 1.0 + }; + + let mut material_sphere: material::Material = material::Material::::new(&material_parameters, &geometry_input_sphere); + material_sphere.geometry.energy_barrier_thickness = 10.*ANGSTROM; + + particle_1.pos.x = 500.*ANGSTROM; + particle_1.pos.y = 0.; + + particle_1.pos_old.x = -1500.*ANGSTROM; + particle_1.pos_old.y = 0.; + + let inside_sphere = material_sphere.inside(particle_1.pos.x, particle_1.pos.y, particle_1.pos.z); + let inside_old_sphere = material_sphere.inside(particle_1.pos_old.x, particle_1.pos_old.y, particle_1.pos_old.z); + + //println!("{} {}", inside, inside_old); + assert!(inside_sphere); + assert!(!inside_old_sphere); + + //Test concentration-dependent surface binding energy + let surface_binding_energy = material_sphere.actual_surface_binding_energy(&particle_1, particle_1.pos.x, particle_1.pos.y, particle_1.pos.z); + assert!(approx_eq!(f64, surface_binding_energy/EV, (2. + 4.)/2., epsilon=1E-24)); + + assert!(material_sphere.inside_energy_barrier(0., 0., 0.)); + assert!(material_sphere.inside_simulation_boundary(0., 0., 0.)); + assert!(material_sphere.inside(0., 0., 0.)); + + let ux = 1214.0*ANGSTROM; + let uy = 123123.0*ANGSTROM; + let uz = 1239.0*ANGSTROM; + let r = (ux.powi(2) + uy.powi(2) + uz.powi(2)).sqrt(); + let R = 1001.0*ANGSTROM; + let u = (ux/r*R, uy/r*R, uz/r*R); + assert!(!material_sphere.inside(u.0, u.1, u.2)); + assert!(material_sphere.inside_energy_barrier(u.0, u.1, u.2)); + assert!(material_sphere.inside_simulation_boundary(u.0, u.1, u.2)); + + assert!(material_sphere.inside_energy_barrier(0., 0., 0.)); + assert!(material_sphere.inside_simulation_boundary(0., 0., 0.)); + assert!(material_sphere.inside(0., 0., 0.)); + + assert!(material_sphere.inside_energy_barrier(-1000.0*ANGSTROM - 1.0*ANGSTROM, 0., 0.)); + assert!(!material_sphere.inside_energy_barrier(-1000.0*ANGSTROM - 11.0*ANGSTROM, 0., 0.)); + + assert!(material_sphere.inside_energy_barrier(0., -1000.0*ANGSTROM - 1.0*ANGSTROM, 0.)); + assert!(!material_sphere.inside_energy_barrier(0., -1000.0*ANGSTROM - 11.0*ANGSTROM, 0.)); + + assert!(material_sphere.inside_energy_barrier(0., 0., -1000.0*ANGSTROM - 1.0*ANGSTROM)); + assert!(!material_sphere.inside_energy_barrier(0., 0., -1000.0*ANGSTROM - 11.0*ANGSTROM)); + + assert!(material_sphere.inside(-1000.0*ANGSTROM + 1.0*ANGSTROM, 0., 0.)); + assert!(!material_sphere.inside(-1000.0*ANGSTROM - 1.0*ANGSTROM, 0., 0.)); + + assert!(material_sphere.inside(0., -1000.0*ANGSTROM + 1.0*ANGSTROM, 0.)); + assert!(!material_sphere.inside(0., -1000.0*ANGSTROM - 1.0*ANGSTROM, 0.)); + + assert!(material_sphere.inside(0., 0., -1000.0*ANGSTROM + 1.0*ANGSTROM)); + assert!(!material_sphere.inside(0., 0., -1000.0*ANGSTROM - 1.0*ANGSTROM)); + + assert!(approx_eq!(f64, material_sphere.closest_point(-2000.*ANGSTROM, 0., 0.).0, (-1000.*ANGSTROM, 0., 0.).0, epsilon=1E-12)); + assert!(approx_eq!(f64, material_sphere.closest_point(0., -2000.*ANGSTROM, 0.).1, (0., -1000.*ANGSTROM, 0.).1, epsilon=1E-12)); + assert!(approx_eq!(f64, material_sphere.closest_point(0., 0., -2000.*ANGSTROM).2, (0., 0., -1000.*ANGSTROM).2, epsilon=1E-12)); +} + #[test] #[cfg(feature = "distributions")] fn test_distributions() { From 738e235d3135eb3fb8b11e8c53fba9ea5f1b9a63 Mon Sep 17 00:00:00 2001 From: Jon Drobny Date: Tue, 16 Mar 2021 11:51:28 -0500 Subject: [PATCH 10/33] Updated workflow. --- .github/workflows/rustbca_compile_check.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rustbca_compile_check.yml b/.github/workflows/rustbca_compile_check.yml index ee02efa..9c57900 100644 --- a/.github/workflows/rustbca_compile_check.yml +++ b/.github/workflows/rustbca_compile_check.yml @@ -37,7 +37,7 @@ jobs: sudo apt install libhdf5-dev - name: Test RustBCA run: | - cargo test --features cpr_rootfinder_netlib,hdf5_input,distributions,parry + cargo test --features cpr_rootfinder_netlib,hdf5_input,distributions,parry3d - name: Run Examples run: | cargo run --release 0D examples/boron_nitride_0D.toml @@ -48,4 +48,4 @@ jobs: ./target/release/rustBCA examples/layered_geometry.toml cat 2000.0eV_0.0001deg_He_TiO2_Al_Sisummary.output ./target/release/rustBCA SPHERE examples/boron_nitride_sphere.toml - cargo run --release --features parry TRIMESH examples/tungsten_twist_trimesh.toml + cargo run --release --features parry3d TRIMESH examples/tungsten_twist_trimesh.toml From 950c66720957178ad83a802a463f422464e3b4f0 Mon Sep 17 00:00:00 2001 From: Jon Drobny <37962344+drobnyjt@users.noreply.github.com> Date: Tue, 16 Mar 2021 12:11:41 -0500 Subject: [PATCH 11/33] Add files via upload --- docs/triangular_mesh/castellated.png | Bin 0 -> 70121 bytes docs/triangular_mesh/cube_2.png | Bin 0 -> 100234 bytes docs/triangular_mesh/mayavi_two_of_them.png | Bin 0 -> 210931 bytes docs/triangular_mesh/tungsten_twist.png | Bin 0 -> 110497 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/triangular_mesh/castellated.png create mode 100644 docs/triangular_mesh/cube_2.png create mode 100644 docs/triangular_mesh/mayavi_two_of_them.png create mode 100644 docs/triangular_mesh/tungsten_twist.png diff --git a/docs/triangular_mesh/castellated.png b/docs/triangular_mesh/castellated.png new file mode 100644 index 0000000000000000000000000000000000000000..3350235ac25d618b16823863e98a0e4c68ccf9a0 GIT binary patch literal 70121 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 literal 0 HcmV?d00001 diff --git a/docs/triangular_mesh/cube_2.png b/docs/triangular_mesh/cube_2.png new file mode 100644 index 0000000000000000000000000000000000000000..224128111a30866d60abe1b117c86ece6e8d077d GIT binary patch literal 100234 zcmeEtWm}YC*Dl?i(k)0TAPpleA`Mc~-Jx`hv^0WJ0z(N{bV;Xx#DE~(AvM6z0}M0! z#^-tWaqM5Pzq}uYVFs>RajtW%Yu)j>+Ulf4j6@h17^E6cRrE11u#mv3j{pz&=C#kJ z4)BKgN?%U^n9)kgTTg?lMMgA*AYE zX#N)3d-Ra_>K8uaOkmLE*r9gn4yC-%7gZyN7Y?5yr;9$txou0c7JPV`JQ7WS6AsC7 zF6_a6f=ep9r%#MPK5mLZx8?Xx1|4-LG=I1 z0Pu?cZ+7s{Pa}820N>o4z13Q?JX_}S93@#n)vc-_GAWb>QF^Pq(G2BGkszgsKKz;zawwM}Cc{5BHAu~}by zH=gT|8~0{g`fXPyeGFJ>&_yW%SWvBx4+r>irwdJid-vs&yDzn~^~f%uuS_DqB1Gs- zc9TYI-W-T9Jig*7SJMKbqF5*iQf#x7WZTgvJ#(a9xjr_wj zm_(l@qE+(xf;l=I7lPJ78=Uber4*d?vY~*Z9#Jb1NZZQIiVV2vEDdfTJDA!75Ogt1 zUS8X}Nrya}TOF2`Q3`r0a`|fhzEpF^gm2TMkt$@dyS=j}$*dZK6I3Nl00fdzl3zeu z-mP6wuQ>*C*@H^mS=(Pww>wf79CW?1Kvc%5O~yz_P|7Eym8kQ`11fmK*TlhhCb$%Tx5huARlQZZonR{aC!Uki12y6o zMbRq4_(qext5v-E4?p@53J+|md~j+dZCs*r-USUpWQZODWbu5sN1HSC^eF7jE^(Hwg_+6`J(z`s zXEX5529juqE=sn!V{hB}5D>D<7c*&Lm((wBmM@ETeZJi;>98Mqs;Co612#s;!ApgE zGr3WUf4Aw>`;+26E0~p`Cl-0_$HI&s_nlF&D)uYOGXkX)dcbA}bxW*u)E?Yz8C(y8 zWkzVu_*|9fykmfLU3-C|z}tyCd>jM+PB*Jp!7Cz~@Xw5RSC*FepRt7}2tY!~)mmeI z>7a@2zp??_Mci$dnStnH(=RFb&SKF+Q|=ApLPD!OH*7Yxo|v+ioe}4AE9&Zm3}koP z#ZQTpmwsB#b7%7C&E_A?NxM3osctK`n3_gif30rmpB|V22D%2=GAz+$4zW}jSryH> zX~=5OlAsf_FzD=gM&Qe+SIw)wCXR=S<3o^3c9;YxYsS>wyvnL7ZfT5MhN47eo(;ka zyL2qS%(-r$8@?%y(Q*9zou-dF$Z*k-*%gorSxai8lTT0LBs$-0&i{MoRp_<4j=Drd zGP(C|uMUN#Q}snEnAqk(@oqC5YF>mvl~tMk92GJ*9(wHkVibSwQQ%P6(}JL3%<>6& zujOVz`)ULXWFShW41Pmo?&s`VPr|(kVD6#5Ub$5PY8pM$1ZR3O9CK3Xw8$U{J zg5v0|R}@nBaTL2!#DMMO;XsMbk!l3e46JxyBJ@C_$aH>3Aj3x8D+(Tlc30d4JJtW0 zCO()dyfeAWfn%j%wT_w*yx%AQ4<RE7`12FX28)Zu#issWR5*Fwcwjf0&M`<7`Zca= zm#g}E?OUgTd7M}fgJul*yIKqTDAzN+P4;8!PUb3kKdw!2_{|h4%t(-^b?tyTVM2bh zP{`|uz1^Gam2o8p0hS@?hCyZWZ3>lWsBA&-`gXbl^cZ?Q5OFjWLxf`?Bt}V| zqbe2)-CYbvJNgehyft3#{*#H+H){r4=w%0v8 zhFNqIH~m(3j^H_!nZbO|H1nZkK^IAGv)#|NkL}fohS$~anwEgTis z{7Cr=MG>VRsiv~qH&2j&q-g+|{f;Jb3&3`GfZu6O?skLSO0G9uo!d?{r1$X9OP%f5 z@y^yJRK<|J8}(z?#%_MxV-_Tm^S?7R4A~Z=bfvTR={eLR91%lB3zg!>psZK0CapIW z{Y9JQ98UMyx1Amx*2DF-+1^opoG5{$I_?un=R3!OoAYB$(F-4Cv#-uB@AlX2+@yQ8 z3yyFUufbzmE{P5H4r9RL~os*iF z2dw!qAyiS+$Y)oqF#t1?E(+&dL|#ZdoBrb0r>|j8L|uI?e=ndTC56J*7XTCK*CG4+ z$qdw=f0N~Yq=tW2z^Y^NhZD-te;*OFoxVX-ui41fFocN%T&9E(mEmOQdMA4ng2qwA zs^7$gX!N^Q^JQm33KcM$2Dvmvm5kU?kisu{@&l2|I2)^cDQeHD{uQEHx=e&1ElMsJ zm`J=Ny6GL1U+;`m1TONUTe1Hg>7^!B>*5U;9dG`OYW zgeSB2gST7|?RZ$rRV$Br^Mjgd>Gj+p5hY*%?e2IM18Tzix8v;<3{|^pbMf1VhJLKD zVYJeCB~;_P)KI(%U}QNdwVBJcFTWvAh;BUIv-6oXi-{bq_pYSKvV=}uF@(z=o1QrX zOiWn}S>l%MbQ|CwK>0FIAa4n(rERm3xYBiV-2|YfHXRYNeDj@ zT>OP#;VA9d7K3NB>`n=~Ct|PzsWJZ-6T?VQi8_^%HZic;@WW{EWUa2_Od&B_X!MZS zwytGcDoO;*2n6xXB1ROYukQKzfQGR{>HUCl6E@{lGkCH@>kPTmt-0xP|2FpC-JX;m z-%T`iE%Fbig%?dF{|nQ(OT4`IM&Xbrye>acenAEF9}tW8-XoGkNS=izIB`_eD8@tY zeLzt7GL8eD^~)3t#B9~^p84CWCeOl-j)X%Hk{gKGLn;yAr}?qEoouODh$O|a=So95 zaf!iD|Cw=3G^iLjiyAF%_{q2)4tmL#Vf{mZ_bxzQMz(4$PCM~m9I64FD2ETuCPs82 zGSG?;L?fwiOi9iC`R!h%O>XECy}B0=qDK$0Qcb-dokT@+vl&Fjg*=_RZSZ6h14K*@ zLgFl`vHa=0txEH_mgnaE2&C=mFp({Dnu|+~N&A-wlzeF=n9}pNgU3>Yfq7KJ73r^t zBoNz;UEcTG)13)`)oMpU2qb$K6l(>E<6~ZBXPdt%<=gS_(x2t!ioilYI>~(z$R)z7 zK~+}Em~a%%I(8=8pyh8K*APZ;`8CPP9;8%!3x3P01!N=jf%lN)oXD!jW^Z})pC;3e zAJy9^G_$a9Y}%J}fpGW`M^+M_?bb>@638(XOhmSyV)#2S_yEDeMA8^PP1r7WS#9 zJ`1IhN*1EK2FA(H|wn z5i=HS$A*mY?qmmMxDp>u(ytH4SNoPFs7eL`^QnKMO^r{yTh{ zA{?zVUF8Grh+pqv7)&54v5WzrSe-(Yu@p#%;?Nwy5at@58s_#UaKx_S)YeYtV8&}B zHP12YR0vp?0-X($eCChsXtDr1L;@JNVWy zijBOUQ*Ixfa47&$0o;hf*DNrF*f^czE2F(>gvUMj4ODn5=pOO`G9x1a3r$coj&^VB zb5aoC@H=?*gu?nrn5+c>r(#>^1&^WcIqjoG0XPRtq^s6_XNcxZCDMWsC$Nn0QDBgO zHjtBY7k7bdog2|tWhVfHr1Eup9Q0IvtA(R6w8N-D(6&_j%T6Gt1oG>tIjmM@J|Y^U znEhFJ@WIX?YWy2WT7wUs%14BP0-+=HQsVbfDCx>x&wLv(@3(`_IQ%^v;3R5y)*-r% zm*^+9R@uS{!4cIWSl$naE?(7%Wx+*Js&}JZ+WN}hXFj~RpofV(iEeK_t4K^eP~lNP z^Z(nt-sEGU!33$vpihbnjZl61Oq zi?ik! z!NGUlsdn712n{c52d~9!g28+Cul(Gq5q75Vn>aQETd^tF z_az@3VoQT$%hGZ+LF~LhbSzYM35$@L1y0W#pN_P`N&b0u#>lzc;AieL@0v=2Ca))F zR?7*e4|UhV~hqVdkSvLPvSyX@1>FyfBMIEGBbHy|98;-Ksfb^c?%e7!!sT8%VX zQTtMGneRJ;$3?)>GypfJ7`*@id$p&bS-GW(EbaRIO^wNKDPqNK9h7?eC?Oe4EuRY% zXEZ1p*PlD}%!Tb@nE9~c0R~g8yULqdw2B_=rGl`-*ZjYD|I)1Pu?RLkcGx45+vw~K z5RfcZ2Pzm0juySl_3Kl1@T;Kq8&@U|7M|R5Ui@S%P|$rM6n0zRcF)n0>h-}#IE&cc zdL3TKKGXYtDPZg1D8Tg9?o3}a2>}c5itM0ERMfYZ3d4^0lbS*%32cHL*o25m5hM%o zl+R%bu-k8B{(rxFbj`P5iwGWH-R+<0-~Ov>1et<4F@*}=bfFdw&e?IgS62xY>rk6o zcQdHg-C_g4h+u8=6oRc%FY^l^MEw{n(VYV~TjVAG4nZf8c-2PA>owrAaPh8jU5*6M z`&Xd$agiJeHe5fw4S8yTriyTj<5AG|OKxvZt}Wbvo`RFy{($}yWSULOaE0;}1*M() zcTMj{+qw1!jXO_6VDz{syECp!`-|70gKpp)&mA5Envml^HjO&1?kivDTG<4@9s1ju z_%Fmrox3v?b1F}qRcEevNS1Jw#6*IEfC|@UL;l4a%3YuBJt9!J%@X@!410D9U;^|* ziT{jmK+wHE=b3UUZ_f~8`AXdC?8L-`3UGQ;zyc(F=bdf7Jhq|@03d;$e)Ftsq-i_BzLP> z+;Ljh|E$^d>Sc2}v+&eb>;8N!W+zw_upDtf^>@jQ%xE`Qcl{N0#BeyC8ui6k*z6XM zzj@g|th6oIR@5o$@z0B7?-J8`+wCkk9Y#%q)Zqorshc-FUgbh0jI{RAUEc^<69gSC zczG<|4YYh`E(U;nkRKi~vbgw(o>6lOX_aJ~>^>`g?m5T;IH~e0Gp8G#Ag}mTtnv=R z@_%^bYj}r8Z+QX*vaPr7CM z^|%qkj~vmYP}(sy#_+1{@Ozk8P(_{)x<~u+p|kMn{MiJBNQ&C5mGU=gmOAA#ak0pzUNJf$c{KW-a|Rao#d%Ggk2W#+wOd_@ z|3f^t^@+_Hy&+JV$+ zj=laFY!G$kRTX z{wv_PqZitL4~GGVBk6gOS4`8m{+3o|ram z2h!~!yA!L-=O&;28G4H(JtP5vT03u9&b}@Uy5N1{pVz4MT`G=Y^qP$v(1(gNY29slpJu2y zfE%3r$8*f?Qw;zg3zLS;+?B(-M1`DpNou&%iLx2=GM(H#PY`;pvoeq8Et zfb&nN$s^oylf+IvaHs{~#N^grJKrqKD;g!$B`$_clmOiNHIcbb*>9U$1+d@L?j?s5 zP()y+C%Oa*7a#^gF1Bmv>z-UxmjgC)32sxjQs?{PaUB9_fGn{zt{K|-z%- z)f0Md;Xsfb3E%wv^BKOwd#9VnAE0MV6bM$Vf#{j-A~2ItPE7b|{*E5NlY;-tlgyf1 zEAM#HRKjVsJj3Bt@?A!6znSC;Cs#(l%!mnRi}2`?T!_jEe>|*&Uii;`;d10=qO_T( z1d$j}aSptClS_8JW;PZ;wS$cPt000~Qg(t>^Dx9MS=+QQ@oiAxt-#$t`!fbXxMnTY zVdcI|D4J^C^im5LW^p%+sein)OBocnbJQa?FL-ujZ2*b3&tsB%3tTJATGt%*zR06` zIT^kBV?wW%O(!;l2vGvWMrm3#k47s4o{{G>vkd0ZUjD1+3*P1qf&+gk#2ic1DU}AD z_b%?pkkQQA4J)&2|CJKWAt7$K@0TQfxXunyLjMW-+39cA<@R!m{-xQV(vXaQ`>co0 zg>kUP(Iv*+ULI7_Ztr|w2)59{3km0n1{VFG#)EL8=Ko358Z{2Z>U0uc^LIn=`^Utf zY=Mgj0tS}<2UK(fh%W6x0I(W(&?td9zH@w7u*tECobjkO7b8J60Vmd0N#%@A%Mfwz ze~^%7M4MjRRU?SFouo!UNG_57>3VJ_CCU+yyx@|D3!o2^?|vOUd2dgLaD&S{#<}&U zxgPj;(4oJfz;O6rHKP~6leBHA&qZL)PwW;I)dC3=eus0ak`xnSUMi{V zzyZtMve)jLe_@ScLwL9H+LZ_ag1HAk-X^a0x_c*geD>3)@B@XLDva>@N5)2DPxgdJ zSXBT{NbnCzw!Eae^xQYkO;0Amd2+AIai z`-2KsnByKz5wVGf&zNF{or<=JCnnbVB>z53{lGy$)fO(`J~!tV_GOzlI`3Yli=vhj zg(jJqPjDvTTAI0!elu@LETF5KQa)d)*7~|Lj3qVfp0Fsf;jz7W(X`*Kn2$i$Al_ zA<@-Fe;;)au}C2?6N9#A@@+@D0br#$&*IK+(z(0;1660|a{u%{`368Mh9OV7p?D2v z*m^a5=}7I8RZx$juzL07r7_|IUrWGhvog4=V8IX-++2|ijU8=6SGcQZ8ikq#cd^p2 zdmlVJyK$s1Q(efV1`f4S+bfahDoEC$GV{143j0Q7tso5(z$W|IO5$O0n~!0LbjGC+ zDV5_n`s9tggKZXp`De6P#HjDj7~GmnyHar?q8%NXdEDzZIEimO0`q0Q=o9TOtNe3O z)`E?m4}lLp(H&O0G2tOmiFfh`Ndw3kJ*fu}R<(h=Xv?~YEhsqA94yb*B+cLEso+KD z?Rec9xV^{5W)7DWXx#~r;S-VXI{4I?ddNb6MH(M-Pwwjg>I6URI_T?|`JO8O0Y@|m z3-q**$G}FK95M)hPHhW!t~tzDE5K6VfT~A;3C8&@t()`dzWlX#{lNpl18TW+hWj## z6sU-PR1Fo3VsKFaWE2Ggui3O-;I{P8Q9Y7xxujf%bczpPfxA$5*DY}9aL5R%VTTf6 zB+SA81F+X*p>@iD#AZ|?AW1YG75h?%g2eEqD9^9)mS5vGVeYT(SNW9D!@^NEU&pY# z{ygY83RpYetKc_%y41}cL!p#HO@11_syOUOIb6#A1u#hwxf;&|idrymZ6=M8TUPYJ z*>)QxkG$STN6HF-5>w=Sb?a^m_Bm#t9TT3NSJ@;h5hFep8){;ZcXOC$}w3j17Z5_-jJb^s^hb7w^r{hc@g*&*26@$pT%#& zJxc#tfeLwFOV7s`+uL*!cCIMRU?22o5=c|r;r{~pLTzt#RsRO86H7|X%li$*$^6~S z90D@W#h%pd7U}y;xU!*t$|H^NSbtk>8Yy*lDVQriuyb;uJTRlx?7a1Ry?IXJzvfT` z2{_mdSoQl-@yqmSd(55oYC%=hhmbyKPSlG$e%bP`-hzqwAMcR3>~0BtCRr#JnECQ( z*CdnNscGqUT||?9Bg=aicC@jPiWYCVoU?Rjv^tfWYI_{oj<|HjV*=#l#rc5CTM(y% zY5F`QpD5GT5UN8E)WO>;-^qfroM1p`?sK2xE!NB4H#;ELehq>kc_0`+eOD?tZAU+z z*G!<7UJ{au->LGWfLwKwgP)OZJqsV-(i$kBH7&olz?UH_JLmKI6ZCha(8y`FY|_5T zl2~&-Xb)IKy51Wt7`vhL-obx)YE_!7%~L=|i6Dsj&U(0Ewf7FM7X**2HsqM?WP4X3=iZoJGw0T3RZV=^Kvkc`s2QBT-J<(D$J^iPJ$Lg|LPi=k z5EGN+!YQT6z6cgQ@Ur{#is`Z`oG@OesI$0)Pb_nl6z89wsH;3aWMPohVTUrmMI88W%uiwLuR3)^nMvfN(3u=$9y} zgQI17aNU!a-{GR;OR=EtQMc8rrWVZy7J@EfPcQESSL#aIUu=jjb%+-D_8;-JkZEMD zo!z|gLnhM`+|{p!2G>1gB4h_0c?NySAt`?N_}T=hUwx(l3~Z%d8>!GL7L6S8s%=on zvSZMSau*(dM?d}Xw2x*hiAdojE(dS_!?+u#8jri&T)x0sr$A)-K#{=m)<{ojux^4j zFG;Y&xoPFHS0caFf!=)K+3U@nW4OWu#lyK}@HRVCaxKtzYFkxnzjbD1tZQU#^n(KkJqBzf0aXLPjN^>J%i7 zha_Sgl?pc%vR0LDmdJ10a_r9Oi}|tA8w%5ZeRQhjZ^rkbqwD4foHD$QLctKH0q%t_ z9HKsu;x|D|61}sFKp%DAjh$-!;0|o3Yeh)teb9dz@VP!NZ3A#zP&_7BWVpWYq`yT2 zu*+zt9Eu8BUJm>OAgDYD$9O*Eq$^Im2G-Q4VyYod`|#ib)!ndL|B_tkKCx+{&0)zU z`=ChpIXm3AuwTW*|4nEI<ExT#yp(&^cot%puGy!u3K;fXQfm>qxO z7AjXW!ym~!BakL4p*w_pa#~26l{Wk%j;JIqUrI^=nnH-~f++KV>LVXqvzq8S^rpJW zxUh;k8EfZ5hJ4|hTnYba|J4@l*PCc=l2VwlpvsimV--t97##?9?2Ov+<^y49 z_54;0uWCY4x1!T@ZbA_s9WBgdQ4#$~zO7(o|8Y%`7W)!QTO{C!VRyVm*5mnbEL|eG znNKacE+l^kV0P?4!owAH^n?iqv!#KNf8Wgnx!yGKF5(R-L4x+9>nh2TL8``(mBQ~? zHlK3SuIRy-mZ?Yhrq6j5y83^YPqbs(owLvoC1&SZ{!HHA+F5oQHg0MD5*ooaTGa#Y zjuppe0D`0T$|0%ab-qHICyVB<9N<HKb4 zdNg}WR#ThgY6~WFoCQYv_RQsFGT9lT-D@E!99&D!64V%#d$9dU{*yGS0VmobL=b!F zN%T}cn;Zb@*}PHmo$tzn3h;$;0IGY^pj7FgRNCJX%?$9x%`SNI_n-R%AvOyKp~)?+ zNsVSdpM&U^h6_IO9q*#y_j$!Are;L)*Z#<=p37BQA}H%?6LxDZkH?b1{T*oftn*oU z>`a&HOROC`yC~V5p9q|JA@X5OI?v_js+S5;T|JHDuVob&waB-KZc@Xf-mXI81)j=! z5FkGgExeg=v(|tpx18?WumhKb-soVG8DjeVU%7ZQjcc7fI7A$qe*jL6AL-@mr-~vn zu3YZH-{pDkml|%o5SJHDPCYMODs0g^z=nPhD46L$_}!Iu>yOdm?N$}VY!%y^jCR3w zYE|dYG?nNzP!ylW!<(>|@~2Tdl+fv4uY1hE9U0sv-r=)z>gly66Q0%@EeU`7PO)D< zhdc>$Cn{sGb}${7@fVZUMIgtAzvRDCyxSiM8(aDwW^Zfzey0}VRAr=|?F=Fvj;~2M1>E6ZF zbFk5vt`~rpe2Z)ao-0KA7k^AhI<^e}nnT6_qdECuw?wcAGAez}yHMC;J^Yp!7Jg+cW%q32os&RA);kSj%EU`}H-76xdnh}* z;-B;haC<@4=*3MWLEVCC=YnETE8aVc>{bi5nK7ZB{U%CyPIz2ciY0Z^Yu8B)HSCz1 zl5TS!&N123zTR8C#SYAfe4tzscURV+D9-MBal-uKJ!dZIvd-1oCySRL_q3Y=`|06Mkum%in;KW)!t-QN4p$oRve&l zeQu5(R{D>p8WpR{L4GOtm(|AZ&+rHCUmFMVULbK@Ynm|@2*)J@M^TCNI~|pA-4ODG z1bM=ki`A9U=i)XsiSV%#P6hM{v=`#i`Y_Iap*I=HkF^r#RX;oU#-cfl+%r{TlaqJ& zj(npgYSV7cFDQ3wO9bShI zg;@(IC=&+)^|;-&f-IF3Dqh$A2Lk-e!;vfHKvHUx5sm;lCF6F&d`B|-=r657LpX|R z%FOC446{Q%IDw(vpQm8#Ij?)Jy(iDA9onT<@j8Yh&xyviB_GAIHXhL2%&Kn~w=O7t zx_+a>vo@qDTXh6Ywo-8nCKH;nlvh1eTVn$-JHvD2-_Jw~GT z;}#!IC8m1v<_wG(i_t4|^(;WF`SMc5c8;7*;|bJN2Rt|G|apk|*nz%@bJ28kFaa4PtiAfBfhMF)Eprmd#hZaKPcW zg|{4bV+ES7HK4d(T?y&m&Ue}Lr8=>DoG0Iu@Cq7OnLYw`tZI@9=(IPD8wgKn_%h%r zOb4C}+3m4RCe{7vgiy^DSv{y;^eXw{`0lU+xss9=b>{su{K(_|j<2vgUg6%OQpYm9 zfC6hgS;<6LTbVPX+(5*PhpIZ@tH9kC15t5yUxF{o77Vvy&st*PG;DJZO14qUSvYq# zVH*{_#q$w&dIzIdGwknyL*A+9ni|p#IeBHY`72fF4Gvw0DaJ~uH~nzu4e5)K{++?E z4;uH^T5wpe-TcUR>!S~Z@Xu3#Y&k}|E=5Ix=^{IP`!^_FApu+^it}hV5=^pHOFTd} zUeTKNXJFuj!$GQ=V!76%o*(-b{evGjhw3or03*b zC6xzFHjMI9jS_k*!S#V)5Mr6nsg~&ed-vDS<8|tXL|*+lPh$^~EP&i)@Lf@Q0f*=> z9{HP^ChoYbq4(>?y3I(sF{?m){ZZB7@9OG7&M75dAB=~v@^6Wc*el5Ni1

k1=9p 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�gG$M9^(KIf>)jVE!H(Hc`q?983m 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 literal 0 HcmV?d00001 diff --git a/docs/triangular_mesh/mayavi_two_of_them.png b/docs/triangular_mesh/mayavi_two_of_them.png new file mode 100644 index 0000000000000000000000000000000000000000..2fba48a23c3dd6e449a534da9e25da6d6360525f GIT binary patch literal 210931 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

b%zmP1en10n*82&yAWPF?$lMwkBS%`Y=j9> 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

02x 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 z zgINIqO$RcrON`JFfVDg=jbn1SFXyng;?aa*>I}sl;ecm7D{{^3q8|RObbH2W;Nt^W!=6IE~Ion4E0=q4ntTjycX6=wG zcijlKuNHa#Qxd@h^eL3HBCt5ZJaXDR5Jz&#Dc_%j7B^B-S#+#NY&n@n+D9pVk zw+fo=gI=lsYCh=(NP(qWQWg(oehoY5tjAG;lIbK<#rSEIcQ8M|@5D__LW~OJ?~@pLSVJLBOGXxrW5CIkMFAY=tf#Zj0L<(GEC(&dkhCZ^B_3*)tmZTlKqY z^Nl}yNcj3~rTmqUNl_0MMsLEmuf-K-ey0QR>rN19Sse)Lq`;CtiG^!rmOm+Pz3`e*Yij^NY%Rac$d2z?0G{_otjy;&c@)Rq&+vEj3m z#uqDXkEI2r`*r#+X4gxSE;|RGuXdg`$M&0+_jewlGaVk995E}kwRCmUe`jiEe!g#x zKHViz8(Ml=pUmm|8~lW}nY6wvm@>Jz&CdKZBsRr&8_h%fOW2pigAc^bWdD_atv2rX zb|GR6R&*OEgGZ&&AaU*Lj&?Ay08{k9VvTURU8~3c2BzN4e3&%;I9ys<)OKWw}Qq2(@4kYHfmD-TnFl(@r8ct)eJ$}GQl1u zcF<%oq1TO-{FMGu04d^q+WtfX^ZxK$W9Qp!rQODxTzBV1!v4envi1sohNUa; zPxz?P| zd}i6%*()Pd7TYEL#t+4vFm1-4(cKTpYaPlO!TPzl`fRBW)--avr#~@|(u1cV>-P3Y zo+>=HcKh*l?*0nu^w6%HXi|Aipmv&R%}ouBKmlRw#3%jUlJ0>A!7l#4)w^nZCG4!W5h`?Mqa_dj?De;N)$s)2xT2Z zUc%kG)`5|%eOG(v5GjzY?@(7~)p-P$elUjIMM)i}>;^Q(r{d(x0FN*Xr8gr>%;LRp zjW>4WIV6RXpcZc$6Q_=DHI<%to1CP{pL-Hnt7!$9__P-i26i_3$Ku=+bU&fP59YjVO-}-lG9$og6+~|ZjtdC* z6aQ6q&oxRRWjk50{F__}=Ud^fwysBK#@pvoad|d#;pC3Zr3D)wX9cs`Eqe;3!r0RV zY6N47^gP(Sw!2Dr1~bk?jg29^DuUYj34-6J(Ufs`Z; zo;PmkrK-{O4Y#13@l6^+zxrI)RyxMCS!`xz9Y;qAp98qe6Cz`cB_l&O=a!R94w~iT z3rlDS6B>zj%jXC;HHJ+yCr@ey$Brl;p-zbGXkp(iU}_DlOpig2LXp^N{oEjv4Ssrd z<_F62bD_cHBie`J)@g=*+7wQIZho67(QjE1BP~cdXwgs$)NV|n^j2Y^YqHLs&T!?b zDsb1sbfInnHss(}b#GVq)LvDQ+gexSt_I%L$Nslp)|9q>p8Q9*pdATJ<~A*vVRd0( zHd{@tfXBhph-b6=iiTR(yE)t}c1&Y7u_B2P%OE) zZ@RF1@)LH(Bd}Fqwckoxw>D?;?0A7?%8>Bcp{L#f$qpWSQ1p@_PtthFwoE|=D))oN zRsG@^FS|6uzrznJ|7PIXknHrkibZLiwd_@Mz43lL``X?pGiYKR3lRCW{|%Y_#n&!` zx|=sIU%KFY<2XKy_p%G9Q8_95PM>L05i|7azRL^Z?>jb${^Ue+)V`!lVD!R zvw=Gs#_RLgc>3$f$sQ)jG#;Wlm$-V(_kQIT;nF`Mk=O1^u{LOi9{~}*7}2^Fx`4sx zVoFQzMrx~yVjg}F!AJp)d61&;$Kkj85g^_H1$Yc;=W~rtp5-Yp|izqhpLVuyn7AlDipZbApdbW|zqgU_5O+IPRf5EnPC!J3BqRpN+{j zs$A02%gZ=ub2|)cS|gxspB6i12G>5ryY}VGw6Ytd0~_8=p)6@rnh3%x@BQ}-KlZ$P zjfJedA2XA5DG{sKQTaZ+UUEo(_*c}S*cz`Fn9LN92YhDn46eywqWr-)Ip zA$%)&;l3j0iTy^+Euve9!t-8sPN%d8fe0kcuj(`_gPH5aN72(PlRRJz8QuiN##@O= zH4^h9>ND^ejIqIcWtUOT!_pt}FFOQ_oqrUs*2;;>%6`xX7j@20u$NaHY0AlOz!)2M zrjyjv)D!K?5gLv5R{Zm4+x6cEhcoc-LWp0Ht8*h*_eJ8&7ZKt}6kA2CU5Z&sfvZU+ zW{G=uD28_Mi#-AE@nPZpDiL!yyzhayw_=D^OkT}0k{4Oz!FuioPdTokFDHCR8O(_73#U+-`b0He_Oj;I_S&ewo;sDTA$J0FWn@X@?;)8QQm6GvMM2;CC z=RwJ=?W;rXxP@M9;)BEP8(aG}rM^lKA6t7bNSa)ufR~umoM5DUe7AlOfAA)Ida^`C z1+Lq;x`8Kni`rdYQL$5C!7YTkZSU-?%A8hvi-q=EV{S&j!8+?BVn4>+aFt1Yn1VG0 zS3)7lvfonVt!Imk>P>H_302Fy-*=21$Z={Lt;w%PrOk~pTgdmS<1r5vJ z^~Z|jwt6+6QQZjpxfc&nbG4+KPTSDj#5L4yW3T&pPixg=@dA#|+B7a)=RhQ{UzuWPyPrj_?UYNuXJ z&~*_-R9kD(8SOjl4S%b=<;Np(f;BDbqQschHY!=Oo=41`YF^{{O^l(&v_r*IG@QVWH-^(`Y zg){xGP;%F%g{q{~;9(S0M`u8^>}F_ZGq5EQiSg7&p#Q83);Gm%ibyx`@4RVDw1@+>YwgCA-{)zNFtD@&4a`)%o-iUEdq09 zV{sKRkg04_OrDsCA03LbB#O@LTe>;+@DTmYS+*T9jtS2BCSoWlfggV`TB~)g5}N;i zv{3&iUc}!b_Y3VCpAb)Sh$OLq;tmu7cd#9-!1KQ&ill;+i2uwyf}Z6xI>+P*5?b=k zv7F%E8qFfZ)3z>27AhHaFd>h7tct0-q8y{}ZdF~Y)l=lWg2PzM9vy}0*w)!Pl-WcW z_cEEADiw}c%4}3rlK_J*arV%?y3i{8Yd;6D)o#R#bXldZ2oeb-+n0 z*kyI4qm!7R6tw@I{57ekZL8t#>VPK^jMu^_aerGG4LdF(eZOkycJpgix#yOFSFzQG z+RA2S&dpQ6D2C?7NSA@83Zd`QnSLieNF40BHo9}YYMWBY&9MT1!E)arZ6|%B@9oUy10EIrqj7?((L#Y~%N6e4)LA(W|UiK$Z+YzYjB> z;>2ncli`Ai@V<3@$d7o9+~?O zTlj|x*tLn5wxS;lHsmsAIsqZh~d=l-}N<$dS>YJ5^I)KBUtmq)>3^ zQfMmswH~31?UqjNLev3O%C^SQ6S+g!H~qx%ag>!;-Z2s-@yR~*@m(;1QP|Xjps7RY z$Dt(fY}QNx+M^fCNatxB&+s1IU;A^)GRFu;w3>I9&T~A0-~pJW3^d@E>fZI6xFUmC zIglSc2xl<&E+OGt4i-)#A27MCU6aG~>OV8G<9?b6Ns;}9;z&B>s|^qm+%)KQ=j_k8 z1+C7mx)&YFt-8*!nA+IW0KH}?zJ;}IiVHpUaysb)PA0O&Nz6kvAnByzJjxn$PgoWt z{a1K;#6~k`jUOMA*AE+1NTG1?wKL61xjLfn-3L(C=}a0!zq097j09Kll)p?%@;pr= zAm7ZCLjZKA@)!7v+{z3K#CLpD|K5(63vIHTHlW)Xj(|ELhN620DnW-&s3&hY2SIx| zuhZvDF%TnU|3C^&mt6TtsjYuHf|WtNcQX&Iu6q2OH%HbuEt@2vi>d&+1)u-w7IYqUOv^%*5{Cht%_w*tc0N>CGemC?;h^pI$Z*izKiK4HWt4DRgBuDMY$9d@AZD|$HRi^6uCA%iki<^NIFSR#pU zAhW~69?k;zHKuH~B=?oP!QIUcMQ1eMYIdbhL@cbn;n+rRa_&|G%I+~xXVtim(_GdJ zVN4peu;(QZKh`_Ae5!{t3^rZHyvB|n#aSmY|Jry2xC|FqII{ln`{g}US&?AXyK8dn zStpxSs~OBT``W$3Ufjd+m_n*hPim?>;U>}?t+W?RrpgZ} zbEC+Wq?S`x8bDwk}Ov#91zG@k;QX#qG$a_)cKKbCjrZqK3u%?@I1bHk{4Y=TC`9b z7mt@wj)+Bg7XMi^)rlIZ#*=`ONl?+7k9#5~&|4f$v`gc=^klEpz>XoXX>z}AMlPYp zLwYdU*=P{7?h9lq87FCL&_Z zYi}qPC}YGZAl1q4Jj!f8z_o@!qU^ZVuUeHAq-Yde=0NDyxD7c7pd}?PDNqR4h4YD` zC7#79RTGWcu0bFFiSQTyif|0tgMejp4*ej^&-weCh|QPA$Qd?RhFpoG(JS6lKKe_I>X_K9g zF^d~6I}la;FI%WSNHACk(d_UzY_X& z+TAPltBfn@xSsjtnzu}KUcjpdD?wtt|(?_u2d2Triy zZ|lWTL%#m%i{e$>cJ1X!V)yVnG}7(c<~Uv_NXzy?eanBKdcZl4^{^_Y-);W=9hbGg z?&YTZhCY&0ru(E!|M41Za`CK_a%&WMmaGWGXhuDCS5vo_E^<&LAfdTle|Ou4aW$$| z5!m?T-}C?fzJNo+LXh`2xwpULR_o=f7~u;^@v#YN7eIe(xP!HR$NOx0!HS?N^lj($ zeLbn^Q+K7TEWvTYZzG397M|xJvb&s%)T+b~u1@&zkg?il$$L)#z=WUYQ9Ts63n>p4 zsNv5Yjs9+fnCh?=ZACQPc~%n;Ty$xr{h+XYwc@V05a^b;Fi7)9y1XwMg%((@xoGH0-?V@b9sMTd|%0G9(;M7yENCl4!?yIut3t zDdkj?!Jag!KwGbe8aNn%#Gg`6W~2p_p2%wW*ssv;+BdlysBVDImJ>wx*R;!x#Apw$ z+0r?YM8}FY{zM+h*Bhx)6VsaV1T26prUU*x0Iv4%onvP2pX zLsImj)l%?T>L%niqDA9mu2R4lfOV84DezB#?b^nx=*`iQ{-}tIq|4RHAoKY}G^T!y;q?X1L_>kll+Dlpg#%ypRaO=!)e(tbh6Ixd&KI(O-T zd9t~U=B^p6jt^TJ{!tX zLC$-X!FnONJcf`ahvilenujB%?h@&tJVVHuH9;clms6?N|0| z`vtc1J{@7vQ33cQYZ&9DgqIQ1tcp&JTJ4V=VCu#;lL_8qZ;S`*QcG~c;)#K^PROrU z)>b?Dix@7@Z9L-G;R?|EAQcgWf)db+F$?*YCte*Fd;p-~?=l8Iq`yqvWRw(LJ0vM} zw7isKMRi<%L-hE$v|159y!!8w~VovxdiXMU_Eaz@rAC^B}mv7`sTKi0qB>=aiy{299evQ z_!(-mvOjTnx#qH%2aFL>BYhElJxgVEN$K{FlQhXQ)8Jav4yizj?{f>K`)cw_H|zI8 z*XHSBn=Bu*`jDG_qv;v6w;xH}r4zpkLR`w#>V|FHvmIN!gVFWO>|fx^ej|`i7$~AE`fly zIu(>#00y@sf%+k_WuYWrlNoSd#zV}SjcxzYz(vSZ>+KxPEND{H%-5E;IZi9jR^5~| zCBddUcZ&qoR01XSXXvnZzlKbYr++0vukz*~S=tCkU<-yr$E_akGQe+H$CZiOjBnS0 zts!L8Od%^h)p4+Iv0u+Arb$yO{9{3xwS0DOma=v@Ec_jtEviV9t*ezj*`1ts)mRkE z!jpr%+=kVZIyz~3_grC2oc;o=!it?a&GbayZ6GnNv-aV72PMUqgB7Om(OGw3@M(v_ z(gqQ&tPcCZX!aaY7S%bKIQQT)NH!KJOjEYFMAMgR%WW~1SdN02*MbFF{moK$Wxk!v zXg^Lw0}Nq(svZi~N!KJ)7+z|>3`|i_ZB35S+N~dq=449VkE-SW+*aWE22vaS0#;@7 z1wcT%xu=H=Y^nA`#HUs(kzGaeioDKFBxZ53_8hJ#5ob`DGP|T)fF=yP$vlNXnE%D5nl40kC4U1i(~7n6?q~ z1tPL8Gh|xHZbau<`vITYg$O%`HE9<;Yz4pY)g-2ge9-sU_w%Pgn!nOA`7ch5EZRE_ z5|^KZGBW5Ax!#RyfdvpX4dPuFn3i*$K3AQ$p61i~@$-%3+@halKD(^R_u@|aiTn`{ zTRka(#}!YqPJ&J|;!&mp!LXB$I#c5!%6}V|bh{lKadm`}$X&d&4PnVpr!hF1^|6zofmShN-$a`$ZiC!E1K*Z(i1u{r z4%Hn1cY+`ghg^^a*CYBP_$lBsJ-{BeX?z_B^r~42NB|&E2Fo!AL>p1S#jkT2K;zcz zEgU$-3Ia*n91l*sAJoKo0uVh8Lg1(e`q>ON`D0ncx#P~=8?sX_$asLI`N>_YU$0ngj4TtUOnoy>=E7;h3b?X6&aNGrqe@-p1G;HuP<^)e9oK@Y?p?d1e z1rqfsi~R+~z!+(RV_Fb%!a$#0fJYgjj2fN42CpR=8hl6{(jE09>z7N*y9ojVFEQf) zeUj2rJ`XUgb#3-&*pSG<;Bnd`B4ExXmgA_R8k5SUr3Bkq983WHrUCr`xjs4o0pR#= zz=6xf)Bo`maQTmmf%E3c9>7VUS7v~hf%E?f|6wQq#s6}^WN%PtCwA^Q_p;k)4*}+Y No-3+9MLn?y`5zBSGco`G literal 0 HcmV?d00001 diff --git a/docs/triangular_mesh/tungsten_twist.png b/docs/triangular_mesh/tungsten_twist.png new file mode 100644 index 0000000000000000000000000000000000000000..4e172bce53ed36659b616cef3d9cbae4503935b7 GIT binary patch literal 110497 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

    eP{TFzKnYU;B7v#QPjCGzreS;_GBBhcrQ!o_6c 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 hXhbgmzki}3?uX)k Date: Thu, 18 Mar 2021 22:30:27 -0500 Subject: [PATCH 12/33] Cleanup of some redundant traits. --- src/geometry.rs | 18 +++--------------- src/input.rs | 2 +- src/material.rs | 2 +- src/parry.rs | 16 ++++------------ src/sphere.rs | 8 ++------ 5 files changed, 11 insertions(+), 35 deletions(-) diff --git a/src/geometry.rs b/src/geometry.rs index 0cce49e..f2f6d89 100644 --- a/src/geometry.rs +++ b/src/geometry.rs @@ -5,7 +5,7 @@ use geo::{Polygon, LineString, Point, point, Closest}; use geo::algorithm::closest_point::ClosestPoint; ///Trait for a Geometry object - all forms of geometry must implement these traits to be used -pub trait Geometry: GeometryInput { +pub trait Geometry { type InputFileFormat: InputFile + Clone; @@ -45,15 +45,11 @@ pub struct Mesh0D { pub energy_barrier_thickness: f64, } -impl GeometryInput for Mesh0D { - type GeometryInput = Mesh0DInput; -} - impl Geometry for Mesh0D { type InputFileFormat = Input0D; - fn new(input: &::GeometryInput) -> Mesh0D { + fn new(input: &<::InputFileFormat as GeometryInput>::GeometryInput) -> Mesh0D { let length_unit: f64 = match input.length_unit.as_str() { "MICRON" => MICRON, @@ -139,10 +135,6 @@ pub struct Mesh1D { pub bottom_energy_barrier_thickness: f64, } -impl GeometryInput for Mesh1D { - type GeometryInput = Mesh1DInput; -} - impl Geometry for Mesh1D { type InputFileFormat = input::Input1D; @@ -326,16 +318,12 @@ impl Mesh2D { } } -impl GeometryInput for Mesh2D { - type GeometryInput = Mesh2DInput; -} - impl Geometry for Mesh2D { type InputFileFormat = Input2D; /// Constructor for Mesh2D object from geometry_input. - fn new(geometry_input: &::GeometryInput) -> Mesh2D { + fn new(geometry_input: &<::InputFileFormat as GeometryInput>::GeometryInput) -> Mesh2D { let triangles = geometry_input.triangles.clone(); let material_boundary_points = geometry_input.material_boundary_points.clone(); diff --git a/src/input.rs b/src/input.rs index fe88e06..561d03c 100644 --- a/src/input.rs +++ b/src/input.rs @@ -172,7 +172,7 @@ pub struct Options { } pub fn input(input_file: String) -> (Vec, material::Material, Options, OutputUnits) -where ::InputFileFormat: Deserialize<'static> + 'static, ::GeometryInput: Clone { +where ::InputFileFormat: Deserialize<'static> + 'static { //Read input file, convert to string, and open with toml let mut input_toml = String::new(); diff --git a/src/material.rs b/src/material.rs index 0afbcda..a448e40 100644 --- a/src/material.rs +++ b/src/material.rs @@ -27,7 +27,7 @@ pub struct Material { pub surface_binding_model: SurfaceBindingModel, pub bulk_binding_model: BulkBindingModel } -impl Material { +impl Material { pub fn new(material_parameters: &MaterialParameters, geometry_input: &<::InputFileFormat as GeometryInput>::GeometryInput) -> Material { diff --git a/src/parry.rs b/src/parry.rs index 39748d7..b69617a 100644 --- a/src/parry.rs +++ b/src/parry.rs @@ -12,10 +12,6 @@ pub struct InputParryBall { pub geometry_input: ParryBallInput, } -impl GeometryInput for InputParryBall { - type GeometryInput = ParryBallInput; -} - impl InputFile for InputParryBall { fn new(string: &str) -> InputParryBall { @@ -54,7 +50,7 @@ pub struct ParryBall { pub ball: Ball, } -impl GeometryInput for ParryBall { +impl GeometryInput for InputParryBall { type GeometryInput = ParryBallInput; } @@ -62,7 +58,7 @@ impl Geometry for ParryBall { type InputFileFormat = InputParryBall; - fn new(input: &::GeometryInput) -> ParryBall { + fn new(input: &<::InputFileFormat as GeometryInput>::GeometryInput) -> ParryBall { let length_unit: f64 = match input.length_unit.as_str() { "MICRON" => MICRON, @@ -144,10 +140,6 @@ pub struct InputParryTriMesh { pub geometry_input: ParryTriMeshInput, } -impl GeometryInput for InputParryTriMesh { - type GeometryInput = ParryTriMeshInput; -} - impl InputFile for InputParryTriMesh { fn new(string: &str) -> InputParryTriMesh { @@ -187,7 +179,7 @@ pub struct ParryTriMesh { pub boundary: AABB, } -impl GeometryInput for ParryTriMesh { +impl GeometryInput for InputParryTriMesh { type GeometryInput = ParryTriMeshInput; } @@ -195,7 +187,7 @@ impl Geometry for ParryTriMesh { type InputFileFormat = InputParryTriMesh; - fn new(input: &::GeometryInput) -> ParryTriMesh { + fn new(input: &<::InputFileFormat as GeometryInput>::GeometryInput) -> ParryTriMesh { let length_unit: f64 = match input.length_unit.as_str() { "MICRON" => MICRON, diff --git a/src/sphere.rs b/src/sphere.rs index 621df69..7617788 100644 --- a/src/sphere.rs +++ b/src/sphere.rs @@ -8,10 +8,6 @@ pub struct InputSphere { pub geometry_input: sphere::SphereInput, } -impl GeometryInput for InputSphere { - type GeometryInput = sphere::SphereInput; -} - impl InputFile for InputSphere { fn new(string: &str) -> InputSphere { @@ -49,7 +45,7 @@ pub struct Sphere { pub energy_barrier_thickness: f64, } -impl GeometryInput for Sphere { +impl GeometryInput for InputSphere { type GeometryInput = SphereInput; } @@ -57,7 +53,7 @@ impl Geometry for Sphere { type InputFileFormat = InputSphere; - fn new(input: &::GeometryInput) -> Sphere { + fn new(input: &<::InputFileFormat as GeometryInput>::GeometryInput) -> Sphere { let length_unit: f64 = match input.length_unit.as_str() { "MICRON" => MICRON, From 0aa5c468f0d72a9ad768ae33b3376decf4c44cbd Mon Sep 17 00:00:00 2001 From: Jon Drobny Date: Fri, 19 Mar 2021 07:36:49 -0500 Subject: [PATCH 13/33] Removed nearest_to_* functions, and instead wrapped that functionality into the equivalent functions *.: --- src/geometry.rs | 59 ++++++++++++++++++++----------------------------- src/material.rs | 24 ++++---------------- src/parry.rs | 12 ---------- src/sphere.rs | 6 ----- src/tests.rs | 4 ++-- 5 files changed, 30 insertions(+), 75 deletions(-) diff --git a/src/geometry.rs b/src/geometry.rs index 0cce49e..90acfe0 100644 --- a/src/geometry.rs +++ b/src/geometry.rs @@ -14,8 +14,6 @@ pub trait Geometry: GeometryInput { fn get_ck(&self, x: f64, y: f64, z: f64) -> f64; fn get_total_density(&self, x: f64, y: f64, z: f64) -> f64; fn get_concentrations(&self, x: f64, y: f64, z: f64) -> &Vec; - fn get_densities_nearest_to(&self, x: f64, y: f64, z: f64) -> &Vec; - fn get_ck_nearest_to(&self, x: f64, y: f64, z: f64) -> f64; fn inside(&self, x: f64, y: f64, z: f64) -> bool; fn inside_simulation_boundary(&self, x: f64, y: f64, z: f64) -> bool; fn inside_energy_barrier(&self, x: f64, y: f64, z: f64) -> bool; @@ -98,12 +96,6 @@ impl Geometry for Mesh0D { fn get_concentrations(&self, x: f64, y: f64, z: f64) -> &Vec { &self.concentrations } - fn get_densities_nearest_to(&self, x: f64, y: f64, z: f64) -> &Vec { - &self.densities - } - fn get_ck_nearest_to(&self, x: f64, y: f64, z: f64) -> f64 { - self.electronic_stopping_correction_factor - } fn inside(&self, x: f64, y: f64, z: f64) -> bool { x > 0.0 } @@ -260,12 +252,6 @@ impl Geometry for Mesh1D { &self.layers[self.layers.len() - 1].concentrations } } - fn get_densities_nearest_to(&self, x: f64, y: f64, z: f64) -> &Vec { - self.get_densities(x, y, z) - } - fn get_ck_nearest_to(&self, x: f64, y: f64, z: f64) -> f64 { - self.get_ck(x, y, z) - } fn inside(&self, x: f64, y: f64, z: f64) -> bool { (x > self.top) & (x < self.bottom) } @@ -420,14 +406,6 @@ impl Geometry for Mesh2D { } } - fn get_ck_nearest_to(&self, x: f64, y: f64, z: f64) -> f64 { - self.nearest_to(x, y, z).get_electronic_stopping_correction_factor() - } - - fn get_densities_nearest_to(&self, x: f64, y: f64, z: f64) -> &Vec { - self.nearest_to(x, y, z).get_densities() - } - fn inside_simulation_boundary(&self, x: f64, y: f64, z: f64) -> bool { self.simulation_boundary.contains(&point!(x: x, y: y)) } @@ -438,37 +416,48 @@ impl Geometry for Mesh2D { } else { panic!("Geometry error: closest point routine failed to find single closest point to ({}, {}, {}).", x, y, z); } - } /// Find the number densities of the triangle that contains or is nearest to (x, y). fn get_densities(&self, x: f64, y: f64, z: f64) -> &Vec { - for cell in &self.mesh { - if cell.contains(x, y, z) { - return &cell.densities; + if self.inside(x, y, z) { + for cell in &self.mesh { + if cell.contains(x, y, z) { + return &cell.densities; + } } + panic!("Geometry error: point ({}, {}) not found in any cell of the mesh.", x, y); + } else { + self.nearest_to(x, y, z).get_densities() } - panic!("Geometry error: point ({}, {}) not found in any cell of the mesh.", x, y); } /// Find the number densities of the triangle that contains or is nearest to (x, y). fn get_ck(&self, x: f64, y: f64, z: f64) -> f64 { - for cell in &self.mesh { - if cell.contains(x, y, z) { - return cell.electronic_stopping_correction_factor; + if self.inside(x, y, z) { + for cell in &self.mesh { + if cell.contains(x, y, z) { + return cell.electronic_stopping_correction_factor; + } } + panic!("Geometry error: point ({}, {}) not found in any cell of the mesh.", x, y); + } else { + return self.nearest_to(x, y, z).electronic_stopping_correction_factor; } - panic!("Geometry error: point ({}, {}) not found in any cell of the mesh.", x, y); } /// Determine the total number density of the triangle that contains or is nearest to (x, y). fn get_total_density(&self, x: f64, y: f64, z: f64) -> f64 { - for cell in &self.mesh { - if cell.contains(x, y, z) { - return cell.densities.iter().sum::(); + if self.inside(x, y, z) { + for cell in &self.mesh { + if cell.contains(x, y, z) { + return cell.densities.iter().sum::(); + } } + panic!("Geometry error: point ({}, {}) not found in any cell of the mesh.", x, y); + } else { + return self.nearest_to(x, y, z).densities.iter().sum::(); } - panic!("Geometry error: point ({}, {}) not found in any cell of the mesh.", x, y); } /// Find the concentrations of the triangle that contains or is nearest to (x, y). diff --git a/src/material.rs b/src/material.rs index 0afbcda..563d689 100644 --- a/src/material.rs +++ b/src/material.rs @@ -67,11 +67,7 @@ impl Material { /// Gets concentrations of triangle that contains or is nearest to (x, y) pub fn get_concentrations(&self, x: f64, y: f64, z: f64) -> Vec { - let total_number_density: f64 = if self.inside(x, y, z) { - self.geometry.get_total_density(x, y, z) - } else { - self.geometry.get_densities_nearest_to(x, y, z).iter().sum::() - }; + let total_number_density: f64 = self.geometry.get_total_density(x, y, z); return self.geometry.get_densities(x, y, z).iter().map(|&i| i / total_number_density).collect(); } @@ -96,11 +92,7 @@ impl Material { /// Gets electronic stopping correction factor for LS and OR pub fn electronic_stopping_correction_factor(&self, x: f64, y: f64, z: f64) -> f64 { - if self.inside(x, y, z) { - return self.geometry.get_ck(x, y, z); - } else { - return self.geometry.get_ck_nearest_to(x, y, z); - } + return self.geometry.get_ck(x, y, z); } /// Determines the local mean free path from the formula sum(n(x, y))^(-1/3) @@ -110,20 +102,12 @@ impl Material { /// Total number density of triangle that contains or is nearest to (x, y). pub fn total_number_density(&self, x: f64, y: f64, z: f64) -> f64 { - if self.inside(x, y, z) { - return self.geometry.get_densities(x, y, z).iter().sum::(); - } else { - return self.geometry.get_densities_nearest_to(x, y, z).iter().sum::(); - } + self.geometry.get_densities(x, y, z).iter().sum::() } /// Lists number density of each species of triangle that contains or is nearest to (x, y). pub fn number_densities(&self, x: f64, y: f64, z: f64) -> &Vec { - if self.inside(x, y, z) { - return &self.geometry.get_densities(x, y, z); - } else { - return &self.geometry.get_densities_nearest_to(x, y, z); - } + return &self.geometry.get_densities(x, y, z); } /// Determines whether a point (x, y) is inside the energy barrier of the material. diff --git a/src/parry.rs b/src/parry.rs index 39748d7..219d80b 100644 --- a/src/parry.rs +++ b/src/parry.rs @@ -106,12 +106,6 @@ impl Geometry for ParryBall { fn get_concentrations(&self, x: f64, y: f64, z: f64) -> &Vec { &self.concentrations } - fn get_densities_nearest_to(&self, x: f64, y: f64, z: f64) -> &Vec { - &self.densities - } - fn get_ck_nearest_to(&self, x: f64, y: f64, z: f64) -> f64 { - self.electronic_stopping_correction_factor - } fn inside(&self, x: f64, y: f64, z: f64) -> bool { let p = Point::new(x , y , z ); self.ball.contains_local_point(&p) @@ -241,12 +235,6 @@ impl Geometry for ParryTriMesh { fn get_concentrations(&self, x: f64, y: f64, z: f64) -> &Vec { &self.concentrations } - fn get_densities_nearest_to(&self, x: f64, y: f64, z: f64) -> &Vec { - &self.densities - } - fn get_ck_nearest_to(&self, x: f64, y: f64, z: f64) -> f64 { - self.electronic_stopping_correction_factor - } fn inside(&self, x: f64, y: f64, z: f64) -> bool { inside_trimesh(&self.trimesh, x, y, z) } diff --git a/src/sphere.rs b/src/sphere.rs index 621df69..28f1304 100644 --- a/src/sphere.rs +++ b/src/sphere.rs @@ -100,12 +100,6 @@ impl Geometry for Sphere { fn get_concentrations(&self, x: f64, y: f64, z: f64) -> &Vec { &self.concentrations } - fn get_densities_nearest_to(&self, x: f64, y: f64, z: f64) -> &Vec { - &self.densities - } - fn get_ck_nearest_to(&self, x: f64, y: f64, z: f64) -> f64 { - self.electronic_stopping_correction_factor - } fn inside(&self, x: f64, y: f64, z: f64) -> bool { let r = (x.powi(2) + y.powi(2) + z.powi(2)).sqrt(); r < self.radius diff --git a/src/tests.rs b/src/tests.rs index 1a5bedc..23d41bb 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -486,8 +486,8 @@ fn test_geometry() { assert_eq!(material_2D.geometry.get_total_density(0., 0., 0.), material_0D.geometry.get_total_density(0., 0., 0.)); assert_eq!(material_2D.geometry.get_concentrations(0., 0., 0.), material_0D.geometry.get_concentrations(0., 0., 0.)); assert_eq!(material_2D.geometry.closest_point(-10., 0., 5.), material_0D.geometry.closest_point(-10., 0., 5.)); - assert_eq!(material_2D.geometry.get_densities_nearest_to(-10., 0., 5.), material_0D.geometry.get_densities_nearest_to(-10., 0., 5.)); - assert_eq!(material_2D.geometry.get_ck_nearest_to(-10., 0., 5.), material_0D.geometry.get_ck_nearest_to(-10., 0., 5.)); + assert_eq!(material_2D.geometry.get_densities(-10., 0., 5.), material_0D.geometry.get_densities(-10., 0., 5.)); + assert_eq!(material_2D.geometry.get_ck(-10., 0., 5.), material_0D.geometry.get_ck(-10., 0., 5.)); } #[test] From ff05ef57eeb78e282a4d622197cedda695818511 Mon Sep 17 00:00:00 2001 From: Jon Drobny Date: Fri, 19 Mar 2021 07:52:25 -0500 Subject: [PATCH 14/33] Ion acceleration outside of targets was not functioning correctly - fix applied. --- src/bca.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/bca.rs b/src/bca.rs index d6b7f97..8ef3c9b 100644 --- a/src/bca.rs +++ b/src/bca.rs @@ -85,11 +85,12 @@ pub fn single_ion_bca(particle: particle::Particle, material: &mate let binary_collision_geometries = bca::determine_mfp_phi_impact_parameter(&mut particle_1, &material, &options); #[cfg(feature = "accelerated_ions")] - if !material.inside(particle_1.pos.x, particle_1.pos.y, particle_1.pos.z) { + let distance_to_target = if !material.inside(particle_1.pos.x, particle_1.pos.y, particle_1.pos.z) { let (x, y, z) = material.geometry.closest_point(particle_1.pos.x, particle_1.pos.y, particle_1.pos.z); - let distance_to = ((x - particle_1.pos.x).powi(2) + (y - particle_1.pos.y).powi(2) + (z - particle_1.pos.z).powi(2)).sqrt(); - mfp += distance_to; - } + ((x - particle_1.pos.x).powi(2) + (y - particle_1.pos.y).powi(2) + (z - particle_1.pos.z).powi(2)).sqrt() + } else { + 0. + }; let mut total_energy_loss = 0.; let mut total_asymptotic_deflection = 0.; @@ -182,9 +183,14 @@ pub fn single_ion_bca(particle: particle::Particle, material: &mate } //Advance particle in space and track total distance traveled + #[cfg(not(feature = "accelerated_ions"))] let distance_traveled = particle::particle_advance(&mut particle_1, binary_collision_geometries[0].mfp, total_asymptotic_deflection); + #[cfg(feature = "accelerated_ions")] + let distance_traveled = particle::particle_advance(&mut particle_1, + binary_collision_geometries[0].mfp + distance_to_target, total_asymptotic_deflection); + //Subtract total energy from all simultaneous collisions and electronic stopping bca::update_particle_energy(&mut particle_1, &material, distance_traveled, total_energy_loss, normalized_distance_of_closest_approach, strong_collision_Z, From 6a1895116f8f0b94c3e35c8cd0bc380448dffe41 Mon Sep 17 00:00:00 2001 From: Jon Drobny <37962344+drobnyjt@users.noreply.github.com> Date: Fri, 19 Mar 2021 09:37:13 -0500 Subject: [PATCH 15/33] Update rustbca_compile_check.yml Update to add dev branch to workflow. --- .github/workflows/rustbca_compile_check.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rustbca_compile_check.yml b/.github/workflows/rustbca_compile_check.yml index 9c57900..bec6e7f 100644 --- a/.github/workflows/rustbca_compile_check.yml +++ b/.github/workflows/rustbca_compile_check.yml @@ -2,9 +2,9 @@ name: rustBCA Compile check on: push: - branches: [ master ] + branches: [ master, dev ] pull_request: - branches: [ master ] + branches: [ master, dev ] schedule: - cron: "0 0 * * *" From 1d655e2efe4ce91fb88430d0c701b7c42d35be3d Mon Sep 17 00:00:00 2001 From: Jon Drobny Date: Fri, 19 Mar 2021 10:45:50 -0500 Subject: [PATCH 16/33] Made sure progress bar always finishes, even for tiny simulations. --- src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.rs b/src/main.rs index 64b8bd0..c6d576d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -125,6 +125,7 @@ fn physics_loop(particle_input_array: Vec Date: Fri, 19 Mar 2021 16:57:33 -0500 Subject: [PATCH 17/33] update --- Cargo.toml | 97 +- RustBCA.h | 87 ++ src/bca.rs | 1534 +++++++++++++++---------------- src/consts.rs | 70 +- src/enums.rs | 492 +++++----- src/geometry.rs | 1304 +++++++++++++------------- src/input.rs | 814 ++++++++--------- src/interactions.rs | 882 +++++++++--------- src/lib.rs | 184 ++++ src/main.rs | 410 ++++----- src/material.rs | 790 ++++++++-------- src/output.rs | 838 ++++++++--------- src/parry.rs | 584 ++++++------ src/particle.rs | 516 +++++------ src/sphere.rs | 244 ++--- src/structs.rs | 174 ++-- src/tests.rs | 2108 +++++++++++++++++++++---------------------- 17 files changed, 5704 insertions(+), 5424 deletions(-) create mode 100644 RustBCA.h create mode 100644 src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 06a44b9..6345365 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,44 +1,53 @@ -[package] -name = "rustBCA" -version = "1.1.0" -authors = ["Jon Drobny "] -edition = "2018" - -[[bin]] -name="rustBCA" - -[dependencies] -rand = "0.8.3" -toml = "0.5.8" -anyhow = "1.0.38" -itertools = "0.10.0" -rayon = "1.5.0" -geo = {version = "0.17.1", optional = false} -indicatif = {version = "0.15.0", features=["rayon"]} -serde = { version = "1.0.123", features = ["derive"] } -hdf5 = {version = "0.7.1", optional = true} -openblas-src = {version = "0.9", optional = true} -netlib-src = {version = "0.8", optional = true} -intel-mkl-src = {version = "0.6.0", optional = true} -rcpr = { git = "https://github.com/drobnyjt/rcpr", optional = true} -ndarray = {version = "0.14.0", features = ["serde"], optional = true} -parry3d-f64 = {version = "0.2.0", optional = true} - -[dev-dependencies] -float-cmp = "0.8.0" - -[profile.release] -lto = "fat" -codegen-units = 1 -opt-level = 3 -debug = false - -[features] -hdf5_input = ["hdf5"] -cpr_rootfinder_openblas = ["rcpr", "openblas-src"] -cpr_rootfinder_netlib = ["rcpr", "netlib-src"] -cpr_rootfinder_intel_mkl = ["rcpr", "intel-mkl-src"] -distributions = ["ndarray"] -no_list_output = [] -parry3d = ["parry3d-f64"] -accelerated_ions = [] +[package] +name = "RustBCA" +version = "1.1.0" +authors = ["Jon Drobny "] +edition = "2018" + +[[bin]] +name="RustBCA" + +[lib] +name = "libRustBCA" +path = "src/lib.rs" +crate-type = ["cdylib"] + +[dependencies] +rand = "0.8.3" +toml = "0.5.8" +anyhow = "1.0.38" +itertools = "0.10.0" +rayon = "1.5.0" +geo = {version = "0.17.1", optional = false} +indicatif = {version = "0.15.0", features=["rayon"]} +serde = { version = "1.0.123", features = ["derive"] } +hdf5 = {version = "0.7.1", optional = true} +openblas-src = {version = "0.9", optional = true} +netlib-src = {version = "0.8", optional = true} +intel-mkl-src = {version = "0.6.0", optional = true} +rcpr = { git = "https://github.com/drobnyjt/rcpr", optional = true} +ndarray = {version = "0.14.0", features = ["serde"], optional = true} +parry3d-f64 = {version = "0.2.0", optional = true} + +[dependencies.pyo3] +version = "0.13.2" +features = ["extension-module"] + +[dev-dependencies] +float-cmp = "0.8.0" + +[profile.release] +lto = "fat" +codegen-units = 1 +opt-level = 3 +debug = false + +[features] +hdf5_input = ["hdf5"] +cpr_rootfinder_openblas = ["rcpr", "openblas-src"] +cpr_rootfinder_netlib = ["rcpr", "netlib-src"] +cpr_rootfinder_intel_mkl = ["rcpr", "intel-mkl-src"] +distributions = ["ndarray"] +no_list_output = [] +parry3d = ["parry3d-f64"] +accelerated_ions = [] diff --git a/RustBCA.h b/RustBCA.h new file mode 100644 index 0000000..72ea4ae --- /dev/null +++ b/RustBCA.h @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include +#include + +static const double PI = M_PI; + +static const double FRAC_2_SQRT_PI = M_2_PI; + +static const double SQRT_2 = M_SQRT2; + +///Fundamental charge in Coulombs. +static const double Q = 1.602176634e-19; + +/// One electron-volt in Joules. +static const double EV = Q; + +/// One atomic mass unit in kilograms. +static const double AMU = 1.66053906660e-27; + +/// One Angstrom in meters. +static const double ANGSTROM = 1e-10; + +/// One micron in meters. +static const double MICRON = 1e-6; + +/// One nanometer in meters. +static const double NM = 1e-9; + +/// One centimeter in meters. +static const double CM = 1e-2; + +/// Vacuum permitivity in Farads/meter. +static const double EPS0 = 8.8541878128e-12; + +/// Bohr radius in meters. +static const double A0 = 5.29177210903e-11; + +/// Electron mass in kilograms. +static const double ME = 9.1093837015e-31; + +/// sqrt(pi). +static const double SQRTPI = (2. / FRAC_2_SQRT_PI); + +/// sqrt(2 * pi). +static const double SQRT2PI = ((2. * SQRT_2) / FRAC_2_SQRT_PI); + +/// Speed of light in meters/second. +static const double C = 299792458.; + +/// Bethe-Bloch electronic stopping prefactor, in SI units. +static const double BETHE_BLOCH_PREFACTOR = ((((((4. * PI) * ((Q * Q) / ((4. * PI) * EPS0))) * ((Q * Q) / ((4. * PI) * EPS0))) / ME) / C) / C); + +/// Lindhard-Scharff electronic stopping prefactor, in SI units. +static const double LINDHARD_SCHARFF_PREFACTOR = (((1.212 * ANGSTROM) * ANGSTROM) * Q); + +/// Lindhard reduced energy prefactor, in SI units. +static const double LINDHARD_REDUCED_ENERGY_PREFACTOR = ((((4. * PI) * EPS0) / Q) / Q); + +struct OutputBCA { + uintptr_t len; + double (*particles)[9]; +}; + +extern "C" { + +OutputBCA simple_bca_c(double x, + double y, + double z, + double ux, + double uy, + double uz, + double E1, + double Z1, + double m1, + double Ec1, + double Es1, + double Z2, + double m2, + double Ec2, + double Es2, + double n2, + double Eb2); + +} // extern "C" diff --git a/src/bca.rs b/src/bca.rs index 8ef3c9b..23b4be0 100644 --- a/src/bca.rs +++ b/src/bca.rs @@ -1,767 +1,767 @@ -use super::*; - -#[cfg(any(feature = "cpr_rootfinder_openblas", feature = "cpr_rootfinder_netlib", feature = "cpr_rootfinder_intel_mkl"))] -use rcpr::chebyshev::*; - -/// Geometrical quantities of binary collision. -pub struct BinaryCollisionGeometry { - pub phi_azimuthal: f64, - pub impact_parameter: f64, - pub mfp: f64 -} - -impl BinaryCollisionGeometry { - /// Constructs a new binary collision geometry object. - pub fn new(phi_azimuthal: f64, impact_parameter: f64, mfp: f64) -> BinaryCollisionGeometry { - BinaryCollisionGeometry { - phi_azimuthal, - impact_parameter, - mfp - } - } -} - -impl fmt::Display for BinaryCollisionGeometry { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Binary collision geometry. \n phi_azimuthal = {} \n p = {} \n mfp = {} \n", - self.phi_azimuthal, self.impact_parameter, self.mfp) - } -} - -/// Resultant quantities of a binary collision. -pub struct BinaryCollisionResult { - pub theta: f64, - pub psi: f64, - pub psi_recoil: f64, - pub recoil_energy: f64, - pub asymptotic_deflection: f64, - pub normalized_distance_of_closest_approach: f64 -} - -impl BinaryCollisionResult { - - /// Constructs a new binary collision result object. - pub fn new(theta: f64, psi: f64, psi_recoil: f64, recoil_energy: f64, - asymptotic_deflection: f64, normalized_distance_of_closest_approach: f64) -> BinaryCollisionResult { - BinaryCollisionResult { - theta, - psi, - psi_recoil, - recoil_energy, - asymptotic_deflection, - normalized_distance_of_closest_approach - } - } -} - -impl fmt::Display for BinaryCollisionResult { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Binary collision result. \n theta = {} \n psi = {} \n psi_r = {} \n E_recoil = {} \n tau = {} \n x0 = {} \n", - self.theta, self.psi, self.psi_recoil, self.recoil_energy, self.asymptotic_deflection, self.normalized_distance_of_closest_approach) - } -} - -/// This function takes a single particle, a material, and an options object and runs a binary-collision-approximation trajectory for that particle in that material, producing a final particle list that consists of the original ion and any material particles displaced thereby. -pub fn single_ion_bca(particle: particle::Particle, material: &material::Material, options: &Options) -> Vec { - - let mut particles: Vec = Vec::new(); - particles.push(particle); - - let mut particle_output: Vec = Vec::new(); - - let mut particle_index = particles.len(); - - while particle_index > 0 { - - //Remove particle from top of vector as particle_1 - let mut particle_1 = particles.pop().unwrap(); - - //println!("Particle start Z: {} E: {} ({}, {}, {})", particle_1.Z, particle_1.E/Q, particle_1.pos.x/ANGSTROM, particle_1.pos.y/ANGSTROM, particle_1.pos.z/ANGSTROM); - - //BCA loop - while !particle_1.stopped & !particle_1.left { - - //Choose impact parameters and azimuthal angles for all collisions, and determine mean free path - let binary_collision_geometries = bca::determine_mfp_phi_impact_parameter(&mut particle_1, &material, &options); - - #[cfg(feature = "accelerated_ions")] - let distance_to_target = if !material.inside(particle_1.pos.x, particle_1.pos.y, particle_1.pos.z) { - let (x, y, z) = material.geometry.closest_point(particle_1.pos.x, particle_1.pos.y, particle_1.pos.z); - ((x - particle_1.pos.x).powi(2) + (y - particle_1.pos.y).powi(2) + (z - particle_1.pos.z).powi(2)).sqrt() - } else { - 0. - }; - - let mut total_energy_loss = 0.; - let mut total_asymptotic_deflection = 0.; - let mut normalized_distance_of_closest_approach = 0.; - let mut strong_collision_Z = 0.; - let mut strong_collision_index: usize = 0; - - //Collision loop - for (k, binary_collision_geometry) in binary_collision_geometries.iter().enumerate().take(options.weak_collision_order + 1) { - - let (species_index, mut particle_2) = bca::choose_collision_partner(&particle_1, &material, - &binary_collision_geometry, &options); - - //If recoil location is inside, proceed with binary collision loop - if material.inside(particle_2.pos.x, particle_2.pos.y, particle_2.pos.z) & material.inside_energy_barrier(particle_1.pos.x, particle_1.pos.y, particle_1.pos.z) { - - //Determine scattering angle from binary collision - let binary_collision_result = bca::calculate_binary_collision(&particle_1, - &particle_2, &binary_collision_geometry, &options) - .with_context(|| format!("Numerical error: binary collision calculation failed at x = {} y = {} with {}", - particle_1.pos.x, particle_2.pos.x, &binary_collision_geometry)) - .unwrap(); - - //println!("{}", binary_collision_result); - - //Only use 0th order collision for local electronic stopping - if k == 0 { - normalized_distance_of_closest_approach = binary_collision_result.normalized_distance_of_closest_approach; - strong_collision_Z = particle_2.Z; - strong_collision_index = species_index; - } - - //Energy transfer to recoil - particle_2.E = binary_collision_result.recoil_energy - material.average_bulk_binding_energy(particle_2.pos.x, particle_2.pos.y, particle_2.pos.z); - particle_2.energy_origin = particle_2.E; - - //Accumulate asymptotic deflections for primary particle - total_energy_loss += binary_collision_result.recoil_energy; - - //total_deflection_angle += psi; - total_asymptotic_deflection += binary_collision_result.asymptotic_deflection; - - //Rotate particle 1, 2 by lab frame scattering angles - particle::rotate_particle(&mut particle_1, binary_collision_result.psi, - binary_collision_geometry.phi_azimuthal); - - particle::rotate_particle(&mut particle_2, -binary_collision_result.psi_recoil, - binary_collision_geometry.phi_azimuthal); - - particle_2.dir_old.x = particle_2.dir.x; - particle_2.dir_old.y = particle_2.dir.y; - particle_2.dir_old.z = particle_2.dir.z; - - //Only track number of strong collisions, i.e., k = 0 - if (binary_collision_result.psi > 0.) & (k == 0) { - particle_1.number_collision_events += 1; - } - - //Deep recoil suppression - //See Eckstein 1991 7.5.3 for recoil suppression function - if options.track_recoils & options.suppress_deep_recoils { - let E = particle_1.E; - let Za: f64 = particle_1.Z; - let Zb: f64 = particle_2.Z; - - let Ma: f64 = particle_1.m; - let Mb: f64 = particle_2.m; - - let n = material.total_number_density(particle_2.pos.x, particle_2.pos.y, particle_2.pos.z); - //We just need the lindhard screening length here, so the particular potential is not important - let a: f64 = interactions::screening_length(Za, Zb, InteractionPotential::MOLIERE); - let reduced_energy: f64 = LINDHARD_REDUCED_ENERGY_PREFACTOR*a*Mb/(Ma+Mb)/Za/Zb*E; - let estimated_range_of_recoils = (reduced_energy.powf(0.3) + 0.1).powi(3)/n/a/a; - - let (x2, y2, z2) = material.closest_point(particle_2.pos.x, particle_2.pos.y, particle_2.pos.z); - let dx = x2 - particle_2.pos.x; - let dy = y2 - particle_2.pos.y; - let dz = z2 - particle_2.pos.z; - let distance_to_surface = (dx*dx + dy*dy + dz*dz).sqrt(); - - if (distance_to_surface < estimated_range_of_recoils) & (particle_2.E > particle_2.Ec) { - particles.push(particle_2); - } - - //If transferred energy > cutoff energy, add recoil to particle vector - } else if options.track_recoils & (particle_2.E > particle_2.Ec) { - particles.push(particle_2); - } - } - } - - //Advance particle in space and track total distance traveled - #[cfg(not(feature = "accelerated_ions"))] - let distance_traveled = particle::particle_advance(&mut particle_1, - binary_collision_geometries[0].mfp, total_asymptotic_deflection); - - #[cfg(feature = "accelerated_ions")] - let distance_traveled = particle::particle_advance(&mut particle_1, - binary_collision_geometries[0].mfp + distance_to_target, total_asymptotic_deflection); - - //Subtract total energy from all simultaneous collisions and electronic stopping - bca::update_particle_energy(&mut particle_1, &material, distance_traveled, - total_energy_loss, normalized_distance_of_closest_approach, strong_collision_Z, - strong_collision_index, &options); - //println!("Particle finished collision loop Z: {} E: {} ({}, {}, {})", particle_1.Z, particle_1.E/Q, particle_1.pos.x/ANGSTROM, particle_1.pos.y/ANGSTROM, particle_1.pos.z/ANGSTROM); - - //println!("{} {} {}", energy_0/EV, energy_1/EV, (energy_1 - energy_0)/EV); - - //Check boundary conditions on leaving and stopping - material::boundary_condition_planar(&mut particle_1, &material); - - //Set particle index to topmost particle - particle_index = particles.len(); - } - //println!("Particle stopped or left Z: {} E: {} ({}, {}, {})", particle_1.Z, particle_1.E/Q, particle_1.pos.x/ANGSTROM, particle_1.pos.y/ANGSTROM, particle_1.pos.z/ANGSTROM); - particle_output.push(particle_1); - } - particle_output -} - -/// For a particle in a material, determine the mean free path and choose the azimuthal angle and impact parameter. -/// The mean free path can be exponentially distributed (gaseous) or constant (amorphous solid/liquid). Azimuthal angles are chosen uniformly. Impact parameters are chosen for collision partners distributed uniformly on a disk of density-dependent radius. -pub fn determine_mfp_phi_impact_parameter(particle_1: &mut particle::Particle, material: &material::Material, options: &Options) -> Vec { - - let x = particle_1.pos.x; - let y = particle_1.pos.y; - let z = particle_1.pos.z; - - let mut mfp = material.mfp(x, y, z); - - let mut phis_azimuthal = Vec::with_capacity(options.weak_collision_order + 1); - let mut binary_collision_geometries = Vec::with_capacity(options.weak_collision_order + 1); - - //Each weak collision gets its own aziumuthal angle in annuli around collision point - for k in 0..options.weak_collision_order + 1 { - phis_azimuthal.push(2.*PI*rand::random::()); - } - - if options.high_energy_free_flight_paths { - - let Ma: f64 = particle_1.m; - let Mb: f64 = material.average_mass(x, y, z); - let Za: f64 = particle_1.Z; - let Zb: f64 = material.average_Z(x, y, z); - let n: &Vec = material.number_densities(x, y, z); - let ck: f64 = material.electronic_stopping_correction_factor(x, y, z); - let E: f64 = particle_1.E; - let Ec: f64 = particle_1.Ec; - //We just need the Lindhard screening length here, so the particular potential is not important - let a: f64 = interactions::screening_length(Za, Zb, InteractionPotential::MOLIERE); - let reduced_energy: f64 = LINDHARD_REDUCED_ENERGY_PREFACTOR*a*Mb/(Ma+Mb)/Za/Zb*E; - - //Minimum energy transfer for generating scattering event set to cutoff energy - let E_min = Ec*(Ma + Mb)*(Ma + Mb)/4./Ma/Mb; - let reduced_energy_min: f64 = LINDHARD_REDUCED_ENERGY_PREFACTOR*a*Mb/(Ma+Mb)/Za/Zb*E_min; - - //Free flight path formulation here described in SRIM textbook chapter 7, and Eckstein 1991 7.5.2 - let ep = (reduced_energy*reduced_energy_min).sqrt(); - let mut pmax = a/(ep + ep.sqrt() + 0.125*ep.powf(0.1)); - let mut ffp = 1./(material.total_number_density(x, y, z)*pmax*pmax*PI); - let stopping_powers = material.electronic_stopping_cross_sections(particle_1, ElectronicStoppingMode::INTERPOLATED); - - let delta_energy_electronic = stopping_powers.iter().zip(material.number_densities(x, y, z)).map(|(&se, number_density)| se*number_density).sum::()*ffp*ck; - - //If losing too much energy, scale free-flight-path down - //5 percent limit set in original TRIM paper, Biersack and Haggmark 1980 - if delta_energy_electronic > 0.05*E { - ffp *= 0.05*E/delta_energy_electronic; - pmax = (1./(material.total_number_density(x, y, z)*PI*ffp)).sqrt() - } - - //If free-flight-path less than the interatomic spacing, revert to amorphous solid model - //Mentioned in Eckstein 1991, Ziegler, Biersack, and Ziegler 2008 (SRIM textbook 7-8) - if ffp < mfp { - - ffp = mfp; - //Cylindrical geometry - pmax = mfp/SQRT2PI; - let mut impact_parameter = Vec::with_capacity(1); - let random_number = rand::random::(); - let p = pmax*random_number.sqrt(); - impact_parameter.push(p); - - //Atomically rough surface - scatter initial collisions using mfp near interface - if particle_1.first_step { - ffp = mfp*rand::random::(); - particle_1.first_step = false; - } - - ffp *= match options.mean_free_path_model { - MeanFreePathModel::GASEOUS => -rand::random::().ln(), - MeanFreePathModel::LIQUID => 1.0, - MeanFreePathModel::THRESHOLD{density} => { - if material.geometry.get_densities(x, y, z).iter().sum::() < density { - -rand::random::().ln() - } else { - 1.0 - } - } - }; - - binary_collision_geometries.push(BinaryCollisionGeometry::new(phis_azimuthal[0], impact_parameter[0], ffp)); - return binary_collision_geometries; - - } else { - - //Impact parameter chosen as sqrt(-ln(R))*pmax, as in Biersack and Haggmark 1980, - //And Mendenhall Weller 2005 - //And SRIM textbook chapter 7 - //And Eckstein 1991 - let mut impact_parameter = Vec::with_capacity(1); - let random_number = rand::random::(); - let p = pmax*(-random_number.ln()).sqrt(); - impact_parameter.push(p); - - //Atomically rough surface - scatter initial collisions using mfp near interface - if particle_1.first_step { - ffp = mfp*rand::random::(); - particle_1.first_step = false; - } - - if options.mean_free_path_model == MeanFreePathModel::GASEOUS { - ffp *= -rand::random::().ln(); - } - - binary_collision_geometries.push(BinaryCollisionGeometry::new(phis_azimuthal[0], impact_parameter[0], ffp)); - return binary_collision_geometries; - } - - } else { - - //If not using free flight paths, use weak collision model - let pmax = mfp/SQRTPI; - - //Cylindrical geometry - let mut impact_parameters = Vec::with_capacity(options.weak_collision_order + 1); - for k in 0..(options.weak_collision_order + 1) { - let random_number = rand::random::(); - let p = pmax*(random_number + k as f64).sqrt(); - impact_parameters.push(p) - } - - //Atomically rough surface - scatter initial collisions - if particle_1.first_step { - mfp *= rand::random::(); - particle_1.first_step = false; - } - - if options.mean_free_path_model == MeanFreePathModel::GASEOUS { - mfp *= -rand::random::().ln(); - } - - - for k in 0..(options.weak_collision_order + 1) { - binary_collision_geometries.push(BinaryCollisionGeometry::new(phis_azimuthal[k], impact_parameters[k], mfp)) - } - - return binary_collision_geometries; - } -} - -/// For a particle in a material, and for a particular binary collision geometry, choose a species for the collision partner. -pub fn choose_collision_partner(particle_1: &particle::Particle, material: &material::Material, binary_collision_geometry: &BinaryCollisionGeometry, options: &Options) -> (usize, particle::Particle) { - let x = particle_1.pos.x; - let y = particle_1.pos.y; - let z = particle_1.pos.z; - - let impact_parameter = binary_collision_geometry.impact_parameter; - let mfp = binary_collision_geometry.mfp; - let phi_azimuthal = binary_collision_geometry.phi_azimuthal; - - //Determine cosines and sines - let sinphi: f64 = phi_azimuthal.sin(); - let cosx: f64 = particle_1.dir.x; - let cosy: f64 = particle_1.dir.y; - let cosz: f64 = particle_1.dir.z; - let sinx: f64 = (1. - cosx*cosx).sqrt(); - let cosphi: f64 = phi_azimuthal.cos(); - - //Find recoil location - let x_recoil: f64 = x + mfp*cosx - impact_parameter*cosphi*sinx; - let y_recoil: f64 = y + mfp*cosy - impact_parameter*(sinphi*cosz - cosphi*cosy*cosx)/sinx; - let z_recoil: f64 = z + mfp*cosz + impact_parameter*(sinphi*cosy - cosphi*cosx*cosz)/sinx; - - //Choose recoil Z, M - let (species_index, Z_recoil, M_recoil, Ec_recoil, Es_recoil, interaction_index) = material.choose(x_recoil, y_recoil, z_recoil); - - return (species_index, - particle::Particle::new( - M_recoil, Z_recoil, 0., Ec_recoil, Es_recoil, - x_recoil, y_recoil, z_recoil, - cosx, cosy, cosz, - false, options.track_recoil_trajectories, interaction_index - ) - ) -} - -/// Calculate the distance of closest approach of two particles given a particular binary collision geometry. -fn distance_of_closest_approach(particle_1: &particle::Particle, particle_2: &particle::Particle, binary_collision_geometry: &BinaryCollisionGeometry, options: &Options) -> f64 { - let Za: f64 = particle_1.Z; - let Zb: f64 = particle_2.Z; - let Ma: f64 = particle_1.m; - let Mb: f64 = particle_2.m; - let E0: f64 = particle_1.E; - let relative_energy = E0*Mb/(Ma + Mb); - let p = binary_collision_geometry.impact_parameter; - - let interaction_potential = options.interaction_potential[particle_1.interaction_index][particle_2.interaction_index]; - - if let InteractionPotential::COULOMB{Za: Z1, Zb: Z2} = interaction_potential { - let doca = Z1*Z2*Q*Q/relative_energy/PI/EPS0/8. + (64.*(relative_energy*PI*p*EPS0).powi(2) + (Z1*Z2*Q*Q).powi(2)).sqrt()/relative_energy/PI/EPS0/8.; - return doca/interactions::screening_length(Z1, Z2, interaction_potential); - } - - let root_finder = if relative_energy < interactions::energy_threshold_single_root(interaction_potential) { - options.root_finder[particle_1.interaction_index][particle_2.interaction_index] - } else {Rootfinder::NEWTON{max_iterations: 100, tolerance: 1E-6}}; - - #[cfg(any(feature = "cpr_rootfinder_openblas", feature = "cpr_rootfinder_netlib", feature = "cpr_rootfinder_intel_mkl"))] - match root_finder { - Rootfinder::POLYNOMIAL{complex_threshold} => polynomial_rootfinder(Za, Zb, Ma, Mb, E0, p, interaction_potential, complex_threshold) - .with_context(|| format!("Numerical error: Polynomial rootfinder failed for {} at {} eV with p = {} A.", interaction_potential, E0/EV, p/ANGSTROM)) - .unwrap(), - Rootfinder::CPR{n0, nmax, epsilon, complex_threshold, truncation_threshold, far_from_zero, interval_limit, derivative_free} => - cpr_rootfinder(Za, Zb, Ma, Mb, E0, p, interaction_potential, n0, nmax, epsilon, complex_threshold, truncation_threshold, far_from_zero, interval_limit, derivative_free) - .with_context(|| format!("Numerical error: CPR rootfinder failed for {} at {} eV with p = {} A.", interaction_potential, E0/EV, p/ANGSTROM)) - .unwrap(), - Rootfinder::NEWTON{max_iterations, tolerance} => newton_rootfinder(Za, Zb, Ma, Mb, E0, p, interaction_potential, max_iterations, tolerance) - .with_context(|| format!("Numerical error: Newton rootfinder failed for {} at {} eV with p = {} A.", interaction_potential, E0/EV, p/ANGSTROM)) - .unwrap(), - Rootfinder::DEFAULTNEWTON => newton_rootfinder(Za, Zb, Ma, Mb, E0, p, interaction_potential, 100, 1E-3) - .with_context(|| format!("Numerical error: Newton rootfinder failed for {} at {} eV with p = {} A.", interaction_potential, E0/EV, p/ANGSTROM)) - .unwrap(), - } - - #[cfg(not(any(feature = "cpr_rootfinder_openblas", feature = "cpr_rootfinder_netlib", feature = "cpr_rootfinder_intel_mkl")))] - match root_finder { - Rootfinder::NEWTON{max_iterations, tolerance} => newton_rootfinder(Za, Zb, Ma, Mb, E0, p, interaction_potential, max_iterations, tolerance) - .with_context(|| format!("Numerical error: Newton rootfinder failed for {} at {} eV with p = {} A.", interaction_potential, E0/EV, p/ANGSTROM)) - .unwrap(), - Rootfinder::DEFAULTNEWTON => newton_rootfinder(Za, Zb, Ma, Mb, E0, p, interaction_potential, 100, 1E-3) - .with_context(|| format!("Numerical error: Newton rootfinder failed for {} at {} eV with p = {} A.", interaction_potential, E0/EV, p/ANGSTROM)) - .unwrap(), - _ => panic!("Input error: unimplemented root-finder. Choose NEWTON or build with cpr_rootfinder to enable CPR and POLYNOMIAL") - } -} - -/// Update a particle's energy following nuclear and electronic interactions of a single BCA step. -pub fn update_particle_energy(particle_1: &mut particle::Particle, material: &material::Material, distance_traveled: f64, - recoil_energy: f64, x0: f64, strong_collision_Z: f64, strong_collision_index: usize, options: &Options) { - - //If particle energy drops below zero before electronic stopping calcualtion, it produces NaNs - particle_1.E -= recoil_energy; - assert!(!particle_1.E.is_nan(), "Numerical error: particle energy is NaN following collision."); - if particle_1.E < 0. { - particle_1.E = 0.; - } - - let x = particle_1.pos.x; - let y = particle_1.pos.y; - let z = particle_1.pos.z; - - if material.inside(x, y, z) { - - let interaction_potential = options.interaction_potential[particle_1.interaction_index][material.interaction_index[strong_collision_index]]; - let electronic_stopping_powers = material.electronic_stopping_cross_sections(particle_1, options.electronic_stopping_mode); - let n = material.number_densities(x, y, z); - - let delta_energy = match options.electronic_stopping_mode { - ElectronicStoppingMode::INTERPOLATED => electronic_stopping_powers.iter().zip(n).map(|(se, number_density)| se*number_density).collect::>().iter().sum::()*distance_traveled, - ElectronicStoppingMode::LOW_ENERGY_NONLOCAL => electronic_stopping_powers.iter().zip(n).map(|(se, number_density)| se*number_density).collect::>().iter().sum::()*distance_traveled, - ElectronicStoppingMode::LOW_ENERGY_LOCAL => oen_robinson_loss(particle_1.Z, strong_collision_Z, electronic_stopping_powers[strong_collision_index], x0, interaction_potential), - ElectronicStoppingMode::LOW_ENERGY_EQUIPARTITION => { - - let delta_energy_local = oen_robinson_loss(particle_1.Z, strong_collision_Z, electronic_stopping_powers[strong_collision_index], x0, interaction_potential); - let delta_energy_nonlocal = electronic_stopping_powers.iter().zip(n).map(|(se, number_density)| se*number_density).collect::>().iter().sum::()*distance_traveled; - - 0.5*delta_energy_local + 0.5*delta_energy_nonlocal - }, - }; - - particle_1.E += -delta_energy; - //Make sure particle energy doesn't become negative again - assert!(!particle_1.E.is_nan(), "Numerical error: particle energy is NaN following electronic stopping."); - if particle_1.E < 0. { - particle_1.E = 0.; - } - - particle_1.energy_loss(&options, recoil_energy, delta_energy); - } else if recoil_energy > 0. { - particle_1.energy_loss(&options, recoil_energy, 0.); - } -} - -/// Calculate the binary collision result of two particles for a given binary collision geometry. If the calculation fails, return an Error. -pub fn calculate_binary_collision(particle_1: &particle::Particle, particle_2: &particle::Particle, binary_collision_geometry: &BinaryCollisionGeometry, options: &Options) -> Result { - let Za: f64 = particle_1.Z; - let Zb: f64 = particle_2.Z; - let Ma: f64 = particle_1.m; - let Mb: f64 = particle_2.m; - let E0: f64 = particle_1.E; - let mu: f64 = Mb/(Ma + Mb); - - let interaction_potential = options.interaction_potential[particle_1.interaction_index][particle_2.interaction_index]; - let scattering_integral = options.scattering_integral[particle_1.interaction_index][particle_2.interaction_index]; - - let a: f64 = interactions::screening_length(Za, Zb, interaction_potential); - let x0 = distance_of_closest_approach(particle_1, particle_2, binary_collision_geometry, options); - - let theta: f64 = match scattering_integral { - ScatteringIntegral::MENDENHALL_WELLER => mendenhall_weller(Za, Zb, Ma, Mb, E0, binary_collision_geometry.impact_parameter, x0, interaction_potential), - ScatteringIntegral::GAUSS_MEHLER{n_points} => gauss_mehler(Za, Zb, Ma, Mb, E0, binary_collision_geometry.impact_parameter, x0, interaction_potential, n_points), - ScatteringIntegral::GAUSS_LEGENDRE => gauss_legendre(Za, Zb, Ma, Mb, E0, binary_collision_geometry.impact_parameter, x0, interaction_potential), - ScatteringIntegral::MAGIC => magic(Za, Zb, Ma, Mb, E0, binary_collision_geometry.impact_parameter, x0, interaction_potential), - }; - - if theta.is_nan() { - return Err(anyhow!("Numerical error: CoM deflection angle is NaN for {}. Check input parameters.", binary_collision_geometry)); - } - - //See Eckstein 1991 for details on center of mass and lab frame angles - let asymptotic_deflection = match interaction_potential { - InteractionPotential::COULOMB{..} => 0., - _ => x0*a*(theta/2.).sin() - }; - let psi = (theta.sin().atan2(Ma/Mb + theta.cos())).abs(); - let psi_recoil = (theta.sin().atan2(1. - theta.cos())).abs(); - let recoil_energy = 4.*(Ma*Mb)/(Ma + Mb).powi(2)*E0*(theta/2.).sin().powi(2); - - Ok(BinaryCollisionResult::new(theta, psi, psi_recoil, recoil_energy, asymptotic_deflection, x0)) -} - - -/// Mendenhall-Weller scattering integrand. -fn scattering_integral_mw(x: f64, beta: f64, reduced_energy: f64, interaction_potential: InteractionPotential) -> f64 { - //Function for scattering integral - see Mendenhall and Weller, 1991 & 2005 - return (1. - interactions::phi(x, interaction_potential)/x/reduced_energy - beta*beta/x/x).powf(-0.5); -} - -/// Gauss-Legendre scattering integrand. -fn scattering_function_gl(u: f64, impact_parameter: f64, r0: f64, relative_energy: f64, interaction_potential: &dyn Fn(f64) -> f64) -> Result { - let result = 4.*impact_parameter*u/(r0*(1. - interaction_potential(r0/(1. - u*u))/relative_energy - impact_parameter*impact_parameter*(1. - u*u).powi(2)/r0/r0).sqrt()); - - if result.is_nan() { - Err(anyhow!("Numerical error: Gauss-Legendre scattering integrand complex. Likely incorrect distance of closest approach Er = {}, r0 = {} A, p = {} A - check root-finder.", - relative_energy/EV, r0/ANGSTROM, impact_parameter/ANGSTROM)) - } else { - Ok(result) - } -} - -/// Gauss-Mehler scattering integrand. -fn scattering_function_gm(u: f64, impact_parameter: f64, r0: f64, relative_energy: f64, interaction_potential: &dyn Fn(f64) -> f64) -> Result { - let result = impact_parameter/r0/(1. - interaction_potential(r0/u)/relative_energy - (impact_parameter*u/r0).powi(2)).sqrt(); - - if result.is_nan() { - Err(anyhow!("Numerical error: Gauss-Mehler scattering integrand complex. Likely incorrect distance of closest approach Er = {} eV r0 = {} A, p = {} A - check root-finder.", - relative_energy/EV, r0/ANGSTROM, impact_parameter/ANGSTROM)) - } else { - Ok(result) - } -} - -/// Compute the scattering integral for a given relative energy, distance of closest approach `r0`, and interaction potential using a Gauss-Mehler, n-point quadrature. -fn scattering_integral_gauss_mehler(impact_parameter: f64, relative_energy: f64, r0: f64, interaction_potential: &dyn Fn(f64) -> f64, n_points: usize) -> f64 { - let x: Vec = (1..=n_points).map(|i| ((2.*i as f64 - 1.)/4./n_points as f64*PI).cos()).collect(); - let w: Vec = (1..=n_points).map(|i| PI/n_points as f64*((2.*i as f64 - 1.)/4./n_points as f64*PI).sin()).collect(); - - PI - x.iter().zip(w) - .map(|(&x, w)| w*scattering_function_gm(x, impact_parameter, r0, relative_energy, interaction_potential) - .with_context(|| format!("Numerical error: NaN in Gauss-Mehler scattering integral at x = {} with Er = {} eV and p = {} A.", x, relative_energy/EV, impact_parameter/ANGSTROM)) - .unwrap()).sum::() -} - -/// Compute the scattering integral for a given relative energy, distance of closest approach `r0`, and interaction potential using a Gauss-Legendre, 5-point quadrature. -fn scattering_integral_gauss_legendre(impact_parameter: f64, relative_energy: f64, r0: f64, interaction_potential: &dyn Fn(f64) -> f64) -> f64 { - let x: Vec = vec![0., -0.538469, 0.538469, -0.90618, 0.90618].iter().map(|x| x/2. + 1./2.).collect(); - let w: Vec = vec![0.568889, 0.478629, 0.478629, 0.236927, 0.236927].iter().map(|w| w/2.).collect(); - - PI - x.iter().zip(w) - .map(|(&x, w)| w*scattering_function_gl(x, impact_parameter, r0, relative_energy, interaction_potential) - .with_context(|| format!("Numerical error: NaN in Gauss-Legendre scattering integral at x = {} with Er = {} eV and p = {} A.", x, relative_energy/EV, impact_parameter/ANGSTROM)) - .unwrap()).sum::() -} - -#[cfg(any(feature = "cpr_rootfinder_openblas", feature = "cpr_rootfinder_netlib", feature = "cpr_rootfinder_intel_mkl"))] -/// Computes the distance of closest approach of two particles with atomic numbers `Za`, `Zb` and masses `Ma`, `Mb` for an inverse-polynomial interaction potential (e.g., Lennard-Jones) for a given impact parameter and incident energy `E0`. -/// Slightly complex roots with an imaginary part smaller than `polynom_complex_threshold` are considered real roots. -pub fn polynomial_rootfinder(Za: f64, Zb: f64, Ma: f64, Mb: f64, E0: f64, impact_parameter: f64, - interaction_potential: InteractionPotential, polynom_complex_threshold: f64) -> Result { - - let a: f64 = interactions::screening_length(Za, Zb, interaction_potential); - let reduced_energy: f64 = LINDHARD_REDUCED_ENERGY_PREFACTOR*a*Mb/(Ma+Mb)/Za/Zb*E0; - let relative_energy = E0*Mb/(Ma + Mb); - - let coefficients = interactions::polynomial_coefficients(relative_energy, impact_parameter, interaction_potential); - let roots = real_polynomial_roots(coefficients.clone(), polynom_complex_threshold).unwrap(); - let max_root = roots.iter().cloned().fold(f64::NAN, f64::max); - let inverse_transformed_root = interactions::inverse_transform(max_root, interaction_potential); - - if roots.is_empty() || inverse_transformed_root.is_nan() { - return Err(anyhow!("Numerical error: polynomial rootfinder failed to find root with coefficients {:?}", - coefficients)) - } else { - return Ok(inverse_transformed_root/a) - } -} - -#[cfg(any(feature = "cpr_rootfinder_openblas", feature = "cpr_rootfinder_netlib", feature = "cpr_rootfinder_intel_mkl"))] -/// Computes the distance of closest approach of two particles with atomic numbers `Za`, `Zb` and masses `Ma`, `Mb` for an arbitrary interaction potential (e.g., Morse) for a given impact parameter and incident energy `E0` using the Chebyshev-Proxy Root-Finder method. -/// -/// # Args: -/// -/// `Za`, `Zb`, `Ma`, `Mb`: atomic numbers and masses of the two particles -/// `E0`: incident energy of particle a in the lab frame. -/// `impact_parameter`: the impact parameter between particles a and b. -/// `interaction_potentaial`: the interaction potential between particles a and b. -/// `n0`: initial degree of Chebyshev interpolants. -/// `nmax`: maximum degree of Chebyshev interpolants. -/// `epsilon`: absolute tolerance of Chebyshev interpolant. -/// `complex_threshold`: slightly-complex roots with an imaginary part below this value are considered real. -/// `far_from_zero`: if the distance of closest approach function, evaluated over an interval [a, b] on the Lobatto grid, is always greater than this value, it is assumed that there are no roots in the interval [a, b]. -/// `truncation_threshold`: trailing terms of the Chebyshev interpolant with coefficients smaller than this value are ignored. -/// `interval_limit`: if subdivision produces an interval smaller than this value, the root-finder will panic. -/// `derivative_free`: if false, use Newton's method to polish roots from the CPR. If true, use the secant method. -/// -/// # Returns -/// Returns the distance of closest approach or an error if the root-finder failed. -pub fn cpr_rootfinder(Za: f64, Zb: f64, Ma: f64, Mb: f64, E0: f64, impact_parameter: f64, - interaction_potential: InteractionPotential, n0: usize, nmax: usize, epsilon: f64, - complex_threshold: f64, truncation_threshold: f64, far_from_zero: f64, - interval_limit: f64, derivative_free: bool) -> Result { - - //Lindhard screening length and reduced energy - let a = interactions::screening_length(Za, Zb, interaction_potential); - let reduced_energy = LINDHARD_REDUCED_ENERGY_PREFACTOR*a*Mb/(Ma+Mb)/Za/Zb*E0; - let relative_energy = E0*Mb/(Ma + Mb); - let p = impact_parameter; - - let f = |r: f64| -> f64 {interactions::distance_of_closest_approach_function(r, a, Za, Zb, relative_energy, impact_parameter, interaction_potential)}; - let g = |r: f64| -> f64 {interactions::distance_of_closest_approach_function_singularity_free(r, a, Za, Zb, relative_energy, impact_parameter, interaction_potential)* - interactions::scaling_function(r, impact_parameter, interaction_potential)}; - - //Using upper bound const, ~10, construct upper bound as a plateau near 0 and linear increase away from that - //let upper_bound = f64::max(upper_bound_const*p, upper_bound_const*a); - let upper_bound = impact_parameter + interactions::crossing_point_doca(interaction_potential); - - let roots = match derivative_free { - true => find_roots_with_secant_polishing(&g, &f, 0., upper_bound, - n0, epsilon, nmax, complex_threshold, - truncation_threshold, interval_limit, far_from_zero), - - false => { - let df = |r: f64| -> f64 {interactions::diff_distance_of_closest_approach_function(r, a, Za, Zb, relative_energy, impact_parameter, interaction_potential)}; - find_roots_with_newton_polishing(&g, &f, &df, 0., upper_bound, - n0, epsilon, nmax, complex_threshold, - truncation_threshold, interval_limit, far_from_zero) - } - }.with_context(|| format!("Numerical error: CPR Rootfinder failed to converge when calculating distance of closest approach for Er = {} eV p = {} A using {}.", - relative_energy/EV, impact_parameter/ANGSTROM, interaction_potential)) - .unwrap(); - - let max_root = roots.iter().cloned().fold(f64::NAN, f64::max)/a; - - if roots.is_empty() || max_root.is_nan() { - return Err(anyhow!("Numerical error: CPR rootfinder failed to find root. x0: {}, F(a): {}, F(b): {};", max_root, g(0.), g(upper_bound))); - } else { - return Ok(max_root); - } -} - -/// Use Newton's method to find the distance of closest approach between two particles a and b. -pub fn newton_rootfinder(Za: f64, Zb: f64, Ma: f64, Mb: f64, E0: f64, impact_parameter: f64, - interaction_potential: InteractionPotential, max_iterations: usize, tolerance: f64) -> Result { - - //Lindhard screening length and reduced energy - let a = interactions::screening_length(Za, Zb, interaction_potential); - let reduced_energy = LINDHARD_REDUCED_ENERGY_PREFACTOR*a*Mb/(Ma+Mb)/Za/Zb*E0; - let relative_energy = E0*Mb/(Ma + Mb); - let beta = impact_parameter/a; - - let f = |r: f64| -> f64 {interactions::distance_of_closest_approach_function(r, a, Za, Zb, relative_energy, impact_parameter, interaction_potential)}; - let df = |r: f64| -> f64 {interactions::diff_distance_of_closest_approach_function(r, a, Za, Zb, relative_energy, impact_parameter, interaction_potential)}; - - //Guess for large reduced energy from Mendenhall and Weller 1991 - //For small energies, use pure Newton-Raphson with arbitrary guess of 1 - let mut x0 = beta; - let mut xn: f64; - if reduced_energy > 5. { - let inv_er_2 = 0.5/reduced_energy; - x0 = inv_er_2 + (inv_er_2*inv_er_2 + beta*beta).sqrt(); - } - - //Newton-Raphson to determine distance of closest approach - let mut err: f64 = tolerance + 1.; - for k in 0..max_iterations { - xn = x0 - f(x0*a)/df(x0*a); - err = (xn - x0)*(xn - x0); - x0 = xn; - if err < tolerance { - return Ok(x0); - } - } - return Err(anyhow!("Numerical error: exceeded maximum number of Newton-Raphson iterations, {}. E: {}; x0: {}; Error: {}; Tolerance: {}", - max_iterations, E0, x0, err, tolerance)); -} - -/// Gauss-Mehler quadrature. -pub fn gauss_mehler(Za: f64, Zb: f64, Ma: f64, Mb: f64, E0: f64, impact_parameter: f64, x0: f64, interaction_potential: InteractionPotential, n_points: usize) -> f64 { - let a: f64 = interactions::screening_length(Za, Zb, interaction_potential); - let r0 = x0*a; - let V = |r| {interactions::interaction_potential(r, a, Za, Zb, interaction_potential)}; - let relative_energy = E0*Mb/(Ma + Mb); - scattering_integral_gauss_mehler(impact_parameter, relative_energy, r0, &V, n_points) -} - -/// Gauss-Legendre quadrature. -pub fn gauss_legendre(Za: f64, Zb: f64, Ma: f64, Mb: f64, E0: f64, impact_parameter: f64, x0: f64, interaction_potential: InteractionPotential) -> f64 { - let a: f64 = interactions::screening_length(Za, Zb, interaction_potential); - let r0 = x0*a; - let V = |r| {interactions::interaction_potential(r, a, Za, Zb, interaction_potential)}; - let relative_energy = E0*Mb/(Ma + Mb); - scattering_integral_gauss_legendre(impact_parameter, relative_energy, r0, &V) -} - -/// Ziegler's MAGIC algorithm for approximating the scattering integral. -pub fn magic(Za: f64, Zb: f64, Ma: f64, Mb: f64, E0: f64, impact_parameter: f64, x0: f64, interaction_potential: InteractionPotential) -> f64 { - //MAGIC algorithm - //Since this is legacy code I don't think I will clean this up - let C_ = match interaction_potential { - InteractionPotential::MOLIERE => vec![ 0.6743, 0.009611, 0.005175, 6.314, 10.0 ], - InteractionPotential::KR_C => vec![ 0.7887, 0.01166, 00.006913, 17.16, 10.79 ], - InteractionPotential::ZBL => vec![ 0.99229, 0.011615, 0.0071222, 9.3066, 14.813 ], - InteractionPotential::TRIDYN => vec![1.0144, 0.235809, 0.126, 69350., 83550.], //Undocumented Tridyn constants - _ => panic!("Input error: unimplemented interaction potential {} for MAGIC algorithm. Use a screened Coulomb potential.", interaction_potential) - }; - let a: f64 = interactions::screening_length(Za, Zb, interaction_potential); - let beta: f64 = impact_parameter/a; - let V0 = Za*Zb*Q*Q/4.0/PI/EPS0/a; - let relative_energy = E0*Mb/(Ma + Mb); - let reduced_energy = relative_energy/V0; - let r0 = a*x0; - let V = V0*a/r0*interactions::phi(x0, interaction_potential); - let dV = -V/r0 + V0/r0*interactions::dphi(x0, interaction_potential); - let rho = -2.0*(relative_energy - V)/dV; - let D = 2.0*(1.0 + C_[0]/reduced_energy.sqrt())*reduced_energy*beta.powf((C_[1] + reduced_energy.sqrt())/(C_[2] + reduced_energy.sqrt())); - let G = (C_[4] + reduced_energy)/(C_[3] + reduced_energy)*((1. + D*D).sqrt() - D); - let delta = D*G/(1.0 + G)*(x0 - beta); - let ctheta2 = (beta + rho/a + delta)/(x0 + rho/a); - 2.*((beta + rho/a + delta)/(x0 + rho/a)).acos() -} - -/// Mendenhall-Weller quadrature. -pub fn mendenhall_weller(Za: f64, Zb: f64, Ma: f64, Mb: f64, E0: f64, impact_parameter: f64, x0: f64, interaction_potential: InteractionPotential) -> f64 { - //Lindhard screening length and reduced energy - let a: f64 = interactions::screening_length(Za, Zb, interaction_potential); - let reduced_energy: f64 = LINDHARD_REDUCED_ENERGY_PREFACTOR*a*Mb/(Ma+Mb)/Za/Zb*E0; - let beta: f64 = impact_parameter/a; - - //Scattering integral quadrature from Mendenhall and Weller 2005 - let lambda0 = (0.5 + beta*beta/x0/x0/2. - interactions::dphi(x0, interaction_potential)/2./reduced_energy).powf(-1./2.); - let alpha = 1./12.*(1. + lambda0 + 5.*(0.4206*scattering_integral_mw(x0/0.9072, beta, reduced_energy, interaction_potential) + 0.9072*scattering_integral_mw(x0/0.4206, beta, reduced_energy, interaction_potential))); - PI*(1. - beta*alpha/x0) -} - -/// Oen-Robinson local electronic energy loss for a collision between particles a and b. -fn oen_robinson_loss(Za: f64, Zb: f64, Se: f64, x0: f64, interaction_potential: InteractionPotential) -> f64 { - //Oen-Robinson local electronic stopping power - let a = interactions::screening_length(Za, Zb, interaction_potential); - - //d1 is the first (smallest) interior constant of the screening function - let d1 = interactions::first_screening_radius(interaction_potential); - d1*d1/2./PI*Se*(-d1*x0).exp()/a/a -} +use super::*; + +#[cfg(any(feature = "cpr_rootfinder_openblas", feature = "cpr_rootfinder_netlib", feature = "cpr_rootfinder_intel_mkl"))] +use rcpr::chebyshev::*; + +/// Geometrical quantities of binary collision. +pub struct BinaryCollisionGeometry { + pub phi_azimuthal: f64, + pub impact_parameter: f64, + pub mfp: f64 +} + +impl BinaryCollisionGeometry { + /// Constructs a new binary collision geometry object. + pub fn new(phi_azimuthal: f64, impact_parameter: f64, mfp: f64) -> BinaryCollisionGeometry { + BinaryCollisionGeometry { + phi_azimuthal, + impact_parameter, + mfp + } + } +} + +impl fmt::Display for BinaryCollisionGeometry { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Binary collision geometry. \n phi_azimuthal = {} \n p = {} \n mfp = {} \n", + self.phi_azimuthal, self.impact_parameter, self.mfp) + } +} + +/// Resultant quantities of a binary collision. +pub struct BinaryCollisionResult { + pub theta: f64, + pub psi: f64, + pub psi_recoil: f64, + pub recoil_energy: f64, + pub asymptotic_deflection: f64, + pub normalized_distance_of_closest_approach: f64 +} + +impl BinaryCollisionResult { + + /// Constructs a new binary collision result object. + pub fn new(theta: f64, psi: f64, psi_recoil: f64, recoil_energy: f64, + asymptotic_deflection: f64, normalized_distance_of_closest_approach: f64) -> BinaryCollisionResult { + BinaryCollisionResult { + theta, + psi, + psi_recoil, + recoil_energy, + asymptotic_deflection, + normalized_distance_of_closest_approach + } + } +} + +impl fmt::Display for BinaryCollisionResult { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Binary collision result. \n theta = {} \n psi = {} \n psi_r = {} \n E_recoil = {} \n tau = {} \n x0 = {} \n", + self.theta, self.psi, self.psi_recoil, self.recoil_energy, self.asymptotic_deflection, self.normalized_distance_of_closest_approach) + } +} + +/// This function takes a single particle, a material, and an options object and runs a binary-collision-approximation trajectory for that particle in that material, producing a final particle list that consists of the original ion and any material particles displaced thereby. +pub fn single_ion_bca(particle: particle::Particle, material: &material::Material, options: &Options) -> Vec { + + let mut particles: Vec = Vec::new(); + particles.push(particle); + + let mut particle_output: Vec = Vec::new(); + + let mut particle_index = particles.len(); + + while particle_index > 0 { + + //Remove particle from top of vector as particle_1 + let mut particle_1 = particles.pop().unwrap(); + + //println!("Particle start Z: {} E: {} ({}, {}, {})", particle_1.Z, particle_1.E/Q, particle_1.pos.x/ANGSTROM, particle_1.pos.y/ANGSTROM, particle_1.pos.z/ANGSTROM); + + //BCA loop + while !particle_1.stopped & !particle_1.left { + + //Choose impact parameters and azimuthal angles for all collisions, and determine mean free path + let binary_collision_geometries = bca::determine_mfp_phi_impact_parameter(&mut particle_1, &material, &options); + + #[cfg(feature = "accelerated_ions")] + let distance_to_target = if !material.inside(particle_1.pos.x, particle_1.pos.y, particle_1.pos.z) { + let (x, y, z) = material.geometry.closest_point(particle_1.pos.x, particle_1.pos.y, particle_1.pos.z); + ((x - particle_1.pos.x).powi(2) + (y - particle_1.pos.y).powi(2) + (z - particle_1.pos.z).powi(2)).sqrt() + } else { + 0. + }; + + let mut total_energy_loss = 0.; + let mut total_asymptotic_deflection = 0.; + let mut normalized_distance_of_closest_approach = 0.; + let mut strong_collision_Z = 0.; + let mut strong_collision_index: usize = 0; + + //Collision loop + for (k, binary_collision_geometry) in binary_collision_geometries.iter().enumerate().take(options.weak_collision_order + 1) { + + let (species_index, mut particle_2) = bca::choose_collision_partner(&particle_1, &material, + &binary_collision_geometry, &options); + + //If recoil location is inside, proceed with binary collision loop + if material.inside(particle_2.pos.x, particle_2.pos.y, particle_2.pos.z) & material.inside_energy_barrier(particle_1.pos.x, particle_1.pos.y, particle_1.pos.z) { + + //Determine scattering angle from binary collision + let binary_collision_result = bca::calculate_binary_collision(&particle_1, + &particle_2, &binary_collision_geometry, &options) + .with_context(|| format!("Numerical error: binary collision calculation failed at x = {} y = {} with {}", + particle_1.pos.x, particle_2.pos.x, &binary_collision_geometry)) + .unwrap(); + + //println!("{}", binary_collision_result); + + //Only use 0th order collision for local electronic stopping + if k == 0 { + normalized_distance_of_closest_approach = binary_collision_result.normalized_distance_of_closest_approach; + strong_collision_Z = particle_2.Z; + strong_collision_index = species_index; + } + + //Energy transfer to recoil + particle_2.E = binary_collision_result.recoil_energy - material.average_bulk_binding_energy(particle_2.pos.x, particle_2.pos.y, particle_2.pos.z); + particle_2.energy_origin = particle_2.E; + + //Accumulate asymptotic deflections for primary particle + total_energy_loss += binary_collision_result.recoil_energy; + + //total_deflection_angle += psi; + total_asymptotic_deflection += binary_collision_result.asymptotic_deflection; + + //Rotate particle 1, 2 by lab frame scattering angles + particle::rotate_particle(&mut particle_1, binary_collision_result.psi, + binary_collision_geometry.phi_azimuthal); + + particle::rotate_particle(&mut particle_2, -binary_collision_result.psi_recoil, + binary_collision_geometry.phi_azimuthal); + + particle_2.dir_old.x = particle_2.dir.x; + particle_2.dir_old.y = particle_2.dir.y; + particle_2.dir_old.z = particle_2.dir.z; + + //Only track number of strong collisions, i.e., k = 0 + if (binary_collision_result.psi > 0.) & (k == 0) { + particle_1.number_collision_events += 1; + } + + //Deep recoil suppression + //See Eckstein 1991 7.5.3 for recoil suppression function + if options.track_recoils & options.suppress_deep_recoils { + let E = particle_1.E; + let Za: f64 = particle_1.Z; + let Zb: f64 = particle_2.Z; + + let Ma: f64 = particle_1.m; + let Mb: f64 = particle_2.m; + + let n = material.total_number_density(particle_2.pos.x, particle_2.pos.y, particle_2.pos.z); + //We just need the lindhard screening length here, so the particular potential is not important + let a: f64 = interactions::screening_length(Za, Zb, InteractionPotential::MOLIERE); + let reduced_energy: f64 = LINDHARD_REDUCED_ENERGY_PREFACTOR*a*Mb/(Ma+Mb)/Za/Zb*E; + let estimated_range_of_recoils = (reduced_energy.powf(0.3) + 0.1).powi(3)/n/a/a; + + let (x2, y2, z2) = material.closest_point(particle_2.pos.x, particle_2.pos.y, particle_2.pos.z); + let dx = x2 - particle_2.pos.x; + let dy = y2 - particle_2.pos.y; + let dz = z2 - particle_2.pos.z; + let distance_to_surface = (dx*dx + dy*dy + dz*dz).sqrt(); + + if (distance_to_surface < estimated_range_of_recoils) & (particle_2.E > particle_2.Ec) { + particles.push(particle_2); + } + + //If transferred energy > cutoff energy, add recoil to particle vector + } else if options.track_recoils & (particle_2.E > particle_2.Ec) { + particles.push(particle_2); + } + } + } + + //Advance particle in space and track total distance traveled + #[cfg(not(feature = "accelerated_ions"))] + let distance_traveled = particle::particle_advance(&mut particle_1, + binary_collision_geometries[0].mfp, total_asymptotic_deflection); + + #[cfg(feature = "accelerated_ions")] + let distance_traveled = particle::particle_advance(&mut particle_1, + binary_collision_geometries[0].mfp + distance_to_target, total_asymptotic_deflection); + + //Subtract total energy from all simultaneous collisions and electronic stopping + bca::update_particle_energy(&mut particle_1, &material, distance_traveled, + total_energy_loss, normalized_distance_of_closest_approach, strong_collision_Z, + strong_collision_index, &options); + //println!("Particle finished collision loop Z: {} E: {} ({}, {}, {})", particle_1.Z, particle_1.E/Q, particle_1.pos.x/ANGSTROM, particle_1.pos.y/ANGSTROM, particle_1.pos.z/ANGSTROM); + + //println!("{} {} {}", energy_0/EV, energy_1/EV, (energy_1 - energy_0)/EV); + + //Check boundary conditions on leaving and stopping + material::boundary_condition_planar(&mut particle_1, &material); + + //Set particle index to topmost particle + particle_index = particles.len(); + } + //println!("Particle stopped or left Z: {} E: {} ({}, {}, {})", particle_1.Z, particle_1.E/Q, particle_1.pos.x/ANGSTROM, particle_1.pos.y/ANGSTROM, particle_1.pos.z/ANGSTROM); + particle_output.push(particle_1); + } + particle_output +} + +/// For a particle in a material, determine the mean free path and choose the azimuthal angle and impact parameter. +/// The mean free path can be exponentially distributed (gaseous) or constant (amorphous solid/liquid). Azimuthal angles are chosen uniformly. Impact parameters are chosen for collision partners distributed uniformly on a disk of density-dependent radius. +pub fn determine_mfp_phi_impact_parameter(particle_1: &mut particle::Particle, material: &material::Material, options: &Options) -> Vec { + + let x = particle_1.pos.x; + let y = particle_1.pos.y; + let z = particle_1.pos.z; + + let mut mfp = material.mfp(x, y, z); + + let mut phis_azimuthal = Vec::with_capacity(options.weak_collision_order + 1); + let mut binary_collision_geometries = Vec::with_capacity(options.weak_collision_order + 1); + + //Each weak collision gets its own aziumuthal angle in annuli around collision point + for k in 0..options.weak_collision_order + 1 { + phis_azimuthal.push(2.*PI*rand::random::()); + } + + if options.high_energy_free_flight_paths { + + let Ma: f64 = particle_1.m; + let Mb: f64 = material.average_mass(x, y, z); + let Za: f64 = particle_1.Z; + let Zb: f64 = material.average_Z(x, y, z); + let n: &Vec = material.number_densities(x, y, z); + let ck: f64 = material.electronic_stopping_correction_factor(x, y, z); + let E: f64 = particle_1.E; + let Ec: f64 = particle_1.Ec; + //We just need the Lindhard screening length here, so the particular potential is not important + let a: f64 = interactions::screening_length(Za, Zb, InteractionPotential::MOLIERE); + let reduced_energy: f64 = LINDHARD_REDUCED_ENERGY_PREFACTOR*a*Mb/(Ma+Mb)/Za/Zb*E; + + //Minimum energy transfer for generating scattering event set to cutoff energy + let E_min = Ec*(Ma + Mb)*(Ma + Mb)/4./Ma/Mb; + let reduced_energy_min: f64 = LINDHARD_REDUCED_ENERGY_PREFACTOR*a*Mb/(Ma+Mb)/Za/Zb*E_min; + + //Free flight path formulation here described in SRIM textbook chapter 7, and Eckstein 1991 7.5.2 + let ep = (reduced_energy*reduced_energy_min).sqrt(); + let mut pmax = a/(ep + ep.sqrt() + 0.125*ep.powf(0.1)); + let mut ffp = 1./(material.total_number_density(x, y, z)*pmax*pmax*PI); + let stopping_powers = material.electronic_stopping_cross_sections(particle_1, ElectronicStoppingMode::INTERPOLATED); + + let delta_energy_electronic = stopping_powers.iter().zip(material.number_densities(x, y, z)).map(|(&se, number_density)| se*number_density).sum::()*ffp*ck; + + //If losing too much energy, scale free-flight-path down + //5 percent limit set in original TRIM paper, Biersack and Haggmark 1980 + if delta_energy_electronic > 0.05*E { + ffp *= 0.05*E/delta_energy_electronic; + pmax = (1./(material.total_number_density(x, y, z)*PI*ffp)).sqrt() + } + + //If free-flight-path less than the interatomic spacing, revert to amorphous solid model + //Mentioned in Eckstein 1991, Ziegler, Biersack, and Ziegler 2008 (SRIM textbook 7-8) + if ffp < mfp { + + ffp = mfp; + //Cylindrical geometry + pmax = mfp/SQRT2PI; + let mut impact_parameter = Vec::with_capacity(1); + let random_number = rand::random::(); + let p = pmax*random_number.sqrt(); + impact_parameter.push(p); + + //Atomically rough surface - scatter initial collisions using mfp near interface + if particle_1.first_step { + ffp = mfp*rand::random::(); + particle_1.first_step = false; + } + + ffp *= match options.mean_free_path_model { + MeanFreePathModel::GASEOUS => -rand::random::().ln(), + MeanFreePathModel::LIQUID => 1.0, + MeanFreePathModel::THRESHOLD{density} => { + if material.geometry.get_densities(x, y, z).iter().sum::() < density { + -rand::random::().ln() + } else { + 1.0 + } + } + }; + + binary_collision_geometries.push(BinaryCollisionGeometry::new(phis_azimuthal[0], impact_parameter[0], ffp)); + return binary_collision_geometries; + + } else { + + //Impact parameter chosen as sqrt(-ln(R))*pmax, as in Biersack and Haggmark 1980, + //And Mendenhall Weller 2005 + //And SRIM textbook chapter 7 + //And Eckstein 1991 + let mut impact_parameter = Vec::with_capacity(1); + let random_number = rand::random::(); + let p = pmax*(-random_number.ln()).sqrt(); + impact_parameter.push(p); + + //Atomically rough surface - scatter initial collisions using mfp near interface + if particle_1.first_step { + ffp = mfp*rand::random::(); + particle_1.first_step = false; + } + + if options.mean_free_path_model == MeanFreePathModel::GASEOUS { + ffp *= -rand::random::().ln(); + } + + binary_collision_geometries.push(BinaryCollisionGeometry::new(phis_azimuthal[0], impact_parameter[0], ffp)); + return binary_collision_geometries; + } + + } else { + + //If not using free flight paths, use weak collision model + let pmax = mfp/SQRTPI; + + //Cylindrical geometry + let mut impact_parameters = Vec::with_capacity(options.weak_collision_order + 1); + for k in 0..(options.weak_collision_order + 1) { + let random_number = rand::random::(); + let p = pmax*(random_number + k as f64).sqrt(); + impact_parameters.push(p) + } + + //Atomically rough surface - scatter initial collisions + if particle_1.first_step { + mfp *= rand::random::(); + particle_1.first_step = false; + } + + if options.mean_free_path_model == MeanFreePathModel::GASEOUS { + mfp *= -rand::random::().ln(); + } + + + for k in 0..(options.weak_collision_order + 1) { + binary_collision_geometries.push(BinaryCollisionGeometry::new(phis_azimuthal[k], impact_parameters[k], mfp)) + } + + return binary_collision_geometries; + } +} + +/// For a particle in a material, and for a particular binary collision geometry, choose a species for the collision partner. +pub fn choose_collision_partner(particle_1: &particle::Particle, material: &material::Material, binary_collision_geometry: &BinaryCollisionGeometry, options: &Options) -> (usize, particle::Particle) { + let x = particle_1.pos.x; + let y = particle_1.pos.y; + let z = particle_1.pos.z; + + let impact_parameter = binary_collision_geometry.impact_parameter; + let mfp = binary_collision_geometry.mfp; + let phi_azimuthal = binary_collision_geometry.phi_azimuthal; + + //Determine cosines and sines + let sinphi: f64 = phi_azimuthal.sin(); + let cosx: f64 = particle_1.dir.x; + let cosy: f64 = particle_1.dir.y; + let cosz: f64 = particle_1.dir.z; + let sinx: f64 = (1. - cosx*cosx).sqrt(); + let cosphi: f64 = phi_azimuthal.cos(); + + //Find recoil location + let x_recoil: f64 = x + mfp*cosx - impact_parameter*cosphi*sinx; + let y_recoil: f64 = y + mfp*cosy - impact_parameter*(sinphi*cosz - cosphi*cosy*cosx)/sinx; + let z_recoil: f64 = z + mfp*cosz + impact_parameter*(sinphi*cosy - cosphi*cosx*cosz)/sinx; + + //Choose recoil Z, M + let (species_index, Z_recoil, M_recoil, Ec_recoil, Es_recoil, interaction_index) = material.choose(x_recoil, y_recoil, z_recoil); + + return (species_index, + particle::Particle::new( + M_recoil, Z_recoil, 0., Ec_recoil, Es_recoil, + x_recoil, y_recoil, z_recoil, + cosx, cosy, cosz, + false, options.track_recoil_trajectories, interaction_index + ) + ) +} + +/// Calculate the distance of closest approach of two particles given a particular binary collision geometry. +fn distance_of_closest_approach(particle_1: &particle::Particle, particle_2: &particle::Particle, binary_collision_geometry: &BinaryCollisionGeometry, options: &Options) -> f64 { + let Za: f64 = particle_1.Z; + let Zb: f64 = particle_2.Z; + let Ma: f64 = particle_1.m; + let Mb: f64 = particle_2.m; + let E0: f64 = particle_1.E; + let relative_energy = E0*Mb/(Ma + Mb); + let p = binary_collision_geometry.impact_parameter; + + let interaction_potential = options.interaction_potential[particle_1.interaction_index][particle_2.interaction_index]; + + if let InteractionPotential::COULOMB{Za: Z1, Zb: Z2} = interaction_potential { + let doca = Z1*Z2*Q*Q/relative_energy/PI/EPS0/8. + (64.*(relative_energy*PI*p*EPS0).powi(2) + (Z1*Z2*Q*Q).powi(2)).sqrt()/relative_energy/PI/EPS0/8.; + return doca/interactions::screening_length(Z1, Z2, interaction_potential); + } + + let root_finder = if relative_energy < interactions::energy_threshold_single_root(interaction_potential) { + options.root_finder[particle_1.interaction_index][particle_2.interaction_index] + } else {Rootfinder::NEWTON{max_iterations: 100, tolerance: 1E-6}}; + + #[cfg(any(feature = "cpr_rootfinder_openblas", feature = "cpr_rootfinder_netlib", feature = "cpr_rootfinder_intel_mkl"))] + match root_finder { + Rootfinder::POLYNOMIAL{complex_threshold} => polynomial_rootfinder(Za, Zb, Ma, Mb, E0, p, interaction_potential, complex_threshold) + .with_context(|| format!("Numerical error: Polynomial rootfinder failed for {} at {} eV with p = {} A.", interaction_potential, E0/EV, p/ANGSTROM)) + .unwrap(), + Rootfinder::CPR{n0, nmax, epsilon, complex_threshold, truncation_threshold, far_from_zero, interval_limit, derivative_free} => + cpr_rootfinder(Za, Zb, Ma, Mb, E0, p, interaction_potential, n0, nmax, epsilon, complex_threshold, truncation_threshold, far_from_zero, interval_limit, derivative_free) + .with_context(|| format!("Numerical error: CPR rootfinder failed for {} at {} eV with p = {} A.", interaction_potential, E0/EV, p/ANGSTROM)) + .unwrap(), + Rootfinder::NEWTON{max_iterations, tolerance} => newton_rootfinder(Za, Zb, Ma, Mb, E0, p, interaction_potential, max_iterations, tolerance) + .with_context(|| format!("Numerical error: Newton rootfinder failed for {} at {} eV with p = {} A.", interaction_potential, E0/EV, p/ANGSTROM)) + .unwrap(), + Rootfinder::DEFAULTNEWTON => newton_rootfinder(Za, Zb, Ma, Mb, E0, p, interaction_potential, 100, 1E-3) + .with_context(|| format!("Numerical error: Newton rootfinder failed for {} at {} eV with p = {} A.", interaction_potential, E0/EV, p/ANGSTROM)) + .unwrap(), + } + + #[cfg(not(any(feature = "cpr_rootfinder_openblas", feature = "cpr_rootfinder_netlib", feature = "cpr_rootfinder_intel_mkl")))] + match root_finder { + Rootfinder::NEWTON{max_iterations, tolerance} => newton_rootfinder(Za, Zb, Ma, Mb, E0, p, interaction_potential, max_iterations, tolerance) + .with_context(|| format!("Numerical error: Newton rootfinder failed for {} at {} eV with p = {} A.", interaction_potential, E0/EV, p/ANGSTROM)) + .unwrap(), + Rootfinder::DEFAULTNEWTON => newton_rootfinder(Za, Zb, Ma, Mb, E0, p, interaction_potential, 100, 1E-3) + .with_context(|| format!("Numerical error: Newton rootfinder failed for {} at {} eV with p = {} A.", interaction_potential, E0/EV, p/ANGSTROM)) + .unwrap(), + _ => panic!("Input error: unimplemented root-finder. Choose NEWTON or build with cpr_rootfinder to enable CPR and POLYNOMIAL") + } +} + +/// Update a particle's energy following nuclear and electronic interactions of a single BCA step. +pub fn update_particle_energy(particle_1: &mut particle::Particle, material: &material::Material, distance_traveled: f64, + recoil_energy: f64, x0: f64, strong_collision_Z: f64, strong_collision_index: usize, options: &Options) { + + //If particle energy drops below zero before electronic stopping calcualtion, it produces NaNs + particle_1.E -= recoil_energy; + assert!(!particle_1.E.is_nan(), "Numerical error: particle energy is NaN following collision."); + if particle_1.E < 0. { + particle_1.E = 0.; + } + + let x = particle_1.pos.x; + let y = particle_1.pos.y; + let z = particle_1.pos.z; + + if material.inside(x, y, z) { + + let interaction_potential = options.interaction_potential[particle_1.interaction_index][material.interaction_index[strong_collision_index]]; + let electronic_stopping_powers = material.electronic_stopping_cross_sections(particle_1, options.electronic_stopping_mode); + let n = material.number_densities(x, y, z); + + let delta_energy = match options.electronic_stopping_mode { + ElectronicStoppingMode::INTERPOLATED => electronic_stopping_powers.iter().zip(n).map(|(se, number_density)| se*number_density).collect::>().iter().sum::()*distance_traveled, + ElectronicStoppingMode::LOW_ENERGY_NONLOCAL => electronic_stopping_powers.iter().zip(n).map(|(se, number_density)| se*number_density).collect::>().iter().sum::()*distance_traveled, + ElectronicStoppingMode::LOW_ENERGY_LOCAL => oen_robinson_loss(particle_1.Z, strong_collision_Z, electronic_stopping_powers[strong_collision_index], x0, interaction_potential), + ElectronicStoppingMode::LOW_ENERGY_EQUIPARTITION => { + + let delta_energy_local = oen_robinson_loss(particle_1.Z, strong_collision_Z, electronic_stopping_powers[strong_collision_index], x0, interaction_potential); + let delta_energy_nonlocal = electronic_stopping_powers.iter().zip(n).map(|(se, number_density)| se*number_density).collect::>().iter().sum::()*distance_traveled; + + 0.5*delta_energy_local + 0.5*delta_energy_nonlocal + }, + }; + + particle_1.E += -delta_energy; + //Make sure particle energy doesn't become negative again + assert!(!particle_1.E.is_nan(), "Numerical error: particle energy is NaN following electronic stopping."); + if particle_1.E < 0. { + particle_1.E = 0.; + } + + particle_1.energy_loss(&options, recoil_energy, delta_energy); + } else if recoil_energy > 0. { + particle_1.energy_loss(&options, recoil_energy, 0.); + } +} + +/// Calculate the binary collision result of two particles for a given binary collision geometry. If the calculation fails, return an Error. +pub fn calculate_binary_collision(particle_1: &particle::Particle, particle_2: &particle::Particle, binary_collision_geometry: &BinaryCollisionGeometry, options: &Options) -> Result { + let Za: f64 = particle_1.Z; + let Zb: f64 = particle_2.Z; + let Ma: f64 = particle_1.m; + let Mb: f64 = particle_2.m; + let E0: f64 = particle_1.E; + let mu: f64 = Mb/(Ma + Mb); + + let interaction_potential = options.interaction_potential[particle_1.interaction_index][particle_2.interaction_index]; + let scattering_integral = options.scattering_integral[particle_1.interaction_index][particle_2.interaction_index]; + + let a: f64 = interactions::screening_length(Za, Zb, interaction_potential); + let x0 = distance_of_closest_approach(particle_1, particle_2, binary_collision_geometry, options); + + let theta: f64 = match scattering_integral { + ScatteringIntegral::MENDENHALL_WELLER => mendenhall_weller(Za, Zb, Ma, Mb, E0, binary_collision_geometry.impact_parameter, x0, interaction_potential), + ScatteringIntegral::GAUSS_MEHLER{n_points} => gauss_mehler(Za, Zb, Ma, Mb, E0, binary_collision_geometry.impact_parameter, x0, interaction_potential, n_points), + ScatteringIntegral::GAUSS_LEGENDRE => gauss_legendre(Za, Zb, Ma, Mb, E0, binary_collision_geometry.impact_parameter, x0, interaction_potential), + ScatteringIntegral::MAGIC => magic(Za, Zb, Ma, Mb, E0, binary_collision_geometry.impact_parameter, x0, interaction_potential), + }; + + if theta.is_nan() { + return Err(anyhow!("Numerical error: CoM deflection angle is NaN for {}. Check input parameters.", binary_collision_geometry)); + } + + //See Eckstein 1991 for details on center of mass and lab frame angles + let asymptotic_deflection = match interaction_potential { + InteractionPotential::COULOMB{..} => 0., + _ => x0*a*(theta/2.).sin() + }; + let psi = (theta.sin().atan2(Ma/Mb + theta.cos())).abs(); + let psi_recoil = (theta.sin().atan2(1. - theta.cos())).abs(); + let recoil_energy = 4.*(Ma*Mb)/(Ma + Mb).powi(2)*E0*(theta/2.).sin().powi(2); + + Ok(BinaryCollisionResult::new(theta, psi, psi_recoil, recoil_energy, asymptotic_deflection, x0)) +} + + +/// Mendenhall-Weller scattering integrand. +fn scattering_integral_mw(x: f64, beta: f64, reduced_energy: f64, interaction_potential: InteractionPotential) -> f64 { + //Function for scattering integral - see Mendenhall and Weller, 1991 & 2005 + return (1. - interactions::phi(x, interaction_potential)/x/reduced_energy - beta*beta/x/x).powf(-0.5); +} + +/// Gauss-Legendre scattering integrand. +fn scattering_function_gl(u: f64, impact_parameter: f64, r0: f64, relative_energy: f64, interaction_potential: &dyn Fn(f64) -> f64) -> Result { + let result = 4.*impact_parameter*u/(r0*(1. - interaction_potential(r0/(1. - u*u))/relative_energy - impact_parameter*impact_parameter*(1. - u*u).powi(2)/r0/r0).sqrt()); + + if result.is_nan() { + Err(anyhow!("Numerical error: Gauss-Legendre scattering integrand complex. Likely incorrect distance of closest approach Er = {}, r0 = {} A, p = {} A - check root-finder.", + relative_energy/EV, r0/ANGSTROM, impact_parameter/ANGSTROM)) + } else { + Ok(result) + } +} + +/// Gauss-Mehler scattering integrand. +fn scattering_function_gm(u: f64, impact_parameter: f64, r0: f64, relative_energy: f64, interaction_potential: &dyn Fn(f64) -> f64) -> Result { + let result = impact_parameter/r0/(1. - interaction_potential(r0/u)/relative_energy - (impact_parameter*u/r0).powi(2)).sqrt(); + + if result.is_nan() { + Err(anyhow!("Numerical error: Gauss-Mehler scattering integrand complex. Likely incorrect distance of closest approach Er = {} eV r0 = {} A, p = {} A - check root-finder.", + relative_energy/EV, r0/ANGSTROM, impact_parameter/ANGSTROM)) + } else { + Ok(result) + } +} + +/// Compute the scattering integral for a given relative energy, distance of closest approach `r0`, and interaction potential using a Gauss-Mehler, n-point quadrature. +fn scattering_integral_gauss_mehler(impact_parameter: f64, relative_energy: f64, r0: f64, interaction_potential: &dyn Fn(f64) -> f64, n_points: usize) -> f64 { + let x: Vec = (1..=n_points).map(|i| ((2.*i as f64 - 1.)/4./n_points as f64*PI).cos()).collect(); + let w: Vec = (1..=n_points).map(|i| PI/n_points as f64*((2.*i as f64 - 1.)/4./n_points as f64*PI).sin()).collect(); + + PI - x.iter().zip(w) + .map(|(&x, w)| w*scattering_function_gm(x, impact_parameter, r0, relative_energy, interaction_potential) + .with_context(|| format!("Numerical error: NaN in Gauss-Mehler scattering integral at x = {} with Er = {} eV and p = {} A.", x, relative_energy/EV, impact_parameter/ANGSTROM)) + .unwrap()).sum::() +} + +/// Compute the scattering integral for a given relative energy, distance of closest approach `r0`, and interaction potential using a Gauss-Legendre, 5-point quadrature. +fn scattering_integral_gauss_legendre(impact_parameter: f64, relative_energy: f64, r0: f64, interaction_potential: &dyn Fn(f64) -> f64) -> f64 { + let x: Vec = vec![0., -0.538469, 0.538469, -0.90618, 0.90618].iter().map(|x| x/2. + 1./2.).collect(); + let w: Vec = vec![0.568889, 0.478629, 0.478629, 0.236927, 0.236927].iter().map(|w| w/2.).collect(); + + PI - x.iter().zip(w) + .map(|(&x, w)| w*scattering_function_gl(x, impact_parameter, r0, relative_energy, interaction_potential) + .with_context(|| format!("Numerical error: NaN in Gauss-Legendre scattering integral at x = {} with Er = {} eV and p = {} A.", x, relative_energy/EV, impact_parameter/ANGSTROM)) + .unwrap()).sum::() +} + +#[cfg(any(feature = "cpr_rootfinder_openblas", feature = "cpr_rootfinder_netlib", feature = "cpr_rootfinder_intel_mkl"))] +/// Computes the distance of closest approach of two particles with atomic numbers `Za`, `Zb` and masses `Ma`, `Mb` for an inverse-polynomial interaction potential (e.g., Lennard-Jones) for a given impact parameter and incident energy `E0`. +/// Slightly complex roots with an imaginary part smaller than `polynom_complex_threshold` are considered real roots. +pub fn polynomial_rootfinder(Za: f64, Zb: f64, Ma: f64, Mb: f64, E0: f64, impact_parameter: f64, + interaction_potential: InteractionPotential, polynom_complex_threshold: f64) -> Result { + + let a: f64 = interactions::screening_length(Za, Zb, interaction_potential); + let reduced_energy: f64 = LINDHARD_REDUCED_ENERGY_PREFACTOR*a*Mb/(Ma+Mb)/Za/Zb*E0; + let relative_energy = E0*Mb/(Ma + Mb); + + let coefficients = interactions::polynomial_coefficients(relative_energy, impact_parameter, interaction_potential); + let roots = real_polynomial_roots(coefficients.clone(), polynom_complex_threshold).unwrap(); + let max_root = roots.iter().cloned().fold(f64::NAN, f64::max); + let inverse_transformed_root = interactions::inverse_transform(max_root, interaction_potential); + + if roots.is_empty() || inverse_transformed_root.is_nan() { + return Err(anyhow!("Numerical error: polynomial rootfinder failed to find root with coefficients {:?}", + coefficients)) + } else { + return Ok(inverse_transformed_root/a) + } +} + +#[cfg(any(feature = "cpr_rootfinder_openblas", feature = "cpr_rootfinder_netlib", feature = "cpr_rootfinder_intel_mkl"))] +/// Computes the distance of closest approach of two particles with atomic numbers `Za`, `Zb` and masses `Ma`, `Mb` for an arbitrary interaction potential (e.g., Morse) for a given impact parameter and incident energy `E0` using the Chebyshev-Proxy Root-Finder method. +/// +/// # Args: +/// +/// `Za`, `Zb`, `Ma`, `Mb`: atomic numbers and masses of the two particles +/// `E0`: incident energy of particle a in the lab frame. +/// `impact_parameter`: the impact parameter between particles a and b. +/// `interaction_potentaial`: the interaction potential between particles a and b. +/// `n0`: initial degree of Chebyshev interpolants. +/// `nmax`: maximum degree of Chebyshev interpolants. +/// `epsilon`: absolute tolerance of Chebyshev interpolant. +/// `complex_threshold`: slightly-complex roots with an imaginary part below this value are considered real. +/// `far_from_zero`: if the distance of closest approach function, evaluated over an interval [a, b] on the Lobatto grid, is always greater than this value, it is assumed that there are no roots in the interval [a, b]. +/// `truncation_threshold`: trailing terms of the Chebyshev interpolant with coefficients smaller than this value are ignored. +/// `interval_limit`: if subdivision produces an interval smaller than this value, the root-finder will panic. +/// `derivative_free`: if false, use Newton's method to polish roots from the CPR. If true, use the secant method. +/// +/// # Returns +/// Returns the distance of closest approach or an error if the root-finder failed. +pub fn cpr_rootfinder(Za: f64, Zb: f64, Ma: f64, Mb: f64, E0: f64, impact_parameter: f64, + interaction_potential: InteractionPotential, n0: usize, nmax: usize, epsilon: f64, + complex_threshold: f64, truncation_threshold: f64, far_from_zero: f64, + interval_limit: f64, derivative_free: bool) -> Result { + + //Lindhard screening length and reduced energy + let a = interactions::screening_length(Za, Zb, interaction_potential); + let reduced_energy = LINDHARD_REDUCED_ENERGY_PREFACTOR*a*Mb/(Ma+Mb)/Za/Zb*E0; + let relative_energy = E0*Mb/(Ma + Mb); + let p = impact_parameter; + + let f = |r: f64| -> f64 {interactions::distance_of_closest_approach_function(r, a, Za, Zb, relative_energy, impact_parameter, interaction_potential)}; + let g = |r: f64| -> f64 {interactions::distance_of_closest_approach_function_singularity_free(r, a, Za, Zb, relative_energy, impact_parameter, interaction_potential)* + interactions::scaling_function(r, impact_parameter, interaction_potential)}; + + //Using upper bound const, ~10, construct upper bound as a plateau near 0 and linear increase away from that + //let upper_bound = f64::max(upper_bound_const*p, upper_bound_const*a); + let upper_bound = impact_parameter + interactions::crossing_point_doca(interaction_potential); + + let roots = match derivative_free { + true => find_roots_with_secant_polishing(&g, &f, 0., upper_bound, + n0, epsilon, nmax, complex_threshold, + truncation_threshold, interval_limit, far_from_zero), + + false => { + let df = |r: f64| -> f64 {interactions::diff_distance_of_closest_approach_function(r, a, Za, Zb, relative_energy, impact_parameter, interaction_potential)}; + find_roots_with_newton_polishing(&g, &f, &df, 0., upper_bound, + n0, epsilon, nmax, complex_threshold, + truncation_threshold, interval_limit, far_from_zero) + } + }.with_context(|| format!("Numerical error: CPR Rootfinder failed to converge when calculating distance of closest approach for Er = {} eV p = {} A using {}.", + relative_energy/EV, impact_parameter/ANGSTROM, interaction_potential)) + .unwrap(); + + let max_root = roots.iter().cloned().fold(f64::NAN, f64::max)/a; + + if roots.is_empty() || max_root.is_nan() { + return Err(anyhow!("Numerical error: CPR rootfinder failed to find root. x0: {}, F(a): {}, F(b): {};", max_root, g(0.), g(upper_bound))); + } else { + return Ok(max_root); + } +} + +/// Use Newton's method to find the distance of closest approach between two particles a and b. +pub fn newton_rootfinder(Za: f64, Zb: f64, Ma: f64, Mb: f64, E0: f64, impact_parameter: f64, + interaction_potential: InteractionPotential, max_iterations: usize, tolerance: f64) -> Result { + + //Lindhard screening length and reduced energy + let a = interactions::screening_length(Za, Zb, interaction_potential); + let reduced_energy = LINDHARD_REDUCED_ENERGY_PREFACTOR*a*Mb/(Ma+Mb)/Za/Zb*E0; + let relative_energy = E0*Mb/(Ma + Mb); + let beta = impact_parameter/a; + + let f = |r: f64| -> f64 {interactions::distance_of_closest_approach_function(r, a, Za, Zb, relative_energy, impact_parameter, interaction_potential)}; + let df = |r: f64| -> f64 {interactions::diff_distance_of_closest_approach_function(r, a, Za, Zb, relative_energy, impact_parameter, interaction_potential)}; + + //Guess for large reduced energy from Mendenhall and Weller 1991 + //For small energies, use pure Newton-Raphson with arbitrary guess of 1 + let mut x0 = beta; + let mut xn: f64; + if reduced_energy > 5. { + let inv_er_2 = 0.5/reduced_energy; + x0 = inv_er_2 + (inv_er_2*inv_er_2 + beta*beta).sqrt(); + } + + //Newton-Raphson to determine distance of closest approach + let mut err: f64 = tolerance + 1.; + for k in 0..max_iterations { + xn = x0 - f(x0*a)/df(x0*a); + err = (xn - x0)*(xn - x0); + x0 = xn; + if err < tolerance { + return Ok(x0); + } + } + return Err(anyhow!("Numerical error: exceeded maximum number of Newton-Raphson iterations, {}. E: {}; x0: {}; Error: {}; Tolerance: {}", + max_iterations, E0, x0, err, tolerance)); +} + +/// Gauss-Mehler quadrature. +pub fn gauss_mehler(Za: f64, Zb: f64, Ma: f64, Mb: f64, E0: f64, impact_parameter: f64, x0: f64, interaction_potential: InteractionPotential, n_points: usize) -> f64 { + let a: f64 = interactions::screening_length(Za, Zb, interaction_potential); + let r0 = x0*a; + let V = |r| {interactions::interaction_potential(r, a, Za, Zb, interaction_potential)}; + let relative_energy = E0*Mb/(Ma + Mb); + scattering_integral_gauss_mehler(impact_parameter, relative_energy, r0, &V, n_points) +} + +/// Gauss-Legendre quadrature. +pub fn gauss_legendre(Za: f64, Zb: f64, Ma: f64, Mb: f64, E0: f64, impact_parameter: f64, x0: f64, interaction_potential: InteractionPotential) -> f64 { + let a: f64 = interactions::screening_length(Za, Zb, interaction_potential); + let r0 = x0*a; + let V = |r| {interactions::interaction_potential(r, a, Za, Zb, interaction_potential)}; + let relative_energy = E0*Mb/(Ma + Mb); + scattering_integral_gauss_legendre(impact_parameter, relative_energy, r0, &V) +} + +/// Ziegler's MAGIC algorithm for approximating the scattering integral. +pub fn magic(Za: f64, Zb: f64, Ma: f64, Mb: f64, E0: f64, impact_parameter: f64, x0: f64, interaction_potential: InteractionPotential) -> f64 { + //MAGIC algorithm + //Since this is legacy code I don't think I will clean this up + let C_ = match interaction_potential { + InteractionPotential::MOLIERE => vec![ 0.6743, 0.009611, 0.005175, 6.314, 10.0 ], + InteractionPotential::KR_C => vec![ 0.7887, 0.01166, 00.006913, 17.16, 10.79 ], + InteractionPotential::ZBL => vec![ 0.99229, 0.011615, 0.0071222, 9.3066, 14.813 ], + InteractionPotential::TRIDYN => vec![1.0144, 0.235809, 0.126, 69350., 83550.], //Undocumented Tridyn constants + _ => panic!("Input error: unimplemented interaction potential {} for MAGIC algorithm. Use a screened Coulomb potential.", interaction_potential) + }; + let a: f64 = interactions::screening_length(Za, Zb, interaction_potential); + let beta: f64 = impact_parameter/a; + let V0 = Za*Zb*Q*Q/4.0/PI/EPS0/a; + let relative_energy = E0*Mb/(Ma + Mb); + let reduced_energy = relative_energy/V0; + let r0 = a*x0; + let V = V0*a/r0*interactions::phi(x0, interaction_potential); + let dV = -V/r0 + V0/r0*interactions::dphi(x0, interaction_potential); + let rho = -2.0*(relative_energy - V)/dV; + let D = 2.0*(1.0 + C_[0]/reduced_energy.sqrt())*reduced_energy*beta.powf((C_[1] + reduced_energy.sqrt())/(C_[2] + reduced_energy.sqrt())); + let G = (C_[4] + reduced_energy)/(C_[3] + reduced_energy)*((1. + D*D).sqrt() - D); + let delta = D*G/(1.0 + G)*(x0 - beta); + let ctheta2 = (beta + rho/a + delta)/(x0 + rho/a); + 2.*((beta + rho/a + delta)/(x0 + rho/a)).acos() +} + +/// Mendenhall-Weller quadrature. +pub fn mendenhall_weller(Za: f64, Zb: f64, Ma: f64, Mb: f64, E0: f64, impact_parameter: f64, x0: f64, interaction_potential: InteractionPotential) -> f64 { + //Lindhard screening length and reduced energy + let a: f64 = interactions::screening_length(Za, Zb, interaction_potential); + let reduced_energy: f64 = LINDHARD_REDUCED_ENERGY_PREFACTOR*a*Mb/(Ma+Mb)/Za/Zb*E0; + let beta: f64 = impact_parameter/a; + + //Scattering integral quadrature from Mendenhall and Weller 2005 + let lambda0 = (0.5 + beta*beta/x0/x0/2. - interactions::dphi(x0, interaction_potential)/2./reduced_energy).powf(-1./2.); + let alpha = 1./12.*(1. + lambda0 + 5.*(0.4206*scattering_integral_mw(x0/0.9072, beta, reduced_energy, interaction_potential) + 0.9072*scattering_integral_mw(x0/0.4206, beta, reduced_energy, interaction_potential))); + PI*(1. - beta*alpha/x0) +} + +/// Oen-Robinson local electronic energy loss for a collision between particles a and b. +fn oen_robinson_loss(Za: f64, Zb: f64, Se: f64, x0: f64, interaction_potential: InteractionPotential) -> f64 { + //Oen-Robinson local electronic stopping power + let a = interactions::screening_length(Za, Zb, interaction_potential); + + //d1 is the first (smallest) interior constant of the screening function + let d1 = interactions::first_screening_radius(interaction_potential); + d1*d1/2./PI*Se*(-d1*x0).exp()/a/a +} diff --git a/src/consts.rs b/src/consts.rs index eb36cd5..a0dfcd2 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -1,35 +1,35 @@ -use super::*; - -//Physical constants -///Fundamental charge in Coulombs. -pub const Q: f64 = 1.602176634E-19; -/// One electron-volt in Joules. -pub const EV: f64 = Q; -/// One atomic mass unit in kilograms. -pub const AMU: f64 = 1.66053906660E-27; -/// One Angstrom in meters. -pub const ANGSTROM: f64 = 1E-10; -/// One micron in meters. -pub const MICRON: f64 = 1E-6; -/// One nanometer in meters. -pub const NM: f64 = 1E-9; -/// One centimeter in meters. -pub const CM: f64 = 1E-2; -/// Vacuum permitivity in Farads/meter. -pub const EPS0: f64 = 8.8541878128E-12; -/// Bohr radius in meters. -pub const A0: f64 = 5.29177210903E-11; -/// Electron mass in kilograms. -pub const ME: f64 = 9.1093837015E-31; -/// sqrt(pi). -pub const SQRTPI: f64 = 2. / FRAC_2_SQRT_PI; -/// sqrt(2 * pi). -pub const SQRT2PI: f64 = 2. * SQRT_2 / FRAC_2_SQRT_PI; -/// Speed of light in meters/second. -pub const C: f64 = 299792458.; -/// Bethe-Bloch electronic stopping prefactor, in SI units. -pub const BETHE_BLOCH_PREFACTOR: f64 = 4.*PI*(Q*Q/(4.*PI*EPS0))*(Q*Q/(4.*PI*EPS0))/ME/C/C; -/// Lindhard-Scharff electronic stopping prefactor, in SI units. -pub const LINDHARD_SCHARFF_PREFACTOR: f64 = 1.212*ANGSTROM*ANGSTROM*Q; -/// Lindhard reduced energy prefactor, in SI units. -pub const LINDHARD_REDUCED_ENERGY_PREFACTOR: f64 = 4.*PI*EPS0/Q/Q; +use super::*; + +//Physical constants +///Fundamental charge in Coulombs. +pub const Q: f64 = 1.602176634E-19; +/// One electron-volt in Joules. +pub const EV: f64 = Q; +/// One atomic mass unit in kilograms. +pub const AMU: f64 = 1.66053906660E-27; +/// One Angstrom in meters. +pub const ANGSTROM: f64 = 1E-10; +/// One micron in meters. +pub const MICRON: f64 = 1E-6; +/// One nanometer in meters. +pub const NM: f64 = 1E-9; +/// One centimeter in meters. +pub const CM: f64 = 1E-2; +/// Vacuum permitivity in Farads/meter. +pub const EPS0: f64 = 8.8541878128E-12; +/// Bohr radius in meters. +pub const A0: f64 = 5.29177210903E-11; +/// Electron mass in kilograms. +pub const ME: f64 = 9.1093837015E-31; +/// sqrt(pi). +pub const SQRTPI: f64 = 2. / FRAC_2_SQRT_PI; +/// sqrt(2 * pi). +pub const SQRT2PI: f64 = 2. * SQRT_2 / FRAC_2_SQRT_PI; +/// Speed of light in meters/second. +pub const C: f64 = 299792458.; +/// Bethe-Bloch electronic stopping prefactor, in SI units. +pub const BETHE_BLOCH_PREFACTOR: f64 = 4.*PI*(Q*Q/(4.*PI*EPS0))*(Q*Q/(4.*PI*EPS0))/ME/C/C; +/// Lindhard-Scharff electronic stopping prefactor, in SI units. +pub const LINDHARD_SCHARFF_PREFACTOR: f64 = 1.212*ANGSTROM*ANGSTROM*Q; +/// Lindhard reduced energy prefactor, in SI units. +pub const LINDHARD_REDUCED_ENERGY_PREFACTOR: f64 = 4.*PI*EPS0/Q/Q; diff --git a/src/enums.rs b/src/enums.rs index c3aa849..07a7cbd 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -1,246 +1,246 @@ -use super::*; - -pub enum MaterialType { - MESH0D(material::Material), - MESH1D(material::Material), - MESH2D(material::Material), - SPHERE(material::Material), - #[cfg(feature = "parry3d")] - BALL(material::Material), - #[cfg(feature = "parry3d")] - TRIMESH(material::Material) -} - -#[derive(Deserialize)] -pub enum GeometryType { - MESH0D, - MESH1D, - MESH2D, - SPHERE, - #[cfg(feature = "parry3d")] - BALL, - #[cfg(feature = "parry3d")] - TRIMESH, -} - -/// Mode of electronic stopping to use. -#[derive(Deserialize, PartialEq, Clone, Copy)] -pub enum ElectronicStoppingMode { - /// Biersack-Varelas interpolated electronic stopping. Valid for ~eV/nucleon to ~GeV/nucleon. - INTERPOLATED, - /// Oen-Robinson Firsov-type local electronic stopping. Valid up to ~25 keV/nucleon. - LOW_ENERGY_LOCAL, - /// Lindhard-Scharff nonlocal electronic stopping. Valid up to ~25 keV/nucleon. - LOW_ENERGY_NONLOCAL, - /// Equipartition between Oen-Robinson and Lindhard-Scharff electronic stopping formulas. - LOW_ENERGY_EQUIPARTITION, -} - -impl fmt::Display for ElectronicStoppingMode { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - ElectronicStoppingMode::INTERPOLATED => write!(f, "Biersack-Varelas electronic stopping"), - ElectronicStoppingMode::LOW_ENERGY_NONLOCAL => write!(f, "Lindhard-Scharff electronic stopping"), - ElectronicStoppingMode::LOW_ENERGY_LOCAL => write!(f, "Oen-Robinson electronic stopping"), - ElectronicStoppingMode::LOW_ENERGY_EQUIPARTITION => write!(f, "Equipartition with Lindhard-Scharff and Oen-Robinson"), - } - } -} - -/// Mode of surface binding energy calculation. -#[derive(Deserialize, PartialEq, Clone, Copy)] -pub enum SurfaceBindingCalculation { - /// Surface binding energies will be determined individually depending only on the particle's `Es`. - INDIVIDUAL, - /// Surface binding energies will be a concentration-weighted average of material surface-binding energies, unless `particle.Es == 0` in which case it will be zero. - TARGET, - /// Surface binding energies will be the average of the particle and TARGET, unless either is zero in which case it will be zero. - AVERAGE, -} - -impl fmt::Display for SurfaceBindingCalculation { - fn fmt (&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - SurfaceBindingCalculation::INDIVIDUAL => write!(f, - "Individual surface binding energies."), - SurfaceBindingCalculation::TARGET => write!(f, - "Concentration-dependent linear combinaion of target binding energies."), - SurfaceBindingCalculation::AVERAGE => write!(f, - "Average between particle and concentration-dependent linear combination of target binding energies."), - } - } -} - -#[derive(Deserialize, PartialEq, Clone, Copy)] -pub enum SurfaceBindingModel { - /// Isotropic surface binding potential - results in no refraction - ISOTROPIC{calculation: SurfaceBindingCalculation}, - /// Planar surface binding potential - particles refract through potential - PLANAR{calculation: SurfaceBindingCalculation}, - /// Planar surface binding potential default - particles refract through potential - TARGET, - INDIVIDUAL, - AVERAGE -} - -impl fmt::Display for SurfaceBindingModel { - fn fmt (&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - SurfaceBindingModel::ISOTROPIC{..} => write!(f, - "Locally isotropic surface binding energy."), - SurfaceBindingModel::PLANAR{..} => write!(f, - "Locally planar surface binding energy."), - SurfaceBindingModel::TARGET => write!(f, "Locally planar surface binding energy."), - SurfaceBindingModel::INDIVIDUAL =>write!(f, "Locally planar surface binding energy."), - SurfaceBindingModel::AVERAGE =>write!(f, "Locally planar surface binding energy."), - } - } -} - -/// Mode of bulk binding energy calculation. -#[derive(Deserialize, PartialEq, Clone, Copy)] -pub enum BulkBindingModel { - /// Bulk binding energies will be determined individually depending only on the particle's `Eb`. - INDIVIDUAL, - /// Bulk binding energies will be the concentration-weighted average, unless either is zero in which case it will be zero. - AVERAGE, -} - -impl fmt::Display for BulkBindingModel { - fn fmt (&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - BulkBindingModel::INDIVIDUAL => write!(f, - "Individual bulk binding energies."), - - BulkBindingModel::AVERAGE => write!(f, - "Concentration-weighted average bulk binding energy."), - } - } -} - -/// Mean-free-path model. -#[derive(Deserialize, PartialEq, Clone, Copy)] -pub enum MeanFreePathModel { - /// Constant mean-free-path for liquids and amorphous solids. - LIQUID, - /// Exponentially-distributed mean-free-paths for gases. - GASEOUS, - /// Switch from gas (below threshold) to liquid (above threshold). - THRESHOLD{density: f64} -} - -impl fmt::Display for MeanFreePathModel { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - MeanFreePathModel::LIQUID => write!(f, "Amorphous Solid/Liquid Model"), - MeanFreePathModel::GASEOUS => write!(f, "Gaseous Model"), - MeanFreePathModel::THRESHOLD{density} => write!(f, "Gaseous if n0 < {}, Liquid/Amorphous Solid otherwise.", density) - } - } -} - -/// Interatomic potentials between particles in rustbca. -#[derive(Deserialize, Clone, Copy)] -pub enum InteractionPotential { - /// TRIDYN-style Kr-C. Equivalent to KR_C, except for the MAGIC constants. - TRIDYN, - /// Moliere's approximation to the Thomas-Fermi interatomic potential. - MOLIERE, - /// Krypton-Carbon "universal" interatomic potential. - KR_C, - /// Ziegler-Biersack-Littmark "unviversal" semi-empirical interatomic potential. - ZBL, - /// Lenz-Jensen screened Coulomb potential. - LENZ_JENSEN, - /// Lennard-Jones 12-6 potential, with user-defined sigma and epsilon. - LENNARD_JONES_12_6 {sigma: f64, epsilon: f64}, - /// Lennard-Jones 6.5-6 potential, with user-defined sigma and epsilon. - LENNARD_JONES_65_6 {sigma: f64, epsilon: f64}, - /// Morse potential, with user-defined D, alpha, and r0. - MORSE{D: f64, alpha: f64, r0: f64}, - /// Tungsten-tungsten cubic spline potential (Following Bjorkas et al.) - WW, - /// Unscreened Coulombic interatomic potential between ions with charges Za and Zb. - COULOMB{Za: f64, Zb: f64} -} - -impl fmt::Display for InteractionPotential { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - InteractionPotential::TRIDYN => write!(f, "TRIDYN-style Kr-C (Different MAGIC constants)"), - InteractionPotential::MOLIERE => write!(f, "Moliere Potential"), - InteractionPotential::KR_C => write!(f, "Kr-C Potential"), - InteractionPotential::ZBL => write!(f, "ZBL Potential"), - InteractionPotential::LENZ_JENSEN => write!(f, "Lenz-Jensen Potential"), - InteractionPotential::LENNARD_JONES_12_6{sigma, epsilon} => write!(f, "Lennard-Jones 12-6 Potential with sigma = {} A, epsilon = {} eV", sigma/ANGSTROM, epsilon/EV), - InteractionPotential::LENNARD_JONES_65_6{sigma, epsilon} => write!(f, "Lennard-Jones 6.5-6 Potential with sigma = {} A, epsilon = {} eV", sigma/ANGSTROM, epsilon/EV), - InteractionPotential::MORSE{D, alpha, r0} => write!(f, "Morse potential with D = {} eV, alpha = {} 1/A, and r0 = {} A", D/EV, alpha*ANGSTROM, r0/ANGSTROM), - InteractionPotential::WW => write!(f, "W-W cubic spline interaction potential."), - InteractionPotential::COULOMB{Za, Zb} => write!(f, "Coulombic interaction with Za = {} and Zb = {}", Za, Zb) - } - } -} - -impl PartialEq for InteractionPotential { - fn eq(&self, other: &Self) -> bool { - discriminant(self) == discriminant(other) - } -} - -/// Method for solving the scattering integral. -#[derive(Deserialize, Clone, Copy)] -pub enum ScatteringIntegral { - /// Mendenhall-Weller Gauss-Lobatto 4-point quadrature. - MENDENHALL_WELLER, - /// Ziegler's MAGIC algorithm. - MAGIC, - /// Gauss-Mehler n-point quadrature. - GAUSS_MEHLER{n_points: usize}, - /// Gauss-Legendre 5-point quadrature. - GAUSS_LEGENDRE -} - -impl fmt::Display for ScatteringIntegral { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - ScatteringIntegral::MENDENHALL_WELLER => write!(f, "Mendenhall-Weller 4-Point Lobatto Quadrature"), - ScatteringIntegral::MAGIC => write!(f, "MAGIC Algorithm"), - ScatteringIntegral::GAUSS_MEHLER{n_points} => write!(f, "Gauss-Mehler {}-point Quadrature", n_points), - ScatteringIntegral::GAUSS_LEGENDRE => write!(f, "Gauss-Legendre 5-point Quadrature"), - } - } -} - -impl PartialEq for ScatteringIntegral { - fn eq(&self, other: &Self) -> bool { - discriminant(self) == discriminant(other) - } -} - -/// Root-finding algorithm. -#[derive(Deserialize, Clone, Copy)] -pub enum Rootfinder { - /// Newton root-finder with user-defined `max_iterations` and `tolerance`. - NEWTON{max_iterations: usize, tolerance: f64}, - CPR{n0: usize, nmax: usize, epsilon: f64, complex_threshold: f64, truncation_threshold: f64, - far_from_zero: f64, interval_limit: f64, derivative_free: bool}, - POLYNOMIAL{complex_threshold: f64}, - DEFAULTNEWTON, -} - -impl fmt::Display for Rootfinder { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Rootfinder::NEWTON{max_iterations, tolerance} => write!(f, "Newton-Raphson Rootfinder with maximum {} iterations and toleance = {}", max_iterations, tolerance), - Rootfinder::CPR{n0, nmax, epsilon, complex_threshold, truncation_threshold, far_from_zero, interval_limit, derivative_free} => - write!(f, "Chebyshev-Proxy Rootfinder with {}-polishing", match derivative_free { true => "Secant", false => "Newton"}), - Rootfinder::POLYNOMIAL{complex_threshold} => write!(f, "Frobenius Companion Matrix Polynomial Real Rootfinder with a complex tolerance of {}", complex_threshold), - Rootfinder::DEFAULTNEWTON => write!(f, "Newton-Raphson Rootfinder with maximum {} iterations and toleance = {}", 100, 1E-3), - } - } -} -impl PartialEq for Rootfinder { - fn eq(&self, other: &Self) -> bool { - discriminant(self) == discriminant(other) - } -} +use super::*; + +pub enum MaterialType { + MESH0D(material::Material), + MESH1D(material::Material), + MESH2D(material::Material), + SPHERE(material::Material), + #[cfg(feature = "parry3d")] + BALL(material::Material), + #[cfg(feature = "parry3d")] + TRIMESH(material::Material) +} + +#[derive(Deserialize)] +pub enum GeometryType { + MESH0D, + MESH1D, + MESH2D, + SPHERE, + #[cfg(feature = "parry3d")] + BALL, + #[cfg(feature = "parry3d")] + TRIMESH, +} + +/// Mode of electronic stopping to use. +#[derive(Deserialize, PartialEq, Clone, Copy)] +pub enum ElectronicStoppingMode { + /// Biersack-Varelas interpolated electronic stopping. Valid for ~eV/nucleon to ~GeV/nucleon. + INTERPOLATED, + /// Oen-Robinson Firsov-type local electronic stopping. Valid up to ~25 keV/nucleon. + LOW_ENERGY_LOCAL, + /// Lindhard-Scharff nonlocal electronic stopping. Valid up to ~25 keV/nucleon. + LOW_ENERGY_NONLOCAL, + /// Equipartition between Oen-Robinson and Lindhard-Scharff electronic stopping formulas. + LOW_ENERGY_EQUIPARTITION, +} + +impl fmt::Display for ElectronicStoppingMode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ElectronicStoppingMode::INTERPOLATED => write!(f, "Biersack-Varelas electronic stopping"), + ElectronicStoppingMode::LOW_ENERGY_NONLOCAL => write!(f, "Lindhard-Scharff electronic stopping"), + ElectronicStoppingMode::LOW_ENERGY_LOCAL => write!(f, "Oen-Robinson electronic stopping"), + ElectronicStoppingMode::LOW_ENERGY_EQUIPARTITION => write!(f, "Equipartition with Lindhard-Scharff and Oen-Robinson"), + } + } +} + +/// Mode of surface binding energy calculation. +#[derive(Deserialize, PartialEq, Clone, Copy)] +pub enum SurfaceBindingCalculation { + /// Surface binding energies will be determined individually depending only on the particle's `Es`. + INDIVIDUAL, + /// Surface binding energies will be a concentration-weighted average of material surface-binding energies, unless `particle.Es == 0` in which case it will be zero. + TARGET, + /// Surface binding energies will be the average of the particle and TARGET, unless either is zero in which case it will be zero. + AVERAGE, +} + +impl fmt::Display for SurfaceBindingCalculation { + fn fmt (&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SurfaceBindingCalculation::INDIVIDUAL => write!(f, + "Individual surface binding energies."), + SurfaceBindingCalculation::TARGET => write!(f, + "Concentration-dependent linear combinaion of target binding energies."), + SurfaceBindingCalculation::AVERAGE => write!(f, + "Average between particle and concentration-dependent linear combination of target binding energies."), + } + } +} + +#[derive(Deserialize, PartialEq, Clone, Copy)] +pub enum SurfaceBindingModel { + /// Isotropic surface binding potential - results in no refraction + ISOTROPIC{calculation: SurfaceBindingCalculation}, + /// Planar surface binding potential - particles refract through potential + PLANAR{calculation: SurfaceBindingCalculation}, + /// Planar surface binding potential default - particles refract through potential + TARGET, + INDIVIDUAL, + AVERAGE +} + +impl fmt::Display for SurfaceBindingModel { + fn fmt (&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SurfaceBindingModel::ISOTROPIC{..} => write!(f, + "Locally isotropic surface binding energy."), + SurfaceBindingModel::PLANAR{..} => write!(f, + "Locally planar surface binding energy."), + SurfaceBindingModel::TARGET => write!(f, "Locally planar surface binding energy."), + SurfaceBindingModel::INDIVIDUAL =>write!(f, "Locally planar surface binding energy."), + SurfaceBindingModel::AVERAGE =>write!(f, "Locally planar surface binding energy."), + } + } +} + +/// Mode of bulk binding energy calculation. +#[derive(Deserialize, PartialEq, Clone, Copy)] +pub enum BulkBindingModel { + /// Bulk binding energies will be determined individually depending only on the particle's `Eb`. + INDIVIDUAL, + /// Bulk binding energies will be the concentration-weighted average, unless either is zero in which case it will be zero. + AVERAGE, +} + +impl fmt::Display for BulkBindingModel { + fn fmt (&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + BulkBindingModel::INDIVIDUAL => write!(f, + "Individual bulk binding energies."), + + BulkBindingModel::AVERAGE => write!(f, + "Concentration-weighted average bulk binding energy."), + } + } +} + +/// Mean-free-path model. +#[derive(Deserialize, PartialEq, Clone, Copy)] +pub enum MeanFreePathModel { + /// Constant mean-free-path for liquids and amorphous solids. + LIQUID, + /// Exponentially-distributed mean-free-paths for gases. + GASEOUS, + /// Switch from gas (below threshold) to liquid (above threshold). + THRESHOLD{density: f64} +} + +impl fmt::Display for MeanFreePathModel { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + MeanFreePathModel::LIQUID => write!(f, "Amorphous Solid/Liquid Model"), + MeanFreePathModel::GASEOUS => write!(f, "Gaseous Model"), + MeanFreePathModel::THRESHOLD{density} => write!(f, "Gaseous if n0 < {}, Liquid/Amorphous Solid otherwise.", density) + } + } +} + +/// Interatomic potentials between particles in rustbca. +#[derive(Deserialize, Clone, Copy)] +pub enum InteractionPotential { + /// TRIDYN-style Kr-C. Equivalent to KR_C, except for the MAGIC constants. + TRIDYN, + /// Moliere's approximation to the Thomas-Fermi interatomic potential. + MOLIERE, + /// Krypton-Carbon "universal" interatomic potential. + KR_C, + /// Ziegler-Biersack-Littmark "unviversal" semi-empirical interatomic potential. + ZBL, + /// Lenz-Jensen screened Coulomb potential. + LENZ_JENSEN, + /// Lennard-Jones 12-6 potential, with user-defined sigma and epsilon. + LENNARD_JONES_12_6 {sigma: f64, epsilon: f64}, + /// Lennard-Jones 6.5-6 potential, with user-defined sigma and epsilon. + LENNARD_JONES_65_6 {sigma: f64, epsilon: f64}, + /// Morse potential, with user-defined D, alpha, and r0. + MORSE{D: f64, alpha: f64, r0: f64}, + /// Tungsten-tungsten cubic spline potential (Following Bjorkas et al.) + WW, + /// Unscreened Coulombic interatomic potential between ions with charges Za and Zb. + COULOMB{Za: f64, Zb: f64} +} + +impl fmt::Display for InteractionPotential { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + InteractionPotential::TRIDYN => write!(f, "TRIDYN-style Kr-C (Different MAGIC constants)"), + InteractionPotential::MOLIERE => write!(f, "Moliere Potential"), + InteractionPotential::KR_C => write!(f, "Kr-C Potential"), + InteractionPotential::ZBL => write!(f, "ZBL Potential"), + InteractionPotential::LENZ_JENSEN => write!(f, "Lenz-Jensen Potential"), + InteractionPotential::LENNARD_JONES_12_6{sigma, epsilon} => write!(f, "Lennard-Jones 12-6 Potential with sigma = {} A, epsilon = {} eV", sigma/ANGSTROM, epsilon/EV), + InteractionPotential::LENNARD_JONES_65_6{sigma, epsilon} => write!(f, "Lennard-Jones 6.5-6 Potential with sigma = {} A, epsilon = {} eV", sigma/ANGSTROM, epsilon/EV), + InteractionPotential::MORSE{D, alpha, r0} => write!(f, "Morse potential with D = {} eV, alpha = {} 1/A, and r0 = {} A", D/EV, alpha*ANGSTROM, r0/ANGSTROM), + InteractionPotential::WW => write!(f, "W-W cubic spline interaction potential."), + InteractionPotential::COULOMB{Za, Zb} => write!(f, "Coulombic interaction with Za = {} and Zb = {}", Za, Zb) + } + } +} + +impl PartialEq for InteractionPotential { + fn eq(&self, other: &Self) -> bool { + discriminant(self) == discriminant(other) + } +} + +/// Method for solving the scattering integral. +#[derive(Deserialize, Clone, Copy)] +pub enum ScatteringIntegral { + /// Mendenhall-Weller Gauss-Lobatto 4-point quadrature. + MENDENHALL_WELLER, + /// Ziegler's MAGIC algorithm. + MAGIC, + /// Gauss-Mehler n-point quadrature. + GAUSS_MEHLER{n_points: usize}, + /// Gauss-Legendre 5-point quadrature. + GAUSS_LEGENDRE +} + +impl fmt::Display for ScatteringIntegral { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ScatteringIntegral::MENDENHALL_WELLER => write!(f, "Mendenhall-Weller 4-Point Lobatto Quadrature"), + ScatteringIntegral::MAGIC => write!(f, "MAGIC Algorithm"), + ScatteringIntegral::GAUSS_MEHLER{n_points} => write!(f, "Gauss-Mehler {}-point Quadrature", n_points), + ScatteringIntegral::GAUSS_LEGENDRE => write!(f, "Gauss-Legendre 5-point Quadrature"), + } + } +} + +impl PartialEq for ScatteringIntegral { + fn eq(&self, other: &Self) -> bool { + discriminant(self) == discriminant(other) + } +} + +/// Root-finding algorithm. +#[derive(Deserialize, Clone, Copy)] +pub enum Rootfinder { + /// Newton root-finder with user-defined `max_iterations` and `tolerance`. + NEWTON{max_iterations: usize, tolerance: f64}, + CPR{n0: usize, nmax: usize, epsilon: f64, complex_threshold: f64, truncation_threshold: f64, + far_from_zero: f64, interval_limit: f64, derivative_free: bool}, + POLYNOMIAL{complex_threshold: f64}, + DEFAULTNEWTON, +} + +impl fmt::Display for Rootfinder { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Rootfinder::NEWTON{max_iterations, tolerance} => write!(f, "Newton-Raphson Rootfinder with maximum {} iterations and toleance = {}", max_iterations, tolerance), + Rootfinder::CPR{n0, nmax, epsilon, complex_threshold, truncation_threshold, far_from_zero, interval_limit, derivative_free} => + write!(f, "Chebyshev-Proxy Rootfinder with {}-polishing", match derivative_free { true => "Secant", false => "Newton"}), + Rootfinder::POLYNOMIAL{complex_threshold} => write!(f, "Frobenius Companion Matrix Polynomial Real Rootfinder with a complex tolerance of {}", complex_threshold), + Rootfinder::DEFAULTNEWTON => write!(f, "Newton-Raphson Rootfinder with maximum {} iterations and toleance = {}", 100, 1E-3), + } + } +} +impl PartialEq for Rootfinder { + fn eq(&self, other: &Self) -> bool { + discriminant(self) == discriminant(other) + } +} diff --git a/src/geometry.rs b/src/geometry.rs index 1e10a3d..462bad5 100644 --- a/src/geometry.rs +++ b/src/geometry.rs @@ -1,652 +1,652 @@ -use super::*; - -use geo::algorithm::contains::Contains; -use geo::{Polygon, LineString, Point, point, Closest}; -use geo::algorithm::closest_point::ClosestPoint; - -///Trait for a Geometry object - all forms of geometry must implement these traits to be used -pub trait Geometry { - - type InputFileFormat: InputFile + Clone; - - fn new(input: &<::InputFileFormat as GeometryInput>::GeometryInput) -> Self; - fn get_densities(&self, x: f64, y: f64, z: f64) -> &Vec; - fn get_ck(&self, x: f64, y: f64, z: f64) -> f64; - fn get_total_density(&self, x: f64, y: f64, z: f64) -> f64; - fn get_concentrations(&self, x: f64, y: f64, z: f64) -> &Vec; - fn inside(&self, x: f64, y: f64, z: f64) -> bool; - fn inside_simulation_boundary(&self, x: f64, y: f64, z: f64) -> bool; - fn inside_energy_barrier(&self, x: f64, y: f64, z: f64) -> bool; - fn closest_point(&self, x: f64, y: f64, z: f64) -> (f64, f64, f64); -} - -pub trait GeometryElement: Clone { - fn contains(&self, x: f64, y: f64, z: f64) -> bool; - fn distance_to(&self, x: f64, y: f64, z: f64) -> f64; - fn get_densities(&self) -> &Vec; - fn get_concentrations(&self) -> &Vec; - fn get_electronic_stopping_correction_factor(&self) -> f64; -} - -#[derive(Deserialize, Clone)] -pub struct Mesh0DInput { - pub length_unit: String, - pub densities: Vec, - pub electronic_stopping_correction_factor: f64, -} - -#[derive(Clone)] -pub struct Mesh0D { - pub densities: Vec, - pub concentrations: Vec, - pub electronic_stopping_correction_factor: f64, - pub energy_barrier_thickness: f64, -} - -impl Geometry for Mesh0D { - - type InputFileFormat = Input0D; - - fn new(input: &<::InputFileFormat as GeometryInput>::GeometryInput) -> Mesh0D { - - let length_unit: f64 = match input.length_unit.as_str() { - "MICRON" => MICRON, - "CM" => CM, - "ANGSTROM" => ANGSTROM, - "NM" => NM, - "M" => 1., - _ => input.length_unit.parse() - .expect(format!( - "Input errror: could nor parse length unit {}. Use a valid float or one of ANGSTROM, NM, MICRON, CM, MM, M", - &input.length_unit.as_str() - ).as_str()), - }; - - let electronic_stopping_correction_factor = input.electronic_stopping_correction_factor; - - let densities: Vec = input.densities.iter().map(|element| element/(length_unit).powi(3)).collect(); - - let total_density: f64 = densities.iter().sum(); - - let energy_barrier_thickness = total_density.powf(-1./3.)/SQRTPI*2.; - - let concentrations: Vec = densities.iter().map(|&density| density/total_density).collect::>(); - - Mesh0D { - densities, - concentrations, - electronic_stopping_correction_factor, - energy_barrier_thickness - } - } - - fn get_densities(&self, x: f64, y: f64, z: f64) -> &Vec { - &self.densities - } - fn get_ck(&self, x: f64, y: f64, z: f64) -> f64 { - self.electronic_stopping_correction_factor - } - fn get_total_density(&self, x: f64, y: f64, z: f64) -> f64{ - self.densities.iter().sum() - } - fn get_concentrations(&self, x: f64, y: f64, z: f64) -> &Vec { - &self.concentrations - } - fn inside(&self, x: f64, y: f64, z: f64) -> bool { - x > 0.0 - } - - fn inside_simulation_boundary(&self, x: f64, y: f64, z: f64) -> bool { - //println!("x: {} energy_barrier_thickness: {}", x/ANGSTROM, self.energy_barrier_thickness/ANGSTROM); - x > -10.*self.energy_barrier_thickness - } - - fn inside_energy_barrier(&self, x: f64, y: f64, z: f64) -> bool { - x > -self.energy_barrier_thickness - } - - fn closest_point(&self, x: f64, y: f64, z: f64) -> (f64, f64, f64) { - (0., y, z) - } -} - -#[derive(Deserialize, Clone)] -pub struct Mesh1DInput { - pub length_unit: String, - pub layer_thicknesses: Vec, - pub densities: Vec>, - pub electronic_stopping_correction_factors: Vec, -} - -#[derive(Clone)] -pub struct Mesh1D { - layers: Vec, - top: f64, - bottom: f64, - pub top_energy_barrier_thickness: f64, - pub bottom_energy_barrier_thickness: f64, -} - -impl Geometry for Mesh1D { - - type InputFileFormat = input::Input1D; - - fn new(geometry_input: &<::InputFileFormat as GeometryInput>::GeometryInput) -> Self { - - let layer_thicknesses = geometry_input.layer_thicknesses.clone(); - let electronic_stopping_correction_factors = geometry_input.electronic_stopping_correction_factors.clone(); - let n = layer_thicknesses.len(); - - let mut layers: Vec = Vec::with_capacity(n); - - //Multiply all coordinates by value of geometry unit. - let length_unit: f64 = match geometry_input.length_unit.as_str() { - "MICRON" => MICRON, - "CM" => CM, - "ANGSTROM" => ANGSTROM, - "NM" => NM, - "M" => 1., - _ => geometry_input.length_unit.parse() - .expect(format!( - "Input errror: could nor parse length unit {}. Use a valid float or one of ANGSTROM, NM, MICRON, CM, MM, M", &geometry_input.length_unit.as_str() - ).as_str()), - }; - - let densities: Vec> = geometry_input.densities - .iter() - .map( |row| row.iter().map(|element| element/(length_unit).powi(3)).collect() ).collect(); - - //Assert all layer density lists are equal length - assert!( - densities.iter() - .all(|density_list| density_list.len() == densities[0].len()), - "Input error: all layer density lists must be the same size." - ); - assert_eq!(layer_thicknesses.len(), densities.len(), "Input error: coordinates and data of unequal length."); - - let mut layer_top = 0.; - let mut layer_bottom = 0.; - for ((layer_thickness, densities), ck) in layer_thicknesses.iter().zip(densities).zip(electronic_stopping_correction_factors) { - - layer_bottom += layer_thickness*length_unit; - - let total_density: f64 = densities.iter().sum(); - let concentrations: Vec = densities.iter().map(|&density| density/total_density).collect::>(); - - layers.push(Layer1D::new(layer_top, layer_bottom, densities, concentrations, ck)); - layer_top = layer_bottom; - } - - let top_energy_barrier_thickness = layers[0].densities.iter().sum::().powf(-1./3.)/SQRTPI*2.; - let bottom_energy_barrier_thickness = layers[layers.len() - 1].densities.iter().sum::().powf(-1./3.)/SQRTPI*2.; - - Mesh1D { - layers, - top: 0.0, - bottom: layer_bottom, - top_energy_barrier_thickness, - bottom_energy_barrier_thickness, - } - } - fn get_densities(&self, x: f64, y: f64, z: f64) -> &Vec { - if self.inside(x, y, z) { - for layer in &self.layers { - if layer.contains(x, y, z) { - return &layer.densities - } - } - panic!("Geometry error: method inside() is returning true for points outside all layers. Check geometry.") - } else if x < self.top { - &self.layers[0].densities - } else { - &self.layers[self.layers.len() - 1].densities - } - } - fn get_ck(&self, x: f64, y: f64, z: f64) -> f64 { - if self.inside(x, y, z) { - for layer in &self.layers { - if layer.contains(x, y, z) { - return layer.electronic_stopping_correction_factor - } - } - panic!("Geometry error: method inside() is returning true for points outside all layers. Check geometry.") - } else if x < self.top { - self.layers[0].electronic_stopping_correction_factor - } else { - self.layers[self.layers.len() - 1].electronic_stopping_correction_factor - } - } - fn get_total_density(&self, x: f64, y: f64, z: f64) -> f64 { - if self.inside(x, y, z) { - for layer in &self.layers { - if layer.contains(x, y, z) { - return layer.densities.iter().sum() - } - } - panic!("Geometry error: method inside() is returning true for points outside all layers. Check geometry.") - } else if x < self.top { - self.layers[0].densities.iter().sum() - } else { - self.layers[self.layers.len() - 1].densities.iter().sum() - } - } - fn get_concentrations(&self, x: f64, y: f64, z: f64) -> &Vec { - if self.inside(x, y, z) { - for layer in &self.layers { - if layer.contains(x, y, z) { - return &layer.concentrations - } - } - panic!("Geometry error: method inside() is returning true for points outside all layers. Check geometry.") - } else if x < self.top { - &self.layers[0].concentrations - } else { - &self.layers[self.layers.len() - 1].concentrations - } - } - fn inside(&self, x: f64, y: f64, z: f64) -> bool { - (x > self.top) & (x < self.bottom) - } - fn inside_simulation_boundary(&self, x: f64, y: f64, z: f64) -> bool { - (x > self.top - 10.*self.top_energy_barrier_thickness) & (x < self.bottom + 10.*self.bottom_energy_barrier_thickness) - } - fn inside_energy_barrier(&self, x: f64, y: f64, z: f64) -> bool { - (x > self.top - self.top_energy_barrier_thickness) & (x < self.bottom + self.bottom_energy_barrier_thickness) - } - fn closest_point(&self, x: f64, y: f64, z: f64) -> (f64, f64, f64) { - let distance_from_bottom = (x - self.bottom).abs(); - let distance_from_top = (x - self.top).abs(); - if distance_from_bottom < distance_from_top { - (self.bottom, y, z) - } else { - (self.top, y, z) - } - } -} - -/// Object that contains raw mesh input data. -#[derive(Deserialize, Clone)] -pub struct Mesh2DInput { - pub length_unit: String, - pub triangles: Vec<(f64, f64, f64, f64, f64, f64)>, - pub material_boundary_points: Vec<(f64, f64)>, - pub simulation_boundary_points: Vec<(f64, f64)>, - pub densities: Vec>, - pub electronic_stopping_correction_factors: Vec, - pub energy_barrier_thickness: f64, -} - -/// Triangular mesh for rustbca. -#[derive(Clone)] -pub struct Mesh2D { - mesh: Vec, - pub boundary: Polygon, - pub simulation_boundary: Polygon, - pub energy_barrier_thickness: f64 -} - -impl Mesh2D { - /// Finds the cell that is nearest to (x, y). - fn nearest_to(&self, x: f64, y: f64, z: f64) -> &Cell2D { - - let mut min_distance: f64 = std::f64::MAX; - let mut index: usize = 0; - - for (cell_index, cell) in self.mesh.iter().enumerate() { - let distance_to = cell.distance_to(x, y, z); - if distance_to < min_distance { - min_distance = distance_to; - index = cell_index; - } - } - - return &self.mesh[index]; - } -} - -impl Geometry for Mesh2D { - - type InputFileFormat = Input2D; - - /// Constructor for Mesh2D object from geometry_input. - fn new(geometry_input: &<::InputFileFormat as GeometryInput>::GeometryInput) -> Mesh2D { - - let triangles = geometry_input.triangles.clone(); - let material_boundary_points = geometry_input.material_boundary_points.clone(); - let simulation_boundary_points = geometry_input.simulation_boundary_points.clone(); - let electronic_stopping_correction_factors = geometry_input.electronic_stopping_correction_factors.clone(); - - let n = triangles.len(); - - let mut cells: Vec = Vec::with_capacity(n); - - //Multiply all coordinates by value of geometry unit. - let length_unit: f64 = match geometry_input.length_unit.as_str() { - "MICRON" => MICRON, - "CM" => CM, - "ANGSTROM" => ANGSTROM, - "NM" => NM, - "M" => 1., - _ => geometry_input.length_unit.parse() - .expect(format!( - "Input errror: could nor parse length unit {}. Use a valid float or one of ANGSTROM, NM, MICRON, CM, MM, M", &geometry_input.length_unit.as_str() - ).as_str()), - }; - - let densities: Vec> = geometry_input.densities - .iter() - .map( |row| row.iter().map(|element| element/(length_unit).powi(3)).collect() ).collect(); - - //Assert all triangle density lists are equal length - assert!( - densities.iter() - .all(|density_list| density_list.len() == densities[0].len()), - "Input error: all triangle density lists must be the same size." - ); - - assert_eq!(triangles.len(), densities.len(), "Input error: coordinates and data of unequal length."); - - for ((coordinate_set, densities), ck) in triangles.iter().zip(densities).zip(electronic_stopping_correction_factors) { - let coordinate_set_converted = ( - coordinate_set.0*length_unit, - coordinate_set.1*length_unit, - coordinate_set.2*length_unit, - coordinate_set.3*length_unit, - coordinate_set.4*length_unit, - coordinate_set.5*length_unit, - ); - - let total_density: f64 = densities.iter().sum(); - let concentrations: Vec = densities.iter().map(|&density| density/total_density).collect::>(); - - cells.push(Cell2D::new(coordinate_set_converted, densities, concentrations, ck)); - } - - let mut boundary_points_converted = Vec::with_capacity(material_boundary_points.len()); - for (x, y) in material_boundary_points.iter() { - boundary_points_converted.push((x*length_unit, y*length_unit)); - } - let boundary: Polygon = Polygon::new(LineString::from(boundary_points_converted), vec![]); - - let mut simulation_boundary_points_converted = Vec::with_capacity(simulation_boundary_points.len()); - for (x, y) in simulation_boundary_points.iter() { - simulation_boundary_points_converted.push((x*length_unit, y*length_unit)); - } - let simulation_boundary: Polygon = Polygon::new(LineString::from(simulation_boundary_points_converted), vec![]); - - let energy_barrier_thickness = geometry_input.energy_barrier_thickness*length_unit; - - Mesh2D { - mesh: cells, - boundary: boundary, - simulation_boundary: simulation_boundary, - energy_barrier_thickness: energy_barrier_thickness, - } - } - - fn inside_energy_barrier(&self, x: f64, y: f64, z: f64) -> bool { - if self.inside(x, y, z) { - true - } else { - let nearest_cell = self.nearest_to(x, y, z); - let distance = nearest_cell.distance_to(x, y, z); - distance < self.energy_barrier_thickness - } - } - - fn inside_simulation_boundary(&self, x: f64, y: f64, z: f64) -> bool { - self.simulation_boundary.contains(&point!(x: x, y: y)) - } - - fn closest_point(&self, x: f64, y: f64, z: f64) -> (f64, f64, f64) { - if let Closest::SinglePoint(p) = self.boundary.closest_point(&point!(x: x, y: y)) { - (p.x(), p.y(), z) - } else { - panic!("Geometry error: closest point routine failed to find single closest point to ({}, {}, {}).", x, y, z); - } - } - - /// Find the number densities of the triangle that contains or is nearest to (x, y). - fn get_densities(&self, x: f64, y: f64, z: f64) -> &Vec { - if self.inside(x, y, z) { - for cell in &self.mesh { - if cell.contains(x, y, z) { - return &cell.densities; - } - } - panic!("Geometry error: point ({}, {}) not found in any cell of the mesh.", x, y); - } else { - self.nearest_to(x, y, z).get_densities() - } - } - - /// Find the number densities of the triangle that contains or is nearest to (x, y). - fn get_ck(&self, x: f64, y: f64, z: f64) -> f64 { - if self.inside(x, y, z) { - for cell in &self.mesh { - if cell.contains(x, y, z) { - return cell.electronic_stopping_correction_factor; - } - } - panic!("Geometry error: point ({}, {}) not found in any cell of the mesh.", x, y); - } else { - return self.nearest_to(x, y, z).electronic_stopping_correction_factor; - } - } - - /// Determine the total number density of the triangle that contains or is nearest to (x, y). - fn get_total_density(&self, x: f64, y: f64, z: f64) -> f64 { - if self.inside(x, y, z) { - for cell in &self.mesh { - if cell.contains(x, y, z) { - return cell.densities.iter().sum::(); - } - } - panic!("Geometry error: point ({}, {}) not found in any cell of the mesh.", x, y); - } else { - return self.nearest_to(x, y, z).densities.iter().sum::(); - } - } - - /// Find the concentrations of the triangle that contains or is nearest to (x, y). - fn get_concentrations(&self, x: f64, y: f64, z: f64) -> &Vec { - if self.inside(x, y, z) { - for cell in &self.mesh { - if cell.contains(x, y, z) { - return &cell.concentrations; - } - } - panic!("Geometry error: method inside() is returning true for points outside all cells. Check boundary points.") - } else { - return &self.nearest_to(x, y, z).concentrations; - } - } - - /// Determines whether the point (x, y) is inside the mesh. - fn inside(&self, x: f64, y: f64, z: f64) -> bool { - self.boundary.contains(&Point::new(x, y)) - } -} - -/// A mesh cell that contains a triangle and the local number densities and concentrations. -#[derive(Clone)] -pub struct Cell2D { - triangle: Triangle2D, - pub densities: Vec, - pub concentrations: Vec, - pub electronic_stopping_correction_factor: f64 -} -impl Cell2D { - /// Constructor for Cell2D from a set of coordinates and a list of densities and concentrations. - pub fn new(coordinate_set: (f64, f64, f64, f64, f64, f64), densities: Vec, concentrations: Vec, electronic_stopping_correction_factor: f64) -> Cell2D { - Cell2D { - triangle: Triangle2D::new(coordinate_set), - densities, - concentrations, - electronic_stopping_correction_factor - } - } -} - -impl GeometryElement for Cell2D { - /// Determines whether this cell contains the point (x, y). - fn contains(&self, x: f64, y: f64, z: f64) -> bool { - self.triangle.contains(x, y) - } - - /// Computes the shortest distance between this cell and the point (x, y). - fn distance_to(&self, x: f64, y: f64, z: f64) -> f64 { - self.triangle.distance_to(x, y) - } - - fn get_densities(&self) -> &Vec { - &self.densities - } - - fn get_concentrations(&self) -> &Vec { - &self.concentrations - } - - fn get_electronic_stopping_correction_factor(&self) -> f64 { - self.electronic_stopping_correction_factor - } -} - -#[derive(Clone)] -pub struct Layer1D { - top: f64, - bottom: f64, - densities: Vec, - concentrations: Vec, - electronic_stopping_correction_factor: f64 -} - -impl Layer1D { - pub fn new(top: f64, bottom: f64, densities: Vec, concentrations: Vec, electronic_stopping_correction_factor: f64) -> Layer1D { - - assert!(top < bottom, "Layer cannot have negative thickness."); - - Layer1D{ - top, - bottom, - densities, - concentrations, - electronic_stopping_correction_factor - } - } -} - -impl GeometryElement for Layer1D { - fn contains(&self, x: f64, y: f64, z: f64) -> bool { - (x > self.top) & (x <= self.bottom) - } - fn distance_to(&self, x: f64, y: f64, z: f64) -> f64 { - let distance_to_top = (x - self.top).abs(); - let distance_to_bottom = (x - self.bottom).abs(); - if distance_to_top < distance_to_bottom { - distance_to_top - } else { - distance_to_bottom - } - } - fn get_densities(&self) -> &Vec { - &self.densities - } - fn get_concentrations(&self) -> &Vec { - &self.concentrations - } - fn get_electronic_stopping_correction_factor(&self) -> f64 { - self.electronic_stopping_correction_factor - } -} - -/// A triangle in 2D, with points (x1, y1), (x2, y2), (x3, y3), and the three line segments bewtween them. -#[derive(Clone)] -pub struct Triangle2D { - x1: f64, - x2: f64, - x3: f64, - y1: f64, - y2: f64, - y3: f64, - segments: Vec<(f64, f64, f64, f64)> -} -impl Triangle2D { - /// Constructor for a triangle from a list of coordinates (x1, x2, x3, y1, y2, y3). - pub fn new(coordinate_set: (f64, f64, f64, f64, f64, f64)) -> Triangle2D { - - let x1 = coordinate_set.0; - let x2 = coordinate_set.1; - let x3 = coordinate_set.2; - - let y1 = coordinate_set.3; - let y2 = coordinate_set.4; - let y3 = coordinate_set.5; - - Triangle2D { - x1, - x2, - x3, - y1, - y2, - y3, - segments: vec![(x1, y1, x2, y2), (x2, y2, x3, y3), (x3, y3, x1, y1)] - } - } - - /// Determines whether the point (x, y) is inside this triangle. - pub fn contains(&self, x: f64, y: f64) -> bool { - let x1 = self.x1; - let x2 = self.x2; - let x3 = self.x3; - let y1 = self.y1; - let y2 = self.y2; - let y3 = self.y3; - - let a = ((y2 - y3)*(x - x3) + (x3 - x2)*(y - y3)) / ((y2 - y3)*(x1 - x3) + (x3 - x2)*(y1 - y3)); - let b = ((y3 - y1)*(x - x3) + (x1 - x3)*(y - y3)) / ((y2 - y3)*(x1 - x3) + (x3 - x2)*(y1 - y3)); - let c = 1. - a - b; - - (0. <= a) & (a <= 1.) & (0. <= b) & (b <= 1.) & (0. <= c) & (c <= 1.) - } - - /// Returns a point (x, y) that is the centroid of the triangle. - pub fn centroid(&self) -> (f64, f64) { - ((self.x1 + self.x2 + self.x3)/3., (self.y1 + self.y2 + self.y3)/3.) - } - - /// Calculates the shortest distance from this triangle to the point (x, y). - pub fn distance_to(&self, x: f64, y: f64) -> f64 { - let mut distance_to = std::f64::MAX; - - for segment in &self.segments { - let length_2 = (segment.2 - segment.0)*(segment.2 - segment.0) + (segment.3 - segment.1)*(segment.3 - segment.1); - - assert!(length_2 != 0., "Geometry error: mesh contains triangle with zero-length side."); - - let u = ((x - segment.0)*(segment.2 - segment.0) + (y - segment.1)*(segment.3 - segment.1))/length_2; - - let distance = if u < 0. { - ((x - segment.0)*(x - segment.0) + (y - segment.1)*(y - segment.1)).sqrt() - } else if u > 1. { - ((x - segment.2)*(x - segment.2) + (y - segment.3)*(y - segment.3)).sqrt() - } else { - let x_intersect = segment.0 + u*(segment.2 - segment.0); - let y_intersect = segment.1 + u*(segment.3 - segment.1); - - ((x - x_intersect)*(x - x_intersect) + (y - y_intersect)*(y - y_intersect)).sqrt() - }; - - if distance < distance_to { - distance_to = distance; - } - } - distance_to - } - - /// Calculates the distance between the center of this triangle and the point (x, y). - pub fn distance_to_center(&self, x: f64, y: f64) -> f64 { - let centroid = self.centroid(); - ((x - centroid.0)*(x - centroid.0) + (y - centroid.1)*(y - centroid.1)).sqrt() - } -} +use super::*; + +use geo::algorithm::contains::Contains; +use geo::{Polygon, LineString, Point, point, Closest}; +use geo::algorithm::closest_point::ClosestPoint; + +///Trait for a Geometry object - all forms of geometry must implement these traits to be used +pub trait Geometry { + + type InputFileFormat: InputFile + Clone; + + fn new(input: &<::InputFileFormat as GeometryInput>::GeometryInput) -> Self; + fn get_densities(&self, x: f64, y: f64, z: f64) -> &Vec; + fn get_ck(&self, x: f64, y: f64, z: f64) -> f64; + fn get_total_density(&self, x: f64, y: f64, z: f64) -> f64; + fn get_concentrations(&self, x: f64, y: f64, z: f64) -> &Vec; + fn inside(&self, x: f64, y: f64, z: f64) -> bool; + fn inside_simulation_boundary(&self, x: f64, y: f64, z: f64) -> bool; + fn inside_energy_barrier(&self, x: f64, y: f64, z: f64) -> bool; + fn closest_point(&self, x: f64, y: f64, z: f64) -> (f64, f64, f64); +} + +pub trait GeometryElement: Clone { + fn contains(&self, x: f64, y: f64, z: f64) -> bool; + fn distance_to(&self, x: f64, y: f64, z: f64) -> f64; + fn get_densities(&self) -> &Vec; + fn get_concentrations(&self) -> &Vec; + fn get_electronic_stopping_correction_factor(&self) -> f64; +} + +#[derive(Deserialize, Clone)] +pub struct Mesh0DInput { + pub length_unit: String, + pub densities: Vec, + pub electronic_stopping_correction_factor: f64, +} + +#[derive(Clone)] +pub struct Mesh0D { + pub densities: Vec, + pub concentrations: Vec, + pub electronic_stopping_correction_factor: f64, + pub energy_barrier_thickness: f64, +} + +impl Geometry for Mesh0D { + + type InputFileFormat = Input0D; + + fn new(input: &<::InputFileFormat as GeometryInput>::GeometryInput) -> Mesh0D { + + let length_unit: f64 = match input.length_unit.as_str() { + "MICRON" => MICRON, + "CM" => CM, + "ANGSTROM" => ANGSTROM, + "NM" => NM, + "M" => 1., + _ => input.length_unit.parse() + .expect(format!( + "Input errror: could nor parse length unit {}. Use a valid float or one of ANGSTROM, NM, MICRON, CM, MM, M", + &input.length_unit.as_str() + ).as_str()), + }; + + let electronic_stopping_correction_factor = input.electronic_stopping_correction_factor; + + let densities: Vec = input.densities.iter().map(|element| element/(length_unit).powi(3)).collect(); + + let total_density: f64 = densities.iter().sum(); + + let energy_barrier_thickness = total_density.powf(-1./3.)/SQRTPI*2.; + + let concentrations: Vec = densities.iter().map(|&density| density/total_density).collect::>(); + + Mesh0D { + densities, + concentrations, + electronic_stopping_correction_factor, + energy_barrier_thickness + } + } + + fn get_densities(&self, x: f64, y: f64, z: f64) -> &Vec { + &self.densities + } + fn get_ck(&self, x: f64, y: f64, z: f64) -> f64 { + self.electronic_stopping_correction_factor + } + fn get_total_density(&self, x: f64, y: f64, z: f64) -> f64{ + self.densities.iter().sum() + } + fn get_concentrations(&self, x: f64, y: f64, z: f64) -> &Vec { + &self.concentrations + } + fn inside(&self, x: f64, y: f64, z: f64) -> bool { + x > 0.0 + } + + fn inside_simulation_boundary(&self, x: f64, y: f64, z: f64) -> bool { + //println!("x: {} energy_barrier_thickness: {}", x/ANGSTROM, self.energy_barrier_thickness/ANGSTROM); + x > -10.*self.energy_barrier_thickness + } + + fn inside_energy_barrier(&self, x: f64, y: f64, z: f64) -> bool { + x > -self.energy_barrier_thickness + } + + fn closest_point(&self, x: f64, y: f64, z: f64) -> (f64, f64, f64) { + (0., y, z) + } +} + +#[derive(Deserialize, Clone)] +pub struct Mesh1DInput { + pub length_unit: String, + pub layer_thicknesses: Vec, + pub densities: Vec>, + pub electronic_stopping_correction_factors: Vec, +} + +#[derive(Clone)] +pub struct Mesh1D { + layers: Vec, + top: f64, + bottom: f64, + pub top_energy_barrier_thickness: f64, + pub bottom_energy_barrier_thickness: f64, +} + +impl Geometry for Mesh1D { + + type InputFileFormat = input::Input1D; + + fn new(geometry_input: &<::InputFileFormat as GeometryInput>::GeometryInput) -> Self { + + let layer_thicknesses = geometry_input.layer_thicknesses.clone(); + let electronic_stopping_correction_factors = geometry_input.electronic_stopping_correction_factors.clone(); + let n = layer_thicknesses.len(); + + let mut layers: Vec = Vec::with_capacity(n); + + //Multiply all coordinates by value of geometry unit. + let length_unit: f64 = match geometry_input.length_unit.as_str() { + "MICRON" => MICRON, + "CM" => CM, + "ANGSTROM" => ANGSTROM, + "NM" => NM, + "M" => 1., + _ => geometry_input.length_unit.parse() + .expect(format!( + "Input errror: could nor parse length unit {}. Use a valid float or one of ANGSTROM, NM, MICRON, CM, MM, M", &geometry_input.length_unit.as_str() + ).as_str()), + }; + + let densities: Vec> = geometry_input.densities + .iter() + .map( |row| row.iter().map(|element| element/(length_unit).powi(3)).collect() ).collect(); + + //Assert all layer density lists are equal length + assert!( + densities.iter() + .all(|density_list| density_list.len() == densities[0].len()), + "Input error: all layer density lists must be the same size." + ); + assert_eq!(layer_thicknesses.len(), densities.len(), "Input error: coordinates and data of unequal length."); + + let mut layer_top = 0.; + let mut layer_bottom = 0.; + for ((layer_thickness, densities), ck) in layer_thicknesses.iter().zip(densities).zip(electronic_stopping_correction_factors) { + + layer_bottom += layer_thickness*length_unit; + + let total_density: f64 = densities.iter().sum(); + let concentrations: Vec = densities.iter().map(|&density| density/total_density).collect::>(); + + layers.push(Layer1D::new(layer_top, layer_bottom, densities, concentrations, ck)); + layer_top = layer_bottom; + } + + let top_energy_barrier_thickness = layers[0].densities.iter().sum::().powf(-1./3.)/SQRTPI*2.; + let bottom_energy_barrier_thickness = layers[layers.len() - 1].densities.iter().sum::().powf(-1./3.)/SQRTPI*2.; + + Mesh1D { + layers, + top: 0.0, + bottom: layer_bottom, + top_energy_barrier_thickness, + bottom_energy_barrier_thickness, + } + } + fn get_densities(&self, x: f64, y: f64, z: f64) -> &Vec { + if self.inside(x, y, z) { + for layer in &self.layers { + if layer.contains(x, y, z) { + return &layer.densities + } + } + panic!("Geometry error: method inside() is returning true for points outside all layers. Check geometry.") + } else if x < self.top { + &self.layers[0].densities + } else { + &self.layers[self.layers.len() - 1].densities + } + } + fn get_ck(&self, x: f64, y: f64, z: f64) -> f64 { + if self.inside(x, y, z) { + for layer in &self.layers { + if layer.contains(x, y, z) { + return layer.electronic_stopping_correction_factor + } + } + panic!("Geometry error: method inside() is returning true for points outside all layers. Check geometry.") + } else if x < self.top { + self.layers[0].electronic_stopping_correction_factor + } else { + self.layers[self.layers.len() - 1].electronic_stopping_correction_factor + } + } + fn get_total_density(&self, x: f64, y: f64, z: f64) -> f64 { + if self.inside(x, y, z) { + for layer in &self.layers { + if layer.contains(x, y, z) { + return layer.densities.iter().sum() + } + } + panic!("Geometry error: method inside() is returning true for points outside all layers. Check geometry.") + } else if x < self.top { + self.layers[0].densities.iter().sum() + } else { + self.layers[self.layers.len() - 1].densities.iter().sum() + } + } + fn get_concentrations(&self, x: f64, y: f64, z: f64) -> &Vec { + if self.inside(x, y, z) { + for layer in &self.layers { + if layer.contains(x, y, z) { + return &layer.concentrations + } + } + panic!("Geometry error: method inside() is returning true for points outside all layers. Check geometry.") + } else if x < self.top { + &self.layers[0].concentrations + } else { + &self.layers[self.layers.len() - 1].concentrations + } + } + fn inside(&self, x: f64, y: f64, z: f64) -> bool { + (x > self.top) & (x < self.bottom) + } + fn inside_simulation_boundary(&self, x: f64, y: f64, z: f64) -> bool { + (x > self.top - 10.*self.top_energy_barrier_thickness) & (x < self.bottom + 10.*self.bottom_energy_barrier_thickness) + } + fn inside_energy_barrier(&self, x: f64, y: f64, z: f64) -> bool { + (x > self.top - self.top_energy_barrier_thickness) & (x < self.bottom + self.bottom_energy_barrier_thickness) + } + fn closest_point(&self, x: f64, y: f64, z: f64) -> (f64, f64, f64) { + let distance_from_bottom = (x - self.bottom).abs(); + let distance_from_top = (x - self.top).abs(); + if distance_from_bottom < distance_from_top { + (self.bottom, y, z) + } else { + (self.top, y, z) + } + } +} + +/// Object that contains raw mesh input data. +#[derive(Deserialize, Clone)] +pub struct Mesh2DInput { + pub length_unit: String, + pub triangles: Vec<(f64, f64, f64, f64, f64, f64)>, + pub material_boundary_points: Vec<(f64, f64)>, + pub simulation_boundary_points: Vec<(f64, f64)>, + pub densities: Vec>, + pub electronic_stopping_correction_factors: Vec, + pub energy_barrier_thickness: f64, +} + +/// Triangular mesh for rustbca. +#[derive(Clone)] +pub struct Mesh2D { + mesh: Vec, + pub boundary: Polygon, + pub simulation_boundary: Polygon, + pub energy_barrier_thickness: f64 +} + +impl Mesh2D { + /// Finds the cell that is nearest to (x, y). + fn nearest_to(&self, x: f64, y: f64, z: f64) -> &Cell2D { + + let mut min_distance: f64 = std::f64::MAX; + let mut index: usize = 0; + + for (cell_index, cell) in self.mesh.iter().enumerate() { + let distance_to = cell.distance_to(x, y, z); + if distance_to < min_distance { + min_distance = distance_to; + index = cell_index; + } + } + + return &self.mesh[index]; + } +} + +impl Geometry for Mesh2D { + + type InputFileFormat = Input2D; + + /// Constructor for Mesh2D object from geometry_input. + fn new(geometry_input: &<::InputFileFormat as GeometryInput>::GeometryInput) -> Mesh2D { + + let triangles = geometry_input.triangles.clone(); + let material_boundary_points = geometry_input.material_boundary_points.clone(); + let simulation_boundary_points = geometry_input.simulation_boundary_points.clone(); + let electronic_stopping_correction_factors = geometry_input.electronic_stopping_correction_factors.clone(); + + let n = triangles.len(); + + let mut cells: Vec = Vec::with_capacity(n); + + //Multiply all coordinates by value of geometry unit. + let length_unit: f64 = match geometry_input.length_unit.as_str() { + "MICRON" => MICRON, + "CM" => CM, + "ANGSTROM" => ANGSTROM, + "NM" => NM, + "M" => 1., + _ => geometry_input.length_unit.parse() + .expect(format!( + "Input errror: could nor parse length unit {}. Use a valid float or one of ANGSTROM, NM, MICRON, CM, MM, M", &geometry_input.length_unit.as_str() + ).as_str()), + }; + + let densities: Vec> = geometry_input.densities + .iter() + .map( |row| row.iter().map(|element| element/(length_unit).powi(3)).collect() ).collect(); + + //Assert all triangle density lists are equal length + assert!( + densities.iter() + .all(|density_list| density_list.len() == densities[0].len()), + "Input error: all triangle density lists must be the same size." + ); + + assert_eq!(triangles.len(), densities.len(), "Input error: coordinates and data of unequal length."); + + for ((coordinate_set, densities), ck) in triangles.iter().zip(densities).zip(electronic_stopping_correction_factors) { + let coordinate_set_converted = ( + coordinate_set.0*length_unit, + coordinate_set.1*length_unit, + coordinate_set.2*length_unit, + coordinate_set.3*length_unit, + coordinate_set.4*length_unit, + coordinate_set.5*length_unit, + ); + + let total_density: f64 = densities.iter().sum(); + let concentrations: Vec = densities.iter().map(|&density| density/total_density).collect::>(); + + cells.push(Cell2D::new(coordinate_set_converted, densities, concentrations, ck)); + } + + let mut boundary_points_converted = Vec::with_capacity(material_boundary_points.len()); + for (x, y) in material_boundary_points.iter() { + boundary_points_converted.push((x*length_unit, y*length_unit)); + } + let boundary: Polygon = Polygon::new(LineString::from(boundary_points_converted), vec![]); + + let mut simulation_boundary_points_converted = Vec::with_capacity(simulation_boundary_points.len()); + for (x, y) in simulation_boundary_points.iter() { + simulation_boundary_points_converted.push((x*length_unit, y*length_unit)); + } + let simulation_boundary: Polygon = Polygon::new(LineString::from(simulation_boundary_points_converted), vec![]); + + let energy_barrier_thickness = geometry_input.energy_barrier_thickness*length_unit; + + Mesh2D { + mesh: cells, + boundary: boundary, + simulation_boundary: simulation_boundary, + energy_barrier_thickness: energy_barrier_thickness, + } + } + + fn inside_energy_barrier(&self, x: f64, y: f64, z: f64) -> bool { + if self.inside(x, y, z) { + true + } else { + let nearest_cell = self.nearest_to(x, y, z); + let distance = nearest_cell.distance_to(x, y, z); + distance < self.energy_barrier_thickness + } + } + + fn inside_simulation_boundary(&self, x: f64, y: f64, z: f64) -> bool { + self.simulation_boundary.contains(&point!(x: x, y: y)) + } + + fn closest_point(&self, x: f64, y: f64, z: f64) -> (f64, f64, f64) { + if let Closest::SinglePoint(p) = self.boundary.closest_point(&point!(x: x, y: y)) { + (p.x(), p.y(), z) + } else { + panic!("Geometry error: closest point routine failed to find single closest point to ({}, {}, {}).", x, y, z); + } + } + + /// Find the number densities of the triangle that contains or is nearest to (x, y). + fn get_densities(&self, x: f64, y: f64, z: f64) -> &Vec { + if self.inside(x, y, z) { + for cell in &self.mesh { + if cell.contains(x, y, z) { + return &cell.densities; + } + } + panic!("Geometry error: point ({}, {}) not found in any cell of the mesh.", x, y); + } else { + self.nearest_to(x, y, z).get_densities() + } + } + + /// Find the number densities of the triangle that contains or is nearest to (x, y). + fn get_ck(&self, x: f64, y: f64, z: f64) -> f64 { + if self.inside(x, y, z) { + for cell in &self.mesh { + if cell.contains(x, y, z) { + return cell.electronic_stopping_correction_factor; + } + } + panic!("Geometry error: point ({}, {}) not found in any cell of the mesh.", x, y); + } else { + return self.nearest_to(x, y, z).electronic_stopping_correction_factor; + } + } + + /// Determine the total number density of the triangle that contains or is nearest to (x, y). + fn get_total_density(&self, x: f64, y: f64, z: f64) -> f64 { + if self.inside(x, y, z) { + for cell in &self.mesh { + if cell.contains(x, y, z) { + return cell.densities.iter().sum::(); + } + } + panic!("Geometry error: point ({}, {}) not found in any cell of the mesh.", x, y); + } else { + return self.nearest_to(x, y, z).densities.iter().sum::(); + } + } + + /// Find the concentrations of the triangle that contains or is nearest to (x, y). + fn get_concentrations(&self, x: f64, y: f64, z: f64) -> &Vec { + if self.inside(x, y, z) { + for cell in &self.mesh { + if cell.contains(x, y, z) { + return &cell.concentrations; + } + } + panic!("Geometry error: method inside() is returning true for points outside all cells. Check boundary points.") + } else { + return &self.nearest_to(x, y, z).concentrations; + } + } + + /// Determines whether the point (x, y) is inside the mesh. + fn inside(&self, x: f64, y: f64, z: f64) -> bool { + self.boundary.contains(&Point::new(x, y)) + } +} + +/// A mesh cell that contains a triangle and the local number densities and concentrations. +#[derive(Clone)] +pub struct Cell2D { + triangle: Triangle2D, + pub densities: Vec, + pub concentrations: Vec, + pub electronic_stopping_correction_factor: f64 +} +impl Cell2D { + /// Constructor for Cell2D from a set of coordinates and a list of densities and concentrations. + pub fn new(coordinate_set: (f64, f64, f64, f64, f64, f64), densities: Vec, concentrations: Vec, electronic_stopping_correction_factor: f64) -> Cell2D { + Cell2D { + triangle: Triangle2D::new(coordinate_set), + densities, + concentrations, + electronic_stopping_correction_factor + } + } +} + +impl GeometryElement for Cell2D { + /// Determines whether this cell contains the point (x, y). + fn contains(&self, x: f64, y: f64, z: f64) -> bool { + self.triangle.contains(x, y) + } + + /// Computes the shortest distance between this cell and the point (x, y). + fn distance_to(&self, x: f64, y: f64, z: f64) -> f64 { + self.triangle.distance_to(x, y) + } + + fn get_densities(&self) -> &Vec { + &self.densities + } + + fn get_concentrations(&self) -> &Vec { + &self.concentrations + } + + fn get_electronic_stopping_correction_factor(&self) -> f64 { + self.electronic_stopping_correction_factor + } +} + +#[derive(Clone)] +pub struct Layer1D { + top: f64, + bottom: f64, + densities: Vec, + concentrations: Vec, + electronic_stopping_correction_factor: f64 +} + +impl Layer1D { + pub fn new(top: f64, bottom: f64, densities: Vec, concentrations: Vec, electronic_stopping_correction_factor: f64) -> Layer1D { + + assert!(top < bottom, "Layer cannot have negative thickness."); + + Layer1D{ + top, + bottom, + densities, + concentrations, + electronic_stopping_correction_factor + } + } +} + +impl GeometryElement for Layer1D { + fn contains(&self, x: f64, y: f64, z: f64) -> bool { + (x > self.top) & (x <= self.bottom) + } + fn distance_to(&self, x: f64, y: f64, z: f64) -> f64 { + let distance_to_top = (x - self.top).abs(); + let distance_to_bottom = (x - self.bottom).abs(); + if distance_to_top < distance_to_bottom { + distance_to_top + } else { + distance_to_bottom + } + } + fn get_densities(&self) -> &Vec { + &self.densities + } + fn get_concentrations(&self) -> &Vec { + &self.concentrations + } + fn get_electronic_stopping_correction_factor(&self) -> f64 { + self.electronic_stopping_correction_factor + } +} + +/// A triangle in 2D, with points (x1, y1), (x2, y2), (x3, y3), and the three line segments bewtween them. +#[derive(Clone)] +pub struct Triangle2D { + x1: f64, + x2: f64, + x3: f64, + y1: f64, + y2: f64, + y3: f64, + segments: Vec<(f64, f64, f64, f64)> +} +impl Triangle2D { + /// Constructor for a triangle from a list of coordinates (x1, x2, x3, y1, y2, y3). + pub fn new(coordinate_set: (f64, f64, f64, f64, f64, f64)) -> Triangle2D { + + let x1 = coordinate_set.0; + let x2 = coordinate_set.1; + let x3 = coordinate_set.2; + + let y1 = coordinate_set.3; + let y2 = coordinate_set.4; + let y3 = coordinate_set.5; + + Triangle2D { + x1, + x2, + x3, + y1, + y2, + y3, + segments: vec![(x1, y1, x2, y2), (x2, y2, x3, y3), (x3, y3, x1, y1)] + } + } + + /// Determines whether the point (x, y) is inside this triangle. + pub fn contains(&self, x: f64, y: f64) -> bool { + let x1 = self.x1; + let x2 = self.x2; + let x3 = self.x3; + let y1 = self.y1; + let y2 = self.y2; + let y3 = self.y3; + + let a = ((y2 - y3)*(x - x3) + (x3 - x2)*(y - y3)) / ((y2 - y3)*(x1 - x3) + (x3 - x2)*(y1 - y3)); + let b = ((y3 - y1)*(x - x3) + (x1 - x3)*(y - y3)) / ((y2 - y3)*(x1 - x3) + (x3 - x2)*(y1 - y3)); + let c = 1. - a - b; + + (0. <= a) & (a <= 1.) & (0. <= b) & (b <= 1.) & (0. <= c) & (c <= 1.) + } + + /// Returns a point (x, y) that is the centroid of the triangle. + pub fn centroid(&self) -> (f64, f64) { + ((self.x1 + self.x2 + self.x3)/3., (self.y1 + self.y2 + self.y3)/3.) + } + + /// Calculates the shortest distance from this triangle to the point (x, y). + pub fn distance_to(&self, x: f64, y: f64) -> f64 { + let mut distance_to = std::f64::MAX; + + for segment in &self.segments { + let length_2 = (segment.2 - segment.0)*(segment.2 - segment.0) + (segment.3 - segment.1)*(segment.3 - segment.1); + + assert!(length_2 != 0., "Geometry error: mesh contains triangle with zero-length side."); + + let u = ((x - segment.0)*(segment.2 - segment.0) + (y - segment.1)*(segment.3 - segment.1))/length_2; + + let distance = if u < 0. { + ((x - segment.0)*(x - segment.0) + (y - segment.1)*(y - segment.1)).sqrt() + } else if u > 1. { + ((x - segment.2)*(x - segment.2) + (y - segment.3)*(y - segment.3)).sqrt() + } else { + let x_intersect = segment.0 + u*(segment.2 - segment.0); + let y_intersect = segment.1 + u*(segment.3 - segment.1); + + ((x - x_intersect)*(x - x_intersect) + (y - y_intersect)*(y - y_intersect)).sqrt() + }; + + if distance < distance_to { + distance_to = distance; + } + } + distance_to + } + + /// Calculates the distance between the center of this triangle and the point (x, y). + pub fn distance_to_center(&self, x: f64, y: f64) -> f64 { + let centroid = self.centroid(); + ((x - centroid.0)*(x - centroid.0) + (y - centroid.1)*(y - centroid.1)).sqrt() + } +} diff --git a/src/input.rs b/src/input.rs index 561d03c..c682d58 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,407 +1,407 @@ -use super::*; - -pub trait InputFile: GeometryInput { - fn new(string: &str) -> Self; - fn get_options(&self) -> &Options; - fn get_material_parameters(&self) -> &material::MaterialParameters; - fn get_particle_parameters(&self) -> &particle::ParticleParameters; - fn get_geometry_input(&self) -> &::GeometryInput; -} - -pub trait GeometryInput { - type GeometryInput; -} - -/// Rustbca's internal representation of an input file. -#[derive(Deserialize, Clone)] -pub struct Input2D { - pub options: Options, - pub material_parameters: material::MaterialParameters, - pub particle_parameters: particle::ParticleParameters, - pub geometry_input: geometry::Mesh2DInput, -} - -impl GeometryInput for Input2D { - type GeometryInput = geometry::Mesh2DInput; -} - -impl InputFile for Input2D { - - fn new(string: &str) -> Input2D { - toml::from_str(string).unwrap() - } - - fn get_options(&self) -> &Options{ - &self.options - } - fn get_material_parameters(&self) -> &material::MaterialParameters{ - &self.material_parameters - } - fn get_particle_parameters(&self) -> &particle::ParticleParameters{ - &self.particle_parameters - } - fn get_geometry_input(&self) -> &Self::GeometryInput{ - &self.geometry_input - } -} - -#[derive(Deserialize, Clone)] -pub struct Input0D { - pub options: Options, - pub material_parameters: material::MaterialParameters, - pub particle_parameters: particle::ParticleParameters, - pub geometry_input: geometry::Mesh0DInput, -} - -impl GeometryInput for Input0D { - type GeometryInput = geometry::Mesh0DInput; -} - -impl InputFile for Input0D { - - fn new(string: &str) -> Input0D { - toml::from_str(string).expect("Could not parse TOML file.") - } - - fn get_options(&self) -> &Options{ - &self.options - } - fn get_material_parameters(&self) -> &material::MaterialParameters{ - &self.material_parameters - } - fn get_particle_parameters(&self) ->& particle::ParticleParameters{ - &self.particle_parameters - } - fn get_geometry_input(&self) -> &Self::GeometryInput{ - &self.geometry_input - } -} - -#[derive(Deserialize, Clone)] -pub struct Input1D { - pub options: Options, - pub material_parameters: material::MaterialParameters, - pub particle_parameters: particle::ParticleParameters, - pub geometry_input: geometry::Mesh1DInput, -} - -impl GeometryInput for Input1D { - type GeometryInput = geometry::Mesh1DInput; -} - -impl InputFile for Input1D { - - fn new(string: &str) -> Input1D { - toml::from_str(string).expect("Could not parse TOML file.") - } - - fn get_options(&self) -> &Options{ - &self.options - } - fn get_material_parameters(&self) -> &material::MaterialParameters{ - &self.material_parameters - } - fn get_particle_parameters(&self) ->& particle::ParticleParameters{ - &self.particle_parameters - } - fn get_geometry_input(&self) -> &Self::GeometryInput{ - &self.geometry_input - } -} - -/// Rustbca's internal representation of the simulation-level options. -#[cfg(not(feature = "distributions"))] -#[derive(Deserialize, Clone)] -pub struct Options { - pub name: String, - pub track_trajectories: bool, - pub track_recoils: bool, - pub track_recoil_trajectories: bool, - pub write_buffer_size: usize, - pub weak_collision_order: usize, - pub suppress_deep_recoils: bool, - pub high_energy_free_flight_paths: bool, - pub electronic_stopping_mode: ElectronicStoppingMode, - pub mean_free_path_model: MeanFreePathModel, - pub interaction_potential: Vec>, - pub scattering_integral: Vec>, - pub root_finder: Vec>, - pub num_threads: usize, - pub num_chunks: u64, - pub use_hdf5: bool, - pub track_displacements: bool, - pub track_energy_losses: bool, -} - -#[cfg(feature = "distributions")] -#[derive(Deserialize, Clone)] -pub struct Options { - pub name: String, - pub track_trajectories: bool, - pub track_recoils: bool, - pub track_recoil_trajectories: bool, - pub write_buffer_size: usize, - pub weak_collision_order: usize, - pub suppress_deep_recoils: bool, - pub high_energy_free_flight_paths: bool, - pub electronic_stopping_mode: ElectronicStoppingMode, - pub mean_free_path_model: MeanFreePathModel, - pub interaction_potential: Vec>, - pub scattering_integral: Vec>, - pub root_finder: Vec>, - pub num_threads: usize, - pub num_chunks: u64, - pub use_hdf5: bool, - pub track_displacements: bool, - pub track_energy_losses: bool, - pub energy_min: f64, - pub energy_max: f64, - pub energy_num: usize, - pub angle_min: f64, - pub angle_max: f64, - pub angle_num: usize, - pub x_min: f64, - pub y_min: f64, - pub z_min: f64, - pub x_max: f64, - pub y_max: f64, - pub z_max: f64, - pub x_num: usize, - pub y_num: usize, - pub z_num: usize, -} - -pub fn input(input_file: String) -> (Vec, material::Material, Options, OutputUnits) -where ::InputFileFormat: Deserialize<'static> + 'static { - - //Read input file, convert to string, and open with toml - let mut input_toml = String::new(); - let mut file = OpenOptions::new() - .read(true) - .write(false) - .create(false) - .open(&input_file) - .expect(format!("Input errror: could not open input file {}.", &input_file).as_str()); - file.read_to_string(&mut input_toml).context("Could not convert TOML file to string.").unwrap(); - - let input: ::InputFileFormat = InputFile::new(&input_toml); - - //Unpack toml information into structs - let options = (*input.get_options()).clone(); - let particle_parameters = (*input.get_particle_parameters()).clone(); - let material_parameters = (*input.get_material_parameters()).clone(); - let material: material::Material = material::Material::::new(&material_parameters, input.get_geometry_input()); - - //Ensure nonsensical threads/chunks options crash on input - assert!(options.num_threads > 0, "Input error: num_threads must be greater than zero."); - assert!(options.num_chunks > 0, "Input error: num_chunks must be greater than zero."); - - //Check that all material arrays are of equal length. - assert!(material.m.len() == material.Z.len(), "Input error: material input arrays of unequal length."); - assert!(material.m.len() == material.Eb.len(), "Input error: material input arrays of unequal length."); - assert!(material.m.len() == material.Es.len(), "Input error: material input arrays of unequal length."); - assert!(material.m.len() == material.interaction_index.len(), "Input error: material input arrays of unequal length."); - - //Check that incompatible options are not on simultaneously - if options.high_energy_free_flight_paths { - assert!(options.electronic_stopping_mode == ElectronicStoppingMode::INTERPOLATED, - "Input error: High energy free flight paths used with low energy stoppping power. Change to INTERPOLATED."); - assert!(options.weak_collision_order == 0, - "Input error: Cannot use weak collisions with free flight paths. Set weak_collision_order = 0."); - } - - if options.mean_free_path_model == MeanFreePathModel::GASEOUS { - assert!(options.weak_collision_order == 0, - "Input error: Cannot use weak collisions with gaseous mean free path model. Set weak_collision_order = 0."); - } - - for i in 0..options.interaction_potential.len() { - assert!(options.interaction_potential.len() == options.interaction_potential[i].len(), - "Input error: interaction matrix not square."); - - assert!(options.scattering_integral.len() == options.scattering_integral[i].len(), - "Input error: scattering intergral matrix not square."); - - assert!(options.root_finder.len() == options.root_finder[i].len(), - "Input error: rootfinder matrix not square."); - } - - for ((interaction_potentials, scattering_integrals), root_finders) in options.interaction_potential.iter().zip(options.scattering_integral.clone()).zip(options.root_finder.clone()) { - for ((interaction_potential, scattering_integral), root_finder) in interaction_potentials.iter().zip(scattering_integrals).zip(root_finders) { - - if cfg!(not(any(feature="cpr_rootfinder_openblas", feature="cpr_rootfinder_netlib", feature="cpr_rootfinder_intel_mkl",))) { - assert!( match root_finder { - Rootfinder::POLYNOMIAL{..} => false, - Rootfinder::CPR{..} => false, - _ => true, - }, - "Input error: CPR rootfinder not enabled. Build with --features cpr_rootfinder"); - } - - assert!( - match (interaction_potential, root_finder) { - (InteractionPotential::LENNARD_JONES_12_6{..}, Rootfinder::CPR{..}) => true, - (InteractionPotential::LENNARD_JONES_12_6{..}, Rootfinder::POLYNOMIAL{..}) => true, - (InteractionPotential::LENNARD_JONES_12_6{..}, _) => false, - (InteractionPotential::LENNARD_JONES_65_6{..}, Rootfinder::CPR{..}) => true, - (InteractionPotential::LENNARD_JONES_65_6{..}, Rootfinder::POLYNOMIAL{..}) => true, - (InteractionPotential::LENNARD_JONES_65_6{..}, _) => false, - (InteractionPotential::MORSE{..}, Rootfinder::CPR{..}) => true, - (InteractionPotential::MORSE{..}, _) => false, - (_, Rootfinder::POLYNOMIAL{..}) => false, - (_, _) => true, - }, - "Input error: cannot use {} with {}. Try switching to a different rootfinder.", interaction_potential, root_finder); - } - } - - //Check that particle arrays are equal length - assert_eq!(particle_parameters.Z.len(), particle_parameters.m.len(), - "Input error: particle input arrays of unequal length."); - assert_eq!(particle_parameters.Z.len(), particle_parameters.E.len(), - "Input error: particle input arrays of unequal length."); - assert_eq!(particle_parameters.Z.len(), particle_parameters.pos.len(), - "Input error: particle input arrays of unequal length."); - assert_eq!(particle_parameters.Z.len(), particle_parameters.dir.len(), - "Input error: particle input arrays of unequal length."); - - //Check that interaction indices are all within interaction matrices - assert!(material.interaction_index.iter().max().unwrap() < &options.interaction_potential.len(), - "Input error: interaction matrix too small for material interaction indices."); - assert!(particle_parameters.interaction_index.iter().max().unwrap() < &options.interaction_potential.len(), - "Input error: interaction matrix too small for particle interaction indices."); - - //N is the number of distinct particles. - let N = particle_parameters.Z.len(); - - //Determine the length, energy, and mass units for particle input - let length_unit: f64 = match particle_parameters.length_unit.as_str() { - "MICRON" => MICRON, - "CM" => CM, - "ANGSTROM" => ANGSTROM, - "NM" => NM, - "M" => 1., - _ => particle_parameters.length_unit.parse() - .expect(format!( - "Input errror: could nor parse length unit {}. Use a valid float or one of ANGSTROM, NM, MICRON, CM, MM, M", &particle_parameters.length_unit.as_str() - ).as_str()), - }; - - let energy_unit: f64 = match particle_parameters.energy_unit.as_str() { - "EV" => EV, - "J" => 1., - "KEV" => EV*1E3, - "MEV" => EV*1E6, - _ => particle_parameters.energy_unit.parse() - .expect(format!( - "Input errror: could nor parse energy unit {}. Use a valid float or one of EV, J, KEV, MEV", &particle_parameters.energy_unit.as_str() - ).as_str()), - }; - - let mass_unit: f64 = match particle_parameters.mass_unit.as_str() { - "AMU" => AMU, - "KG" => 1.0, - _ => particle_parameters.mass_unit.parse() - .expect(format!( - "Input errror: could nor parse mass unit {}. Use a valid float or one of AMU, KG", &particle_parameters.mass_unit.as_str() - ).as_str()), - }; - - //HDF5 - #[cfg(feature = "hdf5_input")] - let particle_input_array: Vec = { - if options.use_hdf5 { - let particle_input_filename = particle_parameters.particle_input_filename.as_str(); - let _e = hdf5::silence_errors(); - let particle_input_file = hdf5::File::open(particle_input_filename) - .context("Input error: cannot open HDF5 file.") - .unwrap(); - let particle_input = particle_input_file.dataset("particles") - .context("Input error: cannot read from HDF5 file.") - .unwrap(); - particle_input.read_raw::().unwrap() - - } else { - let mut particle_input: Vec = Vec::new(); - - for particle_index in 0..N { - let N_ = particle_parameters.N[particle_index]; - let m = particle_parameters.m[particle_index]; - let Z = particle_parameters.Z[particle_index]; - let E = particle_parameters.E[particle_index]; - let Ec = particle_parameters.Ec[particle_index]; - let Es = particle_parameters.Es[particle_index]; - let interaction_index = particle_parameters.interaction_index[particle_index]; - let (x, y, z) = particle_parameters.pos[particle_index]; - let (cosx, cosy, cosz) = particle_parameters.dir[particle_index]; - assert!(cosx < 1., - "Input error: particle x-direction cannot be exactly equal to 1 to avoid numerical gimbal lock."); - for sub_particle_index in 0..N_ { - //Add new particle to particle vector - particle_input.push( - particle::ParticleInput{ - m: m*mass_unit, - Z: Z, - E: E*energy_unit, - Ec: Ec*energy_unit, - Es: Es*energy_unit, - x: x*length_unit, - y: y*length_unit, - z: z*length_unit, - ux: cosx, - uy: cosy, - uz: cosz, - interaction_index: interaction_index - } - ); - } - } - particle_input - } - }; - - #[cfg(not(feature = "hdf5_input"))] - let particle_input_array: Vec = { - if options.use_hdf5 { - panic!("HDF5 particle input not enabled. Enable with: cargo build --features hdf5_input") - } else { - let mut particle_input: Vec = Vec::new(); - - for particle_index in 0..N { - let N_ = particle_parameters.N[particle_index]; - let m = particle_parameters.m[particle_index]; - let Z = particle_parameters.Z[particle_index]; - let E = particle_parameters.E[particle_index]; - let Ec = particle_parameters.Ec[particle_index]; - let Es = particle_parameters.Es[particle_index]; - let interaction_index = particle_parameters.interaction_index[particle_index]; - let (x, y, z) = particle_parameters.pos[particle_index]; - let (cosx, cosy, cosz) = particle_parameters.dir[particle_index]; - assert!(cosx < 1., - "Input error: particle x-direction cannot be exactly equal to 1 to avoid numerical gimbal lock."); - for sub_particle_index in 0..N_ { - - //Add new particle to particle vector - particle_input.push( - particle::ParticleInput{ - m: m*mass_unit, - Z: Z, - E: E*energy_unit, - Ec: Ec*energy_unit, - Es: Es*energy_unit, - x: x*length_unit, - y: y*length_unit, - z: z*length_unit, - ux: cosx, - uy: cosy, - uz: cosz, - interaction_index - } - ); - } - } - particle_input - } - }; - (particle_input_array, material, options, OutputUnits {length_unit, energy_unit, mass_unit}) -} +use super::*; + +pub trait InputFile: GeometryInput { + fn new(string: &str) -> Self; + fn get_options(&self) -> &Options; + fn get_material_parameters(&self) -> &material::MaterialParameters; + fn get_particle_parameters(&self) -> &particle::ParticleParameters; + fn get_geometry_input(&self) -> &::GeometryInput; +} + +pub trait GeometryInput { + type GeometryInput; +} + +/// Rustbca's internal representation of an input file. +#[derive(Deserialize, Clone)] +pub struct Input2D { + pub options: Options, + pub material_parameters: material::MaterialParameters, + pub particle_parameters: particle::ParticleParameters, + pub geometry_input: geometry::Mesh2DInput, +} + +impl GeometryInput for Input2D { + type GeometryInput = geometry::Mesh2DInput; +} + +impl InputFile for Input2D { + + fn new(string: &str) -> Input2D { + toml::from_str(string).unwrap() + } + + fn get_options(&self) -> &Options{ + &self.options + } + fn get_material_parameters(&self) -> &material::MaterialParameters{ + &self.material_parameters + } + fn get_particle_parameters(&self) -> &particle::ParticleParameters{ + &self.particle_parameters + } + fn get_geometry_input(&self) -> &Self::GeometryInput{ + &self.geometry_input + } +} + +#[derive(Deserialize, Clone)] +pub struct Input0D { + pub options: Options, + pub material_parameters: material::MaterialParameters, + pub particle_parameters: particle::ParticleParameters, + pub geometry_input: geometry::Mesh0DInput, +} + +impl GeometryInput for Input0D { + type GeometryInput = geometry::Mesh0DInput; +} + +impl InputFile for Input0D { + + fn new(string: &str) -> Input0D { + toml::from_str(string).expect("Could not parse TOML file.") + } + + fn get_options(&self) -> &Options{ + &self.options + } + fn get_material_parameters(&self) -> &material::MaterialParameters{ + &self.material_parameters + } + fn get_particle_parameters(&self) ->& particle::ParticleParameters{ + &self.particle_parameters + } + fn get_geometry_input(&self) -> &Self::GeometryInput{ + &self.geometry_input + } +} + +#[derive(Deserialize, Clone)] +pub struct Input1D { + pub options: Options, + pub material_parameters: material::MaterialParameters, + pub particle_parameters: particle::ParticleParameters, + pub geometry_input: geometry::Mesh1DInput, +} + +impl GeometryInput for Input1D { + type GeometryInput = geometry::Mesh1DInput; +} + +impl InputFile for Input1D { + + fn new(string: &str) -> Input1D { + toml::from_str(string).expect("Could not parse TOML file.") + } + + fn get_options(&self) -> &Options{ + &self.options + } + fn get_material_parameters(&self) -> &material::MaterialParameters{ + &self.material_parameters + } + fn get_particle_parameters(&self) ->& particle::ParticleParameters{ + &self.particle_parameters + } + fn get_geometry_input(&self) -> &Self::GeometryInput{ + &self.geometry_input + } +} + +/// Rustbca's internal representation of the simulation-level options. +#[cfg(not(feature = "distributions"))] +#[derive(Deserialize, Clone)] +pub struct Options { + pub name: String, + pub track_trajectories: bool, + pub track_recoils: bool, + pub track_recoil_trajectories: bool, + pub write_buffer_size: usize, + pub weak_collision_order: usize, + pub suppress_deep_recoils: bool, + pub high_energy_free_flight_paths: bool, + pub electronic_stopping_mode: ElectronicStoppingMode, + pub mean_free_path_model: MeanFreePathModel, + pub interaction_potential: Vec>, + pub scattering_integral: Vec>, + pub root_finder: Vec>, + pub num_threads: usize, + pub num_chunks: u64, + pub use_hdf5: bool, + pub track_displacements: bool, + pub track_energy_losses: bool, +} + +#[cfg(feature = "distributions")] +#[derive(Deserialize, Clone)] +pub struct Options { + pub name: String, + pub track_trajectories: bool, + pub track_recoils: bool, + pub track_recoil_trajectories: bool, + pub write_buffer_size: usize, + pub weak_collision_order: usize, + pub suppress_deep_recoils: bool, + pub high_energy_free_flight_paths: bool, + pub electronic_stopping_mode: ElectronicStoppingMode, + pub mean_free_path_model: MeanFreePathModel, + pub interaction_potential: Vec>, + pub scattering_integral: Vec>, + pub root_finder: Vec>, + pub num_threads: usize, + pub num_chunks: u64, + pub use_hdf5: bool, + pub track_displacements: bool, + pub track_energy_losses: bool, + pub energy_min: f64, + pub energy_max: f64, + pub energy_num: usize, + pub angle_min: f64, + pub angle_max: f64, + pub angle_num: usize, + pub x_min: f64, + pub y_min: f64, + pub z_min: f64, + pub x_max: f64, + pub y_max: f64, + pub z_max: f64, + pub x_num: usize, + pub y_num: usize, + pub z_num: usize, +} + +pub fn input(input_file: String) -> (Vec, material::Material, Options, OutputUnits) +where ::InputFileFormat: Deserialize<'static> + 'static { + + //Read input file, convert to string, and open with toml + let mut input_toml = String::new(); + let mut file = OpenOptions::new() + .read(true) + .write(false) + .create(false) + .open(&input_file) + .expect(format!("Input errror: could not open input file {}.", &input_file).as_str()); + file.read_to_string(&mut input_toml).context("Could not convert TOML file to string.").unwrap(); + + let input: ::InputFileFormat = InputFile::new(&input_toml); + + //Unpack toml information into structs + let options = (*input.get_options()).clone(); + let particle_parameters = (*input.get_particle_parameters()).clone(); + let material_parameters = (*input.get_material_parameters()).clone(); + let material: material::Material = material::Material::::new(&material_parameters, input.get_geometry_input()); + + //Ensure nonsensical threads/chunks options crash on input + assert!(options.num_threads > 0, "Input error: num_threads must be greater than zero."); + assert!(options.num_chunks > 0, "Input error: num_chunks must be greater than zero."); + + //Check that all material arrays are of equal length. + assert!(material.m.len() == material.Z.len(), "Input error: material input arrays of unequal length."); + assert!(material.m.len() == material.Eb.len(), "Input error: material input arrays of unequal length."); + assert!(material.m.len() == material.Es.len(), "Input error: material input arrays of unequal length."); + assert!(material.m.len() == material.interaction_index.len(), "Input error: material input arrays of unequal length."); + + //Check that incompatible options are not on simultaneously + if options.high_energy_free_flight_paths { + assert!(options.electronic_stopping_mode == ElectronicStoppingMode::INTERPOLATED, + "Input error: High energy free flight paths used with low energy stoppping power. Change to INTERPOLATED."); + assert!(options.weak_collision_order == 0, + "Input error: Cannot use weak collisions with free flight paths. Set weak_collision_order = 0."); + } + + if options.mean_free_path_model == MeanFreePathModel::GASEOUS { + assert!(options.weak_collision_order == 0, + "Input error: Cannot use weak collisions with gaseous mean free path model. Set weak_collision_order = 0."); + } + + for i in 0..options.interaction_potential.len() { + assert!(options.interaction_potential.len() == options.interaction_potential[i].len(), + "Input error: interaction matrix not square."); + + assert!(options.scattering_integral.len() == options.scattering_integral[i].len(), + "Input error: scattering intergral matrix not square."); + + assert!(options.root_finder.len() == options.root_finder[i].len(), + "Input error: rootfinder matrix not square."); + } + + for ((interaction_potentials, scattering_integrals), root_finders) in options.interaction_potential.iter().zip(options.scattering_integral.clone()).zip(options.root_finder.clone()) { + for ((interaction_potential, scattering_integral), root_finder) in interaction_potentials.iter().zip(scattering_integrals).zip(root_finders) { + + if cfg!(not(any(feature="cpr_rootfinder_openblas", feature="cpr_rootfinder_netlib", feature="cpr_rootfinder_intel_mkl",))) { + assert!( match root_finder { + Rootfinder::POLYNOMIAL{..} => false, + Rootfinder::CPR{..} => false, + _ => true, + }, + "Input error: CPR rootfinder not enabled. Build with --features cpr_rootfinder"); + } + + assert!( + match (interaction_potential, root_finder) { + (InteractionPotential::LENNARD_JONES_12_6{..}, Rootfinder::CPR{..}) => true, + (InteractionPotential::LENNARD_JONES_12_6{..}, Rootfinder::POLYNOMIAL{..}) => true, + (InteractionPotential::LENNARD_JONES_12_6{..}, _) => false, + (InteractionPotential::LENNARD_JONES_65_6{..}, Rootfinder::CPR{..}) => true, + (InteractionPotential::LENNARD_JONES_65_6{..}, Rootfinder::POLYNOMIAL{..}) => true, + (InteractionPotential::LENNARD_JONES_65_6{..}, _) => false, + (InteractionPotential::MORSE{..}, Rootfinder::CPR{..}) => true, + (InteractionPotential::MORSE{..}, _) => false, + (_, Rootfinder::POLYNOMIAL{..}) => false, + (_, _) => true, + }, + "Input error: cannot use {} with {}. Try switching to a different rootfinder.", interaction_potential, root_finder); + } + } + + //Check that particle arrays are equal length + assert_eq!(particle_parameters.Z.len(), particle_parameters.m.len(), + "Input error: particle input arrays of unequal length."); + assert_eq!(particle_parameters.Z.len(), particle_parameters.E.len(), + "Input error: particle input arrays of unequal length."); + assert_eq!(particle_parameters.Z.len(), particle_parameters.pos.len(), + "Input error: particle input arrays of unequal length."); + assert_eq!(particle_parameters.Z.len(), particle_parameters.dir.len(), + "Input error: particle input arrays of unequal length."); + + //Check that interaction indices are all within interaction matrices + assert!(material.interaction_index.iter().max().unwrap() < &options.interaction_potential.len(), + "Input error: interaction matrix too small for material interaction indices."); + assert!(particle_parameters.interaction_index.iter().max().unwrap() < &options.interaction_potential.len(), + "Input error: interaction matrix too small for particle interaction indices."); + + //N is the number of distinct particles. + let N = particle_parameters.Z.len(); + + //Determine the length, energy, and mass units for particle input + let length_unit: f64 = match particle_parameters.length_unit.as_str() { + "MICRON" => MICRON, + "CM" => CM, + "ANGSTROM" => ANGSTROM, + "NM" => NM, + "M" => 1., + _ => particle_parameters.length_unit.parse() + .expect(format!( + "Input errror: could nor parse length unit {}. Use a valid float or one of ANGSTROM, NM, MICRON, CM, MM, M", &particle_parameters.length_unit.as_str() + ).as_str()), + }; + + let energy_unit: f64 = match particle_parameters.energy_unit.as_str() { + "EV" => EV, + "J" => 1., + "KEV" => EV*1E3, + "MEV" => EV*1E6, + _ => particle_parameters.energy_unit.parse() + .expect(format!( + "Input errror: could nor parse energy unit {}. Use a valid float or one of EV, J, KEV, MEV", &particle_parameters.energy_unit.as_str() + ).as_str()), + }; + + let mass_unit: f64 = match particle_parameters.mass_unit.as_str() { + "AMU" => AMU, + "KG" => 1.0, + _ => particle_parameters.mass_unit.parse() + .expect(format!( + "Input errror: could nor parse mass unit {}. Use a valid float or one of AMU, KG", &particle_parameters.mass_unit.as_str() + ).as_str()), + }; + + //HDF5 + #[cfg(feature = "hdf5_input")] + let particle_input_array: Vec = { + if options.use_hdf5 { + let particle_input_filename = particle_parameters.particle_input_filename.as_str(); + let _e = hdf5::silence_errors(); + let particle_input_file = hdf5::File::open(particle_input_filename) + .context("Input error: cannot open HDF5 file.") + .unwrap(); + let particle_input = particle_input_file.dataset("particles") + .context("Input error: cannot read from HDF5 file.") + .unwrap(); + particle_input.read_raw::().unwrap() + + } else { + let mut particle_input: Vec = Vec::new(); + + for particle_index in 0..N { + let N_ = particle_parameters.N[particle_index]; + let m = particle_parameters.m[particle_index]; + let Z = particle_parameters.Z[particle_index]; + let E = particle_parameters.E[particle_index]; + let Ec = particle_parameters.Ec[particle_index]; + let Es = particle_parameters.Es[particle_index]; + let interaction_index = particle_parameters.interaction_index[particle_index]; + let (x, y, z) = particle_parameters.pos[particle_index]; + let (cosx, cosy, cosz) = particle_parameters.dir[particle_index]; + assert!(cosx < 1., + "Input error: particle x-direction cannot be exactly equal to 1 to avoid numerical gimbal lock."); + for sub_particle_index in 0..N_ { + //Add new particle to particle vector + particle_input.push( + particle::ParticleInput{ + m: m*mass_unit, + Z: Z, + E: E*energy_unit, + Ec: Ec*energy_unit, + Es: Es*energy_unit, + x: x*length_unit, + y: y*length_unit, + z: z*length_unit, + ux: cosx, + uy: cosy, + uz: cosz, + interaction_index: interaction_index + } + ); + } + } + particle_input + } + }; + + #[cfg(not(feature = "hdf5_input"))] + let particle_input_array: Vec = { + if options.use_hdf5 { + panic!("HDF5 particle input not enabled. Enable with: cargo build --features hdf5_input") + } else { + let mut particle_input: Vec = Vec::new(); + + for particle_index in 0..N { + let N_ = particle_parameters.N[particle_index]; + let m = particle_parameters.m[particle_index]; + let Z = particle_parameters.Z[particle_index]; + let E = particle_parameters.E[particle_index]; + let Ec = particle_parameters.Ec[particle_index]; + let Es = particle_parameters.Es[particle_index]; + let interaction_index = particle_parameters.interaction_index[particle_index]; + let (x, y, z) = particle_parameters.pos[particle_index]; + let (cosx, cosy, cosz) = particle_parameters.dir[particle_index]; + assert!(cosx < 1., + "Input error: particle x-direction cannot be exactly equal to 1 to avoid numerical gimbal lock."); + for sub_particle_index in 0..N_ { + + //Add new particle to particle vector + particle_input.push( + particle::ParticleInput{ + m: m*mass_unit, + Z: Z, + E: E*energy_unit, + Ec: Ec*energy_unit, + Es: Es*energy_unit, + x: x*length_unit, + y: y*length_unit, + z: z*length_unit, + ux: cosx, + uy: cosy, + uz: cosz, + interaction_index + } + ); + } + } + particle_input + } + }; + (particle_input_array, material, options, OutputUnits {length_unit, energy_unit, mass_unit}) +} diff --git a/src/interactions.rs b/src/interactions.rs index 9382f73..3c81cb7 100644 --- a/src/interactions.rs +++ b/src/interactions.rs @@ -1,441 +1,441 @@ -use super::*; - -/// Analytic solutions to outermost root of the interaction potential. -pub fn crossing_point_doca(interaction_potential: InteractionPotential) -> f64 { - - match interaction_potential { - InteractionPotential::LENNARD_JONES_12_6{sigma, ..} | InteractionPotential::LENNARD_JONES_65_6{sigma, ..} => sigma, - InteractionPotential::MORSE{D, alpha, r0} => (alpha*r0 - (2.0_f64).ln())/alpha, - InteractionPotential::WW => 50.*ANGSTROM, - _ => 10.*ANGSTROM, - //_ => panic!("Input error: potential never crosses zero for r > 0. Consider using the Newton rootfinder.") - } - -} - -/// Interaction potential between two particles a and b at a distance `r`. -pub fn interaction_potential(r: f64, a: f64, Za: f64, Zb: f64, interaction_potential: InteractionPotential) -> f64 { - match interaction_potential { - InteractionPotential::MOLIERE | InteractionPotential::KR_C | InteractionPotential::LENZ_JENSEN | InteractionPotential::ZBL | InteractionPotential::TRIDYN => { - screened_coulomb(r, a, Za, Zb, interaction_potential) - }, - InteractionPotential::LENNARD_JONES_12_6{sigma, epsilon} => { - lennard_jones(r, sigma, epsilon) - }, - InteractionPotential::LENNARD_JONES_65_6{sigma, epsilon} => { - lennard_jones_65_6(r, sigma, epsilon) - } - InteractionPotential::MORSE{D, alpha, r0} => { - morse(r, D, alpha, r0) - } - InteractionPotential::WW => { - tungsten_tungsten_cubic_spline(r) - } - InteractionPotential::COULOMB{Za, Zb} => { - coulomb(r, Za, Zb) - } - } -} - -/// Analytic energy threshold above which the distance of closest approach function is guaranteed to have only one root. -pub fn energy_threshold_single_root(interaction_potential: InteractionPotential) -> f64 { - match interaction_potential{ - InteractionPotential::LENNARD_JONES_12_6{..} | InteractionPotential::LENNARD_JONES_65_6{..} => f64::INFINITY, - InteractionPotential::MORSE{..} => f64::INFINITY, - InteractionPotential::WW => f64::INFINITY, - InteractionPotential::COULOMB{..} => f64::INFINITY, - InteractionPotential::MOLIERE | InteractionPotential::KR_C | InteractionPotential::LENZ_JENSEN | InteractionPotential::ZBL | InteractionPotential::TRIDYN => 0., - } -} - -/// Distance of closest approach function for screened coulomb potentials. -fn doca_function(x0: f64, beta: f64, reduced_energy: f64, interaction_potential: InteractionPotential) -> f64 { - //Nonlinear equation to determine distance of closest approach - return x0 - interactions::phi(x0, interaction_potential)/reduced_energy - beta*beta/x0; -} - -/// First derivative w.r.t. `r` of the distance of closest approach function for screened coulomb potentials. -fn diff_doca_function(x0: f64, beta: f64, reduced_energy: f64, interaction_potential: InteractionPotential) -> f64 { - //First differential of distance of closest approach function for N-R solver - return beta*beta/x0/x0 - interactions::dphi(x0, interaction_potential)/reduced_energy + 1. -} - -/// Singularity-free version of the screened-coulomb distance of closest approach function. -fn doca_function_transformed(x0: f64, beta: f64, reduced_energy: f64, interaction_potential: InteractionPotential) -> f64 { - //Singularity free version of doca function - return x0*x0 - x0*interactions::phi(x0, interaction_potential)/reduced_energy - beta*beta; -} - -/// First derivative w.r.t. `r` of the singularity-free version of the screened-coulomb distance of closest approach function. -fn diff_doca_function_transformed(x0: f64, beta: f64, reduced_energy: f64, interaction_potential: InteractionPotential) -> f64 { - //First differential of distance of closest approach function for N-R solver - return 2.*x0 - interactions::phi(x0, interaction_potential)/reduced_energy -} - -/// Distance of closest approach function. The outermost root of this function is the distance of closest approach, or classical turning point, which is the lower bound to the scattering integral. -pub fn distance_of_closest_approach_function(r: f64, a: f64, Za: f64, Zb: f64, relative_energy: f64, impact_parameter: f64, interaction_potential: InteractionPotential) -> f64 { - match interaction_potential { - InteractionPotential::MOLIERE | InteractionPotential::KR_C | InteractionPotential::LENZ_JENSEN | InteractionPotential::ZBL | InteractionPotential::TRIDYN => { - let a: f64 = interactions::screening_length(Za, Zb, interaction_potential); - let reduced_energy: f64 = LINDHARD_REDUCED_ENERGY_PREFACTOR*a/Za/Zb*relative_energy; - let beta: f64 = impact_parameter/a; - doca_function(r/a, beta, reduced_energy, interaction_potential) - - }, - InteractionPotential::LENNARD_JONES_12_6{sigma, epsilon} => { - doca_lennard_jones(r, impact_parameter, relative_energy, sigma, epsilon) - }, - InteractionPotential::LENNARD_JONES_65_6{sigma, epsilon} => { - doca_lennard_jones_65_6(r, impact_parameter, relative_energy, sigma, epsilon) - }, - InteractionPotential::MORSE{D, alpha, r0} => { - doca_morse(r, impact_parameter, relative_energy, D, alpha, r0) - }, - InteractionPotential::WW => { - doca_tungsten_tungsten_cubic_spline(r, impact_parameter, relative_energy) - }, - InteractionPotential::COULOMB{..} => panic!("Coulombic potential cannot be used with rootfinder.") - } -} - -/// Singularity-free distance of closest approach function. The outermost root of this function is the distance of closest approach, or classical turning point, which is the lower bound to the scattering integral. -pub fn distance_of_closest_approach_function_singularity_free(r: f64, a: f64, Za: f64, Zb: f64, relative_energy: f64, impact_parameter: f64, interaction_potential: InteractionPotential) -> f64 { - if r.is_nan() { - panic!("Numerical error: r is NaN in distance of closest approach function. Check Rootfinder.") - } - match interaction_potential { - InteractionPotential::MOLIERE | InteractionPotential::KR_C | InteractionPotential::LENZ_JENSEN | InteractionPotential::ZBL | InteractionPotential::TRIDYN => { - let a: f64 = interactions::screening_length(Za, Zb, interaction_potential); - let reduced_energy: f64 = LINDHARD_REDUCED_ENERGY_PREFACTOR*a/Za/Zb*relative_energy; - let beta: f64 = impact_parameter/a; - doca_function_transformed(r/a, beta, reduced_energy, interaction_potential) - }, - InteractionPotential::LENNARD_JONES_12_6{sigma, epsilon} => { - doca_lennard_jones(r, impact_parameter, relative_energy, sigma, epsilon) - }, - InteractionPotential::LENNARD_JONES_65_6{sigma, epsilon} => { - doca_lennard_jones_65_6(r, impact_parameter, relative_energy, sigma, epsilon) - }, - InteractionPotential::MORSE{D, alpha, r0} => { - doca_morse(r, impact_parameter, relative_energy, D, alpha, r0) - }, - InteractionPotential::WW => { - doca_tungsten_tungsten_cubic_spline(r, impact_parameter, relative_energy) - }, - InteractionPotential::COULOMB{..} => panic!("Coulombic potential cannot be used with rootfinder.") - } -} - -/// Scaling function used to keep the distance of closest approach function ~1 for the CPR rootfinder. -pub fn scaling_function(r: f64, a: f64, interaction_potential: InteractionPotential) -> f64 { - match interaction_potential { - InteractionPotential::MOLIERE | InteractionPotential::KR_C | InteractionPotential::LENZ_JENSEN | InteractionPotential::ZBL | InteractionPotential::TRIDYN => { - 1./(1. + (r/a).powi(2)) - }, - InteractionPotential::LENNARD_JONES_12_6{sigma, ..} => { - let n = 11.; - 1./(1. + (r/sigma).powf(n)) - }, - InteractionPotential::LENNARD_JONES_65_6{sigma, ..} => { - let n = 6.; - 1./(1. + (r/sigma).powf(n)) - }, - InteractionPotential::MORSE{D, alpha, r0} => { - 1./(1. + (r*alpha).powi(2)) - } - InteractionPotential::WW => { - 1. - }, - InteractionPotential::COULOMB{..} => panic!("Coulombic potential cannot be used with rootfinder.") - } -} - -/// First derivative w.r.t. `r` of the distance of closest approach function. The outermost root of this function is the distance of closest approach, or classical turning point, which is the lower bound to the scattering integral. -pub fn diff_distance_of_closest_approach_function(r: f64, a: f64, Za: f64, Zb: f64, relative_energy: f64, impact_parameter: f64, interaction_potential: InteractionPotential) -> f64 { - match interaction_potential { - InteractionPotential::MOLIERE | InteractionPotential::KR_C | InteractionPotential::LENZ_JENSEN |InteractionPotential::ZBL | InteractionPotential::TRIDYN => { - let a: f64 = interactions::screening_length(Za, Zb, interaction_potential); - //let reduced_energy: f64 = LINDHARD_REDUCED_ENERGY_PREFACTOR*a*Mb/(Ma+Mb)/Za/Zb*E0; - let reduced_energy: f64 = LINDHARD_REDUCED_ENERGY_PREFACTOR*a/Za/Zb*relative_energy; - let beta: f64 = impact_parameter/a; - diff_doca_function(r/a, beta, reduced_energy, interaction_potential) - }, - InteractionPotential::LENNARD_JONES_12_6{sigma, epsilon} => { - diff_doca_lennard_jones(r, impact_parameter, relative_energy, sigma, epsilon) - }, - InteractionPotential::LENNARD_JONES_65_6{sigma, epsilon} => { - diff_doca_lennard_jones_65_6(r, impact_parameter, relative_energy, sigma, epsilon) - }, - InteractionPotential::MORSE{D, alpha, r0} => { - diff_doca_morse(r, impact_parameter, relative_energy, D, alpha, r0) - }, - _ => panic!("Input error: {} does not have an implemented derivative. Try using the derivative free CPR-rootfinder.", interaction_potential) - } -} - -/// First derivative w.r.t. `r` of the singularity-free distance of closest approach function. The outermost root of this function is the distance of closest approach, or classical turning point, which is the lower bound to the scattering integral. -pub fn diff_distance_of_closest_approach_function_singularity_free(r: f64, a: f64, Za: f64, Zb: f64, relative_energy: f64, impact_parameter: f64, interaction_potential: InteractionPotential) -> f64 { - match interaction_potential { - InteractionPotential::MOLIERE | InteractionPotential::KR_C | InteractionPotential::LENZ_JENSEN | InteractionPotential::ZBL | InteractionPotential::TRIDYN => { - let a: f64 = interactions::screening_length(Za, Zb, interaction_potential); - //let reduced_energy: f64 = LINDHARD_REDUCED_ENERGY_PREFACTOR*a*Mb/(Ma+Mb)/Za/Zb*E0; - let reduced_energy: f64 = LINDHARD_REDUCED_ENERGY_PREFACTOR*a/Za/Zb*relative_energy; - let beta: f64 = impact_parameter/a; - diff_doca_function_transformed(r/a, beta, reduced_energy, interaction_potential) - }, - InteractionPotential::LENNARD_JONES_12_6{sigma, epsilon} => { - diff_doca_lennard_jones(r, impact_parameter, relative_energy, sigma, epsilon) - }, - InteractionPotential::LENNARD_JONES_65_6{sigma, epsilon} => { - diff_doca_lennard_jones_65_6(r, impact_parameter, relative_energy, sigma, epsilon) - }, - InteractionPotential::MORSE{D, alpha, r0} => { - diff_doca_morse(r, impact_parameter, relative_energy, D, alpha, r0) - }, - _ => panic!("Input error: {} does not have an implemented derivative. Try using the derivative free CPR-rootfinder.", interaction_potential) - } -} - -/// Screened coulomb interaction potential. -pub fn screened_coulomb(r: f64, a: f64, Za: f64, Zb: f64, interaction_potential: InteractionPotential) -> f64 { - Za*Zb*Q*Q/4./PI/EPS0/r*phi(r/a, interaction_potential) -} - -/// Coulombic interaction potential. -pub fn coulomb(r: f64, Za: f64, Zb: f64) -> f64 { - return Za*Zb*Q*Q/4./PI/EPS0/r; -} - -/// Screening functions for screened-coulomb interaction potentials. -pub fn phi(xi: f64, interaction_potential: InteractionPotential) -> f64 { - match interaction_potential { - InteractionPotential::MOLIERE => moliere(xi), - InteractionPotential::KR_C => kr_c(xi), - InteractionPotential::ZBL => zbl(xi), - InteractionPotential::LENZ_JENSEN => lenz_jensen(xi), - InteractionPotential::TRIDYN => kr_c(xi), - _ => panic!("Input error: Screened Coulomb cannot be used with {}", interaction_potential) - } -} - -/// First derivative w.r.t. `r` of the screening functions for screened-coulomb interaction potentials. -pub fn dphi(xi: f64, interaction_potential: InteractionPotential) -> f64 { - match interaction_potential { - InteractionPotential::MOLIERE => diff_moliere(xi), - InteractionPotential::KR_C => diff_kr_c(xi), - InteractionPotential::ZBL => diff_zbl(xi), - InteractionPotential::LENZ_JENSEN => diff_lenz_jensen(xi), - InteractionPotential::TRIDYN => diff_kr_c(xi), - _ => panic!("Input error: Screened Coulomb cannot be used with {}", interaction_potential) - } -} - -/// Screening length of interatomic potentials. -pub fn screening_length(Za: f64, Zb: f64, interaction_potential: InteractionPotential) -> f64 { - match interaction_potential { - //ZBL screening length, Eckstein (4.1.8) - InteractionPotential::ZBL => 0.88534*A0/(Za.powf(0.23) + Zb.powf(0.23)), - //Lindhard/Firsov screening length, Eckstein (4.1.5) - InteractionPotential::MOLIERE | InteractionPotential::KR_C | InteractionPotential::LENZ_JENSEN | InteractionPotential::TRIDYN | InteractionPotential::WW => 0.8853*A0*(Za.sqrt() + Zb.sqrt()).powf(-2./3.), - InteractionPotential::LENNARD_JONES_12_6{..} | InteractionPotential::LENNARD_JONES_65_6{..} => 0.8853*A0*(Za.sqrt() + Zb.sqrt()).powf(-2./3.), - InteractionPotential::MORSE{D, alpha, r0} => alpha, - InteractionPotential::COULOMB{Za: Z1, Zb: Z2} => 0.88534*A0/(Z1.powf(0.23) + Z2.powf(0.23)), - } -} - -/// Coefficients of inverse-polynomial interaction potentials. -pub fn polynomial_coefficients(relative_energy: f64, impact_parameter: f64, interaction_potential: InteractionPotential) -> Vec { - match interaction_potential { - InteractionPotential::LENNARD_JONES_12_6{sigma, epsilon} => { - vec![1., 0., -impact_parameter.powi(2), 0., 0., 0., 4.*epsilon*sigma.powf(6.)/relative_energy, 0., 0., 0., 0., 0., -4.*epsilon*sigma.powf(12.)/relative_energy] - }, - InteractionPotential::LENNARD_JONES_65_6{sigma, epsilon} => { - vec![1., 0., 0., 0., -impact_parameter.powi(2), 0., 0., 0., 0., 0., 0., 0., 4.*epsilon*sigma.powf(6.)/relative_energy, -4.*epsilon*sigma.powf(6.5)/relative_energy] - }, - _ => panic!("Input error: non-polynomial interaction potential used with polynomial root-finder.") - } -} - -/// Inverse-polynomial interaction potentials transformation to remove singularities for the CPR root-finder. -pub fn inverse_transform(x: f64, interaction_potential: InteractionPotential) -> f64 { - match interaction_potential { - InteractionPotential::LENNARD_JONES_12_6{..} => { - x - }, - InteractionPotential::LENNARD_JONES_65_6{..} => { - x*x - }, - _ => panic!("Input error: non-polynomial interaction potential used with polynomial root-finder transformation.") - } -} - -/// Lennard-Jones 12-6 -pub fn lennard_jones(r: f64, sigma: f64, epsilon: f64) -> f64 { - 4.*epsilon*((sigma/r).powf(12.) - (sigma/r).powf(6.)) -} - -/// Lennard-Jones 6.5-6 -pub fn lennard_jones_65_6(r: f64, sigma: f64, epsilon: f64) -> f64 { - 4.*epsilon*((sigma/r).powf(6.5) - (sigma/r).powf(6.)) -} - -/// Morse potential -pub fn morse(r: f64, D: f64, alpha: f64, r0: f64) -> f64 { - D*((-2.*alpha*(r - r0)).exp() - 2.*(-alpha*(r - r0)).exp()) -} - -/// Distance of closest approach function for Morse potential. -pub fn doca_morse(r: f64, impact_parameter: f64, relative_energy: f64, D: f64, alpha: f64, r0: f64) -> f64 { - (r*alpha).powi(2) - (r*alpha).powi(2)*D/relative_energy*((-2.*alpha*(r - r0)).exp() - 2.*(-alpha*(r - r0)).exp()) - (impact_parameter*alpha).powi(2) -} - -/// First derivative w.r.t. `r` of the distance of closest approach function for Morse potential. -pub fn diff_doca_morse(r: f64, impact_parameter: f64, relative_energy: f64, D: f64, alpha: f64, r0: f64) -> f64 { - 2.*alpha.powi(2)*r - 2.*alpha.powi(2)*D*r*(-2.*alpha*(r - r0) - 1.).exp()*(alpha*r*(alpha*(r - r0)).exp() - 2.*(alpha*(r - r0)).exp() - r*alpha + 1.) -} - -/// Distance of closest approach function for LJ 6.5-6 potential. -pub fn doca_lennard_jones_65_6(r: f64, p: f64, relative_energy: f64, sigma: f64, epsilon: f64) -> f64 { - (r/sigma).powf(6.5) - 4.*epsilon/relative_energy*(1. - (r/sigma).powf(0.5)) - (p/sigma).powi(2)*(r/sigma).powf(4.5) -} - -/// Distance of closest approach function for LJ 12-6 potential. -pub fn doca_lennard_jones(r: f64, p: f64, relative_energy: f64, sigma: f64, epsilon: f64) -> f64 { - (r/sigma).powf(12.) - 4.*epsilon/relative_energy*(1. - (r/sigma).powf(6.)) - p.powi(2)*r.powf(10.)/sigma.powf(12.) -} - -/// First derivative w.r.t. `r` of the distance of closest approach function for LJ 12-6 potential. -pub fn diff_doca_lennard_jones(r: f64, p: f64, relative_energy: f64, sigma: f64, epsilon: f64) -> f64 { - 12.*(r/sigma).powf(11.)/sigma + 4.*epsilon/relative_energy*6.*(r/sigma).powf(5.)/sigma - 10.*p.powi(2)*r.powf(9.)/sigma.powf(12.) -} - -/// First derivative w.r.t. `r` of the distance of closest approach function for LJ 6.5-6 potential. -pub fn diff_doca_lennard_jones_65_6(r: f64, p: f64, relative_energy: f64, sigma: f64, epsilon: f64) -> f64 { - 6.5*(r/sigma).powf(5.5)/sigma + 4.*epsilon/relative_energy*0.5*(sigma*r).powf(-0.5) - (p/sigma).powi(2)*4.5*(r/sigma).powf(3.5)/sigma -} - -/// W-W cublic spline potential from Bjorkas et al. -pub fn tungsten_tungsten_cubic_spline(r: f64) -> f64 { - - let x = r/ANGSTROM; - let x1 = 1.10002200044; - let x2 = 2.10004200084; - - if x <= x1 { - - let a = screening_length(74., 74., InteractionPotential::ZBL); - screened_coulomb(r, a, 74., 74., InteractionPotential::ZBL) - - } else if x <= x2 { - - let a = vec![ - 1.389653276380862E4, - -3.596912431628216E4, - 3.739206756369099E4, - -1.933748081656593E4, - 0.495516793802426E4, - -0.050264585985867E4 - ]; - - (a[0] + a[1]*x + a[2]*x.powi(2) + a[3]*x.powi(3) + a[4]*x.powf(4.) + a[5]*x.powf(5.))*EV - - } else { - - let a = vec![ - -0.1036435865158945, - -0.2912948318493851, - -2.096765499656263, - 19.16045452701010, - -41.01619862085917, - 46.05205617244703, - 26.42203930654883, - 15.35211507804088, - 14.12806259323987, - ]; - - let delta = vec![ - 4.268900000000000, - 3.985680000000000, - 3.702460000000000, - 3.419240000000000, - 3.136020000000000, - 2.852800000000000, - 2.741100000000000, - 2.604045000000000, - 2.466990000000000, - ]; - - a.iter().zip(delta).map(|(&a_i, delta_i)| EV*a_i*(delta_i - x).powi(3)*heaviside(delta_i - x)).sum::() - } -} - -/// Distance of closest approach function for the W-W cublic spline potential from Bjorkas et al. -pub fn doca_tungsten_tungsten_cubic_spline(r: f64, p: f64, relative_energy: f64) -> f64 { - - let x = r/ANGSTROM; - let x1 = 1.10002200044; - let x2 = 2.10004200084; - - if x <= x1 { - let a = screening_length(74., 74., InteractionPotential::ZBL); - distance_of_closest_approach_function_singularity_free(r, a, 74., 74., relative_energy, p, InteractionPotential::ZBL) - } else { - (r/ANGSTROM).powi(2) - (r/ANGSTROM).powi(2)*tungsten_tungsten_cubic_spline(r)/relative_energy - p.powi(2)/ANGSTROM.powi(2) - } -} - -fn heaviside(x: f64) -> f64 { - if x >= 0. { - 1. - } else { - 0. - } -} - -fn moliere(xi: f64) -> f64 { - 0.35*(-0.3*xi).exp() + 0.55*(-1.2*xi).exp() + 0.10*(-6.0*xi).exp() -} - -fn kr_c(xi: f64) -> f64 { - 0.190945*(-0.278544*xi).exp() + 0.473674*(-0.637174*xi).exp() + 0.335381*(-1.919249*xi).exp() -} - -fn zbl(xi: f64) -> f64 { - 0.02817*(-0.20162*xi).exp() + 0.28022*(-0.40290*xi).exp() + 0.50986*(-0.94229*xi).exp() + 0.18175*(-3.1998*xi).exp() -} - -fn lenz_jensen(xi: f64) -> f64 { - 0.01018*(-0.206*xi).exp() + 0.24330*(-0.3876*xi).exp() + 0.7466*(-1.038*xi).exp() -} - -fn diff_moliere(xi: f64) -> f64 { - -0.35*0.3*(-0.3*xi).exp() -0.55*1.2*(-1.2*xi).exp() -0.10*6.0*(-6.0*xi).exp() -} - -fn diff_kr_c(xi: f64) -> f64 { - -0.278544*0.190945*(-0.278544*xi).exp() - 0.637174*0.473674*(-0.637174*xi).exp() - 0.335381*1.919249*(-1.919249*xi).exp() -} - -fn diff_zbl(xi: f64) -> f64 { - -0.20162*0.02817*(-0.20162*xi).exp() -0.40290*0.28022*(-0.40290*xi).exp() -0.94229*0.50986*(-0.94229*xi).exp() -3.1998*0.18175*(-3.1998*xi).exp() -} - -fn diff_lenz_jensen(xi: f64) -> f64 { - -0.206*0.01018*(-0.206*xi).exp() -0.3876*0.24330*(-0.3876*xi).exp() -1.038*0.7466*(-1.038*xi).exp() -} - -/// Normalized first screening radius. Used in Oen-Robinson electronic stopping. -pub fn first_screening_radius(interaction_potential: InteractionPotential) -> f64 { - match interaction_potential { - InteractionPotential::MOLIERE => 0.3, - InteractionPotential::KR_C => 0.278544, - InteractionPotential::ZBL => 0.20162, - InteractionPotential::LENZ_JENSEN => 0.206, - InteractionPotential::TRIDYN => 0.278544, - InteractionPotential::LENNARD_JONES_12_6{..} => 1., - InteractionPotential::LENNARD_JONES_65_6{..} => 1., - InteractionPotential::MORSE{..} => 1., - InteractionPotential::WW => 1., - InteractionPotential::COULOMB{..} => 0.3, - } -} +use super::*; + +/// Analytic solutions to outermost root of the interaction potential. +pub fn crossing_point_doca(interaction_potential: InteractionPotential) -> f64 { + + match interaction_potential { + InteractionPotential::LENNARD_JONES_12_6{sigma, ..} | InteractionPotential::LENNARD_JONES_65_6{sigma, ..} => sigma, + InteractionPotential::MORSE{D, alpha, r0} => (alpha*r0 - (2.0_f64).ln())/alpha, + InteractionPotential::WW => 50.*ANGSTROM, + _ => 10.*ANGSTROM, + //_ => panic!("Input error: potential never crosses zero for r > 0. Consider using the Newton rootfinder.") + } + +} + +/// Interaction potential between two particles a and b at a distance `r`. +pub fn interaction_potential(r: f64, a: f64, Za: f64, Zb: f64, interaction_potential: InteractionPotential) -> f64 { + match interaction_potential { + InteractionPotential::MOLIERE | InteractionPotential::KR_C | InteractionPotential::LENZ_JENSEN | InteractionPotential::ZBL | InteractionPotential::TRIDYN => { + screened_coulomb(r, a, Za, Zb, interaction_potential) + }, + InteractionPotential::LENNARD_JONES_12_6{sigma, epsilon} => { + lennard_jones(r, sigma, epsilon) + }, + InteractionPotential::LENNARD_JONES_65_6{sigma, epsilon} => { + lennard_jones_65_6(r, sigma, epsilon) + } + InteractionPotential::MORSE{D, alpha, r0} => { + morse(r, D, alpha, r0) + } + InteractionPotential::WW => { + tungsten_tungsten_cubic_spline(r) + } + InteractionPotential::COULOMB{Za, Zb} => { + coulomb(r, Za, Zb) + } + } +} + +/// Analytic energy threshold above which the distance of closest approach function is guaranteed to have only one root. +pub fn energy_threshold_single_root(interaction_potential: InteractionPotential) -> f64 { + match interaction_potential{ + InteractionPotential::LENNARD_JONES_12_6{..} | InteractionPotential::LENNARD_JONES_65_6{..} => f64::INFINITY, + InteractionPotential::MORSE{..} => f64::INFINITY, + InteractionPotential::WW => f64::INFINITY, + InteractionPotential::COULOMB{..} => f64::INFINITY, + InteractionPotential::MOLIERE | InteractionPotential::KR_C | InteractionPotential::LENZ_JENSEN | InteractionPotential::ZBL | InteractionPotential::TRIDYN => 0., + } +} + +/// Distance of closest approach function for screened coulomb potentials. +fn doca_function(x0: f64, beta: f64, reduced_energy: f64, interaction_potential: InteractionPotential) -> f64 { + //Nonlinear equation to determine distance of closest approach + return x0 - interactions::phi(x0, interaction_potential)/reduced_energy - beta*beta/x0; +} + +/// First derivative w.r.t. `r` of the distance of closest approach function for screened coulomb potentials. +fn diff_doca_function(x0: f64, beta: f64, reduced_energy: f64, interaction_potential: InteractionPotential) -> f64 { + //First differential of distance of closest approach function for N-R solver + return beta*beta/x0/x0 - interactions::dphi(x0, interaction_potential)/reduced_energy + 1. +} + +/// Singularity-free version of the screened-coulomb distance of closest approach function. +fn doca_function_transformed(x0: f64, beta: f64, reduced_energy: f64, interaction_potential: InteractionPotential) -> f64 { + //Singularity free version of doca function + return x0*x0 - x0*interactions::phi(x0, interaction_potential)/reduced_energy - beta*beta; +} + +/// First derivative w.r.t. `r` of the singularity-free version of the screened-coulomb distance of closest approach function. +fn diff_doca_function_transformed(x0: f64, beta: f64, reduced_energy: f64, interaction_potential: InteractionPotential) -> f64 { + //First differential of distance of closest approach function for N-R solver + return 2.*x0 - interactions::phi(x0, interaction_potential)/reduced_energy +} + +/// Distance of closest approach function. The outermost root of this function is the distance of closest approach, or classical turning point, which is the lower bound to the scattering integral. +pub fn distance_of_closest_approach_function(r: f64, a: f64, Za: f64, Zb: f64, relative_energy: f64, impact_parameter: f64, interaction_potential: InteractionPotential) -> f64 { + match interaction_potential { + InteractionPotential::MOLIERE | InteractionPotential::KR_C | InteractionPotential::LENZ_JENSEN | InteractionPotential::ZBL | InteractionPotential::TRIDYN => { + let a: f64 = interactions::screening_length(Za, Zb, interaction_potential); + let reduced_energy: f64 = LINDHARD_REDUCED_ENERGY_PREFACTOR*a/Za/Zb*relative_energy; + let beta: f64 = impact_parameter/a; + doca_function(r/a, beta, reduced_energy, interaction_potential) + + }, + InteractionPotential::LENNARD_JONES_12_6{sigma, epsilon} => { + doca_lennard_jones(r, impact_parameter, relative_energy, sigma, epsilon) + }, + InteractionPotential::LENNARD_JONES_65_6{sigma, epsilon} => { + doca_lennard_jones_65_6(r, impact_parameter, relative_energy, sigma, epsilon) + }, + InteractionPotential::MORSE{D, alpha, r0} => { + doca_morse(r, impact_parameter, relative_energy, D, alpha, r0) + }, + InteractionPotential::WW => { + doca_tungsten_tungsten_cubic_spline(r, impact_parameter, relative_energy) + }, + InteractionPotential::COULOMB{..} => panic!("Coulombic potential cannot be used with rootfinder.") + } +} + +/// Singularity-free distance of closest approach function. The outermost root of this function is the distance of closest approach, or classical turning point, which is the lower bound to the scattering integral. +pub fn distance_of_closest_approach_function_singularity_free(r: f64, a: f64, Za: f64, Zb: f64, relative_energy: f64, impact_parameter: f64, interaction_potential: InteractionPotential) -> f64 { + if r.is_nan() { + panic!("Numerical error: r is NaN in distance of closest approach function. Check Rootfinder.") + } + match interaction_potential { + InteractionPotential::MOLIERE | InteractionPotential::KR_C | InteractionPotential::LENZ_JENSEN | InteractionPotential::ZBL | InteractionPotential::TRIDYN => { + let a: f64 = interactions::screening_length(Za, Zb, interaction_potential); + let reduced_energy: f64 = LINDHARD_REDUCED_ENERGY_PREFACTOR*a/Za/Zb*relative_energy; + let beta: f64 = impact_parameter/a; + doca_function_transformed(r/a, beta, reduced_energy, interaction_potential) + }, + InteractionPotential::LENNARD_JONES_12_6{sigma, epsilon} => { + doca_lennard_jones(r, impact_parameter, relative_energy, sigma, epsilon) + }, + InteractionPotential::LENNARD_JONES_65_6{sigma, epsilon} => { + doca_lennard_jones_65_6(r, impact_parameter, relative_energy, sigma, epsilon) + }, + InteractionPotential::MORSE{D, alpha, r0} => { + doca_morse(r, impact_parameter, relative_energy, D, alpha, r0) + }, + InteractionPotential::WW => { + doca_tungsten_tungsten_cubic_spline(r, impact_parameter, relative_energy) + }, + InteractionPotential::COULOMB{..} => panic!("Coulombic potential cannot be used with rootfinder.") + } +} + +/// Scaling function used to keep the distance of closest approach function ~1 for the CPR rootfinder. +pub fn scaling_function(r: f64, a: f64, interaction_potential: InteractionPotential) -> f64 { + match interaction_potential { + InteractionPotential::MOLIERE | InteractionPotential::KR_C | InteractionPotential::LENZ_JENSEN | InteractionPotential::ZBL | InteractionPotential::TRIDYN => { + 1./(1. + (r/a).powi(2)) + }, + InteractionPotential::LENNARD_JONES_12_6{sigma, ..} => { + let n = 11.; + 1./(1. + (r/sigma).powf(n)) + }, + InteractionPotential::LENNARD_JONES_65_6{sigma, ..} => { + let n = 6.; + 1./(1. + (r/sigma).powf(n)) + }, + InteractionPotential::MORSE{D, alpha, r0} => { + 1./(1. + (r*alpha).powi(2)) + } + InteractionPotential::WW => { + 1. + }, + InteractionPotential::COULOMB{..} => panic!("Coulombic potential cannot be used with rootfinder.") + } +} + +/// First derivative w.r.t. `r` of the distance of closest approach function. The outermost root of this function is the distance of closest approach, or classical turning point, which is the lower bound to the scattering integral. +pub fn diff_distance_of_closest_approach_function(r: f64, a: f64, Za: f64, Zb: f64, relative_energy: f64, impact_parameter: f64, interaction_potential: InteractionPotential) -> f64 { + match interaction_potential { + InteractionPotential::MOLIERE | InteractionPotential::KR_C | InteractionPotential::LENZ_JENSEN |InteractionPotential::ZBL | InteractionPotential::TRIDYN => { + let a: f64 = interactions::screening_length(Za, Zb, interaction_potential); + //let reduced_energy: f64 = LINDHARD_REDUCED_ENERGY_PREFACTOR*a*Mb/(Ma+Mb)/Za/Zb*E0; + let reduced_energy: f64 = LINDHARD_REDUCED_ENERGY_PREFACTOR*a/Za/Zb*relative_energy; + let beta: f64 = impact_parameter/a; + diff_doca_function(r/a, beta, reduced_energy, interaction_potential) + }, + InteractionPotential::LENNARD_JONES_12_6{sigma, epsilon} => { + diff_doca_lennard_jones(r, impact_parameter, relative_energy, sigma, epsilon) + }, + InteractionPotential::LENNARD_JONES_65_6{sigma, epsilon} => { + diff_doca_lennard_jones_65_6(r, impact_parameter, relative_energy, sigma, epsilon) + }, + InteractionPotential::MORSE{D, alpha, r0} => { + diff_doca_morse(r, impact_parameter, relative_energy, D, alpha, r0) + }, + _ => panic!("Input error: {} does not have an implemented derivative. Try using the derivative free CPR-rootfinder.", interaction_potential) + } +} + +/// First derivative w.r.t. `r` of the singularity-free distance of closest approach function. The outermost root of this function is the distance of closest approach, or classical turning point, which is the lower bound to the scattering integral. +pub fn diff_distance_of_closest_approach_function_singularity_free(r: f64, a: f64, Za: f64, Zb: f64, relative_energy: f64, impact_parameter: f64, interaction_potential: InteractionPotential) -> f64 { + match interaction_potential { + InteractionPotential::MOLIERE | InteractionPotential::KR_C | InteractionPotential::LENZ_JENSEN | InteractionPotential::ZBL | InteractionPotential::TRIDYN => { + let a: f64 = interactions::screening_length(Za, Zb, interaction_potential); + //let reduced_energy: f64 = LINDHARD_REDUCED_ENERGY_PREFACTOR*a*Mb/(Ma+Mb)/Za/Zb*E0; + let reduced_energy: f64 = LINDHARD_REDUCED_ENERGY_PREFACTOR*a/Za/Zb*relative_energy; + let beta: f64 = impact_parameter/a; + diff_doca_function_transformed(r/a, beta, reduced_energy, interaction_potential) + }, + InteractionPotential::LENNARD_JONES_12_6{sigma, epsilon} => { + diff_doca_lennard_jones(r, impact_parameter, relative_energy, sigma, epsilon) + }, + InteractionPotential::LENNARD_JONES_65_6{sigma, epsilon} => { + diff_doca_lennard_jones_65_6(r, impact_parameter, relative_energy, sigma, epsilon) + }, + InteractionPotential::MORSE{D, alpha, r0} => { + diff_doca_morse(r, impact_parameter, relative_energy, D, alpha, r0) + }, + _ => panic!("Input error: {} does not have an implemented derivative. Try using the derivative free CPR-rootfinder.", interaction_potential) + } +} + +/// Screened coulomb interaction potential. +pub fn screened_coulomb(r: f64, a: f64, Za: f64, Zb: f64, interaction_potential: InteractionPotential) -> f64 { + Za*Zb*Q*Q/4./PI/EPS0/r*phi(r/a, interaction_potential) +} + +/// Coulombic interaction potential. +pub fn coulomb(r: f64, Za: f64, Zb: f64) -> f64 { + return Za*Zb*Q*Q/4./PI/EPS0/r; +} + +/// Screening functions for screened-coulomb interaction potentials. +pub fn phi(xi: f64, interaction_potential: InteractionPotential) -> f64 { + match interaction_potential { + InteractionPotential::MOLIERE => moliere(xi), + InteractionPotential::KR_C => kr_c(xi), + InteractionPotential::ZBL => zbl(xi), + InteractionPotential::LENZ_JENSEN => lenz_jensen(xi), + InteractionPotential::TRIDYN => kr_c(xi), + _ => panic!("Input error: Screened Coulomb cannot be used with {}", interaction_potential) + } +} + +/// First derivative w.r.t. `r` of the screening functions for screened-coulomb interaction potentials. +pub fn dphi(xi: f64, interaction_potential: InteractionPotential) -> f64 { + match interaction_potential { + InteractionPotential::MOLIERE => diff_moliere(xi), + InteractionPotential::KR_C => diff_kr_c(xi), + InteractionPotential::ZBL => diff_zbl(xi), + InteractionPotential::LENZ_JENSEN => diff_lenz_jensen(xi), + InteractionPotential::TRIDYN => diff_kr_c(xi), + _ => panic!("Input error: Screened Coulomb cannot be used with {}", interaction_potential) + } +} + +/// Screening length of interatomic potentials. +pub fn screening_length(Za: f64, Zb: f64, interaction_potential: InteractionPotential) -> f64 { + match interaction_potential { + //ZBL screening length, Eckstein (4.1.8) + InteractionPotential::ZBL => 0.88534*A0/(Za.powf(0.23) + Zb.powf(0.23)), + //Lindhard/Firsov screening length, Eckstein (4.1.5) + InteractionPotential::MOLIERE | InteractionPotential::KR_C | InteractionPotential::LENZ_JENSEN | InteractionPotential::TRIDYN | InteractionPotential::WW => 0.8853*A0*(Za.sqrt() + Zb.sqrt()).powf(-2./3.), + InteractionPotential::LENNARD_JONES_12_6{..} | InteractionPotential::LENNARD_JONES_65_6{..} => 0.8853*A0*(Za.sqrt() + Zb.sqrt()).powf(-2./3.), + InteractionPotential::MORSE{D, alpha, r0} => alpha, + InteractionPotential::COULOMB{Za: Z1, Zb: Z2} => 0.88534*A0/(Z1.powf(0.23) + Z2.powf(0.23)), + } +} + +/// Coefficients of inverse-polynomial interaction potentials. +pub fn polynomial_coefficients(relative_energy: f64, impact_parameter: f64, interaction_potential: InteractionPotential) -> Vec { + match interaction_potential { + InteractionPotential::LENNARD_JONES_12_6{sigma, epsilon} => { + vec![1., 0., -impact_parameter.powi(2), 0., 0., 0., 4.*epsilon*sigma.powf(6.)/relative_energy, 0., 0., 0., 0., 0., -4.*epsilon*sigma.powf(12.)/relative_energy] + }, + InteractionPotential::LENNARD_JONES_65_6{sigma, epsilon} => { + vec![1., 0., 0., 0., -impact_parameter.powi(2), 0., 0., 0., 0., 0., 0., 0., 4.*epsilon*sigma.powf(6.)/relative_energy, -4.*epsilon*sigma.powf(6.5)/relative_energy] + }, + _ => panic!("Input error: non-polynomial interaction potential used with polynomial root-finder.") + } +} + +/// Inverse-polynomial interaction potentials transformation to remove singularities for the CPR root-finder. +pub fn inverse_transform(x: f64, interaction_potential: InteractionPotential) -> f64 { + match interaction_potential { + InteractionPotential::LENNARD_JONES_12_6{..} => { + x + }, + InteractionPotential::LENNARD_JONES_65_6{..} => { + x*x + }, + _ => panic!("Input error: non-polynomial interaction potential used with polynomial root-finder transformation.") + } +} + +/// Lennard-Jones 12-6 +pub fn lennard_jones(r: f64, sigma: f64, epsilon: f64) -> f64 { + 4.*epsilon*((sigma/r).powf(12.) - (sigma/r).powf(6.)) +} + +/// Lennard-Jones 6.5-6 +pub fn lennard_jones_65_6(r: f64, sigma: f64, epsilon: f64) -> f64 { + 4.*epsilon*((sigma/r).powf(6.5) - (sigma/r).powf(6.)) +} + +/// Morse potential +pub fn morse(r: f64, D: f64, alpha: f64, r0: f64) -> f64 { + D*((-2.*alpha*(r - r0)).exp() - 2.*(-alpha*(r - r0)).exp()) +} + +/// Distance of closest approach function for Morse potential. +pub fn doca_morse(r: f64, impact_parameter: f64, relative_energy: f64, D: f64, alpha: f64, r0: f64) -> f64 { + (r*alpha).powi(2) - (r*alpha).powi(2)*D/relative_energy*((-2.*alpha*(r - r0)).exp() - 2.*(-alpha*(r - r0)).exp()) - (impact_parameter*alpha).powi(2) +} + +/// First derivative w.r.t. `r` of the distance of closest approach function for Morse potential. +pub fn diff_doca_morse(r: f64, impact_parameter: f64, relative_energy: f64, D: f64, alpha: f64, r0: f64) -> f64 { + 2.*alpha.powi(2)*r - 2.*alpha.powi(2)*D*r*(-2.*alpha*(r - r0) - 1.).exp()*(alpha*r*(alpha*(r - r0)).exp() - 2.*(alpha*(r - r0)).exp() - r*alpha + 1.) +} + +/// Distance of closest approach function for LJ 6.5-6 potential. +pub fn doca_lennard_jones_65_6(r: f64, p: f64, relative_energy: f64, sigma: f64, epsilon: f64) -> f64 { + (r/sigma).powf(6.5) - 4.*epsilon/relative_energy*(1. - (r/sigma).powf(0.5)) - (p/sigma).powi(2)*(r/sigma).powf(4.5) +} + +/// Distance of closest approach function for LJ 12-6 potential. +pub fn doca_lennard_jones(r: f64, p: f64, relative_energy: f64, sigma: f64, epsilon: f64) -> f64 { + (r/sigma).powf(12.) - 4.*epsilon/relative_energy*(1. - (r/sigma).powf(6.)) - p.powi(2)*r.powf(10.)/sigma.powf(12.) +} + +/// First derivative w.r.t. `r` of the distance of closest approach function for LJ 12-6 potential. +pub fn diff_doca_lennard_jones(r: f64, p: f64, relative_energy: f64, sigma: f64, epsilon: f64) -> f64 { + 12.*(r/sigma).powf(11.)/sigma + 4.*epsilon/relative_energy*6.*(r/sigma).powf(5.)/sigma - 10.*p.powi(2)*r.powf(9.)/sigma.powf(12.) +} + +/// First derivative w.r.t. `r` of the distance of closest approach function for LJ 6.5-6 potential. +pub fn diff_doca_lennard_jones_65_6(r: f64, p: f64, relative_energy: f64, sigma: f64, epsilon: f64) -> f64 { + 6.5*(r/sigma).powf(5.5)/sigma + 4.*epsilon/relative_energy*0.5*(sigma*r).powf(-0.5) - (p/sigma).powi(2)*4.5*(r/sigma).powf(3.5)/sigma +} + +/// W-W cublic spline potential from Bjorkas et al. +pub fn tungsten_tungsten_cubic_spline(r: f64) -> f64 { + + let x = r/ANGSTROM; + let x1 = 1.10002200044; + let x2 = 2.10004200084; + + if x <= x1 { + + let a = screening_length(74., 74., InteractionPotential::ZBL); + screened_coulomb(r, a, 74., 74., InteractionPotential::ZBL) + + } else if x <= x2 { + + let a = vec![ + 1.389653276380862E4, + -3.596912431628216E4, + 3.739206756369099E4, + -1.933748081656593E4, + 0.495516793802426E4, + -0.050264585985867E4 + ]; + + (a[0] + a[1]*x + a[2]*x.powi(2) + a[3]*x.powi(3) + a[4]*x.powf(4.) + a[5]*x.powf(5.))*EV + + } else { + + let a = vec![ + -0.1036435865158945, + -0.2912948318493851, + -2.096765499656263, + 19.16045452701010, + -41.01619862085917, + 46.05205617244703, + 26.42203930654883, + 15.35211507804088, + 14.12806259323987, + ]; + + let delta = vec![ + 4.268900000000000, + 3.985680000000000, + 3.702460000000000, + 3.419240000000000, + 3.136020000000000, + 2.852800000000000, + 2.741100000000000, + 2.604045000000000, + 2.466990000000000, + ]; + + a.iter().zip(delta).map(|(&a_i, delta_i)| EV*a_i*(delta_i - x).powi(3)*heaviside(delta_i - x)).sum::() + } +} + +/// Distance of closest approach function for the W-W cublic spline potential from Bjorkas et al. +pub fn doca_tungsten_tungsten_cubic_spline(r: f64, p: f64, relative_energy: f64) -> f64 { + + let x = r/ANGSTROM; + let x1 = 1.10002200044; + let x2 = 2.10004200084; + + if x <= x1 { + let a = screening_length(74., 74., InteractionPotential::ZBL); + distance_of_closest_approach_function_singularity_free(r, a, 74., 74., relative_energy, p, InteractionPotential::ZBL) + } else { + (r/ANGSTROM).powi(2) - (r/ANGSTROM).powi(2)*tungsten_tungsten_cubic_spline(r)/relative_energy - p.powi(2)/ANGSTROM.powi(2) + } +} + +fn heaviside(x: f64) -> f64 { + if x >= 0. { + 1. + } else { + 0. + } +} + +fn moliere(xi: f64) -> f64 { + 0.35*(-0.3*xi).exp() + 0.55*(-1.2*xi).exp() + 0.10*(-6.0*xi).exp() +} + +fn kr_c(xi: f64) -> f64 { + 0.190945*(-0.278544*xi).exp() + 0.473674*(-0.637174*xi).exp() + 0.335381*(-1.919249*xi).exp() +} + +fn zbl(xi: f64) -> f64 { + 0.02817*(-0.20162*xi).exp() + 0.28022*(-0.40290*xi).exp() + 0.50986*(-0.94229*xi).exp() + 0.18175*(-3.1998*xi).exp() +} + +fn lenz_jensen(xi: f64) -> f64 { + 0.01018*(-0.206*xi).exp() + 0.24330*(-0.3876*xi).exp() + 0.7466*(-1.038*xi).exp() +} + +fn diff_moliere(xi: f64) -> f64 { + -0.35*0.3*(-0.3*xi).exp() -0.55*1.2*(-1.2*xi).exp() -0.10*6.0*(-6.0*xi).exp() +} + +fn diff_kr_c(xi: f64) -> f64 { + -0.278544*0.190945*(-0.278544*xi).exp() - 0.637174*0.473674*(-0.637174*xi).exp() - 0.335381*1.919249*(-1.919249*xi).exp() +} + +fn diff_zbl(xi: f64) -> f64 { + -0.20162*0.02817*(-0.20162*xi).exp() -0.40290*0.28022*(-0.40290*xi).exp() -0.94229*0.50986*(-0.94229*xi).exp() -3.1998*0.18175*(-3.1998*xi).exp() +} + +fn diff_lenz_jensen(xi: f64) -> f64 { + -0.206*0.01018*(-0.206*xi).exp() -0.3876*0.24330*(-0.3876*xi).exp() -1.038*0.7466*(-1.038*xi).exp() +} + +/// Normalized first screening radius. Used in Oen-Robinson electronic stopping. +pub fn first_screening_radius(interaction_potential: InteractionPotential) -> f64 { + match interaction_potential { + InteractionPotential::MOLIERE => 0.3, + InteractionPotential::KR_C => 0.278544, + InteractionPotential::ZBL => 0.20162, + InteractionPotential::LENZ_JENSEN => 0.206, + InteractionPotential::TRIDYN => 0.278544, + InteractionPotential::LENNARD_JONES_12_6{..} => 1., + InteractionPotential::LENNARD_JONES_65_6{..} => 1., + InteractionPotential::MORSE{..} => 1., + InteractionPotential::WW => 1., + InteractionPotential::COULOMB{..} => 0.3, + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..db34be8 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,184 @@ +#![allow(unused_variables)] +#![allow(non_snake_case)] +#![allow(non_camel_case_types)] + +#[cfg(feature = "cpr_rootfinder_openblas")] +extern crate openblas_src; +#[cfg(feature = "cpr_rootfinder_netlib")] +extern crate netlib_src; +#[cfg(feature = "cpr_rootfinder_intel_mkl")] +extern crate intel_mkl_src; + +use std::{fmt}; +use std::mem::discriminant; + +//Error handling crate +use anyhow::Result; +use anyhow::*; + +//Serializing/Deserializing crate +use serde::*; + +//Array input via hdf5 +#[cfg(feature = "hdf5_input")] +use hdf5::*; + +//I/O +use std::fs::OpenOptions; +use std::io::prelude::*; +use std::io::BufWriter; + +//itertools +use itertools::izip; + +//Math +use std::f64::consts::FRAC_2_SQRT_PI; +use std::f64::consts::PI; +use std::f64::consts::SQRT_2; + +use pyo3::prelude::*; +use pyo3::wrap_pyfunction; + +//Load internal modules +pub mod material; +pub mod particle; +pub mod tests; +pub mod interactions; +pub mod bca; +pub mod geometry; +pub mod input; +pub mod output; +pub mod enums; +pub mod consts; +pub mod structs; +pub mod sphere; + +#[cfg(feature = "parry3d")] +pub mod parry; + +pub use crate::enums::*; +pub use crate::consts::*; +pub use crate::structs::*; +pub use crate::input::{Input2D, Input1D, Input0D, Options, InputFile, GeometryInput}; +pub use crate::output::{OutputUnits}; +pub use crate::geometry::{Geometry, GeometryElement, Mesh0D, Mesh1D, Mesh2D}; +pub use crate::sphere::{Sphere, SphereInput, InputSphere}; + +#[cfg(feature = "parry3d")] +pub use crate::parry::{ParryBall, ParryBallInput, InputParryBall, ParryTriMesh, ParryTriMeshInput, InputParryTriMesh}; + +#[pymodule] +pub fn pybca(py: Python, m: &PyModule) -> PyResult<()> { + m.add_function(wrap_pyfunction!(simple_bca_py, m)?)?; + Ok(()) +} + +#[repr(C)] +pub struct OutputBCA { + len: usize, + pub particles: *mut [f64; 9], +} + +#[no_mangle] +pub extern "C" fn simple_bca_c(x: f64, y: f64, z: f64, ux: f64, uy: f64, uz: f64, E1: f64, Z1: f64, m1: f64, Ec1: f64, Es1: f64, Z2: f64, m2: f64, Ec2: f64, Es2: f64, n2: f64, Eb2: f64) -> OutputBCA { + let mut output = simple_bca(x, y, z, ux, uy, uz, E1, Z1, m1, Ec1, Es1, Z2, m2, Ec2, Es2, n2, Eb2); + let len = output.len(); + let particles = output.as_mut_ptr(); + + std::mem::forget(output); + OutputBCA { + len, + particles + } +} + +#[pyfunction] +pub fn simple_bca_py(x: f64, y: f64, z: f64, ux: f64, uy: f64, uz: f64, E1: f64, Z1: f64, m1: f64, Ec1: f64, Es1: f64, Z2: f64, m2: f64, Ec2: f64, Es2: f64, n2: f64, Eb2: f64) -> Vec<[f64; 9]> { + simple_bca(x, y, z, ux, uy, uz, E1, Z1, m1, Ec1, Es1, Z2, m2, Ec2, Es2, n2, Eb2) +} + +pub fn simple_bca(x: f64, y: f64, z: f64, ux: f64, uy: f64, uz: f64, E1: f64, Z1: f64, m1: f64, Ec1: f64, Es1: f64, Z2: f64, m2: f64, Ec2: f64, Es2: f64, n2: f64, Eb2: f64) -> Vec<[f64; 9]> { + + let options = Options { + name: "test".to_string(), + track_trajectories: false, + track_recoils: true, + track_recoil_trajectories: false, + write_buffer_size: 8000, + weak_collision_order: 3, + suppress_deep_recoils: false, + high_energy_free_flight_paths: false, + electronic_stopping_mode: ElectronicStoppingMode::INTERPOLATED, + mean_free_path_model: MeanFreePathModel::LIQUID, + interaction_potential: vec![vec![InteractionPotential::KR_C]], + scattering_integral: vec![vec![ScatteringIntegral::MENDENHALL_WELLER]], + num_threads: 1, + num_chunks: 1, + use_hdf5: false, + root_finder: vec![vec![Rootfinder::DEFAULTNEWTON]], + track_displacements: false, + track_energy_losses: false, + }; + + let p = particle::Particle { + m: m1*AMU, + Z: Z1, + E: E1*EV, + Ec: Ec1*EV, + Es: Es1*EV, + pos: Vector::new(x, y, z), + dir: Vector::new(ux, uy, uz), + pos_origin: Vector::new(x, y, z), + pos_old: Vector::new(x, y, z), + dir_old: Vector::new(ux, uy, uz), + energy_origin: E1*EV, + asymptotic_deflection: 0.0, + stopped: false, + left: false, + incident: true, + first_step: true, + trajectory: vec![], + energies: vec![], + track_trajectories: false, + number_collision_events: 0, + backreflected: false, + interaction_index : 0 + }; + + let material_parameters = material::MaterialParameters { + energy_unit: "EV".to_string(), + mass_unit: "AMU".to_string(), + Eb: vec![Eb2], + Es: vec![Es2], + Ec: vec![Ec2], + Z: vec![Z2], + m: vec![m2], + interaction_index: vec![0], + surface_binding_model: SurfaceBindingModel::AVERAGE, + bulk_binding_model: BulkBindingModel::INDIVIDUAL, + }; + + let geometry_input = geometry::Mesh0DInput { + length_unit: "ANGSTROM".to_string(), + densities: vec![n2], + electronic_stopping_correction_factor: 1.0 + }; + + let m = material::Material::::new(&material_parameters, &geometry_input); + + let output = bca::single_ion_bca(p, &m, &options); + + output.iter().filter(|particle| (particle.incident) | (particle.left)).map(|particle| + [ + particle.Z, + particle.m/AMU, + particle.E/EV, + particle.pos.x/ANGSTROM, + particle.pos.y/ANGSTROM, + particle.pos.z/ANGSTROM, + particle.dir.x, + particle.dir.y, + particle.dir.z + ] + ).collect() +} diff --git a/src/main.rs b/src/main.rs index c6d576d..e8b6d5a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,205 +1,205 @@ -#![allow(unused_variables)] -#![allow(non_snake_case)] -#![allow(non_camel_case_types)] - -#[cfg(feature = "cpr_rootfinder_openblas")] -extern crate openblas_src; -#[cfg(feature = "cpr_rootfinder_netlib")] -extern crate netlib_src; -#[cfg(feature = "cpr_rootfinder_intel_mkl")] -extern crate intel_mkl_src; - -use std::{env, fmt}; -use std::mem::discriminant; - -//Progress bar crate - works with rayon -use indicatif::{ProgressBar, ProgressStyle}; - -//Error handling crate -use anyhow::Result; -use anyhow::*; - -//Serializing/Deserializing crate -use serde::*; - -//Array input via hdf5 -#[cfg(feature = "hdf5_input")] -use hdf5::*; - -//Parallelization -use rayon::prelude::*; -use rayon::*; - -//I/O -use std::fs::OpenOptions; -use std::io::prelude::*; -use std::io::BufWriter; - -//itertools -use itertools::izip; - -//Math -use std::f64::consts::FRAC_2_SQRT_PI; -use std::f64::consts::PI; -use std::f64::consts::SQRT_2; - -//rng -//use rand::{Rng, thread_rng}; - -//Load internal modules -pub mod material; -pub mod particle; -pub mod tests; -pub mod interactions; -pub mod bca; -pub mod geometry; -pub mod input; -pub mod output; -pub mod enums; -pub mod consts; -pub mod structs; -pub mod sphere; - -#[cfg(feature = "parry3d")] -pub mod parry; - -pub use crate::enums::*; -pub use crate::consts::*; -pub use crate::structs::*; -pub use crate::input::{Input2D, Input1D, Input0D, Options, InputFile, GeometryInput}; -pub use crate::output::{OutputUnits}; -pub use crate::geometry::{Geometry, GeometryElement, Mesh0D, Mesh1D, Mesh2D}; -pub use crate::sphere::{Sphere, SphereInput, InputSphere}; - -#[cfg(feature = "parry3d")] -pub use crate::parry::{ParryBall, ParryBallInput, InputParryBall, ParryTriMesh, ParryTriMeshInput, InputParryTriMesh}; - -fn physics_loop(particle_input_array: Vec, material: material::Material, options: Options, output_units: OutputUnits) { - - println!("Processing {} ions...", particle_input_array.len()); - - let total_count: u64 = particle_input_array.len() as u64; - assert!(total_count/options.num_chunks > 0, "Input error: chunk size == 0 - reduce num_chunks or increase particle count."); - - #[cfg(not(feature = "no_list_output"))] - let mut output_list_streams = output::open_output_lists(&options); - - let mut summary = output::SummaryPerSpecies::new(&options); - - #[cfg(feature = "distributions")] - let mut distributions = output::Distributions::new(&options); - - //Initialize threads with rayon - println!("Initializing with {} threads...", options.num_threads); - if options.num_threads > 1 {let pool = rayon::ThreadPoolBuilder::new().num_threads(options.num_threads).build_global().unwrap();}; - - //Create and configure progress bar - let bar: ProgressBar = ProgressBar::new(total_count); - bar.set_style(ProgressStyle::default_bar() - .template("[{elapsed_precise}][{bar:40.cyan/blue}][{eta_precise}] {percent}%") - .progress_chars("#>-")); - - //Main loop - for (chunk_index, particle_input_chunk) in particle_input_array.chunks((total_count/options.num_chunks) as usize).enumerate() { - - let mut finished_particles: Vec = Vec::new(); - - if options.num_threads > 1 { - // BCA loop is implemented as parallelized extension of a per-chunk initially empty - // finished particle array via map from particle -> finished particles via BCA - finished_particles.par_extend( - particle_input_chunk.into_par_iter() - .map(|particle_input| { - bar.tick(); - bar.inc(1); - bca::single_ion_bca(particle::Particle::from_input(*particle_input, &options), &material, &options) - }).flatten() - ); - } else { - finished_particles.extend( - particle_input_chunk.iter() - .map(|particle_input| { - bar.tick(); - bar.inc(1); - bca::single_ion_bca(particle::Particle::from_input(*particle_input, &options), &material, &options) - }).flatten() - ); - } - bar.finish(); - - // Process this chunk of finished particles for output - for particle in finished_particles { - - summary.update(&particle); - - #[cfg(feature = "distributions")] - distributions.update(&particle, &output_units, &options, total_count as usize); - - #[cfg(not(feature = "no_list_output"))] - output::output_lists(&mut output_list_streams, particle, &options, &output_units); - - } - //Flush all file streams before dropping to ensure all data is written - #[cfg(not(feature = "no_list_output"))] - output::output_list_flush(&mut output_list_streams); - } - - summary.print(&options, &output_units); - - //Write distributions to file - #[cfg(feature = "distributions")] - distributions.print(&options); - - println!("Finished!"); -} - -fn main() { - - let args: Vec = env::args().collect(); - - let (input_file, geometry_type) = match args.len() { - 1 => ("input.toml".to_string(), GeometryType::MESH2D), - 2 => (args[1].clone(), GeometryType::MESH2D), - 3 => (args[2].clone(), match args[1].as_str() { - "0D" => GeometryType::MESH0D, - "1D" => GeometryType::MESH1D, - "2D" => GeometryType::MESH2D, - "SPHERE" => GeometryType::SPHERE, - #[cfg(feature = "parry3d")] - "BALL" => GeometryType::BALL, - #[cfg(feature = "parry3d")] - "TRIMESH" => GeometryType::TRIMESH, - _ => panic!("Unimplemented geometry {}.", args[1].clone()) - }), - _ => panic!("Too many command line arguments. RustBCA accepts 0 (use 'input.toml') 1 () or 2 ( )"), - }; - - match geometry_type { - GeometryType::MESH0D => { - let (particle_input_array, material, options, output_units) = input::input::(input_file); - physics_loop::(particle_input_array, material, options, output_units); - }, - GeometryType::MESH1D => { - let (particle_input_array, material, options, output_units) = input::input::(input_file); - physics_loop::(particle_input_array, material, options, output_units); - }, - GeometryType::MESH2D => { - let (particle_input_array, material, options, output_units) = input::input::(input_file); - physics_loop::(particle_input_array, material, options, output_units); - }, - GeometryType::SPHERE => { - let (particle_input_array, material, options, output_units) = input::input::(input_file); - physics_loop::(particle_input_array, material, options, output_units); - }, - #[cfg(feature = "parry3d")] - GeometryType::BALL => { - let (particle_input_array, material, options, output_units) = input::input::(input_file); - physics_loop::(particle_input_array, material, options, output_units); - } - #[cfg(feature = "parry3d")] - GeometryType::TRIMESH => { - let (particle_input_array, material, options, output_units) = input::input::(input_file); - physics_loop::(particle_input_array, material, options, output_units); - } - } -} +#![allow(unused_variables)] +#![allow(non_snake_case)] +#![allow(non_camel_case_types)] + +#[cfg(feature = "cpr_rootfinder_openblas")] +extern crate openblas_src; +#[cfg(feature = "cpr_rootfinder_netlib")] +extern crate netlib_src; +#[cfg(feature = "cpr_rootfinder_intel_mkl")] +extern crate intel_mkl_src; + +use std::{env, fmt}; +use std::mem::discriminant; + +//Progress bar crate - works with rayon +use indicatif::{ProgressBar, ProgressStyle}; + +//Error handling crate +use anyhow::Result; +use anyhow::*; + +//Serializing/Deserializing crate +use serde::*; + +//Array input via hdf5 +#[cfg(feature = "hdf5_input")] +use hdf5::*; + +//Parallelization +use rayon::prelude::*; +use rayon::*; + +//I/O +use std::fs::OpenOptions; +use std::io::prelude::*; +use std::io::BufWriter; + +//itertools +use itertools::izip; + +//Math +use std::f64::consts::FRAC_2_SQRT_PI; +use std::f64::consts::PI; +use std::f64::consts::SQRT_2; + +//rng +//use rand::{Rng, thread_rng}; + +//Load internal modules +pub mod material; +pub mod particle; +pub mod tests; +pub mod interactions; +pub mod bca; +pub mod geometry; +pub mod input; +pub mod output; +pub mod enums; +pub mod consts; +pub mod structs; +pub mod sphere; + +#[cfg(feature = "parry3d")] +pub mod parry; + +pub use crate::enums::*; +pub use crate::consts::*; +pub use crate::structs::*; +pub use crate::input::{Input2D, Input1D, Input0D, Options, InputFile, GeometryInput}; +pub use crate::output::{OutputUnits}; +pub use crate::geometry::{Geometry, GeometryElement, Mesh0D, Mesh1D, Mesh2D}; +pub use crate::sphere::{Sphere, SphereInput, InputSphere}; + +#[cfg(feature = "parry3d")] +pub use crate::parry::{ParryBall, ParryBallInput, InputParryBall, ParryTriMesh, ParryTriMeshInput, InputParryTriMesh}; + +fn physics_loop(particle_input_array: Vec, material: material::Material, options: Options, output_units: OutputUnits) { + + println!("Processing {} ions...", particle_input_array.len()); + + let total_count: u64 = particle_input_array.len() as u64; + assert!(total_count/options.num_chunks > 0, "Input error: chunk size == 0 - reduce num_chunks or increase particle count."); + + #[cfg(not(feature = "no_list_output"))] + let mut output_list_streams = output::open_output_lists(&options); + + let mut summary = output::SummaryPerSpecies::new(&options); + + #[cfg(feature = "distributions")] + let mut distributions = output::Distributions::new(&options); + + //Initialize threads with rayon + println!("Initializing with {} threads...", options.num_threads); + if options.num_threads > 1 {let pool = rayon::ThreadPoolBuilder::new().num_threads(options.num_threads).build_global().unwrap();}; + + //Create and configure progress bar + let bar: ProgressBar = ProgressBar::new(total_count); + bar.set_style(ProgressStyle::default_bar() + .template("[{elapsed_precise}][{bar:40.cyan/blue}][{eta_precise}] {percent}%") + .progress_chars("#>-")); + + //Main loop + for (chunk_index, particle_input_chunk) in particle_input_array.chunks((total_count/options.num_chunks) as usize).enumerate() { + + let mut finished_particles: Vec = Vec::new(); + + if options.num_threads > 1 { + // BCA loop is implemented as parallelized extension of a per-chunk initially empty + // finished particle array via map from particle -> finished particles via BCA + finished_particles.par_extend( + particle_input_chunk.into_par_iter() + .map(|particle_input| { + bar.tick(); + bar.inc(1); + bca::single_ion_bca(particle::Particle::from_input(*particle_input, &options), &material, &options) + }).flatten() + ); + } else { + finished_particles.extend( + particle_input_chunk.iter() + .map(|particle_input| { + bar.tick(); + bar.inc(1); + bca::single_ion_bca(particle::Particle::from_input(*particle_input, &options), &material, &options) + }).flatten() + ); + } + bar.finish(); + + // Process this chunk of finished particles for output + for particle in finished_particles { + + summary.update(&particle); + + #[cfg(feature = "distributions")] + distributions.update(&particle, &output_units, &options, total_count as usize); + + #[cfg(not(feature = "no_list_output"))] + output::output_lists(&mut output_list_streams, particle, &options, &output_units); + + } + //Flush all file streams before dropping to ensure all data is written + #[cfg(not(feature = "no_list_output"))] + output::output_list_flush(&mut output_list_streams); + } + + summary.print(&options, &output_units); + + //Write distributions to file + #[cfg(feature = "distributions")] + distributions.print(&options); + + println!("Finished!"); +} + +fn main() { + + let args: Vec = env::args().collect(); + + let (input_file, geometry_type) = match args.len() { + 1 => ("input.toml".to_string(), GeometryType::MESH2D), + 2 => (args[1].clone(), GeometryType::MESH2D), + 3 => (args[2].clone(), match args[1].as_str() { + "0D" => GeometryType::MESH0D, + "1D" => GeometryType::MESH1D, + "2D" => GeometryType::MESH2D, + "SPHERE" => GeometryType::SPHERE, + #[cfg(feature = "parry3d")] + "BALL" => GeometryType::BALL, + #[cfg(feature = "parry3d")] + "TRIMESH" => GeometryType::TRIMESH, + _ => panic!("Unimplemented geometry {}.", args[1].clone()) + }), + _ => panic!("Too many command line arguments. RustBCA accepts 0 (use 'input.toml') 1 () or 2 ( )"), + }; + + match geometry_type { + GeometryType::MESH0D => { + let (particle_input_array, material, options, output_units) = input::input::(input_file); + physics_loop::(particle_input_array, material, options, output_units); + }, + GeometryType::MESH1D => { + let (particle_input_array, material, options, output_units) = input::input::(input_file); + physics_loop::(particle_input_array, material, options, output_units); + }, + GeometryType::MESH2D => { + let (particle_input_array, material, options, output_units) = input::input::(input_file); + physics_loop::(particle_input_array, material, options, output_units); + }, + GeometryType::SPHERE => { + let (particle_input_array, material, options, output_units) = input::input::(input_file); + physics_loop::(particle_input_array, material, options, output_units); + }, + #[cfg(feature = "parry3d")] + GeometryType::BALL => { + let (particle_input_array, material, options, output_units) = input::input::(input_file); + physics_loop::(particle_input_array, material, options, output_units); + } + #[cfg(feature = "parry3d")] + GeometryType::TRIMESH => { + let (particle_input_array, material, options, output_units) = input::input::(input_file); + physics_loop::(particle_input_array, material, options, output_units); + } + } +} diff --git a/src/material.rs b/src/material.rs index 4941817..ae12ee9 100644 --- a/src/material.rs +++ b/src/material.rs @@ -1,395 +1,395 @@ -use super::*; - -/// Holds material input parameters from [material_params]. -#[derive(Deserialize, Clone)] -pub struct MaterialParameters { - pub energy_unit: String, - pub mass_unit: String, - pub Eb: Vec, - pub Es: Vec, - pub Ec: Vec, - pub Z: Vec, - pub m: Vec, - pub interaction_index: Vec, - pub surface_binding_model: SurfaceBindingModel, - pub bulk_binding_model: BulkBindingModel -} - -/// Material in rustbca. Includes the material properties and the mesh that defines the material geometry. -pub struct Material { - pub m: Vec, - pub Z: Vec, - pub Eb: Vec, - pub Es: Vec, - pub Ec: Vec, - pub interaction_index: Vec, - pub geometry: Box, - pub surface_binding_model: SurfaceBindingModel, - pub bulk_binding_model: BulkBindingModel -} -impl Material { - - pub fn new(material_parameters: &MaterialParameters, geometry_input: &<::InputFileFormat as GeometryInput>::GeometryInput) -> Material { - - let energy_unit: f64 = match material_parameters.energy_unit.as_str() { - "EV" => EV, - "J" => 1., - "KEV" => EV*1E3, - "MEV" => EV*1E6, - _ => material_parameters.energy_unit.parse() - .expect(format!( - "Input errror: could nor parse energy unit {}. Use a valid float or one of EV, J, KEV, MEV", &material_parameters.energy_unit.as_str() - ).as_str()), - }; - - let mass_unit: f64 = match material_parameters.mass_unit.as_str() { - "AMU" => AMU, - "KG" => 1., - _ => material_parameters.mass_unit.parse() - .expect(format!( - "Input errror: could nor parse mass unit {}. Use a valid float or one of AMU, KG", &material_parameters.mass_unit.as_str() - ).as_str()), - }; - - Material { - m: material_parameters.m.iter().map(|&i| i*mass_unit).collect(), - Z: material_parameters.Z.clone(), - Eb: material_parameters.Eb.iter().map(|&i| i*energy_unit).collect(), - Es: material_parameters.Es.iter().map(|&i| i*energy_unit).collect(), - Ec: material_parameters.Ec.iter().map(|&i| i*energy_unit).collect(), - interaction_index: material_parameters.interaction_index.clone(), - surface_binding_model: material_parameters.surface_binding_model, - bulk_binding_model: material_parameters.bulk_binding_model, - geometry: Box::new(T::new(geometry_input)), - } - } - - /// Gets concentrations of triangle that contains or is nearest to (x, y) - pub fn get_concentrations(&self, x: f64, y: f64, z: f64) -> Vec { - - let total_number_density: f64 = self.geometry.get_total_density(x, y, z); - - return self.geometry.get_densities(x, y, z).iter().map(|&i| i / total_number_density).collect(); - } - - /// Gets cumulative concentrations of triangle that contains or is nearest to (x, y). - /// Used for weighting the random choice of one of the constituent species. - pub fn get_cumulative_concentrations(&self, x: f64, y: f64, z: f64) -> Vec { - let mut sum: f64 = 0.; - let concentrations = self.geometry.get_concentrations(x, y, z); - let mut cumulative_concentrations = Vec::with_capacity(concentrations.len()); - for concentration in concentrations { - sum += concentration; - cumulative_concentrations.push(sum); - } - return cumulative_concentrations; - } - - /// Determines whether (x, y) is inside the material. - pub fn inside(&self, x: f64, y: f64, z: f64) -> bool { - return self.geometry.inside(x, y, z); - } - - /// Gets electronic stopping correction factor for LS and OR - pub fn electronic_stopping_correction_factor(&self, x: f64, y: f64, z: f64) -> f64 { - return self.geometry.get_ck(x, y, z); - } - - /// Determines the local mean free path from the formula sum(n(x, y))^(-1/3) - pub fn mfp(&self, x: f64, y: f64, z: f64) -> f64 { - return self.total_number_density(x, y, z).powf(-1./3.); - } - - /// Total number density of triangle that contains or is nearest to (x, y). - pub fn total_number_density(&self, x: f64, y: f64, z: f64) -> f64 { - self.geometry.get_densities(x, y, z).iter().sum::() - } - - /// Lists number density of each species of triangle that contains or is nearest to (x, y). - pub fn number_densities(&self, x: f64, y: f64, z: f64) -> &Vec { - return &self.geometry.get_densities(x, y, z); - } - - /// Determines whether a point (x, y) is inside the energy barrier of the material. - pub fn inside_energy_barrier(&self, x: f64, y: f64, z: f64) -> bool { - self.geometry.inside_energy_barrier(x, y, z) - } - - /// Determines whether a point (x, y) is inside the simulation boundary. - pub fn inside_simulation_boundary(&self, x:f64, y: f64, z: f64) -> bool { - return self.geometry.inside_simulation_boundary(x, y, z); - } - - /// Finds the closest point on the material boundary to the point (x, y). - pub fn closest_point(&self, x: f64, y: f64, z: f64) -> (f64, f64, f64) { - self.geometry.closest_point(x, y, z) - } - - /// Finds the average, concentration-weighted atomic number, Z_effective, of the triangle that contains or is nearest to (x, y). - pub fn average_Z(&self, x: f64, y: f64, z: f64) -> f64 { - let concentrations = self.geometry.get_concentrations(x, y, z); - return self.Z.iter().zip(concentrations).map(|(charge, concentration)| charge*concentration).collect::>().iter().sum(); - } - - /// Finds the average, concentration-weighted atomic mass, m_effective, of the triangle that contains or is nearest to (x, y). - pub fn average_mass(&self, x: f64, y: f64, z: f64) -> f64 { - let concentrations = self.geometry.get_concentrations(x, y, z); - return self.m.iter().zip(concentrations).map(|(mass, concentration)| mass*concentration).collect::>().iter().sum(); - } - - /// Finds the average, concentration-weighted bulk binding energy of the triangle that contains or is nearest to (x, y). - pub fn average_bulk_binding_energy(&self, x: f64, y: f64, z: f64) -> f64 { - //returns average bulk binding energy - let concentrations = self.geometry.get_concentrations(x, y, z); - return self.Eb.iter().zip(concentrations).map(|(bulk_binding_energy, concentration)| bulk_binding_energy*concentration).collect::>().iter().sum(); - } - - pub fn actual_bulk_binding_energy(&self, species_index: usize, x: f64, y: f64, z: f64) -> f64 { - - match self.bulk_binding_model { - BulkBindingModel::INDIVIDUAL => self.Eb[species_index], - - BulkBindingModel::AVERAGE => { - if self.Eb[species_index] == 0. { - 0. - } else { - self.average_bulk_binding_energy(x, y, z) - } - } - } - } - - /// Finds the concentration-dependent surface binding energy of the triangle that contains or is nearest to (x, y). - /// The surface binding energy is calculated using one of three methods: - /// 1. INDIVIDUAL: the surface binding energies are set individually for each species, as Es. - /// 2. TARGET: the surface binding energy is calculated as the local concentration-weighted average of the target surface binding energies, unless the particle has Es = 0, in which case it is 0. - /// 3. AVERAGE: the surface binding energy is the average of the particle surface binding energy and the TARGET surface binding energy, unless either is 0 in which case it is 0. - pub fn actual_surface_binding_energy(&self, particle: &particle::Particle, x: f64, y: f64, z: f64) -> f64 { - let concentrations = self.geometry.get_concentrations(x, y, z); - - match self.surface_binding_model { - SurfaceBindingModel::TARGET => { - if particle.Es == 0. { - 0. - } else { - self.Es.iter().zip(concentrations).map(|(surface_binding_energy, concentration)| surface_binding_energy*concentration).collect::>().iter().sum() - } - }, - SurfaceBindingModel::INDIVIDUAL => particle.Es, - SurfaceBindingModel::AVERAGE => { - if (particle.Es == 0.) | (self.Es.iter().sum::() == 0.) { - 0. - } else { - 0.5*(particle.Es + self.Es.iter().zip(concentrations).map(|(surface_binding_energy, concentration)| surface_binding_energy*concentration).collect::>().iter().sum::()) - } - }, - SurfaceBindingModel::ISOTROPIC{calculation} | SurfaceBindingModel::PLANAR{calculation} => { - match calculation { - SurfaceBindingCalculation::INDIVIDUAL => particle.Es, - - SurfaceBindingCalculation::TARGET => { - if particle.Es == 0. { - 0. - } else { - self.Es.iter().zip(concentrations).map(|(surface_binding_energy, concentration)| surface_binding_energy*concentration).collect::>().iter().sum() - } - }, - - SurfaceBindingCalculation::AVERAGE => { - if (particle.Es == 0.) | (self.Es.iter().sum::() == 0.) { - 0. - } else { - 0.5*(particle.Es + self.Es.iter().zip(concentrations).map(|(surface_binding_energy, concentration)| surface_binding_energy*concentration).collect::>().iter().sum::()) - } - }, - } - } - } - } - - ///The minimum cutoff energy of all species that make up the material. - pub fn minimum_cutoff_energy(&self) -> f64 { - let mut min_Ec = self.Ec.iter().sum::(); - for Ec in self.Ec.iter() { - if min_Ec > *Ec { - min_Ec = *Ec; - } - } - return min_Ec; - } - - ///Choose the parameters of a target atom as a concentration-weighted random draw from the species in the triangle that contains or is nearest to (x, y). - pub fn choose(&self, x: f64, y: f64, z: f64) -> (usize, f64, f64, f64, f64, usize) { - let random_number: f64 = rand::random::(); - let cumulative_concentrations = self.get_cumulative_concentrations(x, y, z); - - for (component_index, cumulative_concentration) in cumulative_concentrations.iter().enumerate() { - if random_number < *cumulative_concentration { - return (component_index, self.Z[component_index], self.m[component_index], self.Ec[component_index], self.Es[component_index], self.interaction_index[component_index]); - } - } - panic!("Input error: method choose() operation failed to choose a valid species. Check densities."); - } - - /// Calculate the electronic stopping cross-sections using the mode set in [options]. - pub fn electronic_stopping_cross_sections(&self, particle_1: &super::particle::Particle, electronic_stopping_mode: ElectronicStoppingMode) -> Vec { - - let E = particle_1.E; - let Ma = particle_1.m; - let Za = particle_1.Z; - - let mut stopping_powers = Vec::with_capacity(self.Z.len()); - - //Bragg's rule: total stopping power is sum of stopping powers of individual atoms - //Therefore, compute each stopping power separately, and add them up - - let x = particle_1.pos.x; - let y = particle_1.pos.y; - let z = particle_1.pos.y; - let ck = self.electronic_stopping_correction_factor(x, y, z); - - for (n, Zb) in self.number_densities(x, y, z).iter().zip(&self.Z) { - - let beta = (1. - (1. + E/Ma/C.powi(2)).powf(-2.)).sqrt(); - let v = beta*C; - - // This term is an empirical fit to the mean ionization potential - let I0 = match *Zb < 13. { - true => 12. + 7./Zb, - false => 9.76 + 58.5*Zb.powf(-1.19), - }; - let I = Zb*I0*Q; - - //See Biersack and Haggmark - this looks like an empirical shell correction - let B = match *Zb < 3. { - true => 100.*Za/Zb, - false => 5. - }; - - //Bethe stopping modified by Biersack and Varelas - let prefactor = BETHE_BLOCH_PREFACTOR*Zb*Za*Za/beta/beta; - let eb = 2.*ME*v*v/I; - let S_high = prefactor*(eb + 1. + B/eb).ln(); - - //Lindhard-Scharff electronic stopping - let S_low = LINDHARD_SCHARFF_PREFACTOR*(Za.powf(7./6.)*Zb)/(Za.powf(2./3.) + Zb.powf(2./3.)).powf(3./2.)*(E/Q/Ma*AMU).sqrt(); - - let stopping_power = match electronic_stopping_mode { - //Biersack-Varelas Interpolation - ElectronicStoppingMode::INTERPOLATED => 1./(1./S_high + 1./S_low*ck), - //Oen-Robinson - ElectronicStoppingMode::LOW_ENERGY_LOCAL => S_low*ck, - //Lindhard-Scharff - ElectronicStoppingMode::LOW_ENERGY_NONLOCAL => S_low*ck, - //Lindhard-Scharff and Oen-Robinson, using Lindhard Equipartition - ElectronicStoppingMode::LOW_ENERGY_EQUIPARTITION => S_low*ck, - }; - - stopping_powers.push(stopping_power); - } - return stopping_powers; - } -} - -/// Calculate the effects of the planar surface binding potential of a material on a particle. -/// These effects include surface reflection and refraction of particles with non-zero surface binding energies. -pub fn surface_binding_energy(particle_1: &mut particle::Particle, material: &material::Material) { - let x = particle_1.pos.x; - let y = particle_1.pos.y; - let z = particle_1.pos.z; - let x_old = particle_1.pos_old.x; - let y_old = particle_1.pos_old.y; - let z_old = particle_1.pos_old.z; - let cosx = particle_1.dir.x; - let cosy = particle_1.dir.y; - let cosz = particle_1.dir.z; - let E = particle_1.E; - - //Actual surface binding energies - let Es = material.actual_surface_binding_energy(particle_1, x_old, y_old, z_old); - //println!("Actual Es: {}", Es); - let Ec = particle_1.Ec; - - let inside_now = material.inside_energy_barrier(x, y, z); - let inside_old = material.inside_energy_barrier(x_old, y_old, z_old); - - let leaving = !inside_now & inside_old; - let entering = inside_now & !inside_old; - - if entering { - if particle_1.backreflected { - particle_1.backreflected = false; - } else { - let (x2, y2, z2) = material.closest_point(x, y, z); - let dx = x2 - x; - let dy = y2 - y; - let dz = z2 - z; - let mag = (dx*dx + dy*dy + dz*dz).sqrt(); - let costheta = dx*cosx/mag + dy*cosy/mag + dz*cosz/mag; - - match material.surface_binding_model { - SurfaceBindingModel::ISOTROPIC{..} => particle_1.E += Es, - _ => particle::surface_refraction(particle_1, Vector::new(dx/mag, dy/mag, dz/mag), Es) - } - } - } - - if leaving { - - let (x2, y2, z2) = material.closest_point(x, y, z); - let dx = x2 - x; - let dy = y2 - y; - let dz = z2 - z; - let mag = (dx*dx + dy*dy + dz*dz).sqrt(); - let costheta = dx*cosx/mag + dy*cosy/mag + dz*cosz/mag; - - let leaving_energy = match material.surface_binding_model { - SurfaceBindingModel::ISOTROPIC{..} => E, - _ => E*costheta*costheta, - }; - - if costheta < 0. { - if leaving_energy > Es { - - match material.surface_binding_model { - SurfaceBindingModel::PLANAR{..} => { - particle::surface_refraction(particle_1, Vector::new(-dx/mag, -dy/mag, -dz/mag), -Es); - } - _ => particle_1.E += -Es, - } - - } else { - - //Specular reflection at local surface normal - particle_1.dir.x = -2.*(costheta)*dx/mag + cosx; - particle_1.dir.y = -2.*(costheta)*dy/mag + cosy; - particle_1.dir.z = -2.*(costheta)*dz/mag + cosz; - - particle_1.backreflected = true; - } - } - } -} - -/// Apply the boundary conditions of a material on a particle, including stopping, leaving, and reflection/refraction by/through the surface binding potential. -pub fn boundary_condition_planar(particle_1: &mut particle::Particle, material: &material::Material) { - let x = particle_1.pos.x; - let y = particle_1.pos.y; - let z = particle_1.pos.z; - let E = particle_1.E; - let Ec = particle_1.Ec; - - if !material.inside_simulation_boundary(x, y, z) { - particle_1.left = true; - particle_1.add_trajectory(); - } - - if (E < Ec) & !particle_1.left & material.inside_energy_barrier(x, y, z) { - particle_1.stopped = true; - particle_1.add_trajectory(); - } - - if !particle_1.stopped & !particle_1.left { - surface_binding_energy(particle_1, material); - } -} +use super::*; + +/// Holds material input parameters from [material_params]. +#[derive(Deserialize, Clone)] +pub struct MaterialParameters { + pub energy_unit: String, + pub mass_unit: String, + pub Eb: Vec, + pub Es: Vec, + pub Ec: Vec, + pub Z: Vec, + pub m: Vec, + pub interaction_index: Vec, + pub surface_binding_model: SurfaceBindingModel, + pub bulk_binding_model: BulkBindingModel +} + +/// Material in rustbca. Includes the material properties and the mesh that defines the material geometry. +pub struct Material { + pub m: Vec, + pub Z: Vec, + pub Eb: Vec, + pub Es: Vec, + pub Ec: Vec, + pub interaction_index: Vec, + pub geometry: Box, + pub surface_binding_model: SurfaceBindingModel, + pub bulk_binding_model: BulkBindingModel +} +impl Material { + + pub fn new(material_parameters: &MaterialParameters, geometry_input: &<::InputFileFormat as GeometryInput>::GeometryInput) -> Material { + + let energy_unit: f64 = match material_parameters.energy_unit.as_str() { + "EV" => EV, + "J" => 1., + "KEV" => EV*1E3, + "MEV" => EV*1E6, + _ => material_parameters.energy_unit.parse() + .expect(format!( + "Input errror: could nor parse energy unit {}. Use a valid float or one of EV, J, KEV, MEV", &material_parameters.energy_unit.as_str() + ).as_str()), + }; + + let mass_unit: f64 = match material_parameters.mass_unit.as_str() { + "AMU" => AMU, + "KG" => 1., + _ => material_parameters.mass_unit.parse() + .expect(format!( + "Input errror: could nor parse mass unit {}. Use a valid float or one of AMU, KG", &material_parameters.mass_unit.as_str() + ).as_str()), + }; + + Material { + m: material_parameters.m.iter().map(|&i| i*mass_unit).collect(), + Z: material_parameters.Z.clone(), + Eb: material_parameters.Eb.iter().map(|&i| i*energy_unit).collect(), + Es: material_parameters.Es.iter().map(|&i| i*energy_unit).collect(), + Ec: material_parameters.Ec.iter().map(|&i| i*energy_unit).collect(), + interaction_index: material_parameters.interaction_index.clone(), + surface_binding_model: material_parameters.surface_binding_model, + bulk_binding_model: material_parameters.bulk_binding_model, + geometry: Box::new(T::new(geometry_input)), + } + } + + /// Gets concentrations of triangle that contains or is nearest to (x, y) + pub fn get_concentrations(&self, x: f64, y: f64, z: f64) -> Vec { + + let total_number_density: f64 = self.geometry.get_total_density(x, y, z); + + return self.geometry.get_densities(x, y, z).iter().map(|&i| i / total_number_density).collect(); + } + + /// Gets cumulative concentrations of triangle that contains or is nearest to (x, y). + /// Used for weighting the random choice of one of the constituent species. + pub fn get_cumulative_concentrations(&self, x: f64, y: f64, z: f64) -> Vec { + let mut sum: f64 = 0.; + let concentrations = self.geometry.get_concentrations(x, y, z); + let mut cumulative_concentrations = Vec::with_capacity(concentrations.len()); + for concentration in concentrations { + sum += concentration; + cumulative_concentrations.push(sum); + } + return cumulative_concentrations; + } + + /// Determines whether (x, y) is inside the material. + pub fn inside(&self, x: f64, y: f64, z: f64) -> bool { + return self.geometry.inside(x, y, z); + } + + /// Gets electronic stopping correction factor for LS and OR + pub fn electronic_stopping_correction_factor(&self, x: f64, y: f64, z: f64) -> f64 { + return self.geometry.get_ck(x, y, z); + } + + /// Determines the local mean free path from the formula sum(n(x, y))^(-1/3) + pub fn mfp(&self, x: f64, y: f64, z: f64) -> f64 { + return self.total_number_density(x, y, z).powf(-1./3.); + } + + /// Total number density of triangle that contains or is nearest to (x, y). + pub fn total_number_density(&self, x: f64, y: f64, z: f64) -> f64 { + self.geometry.get_densities(x, y, z).iter().sum::() + } + + /// Lists number density of each species of triangle that contains or is nearest to (x, y). + pub fn number_densities(&self, x: f64, y: f64, z: f64) -> &Vec { + return &self.geometry.get_densities(x, y, z); + } + + /// Determines whether a point (x, y) is inside the energy barrier of the material. + pub fn inside_energy_barrier(&self, x: f64, y: f64, z: f64) -> bool { + self.geometry.inside_energy_barrier(x, y, z) + } + + /// Determines whether a point (x, y) is inside the simulation boundary. + pub fn inside_simulation_boundary(&self, x:f64, y: f64, z: f64) -> bool { + return self.geometry.inside_simulation_boundary(x, y, z); + } + + /// Finds the closest point on the material boundary to the point (x, y). + pub fn closest_point(&self, x: f64, y: f64, z: f64) -> (f64, f64, f64) { + self.geometry.closest_point(x, y, z) + } + + /// Finds the average, concentration-weighted atomic number, Z_effective, of the triangle that contains or is nearest to (x, y). + pub fn average_Z(&self, x: f64, y: f64, z: f64) -> f64 { + let concentrations = self.geometry.get_concentrations(x, y, z); + return self.Z.iter().zip(concentrations).map(|(charge, concentration)| charge*concentration).collect::>().iter().sum(); + } + + /// Finds the average, concentration-weighted atomic mass, m_effective, of the triangle that contains or is nearest to (x, y). + pub fn average_mass(&self, x: f64, y: f64, z: f64) -> f64 { + let concentrations = self.geometry.get_concentrations(x, y, z); + return self.m.iter().zip(concentrations).map(|(mass, concentration)| mass*concentration).collect::>().iter().sum(); + } + + /// Finds the average, concentration-weighted bulk binding energy of the triangle that contains or is nearest to (x, y). + pub fn average_bulk_binding_energy(&self, x: f64, y: f64, z: f64) -> f64 { + //returns average bulk binding energy + let concentrations = self.geometry.get_concentrations(x, y, z); + return self.Eb.iter().zip(concentrations).map(|(bulk_binding_energy, concentration)| bulk_binding_energy*concentration).collect::>().iter().sum(); + } + + pub fn actual_bulk_binding_energy(&self, species_index: usize, x: f64, y: f64, z: f64) -> f64 { + + match self.bulk_binding_model { + BulkBindingModel::INDIVIDUAL => self.Eb[species_index], + + BulkBindingModel::AVERAGE => { + if self.Eb[species_index] == 0. { + 0. + } else { + self.average_bulk_binding_energy(x, y, z) + } + } + } + } + + /// Finds the concentration-dependent surface binding energy of the triangle that contains or is nearest to (x, y). + /// The surface binding energy is calculated using one of three methods: + /// 1. INDIVIDUAL: the surface binding energies are set individually for each species, as Es. + /// 2. TARGET: the surface binding energy is calculated as the local concentration-weighted average of the target surface binding energies, unless the particle has Es = 0, in which case it is 0. + /// 3. AVERAGE: the surface binding energy is the average of the particle surface binding energy and the TARGET surface binding energy, unless either is 0 in which case it is 0. + pub fn actual_surface_binding_energy(&self, particle: &particle::Particle, x: f64, y: f64, z: f64) -> f64 { + let concentrations = self.geometry.get_concentrations(x, y, z); + + match self.surface_binding_model { + SurfaceBindingModel::TARGET => { + if particle.Es == 0. { + 0. + } else { + self.Es.iter().zip(concentrations).map(|(surface_binding_energy, concentration)| surface_binding_energy*concentration).collect::>().iter().sum() + } + }, + SurfaceBindingModel::INDIVIDUAL => particle.Es, + SurfaceBindingModel::AVERAGE => { + if (particle.Es == 0.) | (self.Es.iter().sum::() == 0.) { + 0. + } else { + 0.5*(particle.Es + self.Es.iter().zip(concentrations).map(|(surface_binding_energy, concentration)| surface_binding_energy*concentration).collect::>().iter().sum::()) + } + }, + SurfaceBindingModel::ISOTROPIC{calculation} | SurfaceBindingModel::PLANAR{calculation} => { + match calculation { + SurfaceBindingCalculation::INDIVIDUAL => particle.Es, + + SurfaceBindingCalculation::TARGET => { + if particle.Es == 0. { + 0. + } else { + self.Es.iter().zip(concentrations).map(|(surface_binding_energy, concentration)| surface_binding_energy*concentration).collect::>().iter().sum() + } + }, + + SurfaceBindingCalculation::AVERAGE => { + if (particle.Es == 0.) | (self.Es.iter().sum::() == 0.) { + 0. + } else { + 0.5*(particle.Es + self.Es.iter().zip(concentrations).map(|(surface_binding_energy, concentration)| surface_binding_energy*concentration).collect::>().iter().sum::()) + } + }, + } + } + } + } + + ///The minimum cutoff energy of all species that make up the material. + pub fn minimum_cutoff_energy(&self) -> f64 { + let mut min_Ec = self.Ec.iter().sum::(); + for Ec in self.Ec.iter() { + if min_Ec > *Ec { + min_Ec = *Ec; + } + } + return min_Ec; + } + + ///Choose the parameters of a target atom as a concentration-weighted random draw from the species in the triangle that contains or is nearest to (x, y). + pub fn choose(&self, x: f64, y: f64, z: f64) -> (usize, f64, f64, f64, f64, usize) { + let random_number: f64 = rand::random::(); + let cumulative_concentrations = self.get_cumulative_concentrations(x, y, z); + + for (component_index, cumulative_concentration) in cumulative_concentrations.iter().enumerate() { + if random_number < *cumulative_concentration { + return (component_index, self.Z[component_index], self.m[component_index], self.Ec[component_index], self.Es[component_index], self.interaction_index[component_index]); + } + } + panic!("Input error: method choose() operation failed to choose a valid species. Check densities."); + } + + /// Calculate the electronic stopping cross-sections using the mode set in [options]. + pub fn electronic_stopping_cross_sections(&self, particle_1: &super::particle::Particle, electronic_stopping_mode: ElectronicStoppingMode) -> Vec { + + let E = particle_1.E; + let Ma = particle_1.m; + let Za = particle_1.Z; + + let mut stopping_powers = Vec::with_capacity(self.Z.len()); + + //Bragg's rule: total stopping power is sum of stopping powers of individual atoms + //Therefore, compute each stopping power separately, and add them up + + let x = particle_1.pos.x; + let y = particle_1.pos.y; + let z = particle_1.pos.y; + let ck = self.electronic_stopping_correction_factor(x, y, z); + + for (n, Zb) in self.number_densities(x, y, z).iter().zip(&self.Z) { + + let beta = (1. - (1. + E/Ma/C.powi(2)).powf(-2.)).sqrt(); + let v = beta*C; + + // This term is an empirical fit to the mean ionization potential + let I0 = match *Zb < 13. { + true => 12. + 7./Zb, + false => 9.76 + 58.5*Zb.powf(-1.19), + }; + let I = Zb*I0*Q; + + //See Biersack and Haggmark - this looks like an empirical shell correction + let B = match *Zb < 3. { + true => 100.*Za/Zb, + false => 5. + }; + + //Bethe stopping modified by Biersack and Varelas + let prefactor = BETHE_BLOCH_PREFACTOR*Zb*Za*Za/beta/beta; + let eb = 2.*ME*v*v/I; + let S_high = prefactor*(eb + 1. + B/eb).ln(); + + //Lindhard-Scharff electronic stopping + let S_low = LINDHARD_SCHARFF_PREFACTOR*(Za.powf(7./6.)*Zb)/(Za.powf(2./3.) + Zb.powf(2./3.)).powf(3./2.)*(E/Q/Ma*AMU).sqrt(); + + let stopping_power = match electronic_stopping_mode { + //Biersack-Varelas Interpolation + ElectronicStoppingMode::INTERPOLATED => 1./(1./S_high + 1./S_low*ck), + //Oen-Robinson + ElectronicStoppingMode::LOW_ENERGY_LOCAL => S_low*ck, + //Lindhard-Scharff + ElectronicStoppingMode::LOW_ENERGY_NONLOCAL => S_low*ck, + //Lindhard-Scharff and Oen-Robinson, using Lindhard Equipartition + ElectronicStoppingMode::LOW_ENERGY_EQUIPARTITION => S_low*ck, + }; + + stopping_powers.push(stopping_power); + } + return stopping_powers; + } +} + +/// Calculate the effects of the planar surface binding potential of a material on a particle. +/// These effects include surface reflection and refraction of particles with non-zero surface binding energies. +pub fn surface_binding_energy(particle_1: &mut particle::Particle, material: &material::Material) { + let x = particle_1.pos.x; + let y = particle_1.pos.y; + let z = particle_1.pos.z; + let x_old = particle_1.pos_old.x; + let y_old = particle_1.pos_old.y; + let z_old = particle_1.pos_old.z; + let cosx = particle_1.dir.x; + let cosy = particle_1.dir.y; + let cosz = particle_1.dir.z; + let E = particle_1.E; + + //Actual surface binding energies + let Es = material.actual_surface_binding_energy(particle_1, x_old, y_old, z_old); + //println!("Actual Es: {}", Es); + let Ec = particle_1.Ec; + + let inside_now = material.inside_energy_barrier(x, y, z); + let inside_old = material.inside_energy_barrier(x_old, y_old, z_old); + + let leaving = !inside_now & inside_old; + let entering = inside_now & !inside_old; + + if entering { + if particle_1.backreflected { + particle_1.backreflected = false; + } else { + let (x2, y2, z2) = material.closest_point(x, y, z); + let dx = x2 - x; + let dy = y2 - y; + let dz = z2 - z; + let mag = (dx*dx + dy*dy + dz*dz).sqrt(); + let costheta = dx*cosx/mag + dy*cosy/mag + dz*cosz/mag; + + match material.surface_binding_model { + SurfaceBindingModel::ISOTROPIC{..} => particle_1.E += Es, + _ => particle::surface_refraction(particle_1, Vector::new(dx/mag, dy/mag, dz/mag), Es) + } + } + } + + if leaving { + + let (x2, y2, z2) = material.closest_point(x, y, z); + let dx = x2 - x; + let dy = y2 - y; + let dz = z2 - z; + let mag = (dx*dx + dy*dy + dz*dz).sqrt(); + let costheta = dx*cosx/mag + dy*cosy/mag + dz*cosz/mag; + + let leaving_energy = match material.surface_binding_model { + SurfaceBindingModel::ISOTROPIC{..} => E, + _ => E*costheta*costheta, + }; + + if costheta < 0. { + if leaving_energy > Es { + + match material.surface_binding_model { + SurfaceBindingModel::PLANAR{..} => { + particle::surface_refraction(particle_1, Vector::new(-dx/mag, -dy/mag, -dz/mag), -Es); + } + _ => particle_1.E += -Es, + } + + } else { + + //Specular reflection at local surface normal + particle_1.dir.x = -2.*(costheta)*dx/mag + cosx; + particle_1.dir.y = -2.*(costheta)*dy/mag + cosy; + particle_1.dir.z = -2.*(costheta)*dz/mag + cosz; + + particle_1.backreflected = true; + } + } + } +} + +/// Apply the boundary conditions of a material on a particle, including stopping, leaving, and reflection/refraction by/through the surface binding potential. +pub fn boundary_condition_planar(particle_1: &mut particle::Particle, material: &material::Material) { + let x = particle_1.pos.x; + let y = particle_1.pos.y; + let z = particle_1.pos.z; + let E = particle_1.E; + let Ec = particle_1.Ec; + + if !material.inside_simulation_boundary(x, y, z) { + particle_1.left = true; + particle_1.add_trajectory(); + } + + if (E < Ec) & !particle_1.left & material.inside_energy_barrier(x, y, z) { + particle_1.stopped = true; + particle_1.add_trajectory(); + } + + if !particle_1.stopped & !particle_1.left { + surface_binding_energy(particle_1, material); + } +} diff --git a/src/output.rs b/src/output.rs index 41c6971..da0c27c 100644 --- a/src/output.rs +++ b/src/output.rs @@ -1,419 +1,419 @@ -use super::*; -use std::fs::File; - -pub struct OutputUnits { - pub length_unit: f64, - pub energy_unit: f64, - pub mass_unit: f64 -} - -/// Converts from 6D particle coordinates to energy, angle coordinates w.r.t. negative x-axis -pub fn energy_angle_from_particle(particle: &particle::Particle, units: &OutputUnits) -> (f64, f64) { - let energy = particle.E/units.energy_unit; - let ux = particle.dir.x; - let uy = particle.dir.y; - let uz = particle.dir.z; - - let vyz = ((uy).powi(2) + (uz).powi(2)).sqrt(); - let angle = vyz.atan2(-ux) * 180.0 / PI; - - (energy, angle) -} - -#[cfg(feature = "distributions")] -extern crate ndarray; - -#[cfg(feature = "distributions")] -use ndarray::prelude::*; - -/// Distribution tracker for tracking EADs and implantation distributions -#[derive(Serialize)] -#[cfg(feature = "distributions")] -pub struct Distributions { - pub energies: Array1, - pub angles: Array1, - pub x_range: Array1, - pub y_range: Array1, - pub z_range: Array1, - pub reflected_ead: Array2, - pub sputtered_ead: Array2, - pub implanted_x: Array1, - pub implanted_y: Array1, - pub implanted_z: Array1, - pub electronic_energy_loss_x: Array1, - pub nuclear_energy_loss_x: Array1, -} - -#[cfg(feature = "distributions")] -impl Distributions { - pub fn new(options: &Options) -> Distributions { - Distributions { - energies: Array::linspace(options.energy_min, options.energy_max, options.energy_num), - angles: Array::linspace(options.angle_min, options.angle_max, options.angle_num), - x_range: Array::linspace(options.x_min, options.x_max, options.x_num), - y_range: Array::linspace(options.y_min, options.y_max, options.y_num), - z_range: Array::linspace(options.z_min, options.z_max, options.z_num), - reflected_ead: Array::zeros((options.energy_num, options.angle_num)), - sputtered_ead: Array::zeros((options.energy_num, options.angle_num)), - implanted_x: Array::zeros(options.x_num), - implanted_y: Array::zeros(options.y_num), - implanted_z: Array::zeros(options.z_num), - electronic_energy_loss_x: Array::zeros(options.x_num), - nuclear_energy_loss_x: Array::zeros(options.x_num), - } - } - - /// Write distributions to toml - pub fn print(&self, options: &Options) { - - let distribution_output_file = OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(format!("{}{}", options.name, "distributions.toml")) - .context("Could not open distributions output file.") - .unwrap(); - let mut distribution_file_stream = BufWriter::with_capacity(8000, distribution_output_file); - let toml = toml::to_string(&self).unwrap(); - writeln!(distribution_file_stream, "{}", toml).unwrap(); - - } - - /// Updates distributions with a single particle - pub fn update(&mut self, particle: &particle::Particle, units: &OutputUnits, options: &Options, num_incident: usize) { - let (energy, angle) = energy_angle_from_particle(particle, units); - - let delta_energy = self.energies[1] - self.energies[0]; - let energy_index_left: i32 = ((energy - self.energies[0])/delta_energy).floor() as i32; - - let delta_angle = self.angles[1] - self.angles[0]; - let angle_index_left: i32 = ((angle - self.angles[0])/delta_angle).floor() as i32; - - let inside_energy = (energy_index_left >= 0) & (energy_index_left < self.energies.len() as i32); - let inside_angle = (angle_index_left >= 0) & (angle_index_left < self.angles.len() as i32); - - if particle.incident & particle.left { - if inside_energy & inside_angle { - self.reflected_ead[[energy_index_left as usize, angle_index_left as usize]] += 1; - } - } - - if !particle.incident & particle.left { - if inside_energy & inside_angle { - self.sputtered_ead[[energy_index_left as usize, angle_index_left as usize]] += 1; - } - } - - if particle.incident & particle.stopped { - let x = particle.pos.x/units.length_unit; - let y = particle.pos.y/units.length_unit; - let z = particle.pos.z/units.length_unit; - - let delta_x = self.x_range[1] - self.x_range[0]; - let delta_y = self.y_range[1] - self.y_range[0]; - let delta_z = self.z_range[1] - self.z_range[0]; - - let x_index_left: i32 = ((x - self.x_range[0])/delta_x) as i32; - let y_index_left: i32 = ((y - self.y_range[0])/delta_y) as i32; - let z_index_left: i32 = ((z - self.z_range[0])/delta_z) as i32; - - let inside_x = (x_index_left >= 0) & (x_index_left < self.x_range.len() as i32); - let inside_y = (y_index_left >= 0) & (y_index_left < self.y_range.len() as i32); - let inside_z = (z_index_left >= 0) & (z_index_left < self.z_range.len() as i32); - - if inside_x { - self.implanted_x[x_index_left as usize] += 1; - } - if inside_y { - self.implanted_y[y_index_left as usize] += 1; - } - if inside_z { - self.implanted_z[z_index_left as usize] += 1; - } - } - - if options.track_energy_losses & particle.incident { - - for energy_loss in particle.energies.iter() { - let x = energy_loss.x/units.length_unit; - let delta_x = self.x_range[1] - self.x_range[0]; - - let electronic_energy_loss = energy_loss.Ee/units.energy_unit/(num_incident as f64)/delta_x; - let nuclear_energy_loss = energy_loss.En/units.energy_unit/(num_incident as f64)/delta_x; - - let x_index_left: i32 = ((x - self.x_range[0])/delta_x) as i32; - let inside_x = (x_index_left >= 0) & (x_index_left < self.x_range.len() as i32); - - if inside_x { - self.electronic_energy_loss_x[x_index_left as usize] += electronic_energy_loss; - self.nuclear_energy_loss_x[x_index_left as usize] += nuclear_energy_loss; - } - } - } - } -} - -/// File streams for list output -pub struct OutputListStreams { - reflected_file_stream: BufWriter, - sputtered_file_stream: BufWriter, - deposited_file_stream: BufWriter, - trajectory_file_stream: BufWriter, - trajectory_data_stream: BufWriter, - displacements_file_stream: BufWriter, - energy_loss_file_stream: BufWriter, -} - -/// Simulation-wide summary output tracker. -pub struct Summary { - pub num_incident: u64, - pub num_sputtered: u64, - pub num_reflected: u64, -} - -/// Summary tracker of sputtering and reflection -impl Summary { - pub fn new(num_incident: u64) -> Summary { - Summary { - num_incident, - num_sputtered: 0, - num_reflected: 0, - } - } - - pub fn add(&mut self, particle: &particle::Particle) { - if particle.incident & particle.left { - self.num_reflected += 1; - } - if !particle.incident & particle.left { - self.num_sputtered += 1; - } - } -} - -pub struct SummaryPerSpecies { - pub m: Vec, - pub sputtered: Vec, - pub reflected: Vec, - pub deposited: Vec, - pub summary_stream_file: BufWriter, -} - -impl SummaryPerSpecies { - pub fn new(options: &Options) -> SummaryPerSpecies { - - let summary_output_file = OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(format!("{}{}", options.name, "summary.output")) - .context("Could not open output file.") - .unwrap(); - let writer = BufWriter::with_capacity(8000, summary_output_file); - - SummaryPerSpecies { - m: vec![], - sputtered: vec![], - reflected: vec![], - deposited: vec![], - summary_stream_file: writer, - } - } - - pub fn print(&mut self, options: &Options, output_units: &OutputUnits) { - //Write to summary file - writeln!(self.summary_stream_file, "mass, reflected, sputtered, deposited") - .expect(format!("Output error: could not write to {}summary.output.", options.name).as_str()); - - for (mass, reflected, sputtered, deposited) in izip!(&self.m, &self.reflected, &self.sputtered, &self.deposited) { - writeln!(self.summary_stream_file, "{}, {}, {}, {},", mass/output_units.mass_unit, reflected, sputtered, deposited) - .expect(format!("Output error: could not write to {}summary.output.", options.name).as_str()); - } - self.summary_stream_file.flush().unwrap(); - } - - pub fn update(&mut self, particle: &particle::Particle) { - - if self.m.contains(&(particle.m)) { - - let index = self.m.iter().position(|m| *m == particle.m).unwrap(); - - match (particle.incident, particle.left) { - (true, true) => self.reflected[index] += 1, - (true, false) => self.deposited[index] += 1, - (false, true) => self.sputtered[index] += 1, - _ => (), - } - } else { - self.m.push(particle.m); - match (particle.incident, particle.left) { - (true, true) => {self.reflected.push(1); self.deposited.push(0); self.sputtered.push(0);}, - (true, false) => {self.reflected.push(0); self.deposited.push(1); self.sputtered.push(0);}, - (false, true) => {self.reflected.push(0); self.deposited.push(0); self.sputtered.push(1);}, - _ => {self.reflected.push(0); self.deposited.push(0); self.sputtered.push(0);}, - } - } - } -} - -/// Open list output files for streaming write -pub fn open_output_lists(options: &Options) -> OutputListStreams { - //Open output files for streaming output - let reflected_file = OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(format!("{}{}", options.name, "reflected.output")) - .context("Could not open output file.") - .unwrap(); - let reflected_file_stream = BufWriter::with_capacity(options.write_buffer_size, reflected_file); - - let sputtered_file = OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(format!("{}{}", options.name, "sputtered.output")) - .context("Could not open output file.") - .unwrap(); - let sputtered_file_stream = BufWriter::with_capacity(options.write_buffer_size, sputtered_file); - - let deposited_file = OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(format!("{}{}", options.name, "deposited.output")) - .context("Could not open output file.") - .unwrap(); - let deposited_file_stream = BufWriter::with_capacity(options.write_buffer_size, deposited_file); - - let trajectory_file = OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(format!("{}{}", options.name, "trajectories.output")) - .context("Could not open output file.") - .unwrap(); - let trajectory_file_stream = BufWriter::with_capacity(options.write_buffer_size, trajectory_file); - - let trajectory_data = OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(format!("{}{}", options.name, "trajectory_data.output")) - .context("Could not open output file.") - .unwrap(); - let trajectory_data_stream = BufWriter::with_capacity(options.write_buffer_size, trajectory_data); - - let displacements_file = OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(format!("{}{}", options.name, "displacements.output")) - .context("Could not open output file.") - .unwrap(); - let displacements_file_stream = BufWriter::with_capacity(options.write_buffer_size, displacements_file); - - let energy_loss_file = OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(format!("{}{}", options.name, "energy_loss.output")) - .context("Could not open output file.") - .unwrap(); - let energy_loss_file_stream = BufWriter::with_capacity(options.write_buffer_size, energy_loss_file); - - OutputListStreams { - reflected_file_stream, - sputtered_file_stream, - deposited_file_stream, - trajectory_file_stream, - trajectory_data_stream, - displacements_file_stream, - energy_loss_file_stream, - } -} - -/// Write output lists -pub fn output_lists(output_list_streams: &mut OutputListStreams, particle: particle::Particle, options: &Options, output_units: &OutputUnits) { - - let length_unit = output_units.length_unit; - let energy_unit = output_units.energy_unit; - let mass_unit = output_units.mass_unit; - - if !particle.incident & options.track_displacements { - writeln!( - output_list_streams.displacements_file_stream, "{},{},{},{},{},{},{},{},{}", - particle.m/mass_unit, particle.Z, particle.energy_origin/energy_unit, - particle.pos_origin.x/length_unit, particle.pos_origin.y/length_unit, particle.pos_origin.z/length_unit, - particle.pos.x/length_unit, particle.pos.y/length_unit, particle.pos.z/length_unit - ).expect(format!("Output error: could not write to {}displacements.output.", options.name).as_str()); - } - - //Incident particle, left simulation: reflected - if particle.incident & particle.left { - writeln!( - output_list_streams.reflected_file_stream, "{},{},{},{},{},{},{},{},{},{}", - particle.m/mass_unit, particle.Z, particle.E/energy_unit, - particle.pos.x/length_unit, particle.pos.y/length_unit, particle.pos.z/length_unit, - particle.dir.x, particle.dir.y, particle.dir.z, - particle.number_collision_events - ).expect(format!("Output error: could not write to {}reflected.output.", options.name).as_str()); - } - - //Incident particle, stopped in material: deposited - if particle.incident & particle.stopped { - writeln!( - output_list_streams.deposited_file_stream, "{},{},{},{},{},{}", - particle.m/mass_unit, particle.Z, - particle.pos.x/length_unit, particle.pos.y/length_unit, particle.pos.z/length_unit, - particle.number_collision_events - ).expect(format!("Output error: could not write to {}deposited.output.", options.name).as_str()); - } - - //Not an incident particle, left material: sputtered - if !particle.incident & particle.left { - writeln!( - output_list_streams.sputtered_file_stream, "{},{},{},{},{},{},{},{},{},{},{},{},{}", - particle.m/mass_unit, particle.Z, particle.E/energy_unit, - particle.pos.x/length_unit, particle.pos.y/length_unit, particle.pos.z/length_unit, - particle.dir.x, particle.dir.y, particle.dir.z, - particle.number_collision_events, - particle.pos_origin.x/length_unit, particle.pos_origin.y/length_unit, particle.pos_origin.z/length_unit - ).expect(format!("Output error: could not write to {}sputtered.output.", options.name).as_str()); - } - - //Trajectory output - if particle.track_trajectories { - writeln!(output_list_streams.trajectory_data_stream, "{}", particle.trajectory.len()) - .expect(format!("Output error: could not write to {}trajectory_data.output.", options.name).as_str()); - - for pos in particle.trajectory { - writeln!( - output_list_streams.trajectory_file_stream, "{},{},{},{},{},{}", - particle.m/mass_unit, particle.Z, pos.E/energy_unit, - pos.x/length_unit, pos.y/length_unit, pos.z/length_unit, - ).expect(format!("Output error: could not write to {}trajectories.output.", options.name).as_str()); - } - } - - if particle.incident & options.track_energy_losses { - for energy_loss in particle.energies { - writeln!( - output_list_streams.energy_loss_file_stream, "{},{},{},{},{},{},{}", - particle.m/mass_unit, particle.Z, - energy_loss.En/energy_unit, energy_loss.Ee/energy_unit, - energy_loss.x/length_unit, energy_loss.y/length_unit, energy_loss.z/length_unit, - ).expect(format!("Output error: could not write to {}energy_loss.output.", options.name).as_str()); - } - } -} - -/// Flush output list streams -pub fn output_list_flush(output_list_streams: &mut OutputListStreams) { - output_list_streams.displacements_file_stream.flush().unwrap(); - output_list_streams.reflected_file_stream.flush().unwrap(); - output_list_streams.deposited_file_stream.flush().unwrap(); - output_list_streams.sputtered_file_stream.flush().unwrap(); - output_list_streams.trajectory_data_stream.flush().unwrap(); - output_list_streams.trajectory_file_stream.flush().unwrap(); -} +use super::*; +use std::fs::File; + +pub struct OutputUnits { + pub length_unit: f64, + pub energy_unit: f64, + pub mass_unit: f64 +} + +/// Converts from 6D particle coordinates to energy, angle coordinates w.r.t. negative x-axis +pub fn energy_angle_from_particle(particle: &particle::Particle, units: &OutputUnits) -> (f64, f64) { + let energy = particle.E/units.energy_unit; + let ux = particle.dir.x; + let uy = particle.dir.y; + let uz = particle.dir.z; + + let vyz = ((uy).powi(2) + (uz).powi(2)).sqrt(); + let angle = vyz.atan2(-ux) * 180.0 / PI; + + (energy, angle) +} + +#[cfg(feature = "distributions")] +extern crate ndarray; + +#[cfg(feature = "distributions")] +use ndarray::prelude::*; + +/// Distribution tracker for tracking EADs and implantation distributions +#[derive(Serialize)] +#[cfg(feature = "distributions")] +pub struct Distributions { + pub energies: Array1, + pub angles: Array1, + pub x_range: Array1, + pub y_range: Array1, + pub z_range: Array1, + pub reflected_ead: Array2, + pub sputtered_ead: Array2, + pub implanted_x: Array1, + pub implanted_y: Array1, + pub implanted_z: Array1, + pub electronic_energy_loss_x: Array1, + pub nuclear_energy_loss_x: Array1, +} + +#[cfg(feature = "distributions")] +impl Distributions { + pub fn new(options: &Options) -> Distributions { + Distributions { + energies: Array::linspace(options.energy_min, options.energy_max, options.energy_num), + angles: Array::linspace(options.angle_min, options.angle_max, options.angle_num), + x_range: Array::linspace(options.x_min, options.x_max, options.x_num), + y_range: Array::linspace(options.y_min, options.y_max, options.y_num), + z_range: Array::linspace(options.z_min, options.z_max, options.z_num), + reflected_ead: Array::zeros((options.energy_num, options.angle_num)), + sputtered_ead: Array::zeros((options.energy_num, options.angle_num)), + implanted_x: Array::zeros(options.x_num), + implanted_y: Array::zeros(options.y_num), + implanted_z: Array::zeros(options.z_num), + electronic_energy_loss_x: Array::zeros(options.x_num), + nuclear_energy_loss_x: Array::zeros(options.x_num), + } + } + + /// Write distributions to toml + pub fn print(&self, options: &Options) { + + let distribution_output_file = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(format!("{}{}", options.name, "distributions.toml")) + .context("Could not open distributions output file.") + .unwrap(); + let mut distribution_file_stream = BufWriter::with_capacity(8000, distribution_output_file); + let toml = toml::to_string(&self).unwrap(); + writeln!(distribution_file_stream, "{}", toml).unwrap(); + + } + + /// Updates distributions with a single particle + pub fn update(&mut self, particle: &particle::Particle, units: &OutputUnits, options: &Options, num_incident: usize) { + let (energy, angle) = energy_angle_from_particle(particle, units); + + let delta_energy = self.energies[1] - self.energies[0]; + let energy_index_left: i32 = ((energy - self.energies[0])/delta_energy).floor() as i32; + + let delta_angle = self.angles[1] - self.angles[0]; + let angle_index_left: i32 = ((angle - self.angles[0])/delta_angle).floor() as i32; + + let inside_energy = (energy_index_left >= 0) & (energy_index_left < self.energies.len() as i32); + let inside_angle = (angle_index_left >= 0) & (angle_index_left < self.angles.len() as i32); + + if particle.incident & particle.left { + if inside_energy & inside_angle { + self.reflected_ead[[energy_index_left as usize, angle_index_left as usize]] += 1; + } + } + + if !particle.incident & particle.left { + if inside_energy & inside_angle { + self.sputtered_ead[[energy_index_left as usize, angle_index_left as usize]] += 1; + } + } + + if particle.incident & particle.stopped { + let x = particle.pos.x/units.length_unit; + let y = particle.pos.y/units.length_unit; + let z = particle.pos.z/units.length_unit; + + let delta_x = self.x_range[1] - self.x_range[0]; + let delta_y = self.y_range[1] - self.y_range[0]; + let delta_z = self.z_range[1] - self.z_range[0]; + + let x_index_left: i32 = ((x - self.x_range[0])/delta_x) as i32; + let y_index_left: i32 = ((y - self.y_range[0])/delta_y) as i32; + let z_index_left: i32 = ((z - self.z_range[0])/delta_z) as i32; + + let inside_x = (x_index_left >= 0) & (x_index_left < self.x_range.len() as i32); + let inside_y = (y_index_left >= 0) & (y_index_left < self.y_range.len() as i32); + let inside_z = (z_index_left >= 0) & (z_index_left < self.z_range.len() as i32); + + if inside_x { + self.implanted_x[x_index_left as usize] += 1; + } + if inside_y { + self.implanted_y[y_index_left as usize] += 1; + } + if inside_z { + self.implanted_z[z_index_left as usize] += 1; + } + } + + if options.track_energy_losses & particle.incident { + + for energy_loss in particle.energies.iter() { + let x = energy_loss.x/units.length_unit; + let delta_x = self.x_range[1] - self.x_range[0]; + + let electronic_energy_loss = energy_loss.Ee/units.energy_unit/(num_incident as f64)/delta_x; + let nuclear_energy_loss = energy_loss.En/units.energy_unit/(num_incident as f64)/delta_x; + + let x_index_left: i32 = ((x - self.x_range[0])/delta_x) as i32; + let inside_x = (x_index_left >= 0) & (x_index_left < self.x_range.len() as i32); + + if inside_x { + self.electronic_energy_loss_x[x_index_left as usize] += electronic_energy_loss; + self.nuclear_energy_loss_x[x_index_left as usize] += nuclear_energy_loss; + } + } + } + } +} + +/// File streams for list output +pub struct OutputListStreams { + reflected_file_stream: BufWriter, + sputtered_file_stream: BufWriter, + deposited_file_stream: BufWriter, + trajectory_file_stream: BufWriter, + trajectory_data_stream: BufWriter, + displacements_file_stream: BufWriter, + energy_loss_file_stream: BufWriter, +} + +/// Simulation-wide summary output tracker. +pub struct Summary { + pub num_incident: u64, + pub num_sputtered: u64, + pub num_reflected: u64, +} + +/// Summary tracker of sputtering and reflection +impl Summary { + pub fn new(num_incident: u64) -> Summary { + Summary { + num_incident, + num_sputtered: 0, + num_reflected: 0, + } + } + + pub fn add(&mut self, particle: &particle::Particle) { + if particle.incident & particle.left { + self.num_reflected += 1; + } + if !particle.incident & particle.left { + self.num_sputtered += 1; + } + } +} + +pub struct SummaryPerSpecies { + pub m: Vec, + pub sputtered: Vec, + pub reflected: Vec, + pub deposited: Vec, + pub summary_stream_file: BufWriter, +} + +impl SummaryPerSpecies { + pub fn new(options: &Options) -> SummaryPerSpecies { + + let summary_output_file = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(format!("{}{}", options.name, "summary.output")) + .context("Could not open output file.") + .unwrap(); + let writer = BufWriter::with_capacity(8000, summary_output_file); + + SummaryPerSpecies { + m: vec![], + sputtered: vec![], + reflected: vec![], + deposited: vec![], + summary_stream_file: writer, + } + } + + pub fn print(&mut self, options: &Options, output_units: &OutputUnits) { + //Write to summary file + writeln!(self.summary_stream_file, "mass, reflected, sputtered, deposited") + .expect(format!("Output error: could not write to {}summary.output.", options.name).as_str()); + + for (mass, reflected, sputtered, deposited) in izip!(&self.m, &self.reflected, &self.sputtered, &self.deposited) { + writeln!(self.summary_stream_file, "{}, {}, {}, {},", mass/output_units.mass_unit, reflected, sputtered, deposited) + .expect(format!("Output error: could not write to {}summary.output.", options.name).as_str()); + } + self.summary_stream_file.flush().unwrap(); + } + + pub fn update(&mut self, particle: &particle::Particle) { + + if self.m.contains(&(particle.m)) { + + let index = self.m.iter().position(|m| *m == particle.m).unwrap(); + + match (particle.incident, particle.left) { + (true, true) => self.reflected[index] += 1, + (true, false) => self.deposited[index] += 1, + (false, true) => self.sputtered[index] += 1, + _ => (), + } + } else { + self.m.push(particle.m); + match (particle.incident, particle.left) { + (true, true) => {self.reflected.push(1); self.deposited.push(0); self.sputtered.push(0);}, + (true, false) => {self.reflected.push(0); self.deposited.push(1); self.sputtered.push(0);}, + (false, true) => {self.reflected.push(0); self.deposited.push(0); self.sputtered.push(1);}, + _ => {self.reflected.push(0); self.deposited.push(0); self.sputtered.push(0);}, + } + } + } +} + +/// Open list output files for streaming write +pub fn open_output_lists(options: &Options) -> OutputListStreams { + //Open output files for streaming output + let reflected_file = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(format!("{}{}", options.name, "reflected.output")) + .context("Could not open output file.") + .unwrap(); + let reflected_file_stream = BufWriter::with_capacity(options.write_buffer_size, reflected_file); + + let sputtered_file = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(format!("{}{}", options.name, "sputtered.output")) + .context("Could not open output file.") + .unwrap(); + let sputtered_file_stream = BufWriter::with_capacity(options.write_buffer_size, sputtered_file); + + let deposited_file = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(format!("{}{}", options.name, "deposited.output")) + .context("Could not open output file.") + .unwrap(); + let deposited_file_stream = BufWriter::with_capacity(options.write_buffer_size, deposited_file); + + let trajectory_file = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(format!("{}{}", options.name, "trajectories.output")) + .context("Could not open output file.") + .unwrap(); + let trajectory_file_stream = BufWriter::with_capacity(options.write_buffer_size, trajectory_file); + + let trajectory_data = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(format!("{}{}", options.name, "trajectory_data.output")) + .context("Could not open output file.") + .unwrap(); + let trajectory_data_stream = BufWriter::with_capacity(options.write_buffer_size, trajectory_data); + + let displacements_file = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(format!("{}{}", options.name, "displacements.output")) + .context("Could not open output file.") + .unwrap(); + let displacements_file_stream = BufWriter::with_capacity(options.write_buffer_size, displacements_file); + + let energy_loss_file = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(format!("{}{}", options.name, "energy_loss.output")) + .context("Could not open output file.") + .unwrap(); + let energy_loss_file_stream = BufWriter::with_capacity(options.write_buffer_size, energy_loss_file); + + OutputListStreams { + reflected_file_stream, + sputtered_file_stream, + deposited_file_stream, + trajectory_file_stream, + trajectory_data_stream, + displacements_file_stream, + energy_loss_file_stream, + } +} + +/// Write output lists +pub fn output_lists(output_list_streams: &mut OutputListStreams, particle: particle::Particle, options: &Options, output_units: &OutputUnits) { + + let length_unit = output_units.length_unit; + let energy_unit = output_units.energy_unit; + let mass_unit = output_units.mass_unit; + + if !particle.incident & options.track_displacements { + writeln!( + output_list_streams.displacements_file_stream, "{},{},{},{},{},{},{},{},{}", + particle.m/mass_unit, particle.Z, particle.energy_origin/energy_unit, + particle.pos_origin.x/length_unit, particle.pos_origin.y/length_unit, particle.pos_origin.z/length_unit, + particle.pos.x/length_unit, particle.pos.y/length_unit, particle.pos.z/length_unit + ).expect(format!("Output error: could not write to {}displacements.output.", options.name).as_str()); + } + + //Incident particle, left simulation: reflected + if particle.incident & particle.left { + writeln!( + output_list_streams.reflected_file_stream, "{},{},{},{},{},{},{},{},{},{}", + particle.m/mass_unit, particle.Z, particle.E/energy_unit, + particle.pos.x/length_unit, particle.pos.y/length_unit, particle.pos.z/length_unit, + particle.dir.x, particle.dir.y, particle.dir.z, + particle.number_collision_events + ).expect(format!("Output error: could not write to {}reflected.output.", options.name).as_str()); + } + + //Incident particle, stopped in material: deposited + if particle.incident & particle.stopped { + writeln!( + output_list_streams.deposited_file_stream, "{},{},{},{},{},{}", + particle.m/mass_unit, particle.Z, + particle.pos.x/length_unit, particle.pos.y/length_unit, particle.pos.z/length_unit, + particle.number_collision_events + ).expect(format!("Output error: could not write to {}deposited.output.", options.name).as_str()); + } + + //Not an incident particle, left material: sputtered + if !particle.incident & particle.left { + writeln!( + output_list_streams.sputtered_file_stream, "{},{},{},{},{},{},{},{},{},{},{},{},{}", + particle.m/mass_unit, particle.Z, particle.E/energy_unit, + particle.pos.x/length_unit, particle.pos.y/length_unit, particle.pos.z/length_unit, + particle.dir.x, particle.dir.y, particle.dir.z, + particle.number_collision_events, + particle.pos_origin.x/length_unit, particle.pos_origin.y/length_unit, particle.pos_origin.z/length_unit + ).expect(format!("Output error: could not write to {}sputtered.output.", options.name).as_str()); + } + + //Trajectory output + if particle.track_trajectories { + writeln!(output_list_streams.trajectory_data_stream, "{}", particle.trajectory.len()) + .expect(format!("Output error: could not write to {}trajectory_data.output.", options.name).as_str()); + + for pos in particle.trajectory { + writeln!( + output_list_streams.trajectory_file_stream, "{},{},{},{},{},{}", + particle.m/mass_unit, particle.Z, pos.E/energy_unit, + pos.x/length_unit, pos.y/length_unit, pos.z/length_unit, + ).expect(format!("Output error: could not write to {}trajectories.output.", options.name).as_str()); + } + } + + if particle.incident & options.track_energy_losses { + for energy_loss in particle.energies { + writeln!( + output_list_streams.energy_loss_file_stream, "{},{},{},{},{},{},{}", + particle.m/mass_unit, particle.Z, + energy_loss.En/energy_unit, energy_loss.Ee/energy_unit, + energy_loss.x/length_unit, energy_loss.y/length_unit, energy_loss.z/length_unit, + ).expect(format!("Output error: could not write to {}energy_loss.output.", options.name).as_str()); + } + } +} + +/// Flush output list streams +pub fn output_list_flush(output_list_streams: &mut OutputListStreams) { + output_list_streams.displacements_file_stream.flush().unwrap(); + output_list_streams.reflected_file_stream.flush().unwrap(); + output_list_streams.deposited_file_stream.flush().unwrap(); + output_list_streams.sputtered_file_stream.flush().unwrap(); + output_list_streams.trajectory_data_stream.flush().unwrap(); + output_list_streams.trajectory_file_stream.flush().unwrap(); +} diff --git a/src/parry.rs b/src/parry.rs index ffbf6aa..ac99ced 100644 --- a/src/parry.rs +++ b/src/parry.rs @@ -1,292 +1,292 @@ -use super::*; -use parry3d_f64::shape::{Ball, TriMesh}; -use parry3d_f64::query::{PointQuery, Ray, RayCast}; -use parry3d_f64::math::{Isometry, Point, Vector}; -use parry3d_f64::bounding_volume::AABB; - -#[derive(Deserialize, Clone)] -pub struct InputParryBall { - pub options: Options, - pub material_parameters: material::MaterialParameters, - pub particle_parameters: particle::ParticleParameters, - pub geometry_input: ParryBallInput, -} - -impl InputFile for InputParryBall { - - fn new(string: &str) -> InputParryBall { - toml::from_str(string).expect("Could not parse TOML file.") - } - - fn get_options(&self) -> &Options{ - &self.options - } - fn get_material_parameters(&self) -> &material::MaterialParameters{ - &self.material_parameters - } - fn get_particle_parameters(&self) ->& particle::ParticleParameters{ - &self.particle_parameters - } - fn get_geometry_input(&self) -> &Self::GeometryInput{ - &self.geometry_input - } -} - -#[derive(Deserialize, Clone)] -pub struct ParryBallInput { - pub length_unit: String, - pub radius: f64, - pub densities: Vec, - pub electronic_stopping_correction_factor: f64, -} - -#[derive(Clone)] -pub struct ParryBall { - pub densities: Vec, - pub concentrations: Vec, - pub radius: f64, - pub electronic_stopping_correction_factor: f64, - pub energy_barrier_thickness: f64, - pub ball: Ball, -} - -impl GeometryInput for InputParryBall { - type GeometryInput = ParryBallInput; -} - -impl Geometry for ParryBall { - - type InputFileFormat = InputParryBall; - - fn new(input: &<::InputFileFormat as GeometryInput>::GeometryInput) -> ParryBall { - - let length_unit: f64 = match input.length_unit.as_str() { - "MICRON" => MICRON, - "CM" => CM, - "ANGSTROM" => ANGSTROM, - "NM" => NM, - "M" => 1., - _ => input.length_unit.parse() - .expect(format!( - "Input errror: could nor parse length unit {}. Use a valid float or one of ANGSTROM, NM, MICRON, CM, MM, M", - &input.length_unit.as_str() - ).as_str()), - }; - - let electronic_stopping_correction_factor = input.electronic_stopping_correction_factor; - let densities: Vec = input.densities.iter().map(|element| element/(length_unit).powi(3)).collect(); - let total_density: f64 = densities.iter().sum(); - let energy_barrier_thickness = total_density.powf(-1./3.)/SQRTPI*2.; - let concentrations: Vec = densities.iter().map(|&density| density/total_density).collect::>(); - let radius = input.radius*length_unit; - - ParryBall { - densities, - concentrations, - radius, - electronic_stopping_correction_factor, - energy_barrier_thickness, - ball: Ball::new(radius) - } - } - - fn get_densities(&self, x: f64, y: f64, z: f64) -> &Vec { - &self.densities - } - fn get_ck(&self, x: f64, y: f64, z: f64) -> f64 { - self.electronic_stopping_correction_factor - } - fn get_total_density(&self, x: f64, y: f64, z: f64) -> f64{ - self.densities.iter().sum() - } - fn get_concentrations(&self, x: f64, y: f64, z: f64) -> &Vec { - &self.concentrations - } - fn inside(&self, x: f64, y: f64, z: f64) -> bool { - let p = Point::new(x , y , z ); - self.ball.contains_local_point(&p) - } - - fn inside_simulation_boundary(&self, x: f64, y: f64, z: f64) -> bool { - let p = Point::new(x , y , z ); - (self.ball.distance_to_local_point(&p, true) as f64) < 10.*self.energy_barrier_thickness - } - - fn inside_energy_barrier(&self, x: f64, y: f64, z: f64) -> bool { - let p = Point::new(x , y , z ); - (self.ball.distance_to_local_point(&p, true) as f64) < self.energy_barrier_thickness - } - - fn closest_point(&self, x: f64, y: f64, z: f64) -> (f64, f64, f64) { - let p = Point::new(x , y , z ); - let point_projection = self.ball.project_point(&Isometry::identity(), &p, false); - let (x_, y_, z_) = (point_projection.point.x, point_projection.point.y, point_projection.point.z); - (x_ as f64, y_ as f64, z_ as f64) - } -} - - -#[derive(Deserialize, Clone)] -pub struct InputParryTriMesh { - pub options: Options, - pub material_parameters: material::MaterialParameters, - pub particle_parameters: particle::ParticleParameters, - pub geometry_input: ParryTriMeshInput, -} - -impl InputFile for InputParryTriMesh { - - fn new(string: &str) -> InputParryTriMesh { - toml::from_str(string).expect("Could not parse TOML file.") - } - - fn get_options(&self) -> &Options{ - &self.options - } - fn get_material_parameters(&self) -> &material::MaterialParameters{ - &self.material_parameters - } - fn get_particle_parameters(&self) ->& particle::ParticleParameters{ - &self.particle_parameters - } - fn get_geometry_input(&self) -> &Self::GeometryInput{ - &self.geometry_input - } -} - -#[derive(Deserialize, Clone)] -pub struct ParryTriMeshInput { - pub length_unit: String, - pub densities: Vec, - pub electronic_stopping_correction_factor: f64, - pub vertices: Vec<[f64; 3]>, - pub indices: Vec<[u32; 3]>, -} - -#[derive(Clone)] -pub struct ParryTriMesh { - pub densities: Vec, - pub concentrations: Vec, - pub electronic_stopping_correction_factor: f64, - pub energy_barrier_thickness: f64, - pub trimesh: TriMesh, - pub boundary: AABB, -} - -impl GeometryInput for InputParryTriMesh { - type GeometryInput = ParryTriMeshInput; -} - -impl Geometry for ParryTriMesh { - - type InputFileFormat = InputParryTriMesh; - - fn new(input: &<::InputFileFormat as GeometryInput>::GeometryInput) -> ParryTriMesh { - - let length_unit: f64 = match input.length_unit.as_str() { - "MICRON" => MICRON, - "CM" => CM, - "ANGSTROM" => ANGSTROM, - "NM" => NM, - "M" => 1., - _ => input.length_unit.parse() - .expect(format!( - "Input errror: could nor parse length unit {}. Use a valid float or one of ANGSTROM, NM, MICRON, CM, MM, M", - &input.length_unit.as_str() - ).as_str()), - }; - - let electronic_stopping_correction_factor = input.electronic_stopping_correction_factor; - let densities: Vec = input.densities.iter().map(|element| element/(length_unit).powi(3)).collect(); - let total_density: f64 = densities.iter().sum(); - let energy_barrier_thickness = total_density.powf(-1./3.)/SQRTPI*2.; - let concentrations: Vec = densities.iter().map(|&density| density/total_density).collect::>(); - let points = input.vertices.iter().map(|p| Point::new(p[0]*length_unit , p[1]*length_unit , p[2]*length_unit)).collect(); - let trimesh = TriMesh::new(points, input.indices.clone()); - let boundary = trimesh.aabb(&Isometry::identity()); - - ParryTriMesh { - densities, - concentrations, - electronic_stopping_correction_factor, - energy_barrier_thickness, - trimesh, - boundary, - } - } - - fn get_densities(&self, x: f64, y: f64, z: f64) -> &Vec { - &self.densities - } - fn get_ck(&self, x: f64, y: f64, z: f64) -> f64 { - self.electronic_stopping_correction_factor - } - fn get_total_density(&self, x: f64, y: f64, z: f64) -> f64{ - self.densities.iter().sum() - } - fn get_concentrations(&self, x: f64, y: f64, z: f64) -> &Vec { - &self.concentrations - } - fn inside(&self, x: f64, y: f64, z: f64) -> bool { - inside_trimesh(&self.trimesh, x, y, z) - } - - fn inside_simulation_boundary(&self, x: f64, y: f64, z: f64) -> bool { - if self.inside(x, y, z) { - true - } else { - let p = Point::new(x , y , z ); - let distance = self.boundary.distance_to_local_point(&p, true); - //dbg!(distance); - distance < 10.*self.energy_barrier_thickness - } - } - - fn inside_energy_barrier(&self, x: f64, y: f64, z: f64) -> bool { - if self.inside(x, y, z) { - true - } else { - let p = Point::new(x , y , z ); - let distance = self.trimesh.distance_to_local_point(&p, true); - //dbg!(distance/ANGSTROM); - //dbg!(self.energy_barrier_thickness/ANGSTROM); - //dbg!(distance < self.energy_barrier_thickness); - distance < self.energy_barrier_thickness - } - } - - fn closest_point(&self, x: f64, y: f64, z: f64) -> (f64, f64, f64) { - let p = Point::new(x , y , z ); - let point_projection = self.trimesh.project_local_point(&p, false); - let (x_, y_, z_) = (point_projection.point.x, point_projection.point.y, point_projection.point.z); - (x_ as f64, y_ as f64, z_ as f64) - } -} - -fn inside_trimesh(trimesh: &TriMesh, x: f64, y: f64, z: f64) -> bool { - let p = Point::new(x , y , z ); - - if !trimesh.aabb(&Isometry::identity()) - .contains_local_point(&p) { - return false; - } - - let dir = Vector::new(1.0, 0.0, 0.0); - - let result = trimesh.cast_ray_and_get_normal( - &Isometry::identity(), - &Ray::new(p, dir), - 1.0, - false - ); - - //dbg!(result); - - match result { - Some(intersection) => { - //dbg!(intersection.feature); - trimesh.is_backface(intersection.feature) - }, - None => false, - } -} +use super::*; +use parry3d_f64::shape::{Ball, TriMesh}; +use parry3d_f64::query::{PointQuery, Ray, RayCast}; +use parry3d_f64::math::{Isometry, Point, Vector}; +use parry3d_f64::bounding_volume::AABB; + +#[derive(Deserialize, Clone)] +pub struct InputParryBall { + pub options: Options, + pub material_parameters: material::MaterialParameters, + pub particle_parameters: particle::ParticleParameters, + pub geometry_input: ParryBallInput, +} + +impl InputFile for InputParryBall { + + fn new(string: &str) -> InputParryBall { + toml::from_str(string).expect("Could not parse TOML file.") + } + + fn get_options(&self) -> &Options{ + &self.options + } + fn get_material_parameters(&self) -> &material::MaterialParameters{ + &self.material_parameters + } + fn get_particle_parameters(&self) ->& particle::ParticleParameters{ + &self.particle_parameters + } + fn get_geometry_input(&self) -> &Self::GeometryInput{ + &self.geometry_input + } +} + +#[derive(Deserialize, Clone)] +pub struct ParryBallInput { + pub length_unit: String, + pub radius: f64, + pub densities: Vec, + pub electronic_stopping_correction_factor: f64, +} + +#[derive(Clone)] +pub struct ParryBall { + pub densities: Vec, + pub concentrations: Vec, + pub radius: f64, + pub electronic_stopping_correction_factor: f64, + pub energy_barrier_thickness: f64, + pub ball: Ball, +} + +impl GeometryInput for InputParryBall { + type GeometryInput = ParryBallInput; +} + +impl Geometry for ParryBall { + + type InputFileFormat = InputParryBall; + + fn new(input: &<::InputFileFormat as GeometryInput>::GeometryInput) -> ParryBall { + + let length_unit: f64 = match input.length_unit.as_str() { + "MICRON" => MICRON, + "CM" => CM, + "ANGSTROM" => ANGSTROM, + "NM" => NM, + "M" => 1., + _ => input.length_unit.parse() + .expect(format!( + "Input errror: could nor parse length unit {}. Use a valid float or one of ANGSTROM, NM, MICRON, CM, MM, M", + &input.length_unit.as_str() + ).as_str()), + }; + + let electronic_stopping_correction_factor = input.electronic_stopping_correction_factor; + let densities: Vec = input.densities.iter().map(|element| element/(length_unit).powi(3)).collect(); + let total_density: f64 = densities.iter().sum(); + let energy_barrier_thickness = total_density.powf(-1./3.)/SQRTPI*2.; + let concentrations: Vec = densities.iter().map(|&density| density/total_density).collect::>(); + let radius = input.radius*length_unit; + + ParryBall { + densities, + concentrations, + radius, + electronic_stopping_correction_factor, + energy_barrier_thickness, + ball: Ball::new(radius) + } + } + + fn get_densities(&self, x: f64, y: f64, z: f64) -> &Vec { + &self.densities + } + fn get_ck(&self, x: f64, y: f64, z: f64) -> f64 { + self.electronic_stopping_correction_factor + } + fn get_total_density(&self, x: f64, y: f64, z: f64) -> f64{ + self.densities.iter().sum() + } + fn get_concentrations(&self, x: f64, y: f64, z: f64) -> &Vec { + &self.concentrations + } + fn inside(&self, x: f64, y: f64, z: f64) -> bool { + let p = Point::new(x , y , z ); + self.ball.contains_local_point(&p) + } + + fn inside_simulation_boundary(&self, x: f64, y: f64, z: f64) -> bool { + let p = Point::new(x , y , z ); + (self.ball.distance_to_local_point(&p, true) as f64) < 10.*self.energy_barrier_thickness + } + + fn inside_energy_barrier(&self, x: f64, y: f64, z: f64) -> bool { + let p = Point::new(x , y , z ); + (self.ball.distance_to_local_point(&p, true) as f64) < self.energy_barrier_thickness + } + + fn closest_point(&self, x: f64, y: f64, z: f64) -> (f64, f64, f64) { + let p = Point::new(x , y , z ); + let point_projection = self.ball.project_point(&Isometry::identity(), &p, false); + let (x_, y_, z_) = (point_projection.point.x, point_projection.point.y, point_projection.point.z); + (x_ as f64, y_ as f64, z_ as f64) + } +} + + +#[derive(Deserialize, Clone)] +pub struct InputParryTriMesh { + pub options: Options, + pub material_parameters: material::MaterialParameters, + pub particle_parameters: particle::ParticleParameters, + pub geometry_input: ParryTriMeshInput, +} + +impl InputFile for InputParryTriMesh { + + fn new(string: &str) -> InputParryTriMesh { + toml::from_str(string).expect("Could not parse TOML file.") + } + + fn get_options(&self) -> &Options{ + &self.options + } + fn get_material_parameters(&self) -> &material::MaterialParameters{ + &self.material_parameters + } + fn get_particle_parameters(&self) ->& particle::ParticleParameters{ + &self.particle_parameters + } + fn get_geometry_input(&self) -> &Self::GeometryInput{ + &self.geometry_input + } +} + +#[derive(Deserialize, Clone)] +pub struct ParryTriMeshInput { + pub length_unit: String, + pub densities: Vec, + pub electronic_stopping_correction_factor: f64, + pub vertices: Vec<[f64; 3]>, + pub indices: Vec<[u32; 3]>, +} + +#[derive(Clone)] +pub struct ParryTriMesh { + pub densities: Vec, + pub concentrations: Vec, + pub electronic_stopping_correction_factor: f64, + pub energy_barrier_thickness: f64, + pub trimesh: TriMesh, + pub boundary: AABB, +} + +impl GeometryInput for InputParryTriMesh { + type GeometryInput = ParryTriMeshInput; +} + +impl Geometry for ParryTriMesh { + + type InputFileFormat = InputParryTriMesh; + + fn new(input: &<::InputFileFormat as GeometryInput>::GeometryInput) -> ParryTriMesh { + + let length_unit: f64 = match input.length_unit.as_str() { + "MICRON" => MICRON, + "CM" => CM, + "ANGSTROM" => ANGSTROM, + "NM" => NM, + "M" => 1., + _ => input.length_unit.parse() + .expect(format!( + "Input errror: could nor parse length unit {}. Use a valid float or one of ANGSTROM, NM, MICRON, CM, MM, M", + &input.length_unit.as_str() + ).as_str()), + }; + + let electronic_stopping_correction_factor = input.electronic_stopping_correction_factor; + let densities: Vec = input.densities.iter().map(|element| element/(length_unit).powi(3)).collect(); + let total_density: f64 = densities.iter().sum(); + let energy_barrier_thickness = total_density.powf(-1./3.)/SQRTPI*2.; + let concentrations: Vec = densities.iter().map(|&density| density/total_density).collect::>(); + let points = input.vertices.iter().map(|p| Point::new(p[0]*length_unit , p[1]*length_unit , p[2]*length_unit)).collect(); + let trimesh = TriMesh::new(points, input.indices.clone()); + let boundary = trimesh.aabb(&Isometry::identity()); + + ParryTriMesh { + densities, + concentrations, + electronic_stopping_correction_factor, + energy_barrier_thickness, + trimesh, + boundary, + } + } + + fn get_densities(&self, x: f64, y: f64, z: f64) -> &Vec { + &self.densities + } + fn get_ck(&self, x: f64, y: f64, z: f64) -> f64 { + self.electronic_stopping_correction_factor + } + fn get_total_density(&self, x: f64, y: f64, z: f64) -> f64{ + self.densities.iter().sum() + } + fn get_concentrations(&self, x: f64, y: f64, z: f64) -> &Vec { + &self.concentrations + } + fn inside(&self, x: f64, y: f64, z: f64) -> bool { + inside_trimesh(&self.trimesh, x, y, z) + } + + fn inside_simulation_boundary(&self, x: f64, y: f64, z: f64) -> bool { + if self.inside(x, y, z) { + true + } else { + let p = Point::new(x , y , z ); + let distance = self.boundary.distance_to_local_point(&p, true); + //dbg!(distance); + distance < 10.*self.energy_barrier_thickness + } + } + + fn inside_energy_barrier(&self, x: f64, y: f64, z: f64) -> bool { + if self.inside(x, y, z) { + true + } else { + let p = Point::new(x , y , z ); + let distance = self.trimesh.distance_to_local_point(&p, true); + //dbg!(distance/ANGSTROM); + //dbg!(self.energy_barrier_thickness/ANGSTROM); + //dbg!(distance < self.energy_barrier_thickness); + distance < self.energy_barrier_thickness + } + } + + fn closest_point(&self, x: f64, y: f64, z: f64) -> (f64, f64, f64) { + let p = Point::new(x , y , z ); + let point_projection = self.trimesh.project_local_point(&p, false); + let (x_, y_, z_) = (point_projection.point.x, point_projection.point.y, point_projection.point.z); + (x_ as f64, y_ as f64, z_ as f64) + } +} + +fn inside_trimesh(trimesh: &TriMesh, x: f64, y: f64, z: f64) -> bool { + let p = Point::new(x , y , z ); + + if !trimesh.aabb(&Isometry::identity()) + .contains_local_point(&p) { + return false; + } + + let dir = Vector::new(1.0, 0.0, 0.0); + + let result = trimesh.cast_ray_and_get_normal( + &Isometry::identity(), + &Ray::new(p, dir), + 1.0, + false + ); + + //dbg!(result); + + match result { + Some(intersection) => { + //dbg!(intersection.feature); + trimesh.is_backface(intersection.feature) + }, + None => false, + } +} diff --git a/src/particle.rs b/src/particle.rs index 51e8eb1..7c73721 100644 --- a/src/particle.rs +++ b/src/particle.rs @@ -1,258 +1,258 @@ -use super::*; - -/// Rustbca's internal representation of the particle_parameters input. - - -#[cfg(feature = "hdf5_input")] -#[derive(Deserialize, Clone)] -pub struct ParticleParameters { - pub particle_input_filename: String, - pub length_unit: String, - pub energy_unit: String, - pub mass_unit: String, - pub N: Vec, - pub m: Vec, - pub Z: Vec, - pub E: Vec, - pub Ec: Vec, - pub Es: Vec, - pub pos: Vec<(f64, f64, f64)>, - pub dir: Vec<(f64, f64, f64)>, - pub interaction_index: Vec -} - -#[cfg(not(feature = "hdf5_input"))] -#[derive(Deserialize, Clone)] -pub struct ParticleParameters { - pub length_unit: String, - pub energy_unit: String, - pub mass_unit: String, - pub N: Vec, - pub m: Vec, - pub Z: Vec, - pub E: Vec, - pub Ec: Vec, - pub Es: Vec, - pub pos: Vec<(f64, f64, f64)>, - pub dir: Vec<(f64, f64, f64)>, - pub interaction_index: Vec -} - -/// HDF5 version of particle input. -#[derive(Clone, PartialEq, Debug, Copy)] -#[cfg_attr(feature = "hdf5_input", derive(hdf5::H5Type))] -#[repr(C)] -pub struct ParticleInput { - pub m: f64, - pub Z: f64, - pub E: f64, - pub Ec: f64, - pub Es: f64, - pub x: f64, - pub y: f64, - pub z: f64, - pub ux: f64, - pub uy: f64, - pub uz: f64, - pub interaction_index: usize, -} - -/// Particle object. Particles in rustbca include incident ions and material atoms. -#[derive(Clone)] -pub struct Particle { - pub m: f64, - pub Z: f64, - pub E: f64, - pub Ec: f64, - pub Es: f64, - pub pos: Vector, - pub dir: Vector, - pub pos_old: Vector, - pub dir_old: Vector, - pub pos_origin: Vector, - pub energy_origin: f64, - pub asymptotic_deflection: f64, - pub stopped: bool, - pub left: bool, - pub incident: bool, - pub first_step: bool, - pub trajectory: Vec, - pub energies: Vec, - pub track_trajectories: bool, - pub number_collision_events: usize, - pub backreflected: bool, - pub interaction_index: usize, -} -impl Particle { - /// Construct a particle object from input. - pub fn from_input(input: ParticleInput, options: &Options) -> Particle { - let dirx = input.ux; - let diry = input.uy; - let dirz = input.uz; - - let dir_mag = (dirx*dirx + diry*diry + dirz*dirz).sqrt(); - - assert!((dirx/dir_mag).abs() < 1.0 - f64::EPSILON, "Input error: incident direction cannot round to exactly (1, 0, 0) due to gimbal lock. Use a non-zero y-component."); - assert!(input.E > 0., "Input error: incident energy {}; must be greater than zero.", input.E/EV); - - Particle { - m: input.m, - Z: input.Z, - E: input.E, - Ec: input.Ec, - Es: input.Es, - pos: Vector::new(input.x, input.y, input.z), - dir: Vector::new(dirx/dir_mag, diry/dir_mag, dirz/dir_mag), - pos_old: Vector::new(input.x, input.y, input.z), - dir_old: Vector::new(dirx/dir_mag, diry/dir_mag, dirz/dir_mag), - pos_origin: Vector::new(input.x, input.y, input.z), - energy_origin: input.E, - asymptotic_deflection: 0., - stopped: false, - left: false, - incident: true, - first_step: true, - trajectory: vec![Vector4::new(input.E, input.x, input.y, input.z)], - energies: vec![], - track_trajectories: options.track_trajectories, - number_collision_events: 0, - backreflected: false, - interaction_index: input.interaction_index, - } - } - - /// Particle constructor from raw inputs. - pub fn new(m: f64, Z: f64, E: f64, Ec: f64, Es: f64, x: f64, y: f64, z: f64, dirx: f64, diry: f64, dirz: f64, incident: bool, track_trajectories: bool, interaction_index: usize) -> Particle { - let dir_mag = (dirx*dirx + diry*diry + dirz*dirz).sqrt(); - - Particle { - m, - Z, - E, - Ec, - Es, - pos: Vector::new(x, y, z), - dir: Vector::new(dirx/dir_mag, diry/dir_mag, dirz/dir_mag), - pos_old: Vector::new(x, y, z), - dir_old: Vector::new(dirx/dir_mag, diry/dir_mag, dirz/dir_mag), - pos_origin: Vector::new(x, y, z), - energy_origin: E, - asymptotic_deflection: 0., - stopped: false, - left: false, - incident, - first_step: incident, - trajectory: vec![], - energies: vec![EnergyLoss::new(0., 0., x, y, z)], - track_trajectories, - number_collision_events: 0, - backreflected: false, - interaction_index, - } - } - - /// If `track_trajectories`, add the current (E, x, y, z) to the trajectory. - pub fn add_trajectory(&mut self) { - if self.track_trajectories { - self.trajectory.push(Vector4 {E: self.E, x: self.pos.x, y: self.pos.y, z: self.pos.z}); - } - } - - /// If `track_energy_losses`, add the most recent electronic and nuclear energy loss terms and (x, y, z) to the energy loss tracker. - pub fn energy_loss(&mut self, options: &Options, En: f64, Ee: f64) { - if self.incident & options.track_energy_losses { - self.energies.push(EnergyLoss {Ee, En, x: self.pos.x, y: self.pos.y, z: self.pos.z}); - } - } - - /// Get the current momentum. - pub fn get_momentum(&mut self) -> Vector { - let speed = (2.*self.E/self.m).sqrt(); - Vector::new( - self.m*speed*self.dir.x, - self.m*speed*self.dir.y, - self.m*speed*self.dir.z, - ) - } -} - -/// Rotate a particle by a deflection psi at an azimuthal angle phi. -pub fn rotate_particle(particle_1: &mut particle::Particle, psi: f64, phi: f64) { - let cosx: f64 = particle_1.dir.x; - let cosy: f64 = particle_1.dir.y; - let cosz: f64 = particle_1.dir.z; - let cphi: f64 = phi.cos(); - let sphi: f64 = phi.sin(); - let sa = (1. - cosx*cosx).sqrt(); - - //Particle direction update formulas from original TRIDYN paper, see Moeller and Eckstein 1988 - let cpsi: f64 = psi.cos(); - let spsi: f64 = psi.sin(); - let cosx_new: f64 = cpsi*cosx + spsi*cphi*sa; - let cosy_new: f64 = cpsi*cosy - spsi/sa*(cphi*cosx*cosy - sphi*cosz); - let cosz_new: f64 = cpsi*cosz - spsi/sa*(cphi*cosx*cosz + sphi*cosy); - - let dir_new = Vector {x: cosx_new, y: cosy_new, z: cosz_new}; - - particle_1.dir.assign(&dir_new); - particle_1.dir.normalize(); -} - -/// Push particle in space according to previous direction and return the distance traveled. -pub fn particle_advance(particle_1: &mut particle::Particle, mfp: f64, asymptotic_deflection: f64) -> f64 { - - if particle_1.E > particle_1.Ec { - particle_1.add_trajectory(); - } - - //Update previous position - particle_1.pos_old.x = particle_1.pos.x; - particle_1.pos_old.y = particle_1.pos.y; - particle_1.pos_old.z = particle_1.pos.z; - - //In order to keep average denisty constant, must add back previous asymptotic deflection - let distance_traveled = mfp + particle_1.asymptotic_deflection - asymptotic_deflection; - //let distance_traveled = mfp - asymptotic_deflection; - - //dir has been updated, so use previous direction to advance in space - particle_1.pos.x += particle_1.dir_old.x*distance_traveled; - particle_1.pos.y += particle_1.dir_old.y*distance_traveled; - particle_1.pos.z += particle_1.dir_old.z*distance_traveled; - particle_1.asymptotic_deflection = asymptotic_deflection; - - //Update previous direction - particle_1.dir_old.x = particle_1.dir.x; - particle_1.dir_old.y = particle_1.dir.y; - particle_1.dir_old.z = particle_1.dir.z; - - return distance_traveled; -} - -pub fn surface_refraction(particle: &mut Particle, normal: Vector, Es: f64) { - let E = particle.E; - - let costheta = particle.dir.dot(&normal); - - let a = (E/(E + Es)).sqrt(); - let b = -(E).sqrt()*costheta; - let c = (E*costheta.powi(2) + Es).sqrt(); - - let u1x = (E/(E + Es)).sqrt()*particle.dir.x + ((-(E).sqrt()*costheta + (E*costheta.powi(2) + Es).sqrt())/(E + Es).sqrt())*normal.x; - let u1y = (E/(E + Es)).sqrt()*particle.dir.y + ((-(E).sqrt()*costheta + (E*costheta.powi(2) + Es).sqrt())/(E + Es).sqrt())*normal.y; - let u1z = (E/(E + Es)).sqrt()*particle.dir.z + ((-(E).sqrt()*costheta + (E*costheta.powi(2) + Es).sqrt())/(E + Es).sqrt())*normal.z; - particle.dir.x = u1x; - particle.dir.y = u1y; - particle.dir.z = u1z; - particle.E += Es; -} - -/// Calcualte the refraction angle based on the surface binding energy of the material. -pub fn refraction_angle(costheta: f64, energy_old: f64, energy_new: f64) -> f64 { - let costheta = if costheta.abs() > 1. {costheta.signum()} else {costheta}; - let sintheta0 = (1. - costheta*costheta).sqrt(); - let sintheta1 = sintheta0*(energy_old/energy_new).sqrt(); - let delta_theta = sintheta1.asin() - sintheta0.asin(); - assert!(!delta_theta.is_nan(), "Numerical error: refraction returned NaN."); - let sign = -costheta.signum(); - return sign*delta_theta; -} +use super::*; + +/// Rustbca's internal representation of the particle_parameters input. + + +#[cfg(feature = "hdf5_input")] +#[derive(Deserialize, Clone)] +pub struct ParticleParameters { + pub particle_input_filename: String, + pub length_unit: String, + pub energy_unit: String, + pub mass_unit: String, + pub N: Vec, + pub m: Vec, + pub Z: Vec, + pub E: Vec, + pub Ec: Vec, + pub Es: Vec, + pub pos: Vec<(f64, f64, f64)>, + pub dir: Vec<(f64, f64, f64)>, + pub interaction_index: Vec +} + +#[cfg(not(feature = "hdf5_input"))] +#[derive(Deserialize, Clone)] +pub struct ParticleParameters { + pub length_unit: String, + pub energy_unit: String, + pub mass_unit: String, + pub N: Vec, + pub m: Vec, + pub Z: Vec, + pub E: Vec, + pub Ec: Vec, + pub Es: Vec, + pub pos: Vec<(f64, f64, f64)>, + pub dir: Vec<(f64, f64, f64)>, + pub interaction_index: Vec +} + +/// HDF5 version of particle input. +#[derive(Clone, PartialEq, Debug, Copy)] +#[cfg_attr(feature = "hdf5_input", derive(hdf5::H5Type))] +#[repr(C)] +pub struct ParticleInput { + pub m: f64, + pub Z: f64, + pub E: f64, + pub Ec: f64, + pub Es: f64, + pub x: f64, + pub y: f64, + pub z: f64, + pub ux: f64, + pub uy: f64, + pub uz: f64, + pub interaction_index: usize, +} + +/// Particle object. Particles in rustbca include incident ions and material atoms. +#[derive(Clone)] +pub struct Particle { + pub m: f64, + pub Z: f64, + pub E: f64, + pub Ec: f64, + pub Es: f64, + pub pos: Vector, + pub dir: Vector, + pub pos_old: Vector, + pub dir_old: Vector, + pub pos_origin: Vector, + pub energy_origin: f64, + pub asymptotic_deflection: f64, + pub stopped: bool, + pub left: bool, + pub incident: bool, + pub first_step: bool, + pub trajectory: Vec, + pub energies: Vec, + pub track_trajectories: bool, + pub number_collision_events: usize, + pub backreflected: bool, + pub interaction_index: usize, +} +impl Particle { + /// Construct a particle object from input. + pub fn from_input(input: ParticleInput, options: &Options) -> Particle { + let dirx = input.ux; + let diry = input.uy; + let dirz = input.uz; + + let dir_mag = (dirx*dirx + diry*diry + dirz*dirz).sqrt(); + + assert!((dirx/dir_mag).abs() < 1.0 - f64::EPSILON, "Input error: incident direction cannot round to exactly (1, 0, 0) due to gimbal lock. Use a non-zero y-component."); + assert!(input.E > 0., "Input error: incident energy {}; must be greater than zero.", input.E/EV); + + Particle { + m: input.m, + Z: input.Z, + E: input.E, + Ec: input.Ec, + Es: input.Es, + pos: Vector::new(input.x, input.y, input.z), + dir: Vector::new(dirx/dir_mag, diry/dir_mag, dirz/dir_mag), + pos_old: Vector::new(input.x, input.y, input.z), + dir_old: Vector::new(dirx/dir_mag, diry/dir_mag, dirz/dir_mag), + pos_origin: Vector::new(input.x, input.y, input.z), + energy_origin: input.E, + asymptotic_deflection: 0., + stopped: false, + left: false, + incident: true, + first_step: true, + trajectory: vec![Vector4::new(input.E, input.x, input.y, input.z)], + energies: vec![], + track_trajectories: options.track_trajectories, + number_collision_events: 0, + backreflected: false, + interaction_index: input.interaction_index, + } + } + + /// Particle constructor from raw inputs. + pub fn new(m: f64, Z: f64, E: f64, Ec: f64, Es: f64, x: f64, y: f64, z: f64, dirx: f64, diry: f64, dirz: f64, incident: bool, track_trajectories: bool, interaction_index: usize) -> Particle { + let dir_mag = (dirx*dirx + diry*diry + dirz*dirz).sqrt(); + + Particle { + m, + Z, + E, + Ec, + Es, + pos: Vector::new(x, y, z), + dir: Vector::new(dirx/dir_mag, diry/dir_mag, dirz/dir_mag), + pos_old: Vector::new(x, y, z), + dir_old: Vector::new(dirx/dir_mag, diry/dir_mag, dirz/dir_mag), + pos_origin: Vector::new(x, y, z), + energy_origin: E, + asymptotic_deflection: 0., + stopped: false, + left: false, + incident, + first_step: incident, + trajectory: vec![], + energies: vec![EnergyLoss::new(0., 0., x, y, z)], + track_trajectories, + number_collision_events: 0, + backreflected: false, + interaction_index, + } + } + + /// If `track_trajectories`, add the current (E, x, y, z) to the trajectory. + pub fn add_trajectory(&mut self) { + if self.track_trajectories { + self.trajectory.push(Vector4 {E: self.E, x: self.pos.x, y: self.pos.y, z: self.pos.z}); + } + } + + /// If `track_energy_losses`, add the most recent electronic and nuclear energy loss terms and (x, y, z) to the energy loss tracker. + pub fn energy_loss(&mut self, options: &Options, En: f64, Ee: f64) { + if self.incident & options.track_energy_losses { + self.energies.push(EnergyLoss {Ee, En, x: self.pos.x, y: self.pos.y, z: self.pos.z}); + } + } + + /// Get the current momentum. + pub fn get_momentum(&mut self) -> Vector { + let speed = (2.*self.E/self.m).sqrt(); + Vector::new( + self.m*speed*self.dir.x, + self.m*speed*self.dir.y, + self.m*speed*self.dir.z, + ) + } +} + +/// Rotate a particle by a deflection psi at an azimuthal angle phi. +pub fn rotate_particle(particle_1: &mut particle::Particle, psi: f64, phi: f64) { + let cosx: f64 = particle_1.dir.x; + let cosy: f64 = particle_1.dir.y; + let cosz: f64 = particle_1.dir.z; + let cphi: f64 = phi.cos(); + let sphi: f64 = phi.sin(); + let sa = (1. - cosx*cosx).sqrt(); + + //Particle direction update formulas from original TRIDYN paper, see Moeller and Eckstein 1988 + let cpsi: f64 = psi.cos(); + let spsi: f64 = psi.sin(); + let cosx_new: f64 = cpsi*cosx + spsi*cphi*sa; + let cosy_new: f64 = cpsi*cosy - spsi/sa*(cphi*cosx*cosy - sphi*cosz); + let cosz_new: f64 = cpsi*cosz - spsi/sa*(cphi*cosx*cosz + sphi*cosy); + + let dir_new = Vector {x: cosx_new, y: cosy_new, z: cosz_new}; + + particle_1.dir.assign(&dir_new); + particle_1.dir.normalize(); +} + +/// Push particle in space according to previous direction and return the distance traveled. +pub fn particle_advance(particle_1: &mut particle::Particle, mfp: f64, asymptotic_deflection: f64) -> f64 { + + if particle_1.E > particle_1.Ec { + particle_1.add_trajectory(); + } + + //Update previous position + particle_1.pos_old.x = particle_1.pos.x; + particle_1.pos_old.y = particle_1.pos.y; + particle_1.pos_old.z = particle_1.pos.z; + + //In order to keep average denisty constant, must add back previous asymptotic deflection + let distance_traveled = mfp + particle_1.asymptotic_deflection - asymptotic_deflection; + //let distance_traveled = mfp - asymptotic_deflection; + + //dir has been updated, so use previous direction to advance in space + particle_1.pos.x += particle_1.dir_old.x*distance_traveled; + particle_1.pos.y += particle_1.dir_old.y*distance_traveled; + particle_1.pos.z += particle_1.dir_old.z*distance_traveled; + particle_1.asymptotic_deflection = asymptotic_deflection; + + //Update previous direction + particle_1.dir_old.x = particle_1.dir.x; + particle_1.dir_old.y = particle_1.dir.y; + particle_1.dir_old.z = particle_1.dir.z; + + return distance_traveled; +} + +pub fn surface_refraction(particle: &mut Particle, normal: Vector, Es: f64) { + let E = particle.E; + + let costheta = particle.dir.dot(&normal); + + let a = (E/(E + Es)).sqrt(); + let b = -(E).sqrt()*costheta; + let c = (E*costheta.powi(2) + Es).sqrt(); + + let u1x = (E/(E + Es)).sqrt()*particle.dir.x + ((-(E).sqrt()*costheta + (E*costheta.powi(2) + Es).sqrt())/(E + Es).sqrt())*normal.x; + let u1y = (E/(E + Es)).sqrt()*particle.dir.y + ((-(E).sqrt()*costheta + (E*costheta.powi(2) + Es).sqrt())/(E + Es).sqrt())*normal.y; + let u1z = (E/(E + Es)).sqrt()*particle.dir.z + ((-(E).sqrt()*costheta + (E*costheta.powi(2) + Es).sqrt())/(E + Es).sqrt())*normal.z; + particle.dir.x = u1x; + particle.dir.y = u1y; + particle.dir.z = u1z; + particle.E += Es; +} + +/// Calcualte the refraction angle based on the surface binding energy of the material. +pub fn refraction_angle(costheta: f64, energy_old: f64, energy_new: f64) -> f64 { + let costheta = if costheta.abs() > 1. {costheta.signum()} else {costheta}; + let sintheta0 = (1. - costheta*costheta).sqrt(); + let sintheta1 = sintheta0*(energy_old/energy_new).sqrt(); + let delta_theta = sintheta1.asin() - sintheta0.asin(); + assert!(!delta_theta.is_nan(), "Numerical error: refraction returned NaN."); + let sign = -costheta.signum(); + return sign*delta_theta; +} diff --git a/src/sphere.rs b/src/sphere.rs index e5bd862..80034cb 100644 --- a/src/sphere.rs +++ b/src/sphere.rs @@ -1,122 +1,122 @@ -use super::*; - -#[derive(Deserialize, Clone)] -pub struct InputSphere { - pub options: Options, - pub material_parameters: material::MaterialParameters, - pub particle_parameters: particle::ParticleParameters, - pub geometry_input: sphere::SphereInput, -} - -impl InputFile for InputSphere { - - fn new(string: &str) -> InputSphere { - toml::from_str(string).expect("Could not parse TOML file.") - } - - fn get_options(&self) -> &Options{ - &self.options - } - fn get_material_parameters(&self) -> &material::MaterialParameters{ - &self.material_parameters - } - fn get_particle_parameters(&self) ->& particle::ParticleParameters{ - &self.particle_parameters - } - fn get_geometry_input(&self) -> &Self::GeometryInput{ - &self.geometry_input - } -} - -#[derive(Deserialize, Clone)] -pub struct SphereInput { - pub length_unit: String, - pub radius: f64, - pub densities: Vec, - pub electronic_stopping_correction_factor: f64, -} - -#[derive(Clone)] -pub struct Sphere { - pub densities: Vec, - pub concentrations: Vec, - pub radius: f64, - pub electronic_stopping_correction_factor: f64, - pub energy_barrier_thickness: f64, -} - -impl GeometryInput for InputSphere { - type GeometryInput = SphereInput; -} - -impl Geometry for Sphere { - - type InputFileFormat = InputSphere; - - fn new(input: &<::InputFileFormat as GeometryInput>::GeometryInput) -> Sphere { - - let length_unit: f64 = match input.length_unit.as_str() { - "MICRON" => MICRON, - "CM" => CM, - "ANGSTROM" => ANGSTROM, - "NM" => NM, - "M" => 1., - _ => input.length_unit.parse() - .expect(format!( - "Input errror: could nor parse length unit {}. Use a valid float or one of ANGSTROM, NM, MICRON, CM, MM, M", - &input.length_unit.as_str() - ).as_str()), - }; - - let electronic_stopping_correction_factor = input.electronic_stopping_correction_factor; - let densities: Vec = input.densities.iter().map(|element| element/(length_unit).powi(3)).collect(); - let total_density: f64 = densities.iter().sum(); - let energy_barrier_thickness = total_density.powf(-1./3.)/SQRTPI*2.; - let concentrations: Vec = densities.iter().map(|&density| density/total_density).collect::>(); - let radius = input.radius*length_unit; - - Sphere { - densities, - concentrations, - radius, - electronic_stopping_correction_factor, - energy_barrier_thickness, - } - } - - fn get_densities(&self, x: f64, y: f64, z: f64) -> &Vec { - &self.densities - } - fn get_ck(&self, x: f64, y: f64, z: f64) -> f64 { - self.electronic_stopping_correction_factor - } - fn get_total_density(&self, x: f64, y: f64, z: f64) -> f64{ - self.densities.iter().sum() - } - fn get_concentrations(&self, x: f64, y: f64, z: f64) -> &Vec { - &self.concentrations - } - fn inside(&self, x: f64, y: f64, z: f64) -> bool { - let r = (x.powi(2) + y.powi(2) + z.powi(2)).sqrt(); - r < self.radius - } - - fn inside_simulation_boundary(&self, x: f64, y: f64, z: f64) -> bool { - let r = (x.powi(2) + y.powi(2) + z.powi(2)).sqrt(); - r < (10.*self.energy_barrier_thickness + self.radius) - } - - fn inside_energy_barrier(&self, x: f64, y: f64, z: f64) -> bool { - let r = (x.powi(2) + y.powi(2) + z.powi(2)).sqrt(); - r < (self.energy_barrier_thickness + self.radius) - } - - fn closest_point(&self, x: f64, y: f64, z: f64) -> (f64, f64, f64) { - let r = (x.powi(2) + y.powi(2) + z.powi(2)).sqrt(); - let R = self.radius; - let ux = x/r; - let uy = y/r; - let uz = z/r; - (ux*R, uy*R, uz*R) - } -} +use super::*; + +#[derive(Deserialize, Clone)] +pub struct InputSphere { + pub options: Options, + pub material_parameters: material::MaterialParameters, + pub particle_parameters: particle::ParticleParameters, + pub geometry_input: sphere::SphereInput, +} + +impl InputFile for InputSphere { + + fn new(string: &str) -> InputSphere { + toml::from_str(string).expect("Could not parse TOML file.") + } + + fn get_options(&self) -> &Options{ + &self.options + } + fn get_material_parameters(&self) -> &material::MaterialParameters{ + &self.material_parameters + } + fn get_particle_parameters(&self) ->& particle::ParticleParameters{ + &self.particle_parameters + } + fn get_geometry_input(&self) -> &Self::GeometryInput{ + &self.geometry_input + } +} + +#[derive(Deserialize, Clone)] +pub struct SphereInput { + pub length_unit: String, + pub radius: f64, + pub densities: Vec, + pub electronic_stopping_correction_factor: f64, +} + +#[derive(Clone)] +pub struct Sphere { + pub densities: Vec, + pub concentrations: Vec, + pub radius: f64, + pub electronic_stopping_correction_factor: f64, + pub energy_barrier_thickness: f64, +} + +impl GeometryInput for InputSphere { + type GeometryInput = SphereInput; +} + +impl Geometry for Sphere { + + type InputFileFormat = InputSphere; + + fn new(input: &<::InputFileFormat as GeometryInput>::GeometryInput) -> Sphere { + + let length_unit: f64 = match input.length_unit.as_str() { + "MICRON" => MICRON, + "CM" => CM, + "ANGSTROM" => ANGSTROM, + "NM" => NM, + "M" => 1., + _ => input.length_unit.parse() + .expect(format!( + "Input errror: could nor parse length unit {}. Use a valid float or one of ANGSTROM, NM, MICRON, CM, MM, M", + &input.length_unit.as_str() + ).as_str()), + }; + + let electronic_stopping_correction_factor = input.electronic_stopping_correction_factor; + let densities: Vec = input.densities.iter().map(|element| element/(length_unit).powi(3)).collect(); + let total_density: f64 = densities.iter().sum(); + let energy_barrier_thickness = total_density.powf(-1./3.)/SQRTPI*2.; + let concentrations: Vec = densities.iter().map(|&density| density/total_density).collect::>(); + let radius = input.radius*length_unit; + + Sphere { + densities, + concentrations, + radius, + electronic_stopping_correction_factor, + energy_barrier_thickness, + } + } + + fn get_densities(&self, x: f64, y: f64, z: f64) -> &Vec { + &self.densities + } + fn get_ck(&self, x: f64, y: f64, z: f64) -> f64 { + self.electronic_stopping_correction_factor + } + fn get_total_density(&self, x: f64, y: f64, z: f64) -> f64{ + self.densities.iter().sum() + } + fn get_concentrations(&self, x: f64, y: f64, z: f64) -> &Vec { + &self.concentrations + } + fn inside(&self, x: f64, y: f64, z: f64) -> bool { + let r = (x.powi(2) + y.powi(2) + z.powi(2)).sqrt(); + r < self.radius + } + + fn inside_simulation_boundary(&self, x: f64, y: f64, z: f64) -> bool { + let r = (x.powi(2) + y.powi(2) + z.powi(2)).sqrt(); + r < (10.*self.energy_barrier_thickness + self.radius) + } + + fn inside_energy_barrier(&self, x: f64, y: f64, z: f64) -> bool { + let r = (x.powi(2) + y.powi(2) + z.powi(2)).sqrt(); + r < (self.energy_barrier_thickness + self.radius) + } + + fn closest_point(&self, x: f64, y: f64, z: f64) -> (f64, f64, f64) { + let r = (x.powi(2) + y.powi(2) + z.powi(2)).sqrt(); + let R = self.radius; + let ux = x/r; + let uy = y/r; + let uz = z/r; + (ux*R, uy*R, uz*R) + } +} diff --git a/src/structs.rs b/src/structs.rs index 2505da7..8aa611a 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -1,87 +1,87 @@ -/// 3D vector. -#[derive(Clone)] -pub struct Vector { - pub x: f64, - pub y: f64, - pub z: f64, -} -impl Vector { - pub fn new(x: f64, y: f64, z: f64) -> Vector { - Vector { - x, - y, - z - } - } - - /// Calculates vector magnitude. - pub fn magnitude(&self) -> f64 { - (self.x*self.x + self.y*self.y + self.z*self.z).sqrt() - } - - /// Assigns vector values from another vector. - pub fn assign(&mut self, other: &Vector) { - self.x = other.x; - self.y = other.y; - self.z = other.z; - } - - /// Normalizes vector components to magnitude 1. - pub fn normalize(&mut self) { - let magnitude = self.magnitude(); - self.x /= magnitude; - self.y /= magnitude; - self.z /= magnitude; - } - - /// Add this vector and another and return a new vector. - pub fn add(&self, other: &Vector) -> Vector { - Vector::new(self.x + other.x, self.y + other.y, self.z + other.z) - } - - pub fn dot(&self, other: &Vector) -> f64 { - self.x*other.x + self.y*other.y + self.z*other.z - } -} - -/// Vector4 is a trajectory-tracking object that includes x, y, z, and the current energy. -#[derive(Clone)] -pub struct Vector4 { - pub E: f64, - pub x: f64, - pub y: f64, - pub z: f64, -} - -impl Vector4 { - pub fn new(E: f64, x: f64, y: f64, z: f64) -> Vector4 { - Vector4 { - E, - x, - y, - z - } - } -} - -/// Energy loss is an output tracker that tracks the separate nuclear and electronic energy losses. -#[derive(Clone)] -pub struct EnergyLoss { - pub En: f64, - pub Ee: f64, - pub x: f64, - pub y: f64, - pub z: f64, -} - -impl EnergyLoss { - pub fn new(Ee: f64, En: f64, x: f64, y: f64, z: f64) -> EnergyLoss { - EnergyLoss { - En, - Ee, - x, - y, - z - } - } -} +/// 3D vector. +#[derive(Clone)] +pub struct Vector { + pub x: f64, + pub y: f64, + pub z: f64, +} +impl Vector { + pub fn new(x: f64, y: f64, z: f64) -> Vector { + Vector { + x, + y, + z + } + } + + /// Calculates vector magnitude. + pub fn magnitude(&self) -> f64 { + (self.x*self.x + self.y*self.y + self.z*self.z).sqrt() + } + + /// Assigns vector values from another vector. + pub fn assign(&mut self, other: &Vector) { + self.x = other.x; + self.y = other.y; + self.z = other.z; + } + + /// Normalizes vector components to magnitude 1. + pub fn normalize(&mut self) { + let magnitude = self.magnitude(); + self.x /= magnitude; + self.y /= magnitude; + self.z /= magnitude; + } + + /// Add this vector and another and return a new vector. + pub fn add(&self, other: &Vector) -> Vector { + Vector::new(self.x + other.x, self.y + other.y, self.z + other.z) + } + + pub fn dot(&self, other: &Vector) -> f64 { + self.x*other.x + self.y*other.y + self.z*other.z + } +} + +/// Vector4 is a trajectory-tracking object that includes x, y, z, and the current energy. +#[derive(Clone)] +pub struct Vector4 { + pub E: f64, + pub x: f64, + pub y: f64, + pub z: f64, +} + +impl Vector4 { + pub fn new(E: f64, x: f64, y: f64, z: f64) -> Vector4 { + Vector4 { + E, + x, + y, + z + } + } +} + +/// Energy loss is an output tracker that tracks the separate nuclear and electronic energy losses. +#[derive(Clone)] +pub struct EnergyLoss { + pub En: f64, + pub Ee: f64, + pub x: f64, + pub y: f64, + pub z: f64, +} + +impl EnergyLoss { + pub fn new(Ee: f64, En: f64, x: f64, y: f64, z: f64) -> EnergyLoss { + EnergyLoss { + En, + Ee, + x, + y, + z + } + } +} diff --git a/src/tests.rs b/src/tests.rs index 23d41bb..f93667b 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,1054 +1,1054 @@ -#[cfg(test)] -use super::*; -#[cfg(test)] -use float_cmp::*; - -#[test] -#[cfg(feature = "parry3d")] -fn test_parry_cuboid() { - let mass = 1.; - let Z = 1.; - let E = 10.*EV; - let Ec = 1.*EV; - let Es = 5.76*EV; - let x = 0.; - let y = 0.; - let z = 0.; - let cosx = 1./(2.0_f64).sqrt(); - let cosy = 1./(2.0_f64).sqrt(); - let cosz = 0.; - let particle_1 = particle::Particle::new(mass, Z, E, Ec, Es, x, y, z, cosx, cosy, cosz, false, false, 0); - - let material_parameters = material::MaterialParameters{ - energy_unit: "EV".to_string(), - mass_unit: "AMU".to_string(), - Eb: vec![0.0, 0.0], - Es: vec![2.0, 4.0], - Ec: vec![1.0, 1.0], - Z: vec![29., 1.], - m: vec![63.54, 1.0008], - interaction_index: vec![0, 0], - surface_binding_model: SurfaceBindingModel::PLANAR{calculation: SurfaceBindingCalculation::TARGET}, - bulk_binding_model: BulkBindingModel::INDIVIDUAL, - }; - - let radius = 1000.*ANGSTROM; - - //cuboid centered at (0, 0, 0) with l=w=h=1 Angstrom - let geometry_input = parry::ParryTriMeshInput { - length_unit: "ANGSTROM".to_string(), - densities: vec![0.03, 0.03], - electronic_stopping_correction_factor: 1.0, - vertices: vec![ - [-0.5, -0.5, 0.5], - [0.5, -0.5, 0.5], - [-0.5, 0.5, 0.5], - [0.5, 0.5, 0.5], - [-0.5, 0.5, -0.5], - [0.5, 0.5, -0.5], - [-0.5, -0.5, -0.5], - [0.5, -0.5, -0.5], - ], - indices: vec![ - [0, 1, 3], - [0, 3, 2], - [2, 3, 5], - [2, 5, 4], - [4, 5, 7], - [4, 7, 6], - [6, 7, 1], - [6, 1, 0], - [1, 7, 5], - [1, 5, 3], - [6, 0, 2], - [6, 2, 4] - ] - }; - - let mut material_cuboid: material::Material = material::Material::::new(&material_parameters, &geometry_input); - material_cuboid.geometry.energy_barrier_thickness = 10.*ANGSTROM; - - let surface_binding_energy = material_cuboid.actual_surface_binding_energy(&particle_1, particle_1.pos.x, particle_1.pos.y, particle_1.pos.z); - assert!(approx_eq!(f64, surface_binding_energy/EV, (2. + 4.)/2., epsilon=1E-24)); - - //Test origin is inside - assert!(material_cuboid.geometry.inside(0., 0., 0.)); - - //Test all outside areas for containment - assert!(!material_cuboid.geometry.inside(0., 1., 0.)); - assert!(!material_cuboid.geometry.inside(0., -1., 0.)); - assert!(!material_cuboid.geometry.inside(1., 0., 0.)); - assert!(!material_cuboid.geometry.inside(-1., 0., 0.)); - assert!(!material_cuboid.geometry.inside(0., 0., -1.)); - assert!(!material_cuboid.geometry.inside(0., 0., -1.)); - - //distance to origin - let (x, y, z) = material_cuboid.geometry.closest_point(0., 0., 0.); - assert!(approx_eq!(f64, (x.powi(2) + y.powi(2) + z.powi(2)).sqrt(), 0.5*ANGSTROM, epsilon=1E-24)); - - //distance to wall should be zero at wall - let (x, y, z) = material_cuboid.geometry.closest_point(0.5*ANGSTROM, 0., 0.); - assert!(approx_eq!(f64, ((x - 0.5*ANGSTROM).powi(2) + y.powi(2) + z.powi(2)).sqrt(), 0.0, epsilon=1E-24)); -} - -#[test] -#[cfg(feature = "parry3d")] -fn test_parry_sphere() { - let mass = 1.; - let Z = 1.; - let E = 10.*EV; - let Ec = 1.*EV; - let Es = 5.76*EV; - let x = 0.; - let y = 0.; - let z = 0.; - let cosx = 1./(2.0_f64).sqrt(); - let cosy = 1./(2.0_f64).sqrt(); - let cosz = 0.; - let mut particle_1 = particle::Particle::new(mass, Z, E, Ec, Es, x, y, z, cosx, cosy, cosz, false, false, 0); - - let material_parameters = material::MaterialParameters{ - energy_unit: "EV".to_string(), - mass_unit: "AMU".to_string(), - Eb: vec![0.0, 0.0], - Es: vec![2.0, 4.0], - Ec: vec![1.0, 1.0], - Z: vec![29., 1.], - m: vec![63.54, 1.0008], - interaction_index: vec![0, 0], - surface_binding_model: SurfaceBindingModel::PLANAR{calculation: SurfaceBindingCalculation::TARGET}, - bulk_binding_model: BulkBindingModel::INDIVIDUAL, - }; - - let radius = 1000.*ANGSTROM; - - let geometry_input_sphere = parry::ParryBallInput { - length_unit: "ANGSTROM".to_string(), - radius: 1000.0, - densities: vec![0.03, 0.03], - electronic_stopping_correction_factor: 1.0 - }; - - let mut material_sphere: material::Material = material::Material::::new(&material_parameters, &geometry_input_sphere); - material_sphere.geometry.energy_barrier_thickness = 10.*ANGSTROM; - - particle_1.pos.x = 500.*ANGSTROM; - particle_1.pos.y = 0.; - - particle_1.pos_old.x = -1500.*ANGSTROM; - particle_1.pos_old.y = 0.; - - let inside_sphere = material_sphere.inside(particle_1.pos.x, particle_1.pos.y, particle_1.pos.z); - let inside_old_sphere = material_sphere.inside(particle_1.pos_old.x, particle_1.pos_old.y, particle_1.pos_old.z); - - //println!("{} {}", inside, inside_old); - assert!(inside_sphere); - assert!(!inside_old_sphere); - - //Test concentration-dependent surface binding energy - let surface_binding_energy = material_sphere.actual_surface_binding_energy(&particle_1, particle_1.pos.x, particle_1.pos.y, particle_1.pos.z); - assert!(approx_eq!(f64, surface_binding_energy/EV, (2. + 4.)/2., epsilon=1E-24)); - - assert!(material_sphere.inside_energy_barrier(0., 0., 0.)); - assert!(material_sphere.inside_simulation_boundary(0., 0., 0.)); - assert!(material_sphere.inside(0., 0., 0.)); - - let ux = 1214.0*ANGSTROM; - let uy = 123123.0*ANGSTROM; - let uz = 1239.0*ANGSTROM; - let r = (ux.powi(2) + uy.powi(2) + uz.powi(2)).sqrt(); - let R = 1001.0*ANGSTROM; - let u = (ux/r*R, uy/r*R, uz/r*R); - assert!(!material_sphere.inside(u.0, u.1, u.2)); - assert!(material_sphere.inside_energy_barrier(u.0, u.1, u.2)); - assert!(material_sphere.inside_simulation_boundary(u.0, u.1, u.2)); - - assert!(material_sphere.inside_energy_barrier(0., 0., 0.)); - assert!(material_sphere.inside_simulation_boundary(0., 0., 0.)); - assert!(material_sphere.inside(0., 0., 0.)); - - assert!(material_sphere.inside_energy_barrier(-1000.0*ANGSTROM - 1.0*ANGSTROM, 0., 0.)); - assert!(!material_sphere.inside_energy_barrier(-1000.0*ANGSTROM - 11.0*ANGSTROM, 0., 0.)); - - assert!(material_sphere.inside_energy_barrier(0., -1000.0*ANGSTROM - 1.0*ANGSTROM, 0.)); - assert!(!material_sphere.inside_energy_barrier(0., -1000.0*ANGSTROM - 11.0*ANGSTROM, 0.)); - - assert!(material_sphere.inside_energy_barrier(0., 0., -1000.0*ANGSTROM - 1.0*ANGSTROM)); - assert!(!material_sphere.inside_energy_barrier(0., 0., -1000.0*ANGSTROM - 11.0*ANGSTROM)); - - assert!(material_sphere.inside(-1000.0*ANGSTROM + 1.0*ANGSTROM, 0., 0.)); - assert!(!material_sphere.inside(-1000.0*ANGSTROM - 1.0*ANGSTROM, 0., 0.)); - - assert!(material_sphere.inside(0., -1000.0*ANGSTROM + 1.0*ANGSTROM, 0.)); - assert!(!material_sphere.inside(0., -1000.0*ANGSTROM - 1.0*ANGSTROM, 0.)); - - assert!(material_sphere.inside(0., 0., -1000.0*ANGSTROM + 1.0*ANGSTROM)); - assert!(!material_sphere.inside(0., 0., -1000.0*ANGSTROM - 1.0*ANGSTROM)); - - assert!(approx_eq!(f64, material_sphere.closest_point(-2000.*ANGSTROM, 0., 0.).0, (-1000.*ANGSTROM, 0., 0.).0, epsilon=1E-12)); - assert!(approx_eq!(f64, material_sphere.closest_point(0., -2000.*ANGSTROM, 0.).1, (0., -1000.*ANGSTROM, 0.).1, epsilon=1E-12)); - assert!(approx_eq!(f64, material_sphere.closest_point(0., 0., -2000.*ANGSTROM).2, (0., 0., -1000.*ANGSTROM).2, epsilon=1E-12)); -} - -#[test] -#[cfg(feature = "distributions")] -fn test_distributions() { - - let options = Options { - name: "test".to_string(), - track_trajectories: false, - track_recoils: false, - track_recoil_trajectories: false, - write_buffer_size: 8000, - weak_collision_order: 0, - suppress_deep_recoils: false, - high_energy_free_flight_paths: false, - electronic_stopping_mode: ElectronicStoppingMode::INTERPOLATED, - mean_free_path_model: MeanFreePathModel::LIQUID, - interaction_potential: vec![vec![InteractionPotential::KR_C]], - scattering_integral: vec![vec![ScatteringIntegral::MENDENHALL_WELLER]], - num_threads: 1, - num_chunks: 1, - use_hdf5: false, - root_finder: vec![vec![Rootfinder::NEWTON{max_iterations: 100, tolerance: 1E-3}]], - track_displacements: false, - track_energy_losses: true, - energy_min: 0.0, - energy_max: 10.0, - energy_num: 11, - angle_min: 0.0, - angle_max: 90.0, - angle_num: 11, - x_min: 0.0, - y_min: -10.0, - z_min: -10.0, - x_max: 10.0, - y_max: 10.0, - z_max: 10.0, - x_num: 11, - y_num: 11, - z_num: 11, - }; - - let output_units = OutputUnits { - length_unit: 1.0, - energy_unit: 1.0, - mass_unit: 1.0, - }; - - let mass = 1.0; - let Z = 1.0; - let E = 1.5; - let Ec = 0.0; - let Es = 0.0; - let x = 0.0; - let y = 0.0; - let z = 0.0; - let cosx = 0.0; - let cosy = 0.0; - let cosz = 0.0; - let mut particle = particle::Particle::new(mass, Z, E, Ec, Es, x, y, z, cosx, cosy, cosz, false, false, 0); - - - - let mut distributions = output::Distributions::new(&options); - assert_eq!(distributions.x_range[0], 0.); - assert_eq!(distributions.x_range[distributions.x_range.len() - 1], 10.); - assert_eq!(distributions.y_range[0], -10.); - assert_eq!(distributions.y_range[distributions.y_range.len() - 1], 10.); - assert_eq!(distributions.z_range[0], -10.); - assert_eq!(distributions.z_range[distributions.z_range.len() - 1], 10.); - assert_eq!(distributions.x_range.len(), 11); - assert_eq!(distributions.y_range.len(), 11); - assert_eq!(distributions.z_range.len(), 11); - - particle.incident = true; - particle.stopped = true; - particle.pos.x = 0.0; - distributions.update(&particle, &output_units, &options, 1); - assert_eq!(distributions.implanted_x[0], 1); - - particle.pos.x = 10.0; - distributions.update(&particle, &output_units, &options, 1); - assert_eq!(distributions.implanted_x[10], 1); - - distributions.update(&particle, &output_units, &options, 1); - assert_eq!(distributions.implanted_y[5], 3); - assert_eq!(distributions.implanted_z[5], 3); - - particle.incident = false; - particle.stopped = false; - particle.left = true; - particle.E = 0.0; - particle.dir.x = -0.999; - particle.dir.y = 0.001; - particle.dir.z = 0.0; - particle.dir.normalize(); - - distributions.update(&particle, &output_units, &options, 1); - assert_eq!(distributions.sputtered_ead[[0, 0]], 1); - - particle.E = -0.1; - distributions.update(&particle, &output_units, &options, 1); - assert_eq!(distributions.sputtered_ead[[0, 0]], 1); - - particle.E = 10.0; - distributions.update(&particle, &output_units, &options, 1); - assert_eq!(distributions.sputtered_ead[[10, 0]], 1); - - particle.dir = Vector::new(-0.707, 0.707, 0.0); - particle.dir.normalize(); - distributions.update(&particle, &output_units, &options, 1); - assert_eq!(distributions.sputtered_ead[[10, 5]], 1); - - particle.incident = true; - particle.E = 0.0; - particle.dir.x = -0.999; - particle.dir.y = 0.001; - particle.dir.z = 0.0; - particle.dir.normalize(); - - distributions.update(&particle, &output_units, &options, 1); - assert_eq!(distributions.reflected_ead[[0, 0]], 1); - - particle.E = -0.1; - distributions.update(&particle, &output_units, &options, 1); - assert_eq!(distributions.reflected_ead[[0, 0]], 1); - - particle.E = 10.0; - distributions.update(&particle, &output_units, &options, 1); - assert_eq!(distributions.reflected_ead[[10, 0]], 1); - - particle.dir = Vector::new(-0.707, 0.707, 0.0); - particle.dir.normalize(); - distributions.update(&particle, &output_units, &options, 1); - assert_eq!(distributions.reflected_ead[[10, 5]], 1); -} - -#[test] -fn test_spherical_geometry() { - let mass = 1.; - let Z = 1.; - let E = 10.*EV; - let Ec = 1.*EV; - let Es = 5.76*EV; - let x = 0.; - let y = 0.; - let z = 0.; - let cosx = 1./(2.0_f64).sqrt(); - let cosy = 1./(2.0_f64).sqrt(); - let cosz = 0.; - let mut particle_1 = particle::Particle::new(mass, Z, E, Ec, Es, x, y, z, cosx, cosy, cosz, false, false, 0); - - let material_parameters = material::MaterialParameters{ - energy_unit: "EV".to_string(), - mass_unit: "AMU".to_string(), - Eb: vec![0.0, 0.0], - Es: vec![2.0, 4.0], - Ec: vec![1.0, 1.0], - Z: vec![29., 1.], - m: vec![63.54, 1.0008], - interaction_index: vec![0, 0], - surface_binding_model: SurfaceBindingModel::PLANAR{calculation: SurfaceBindingCalculation::TARGET}, - bulk_binding_model: BulkBindingModel::INDIVIDUAL, - }; - - let radius = 1000.*ANGSTROM; - - let geometry_input_sphere = sphere::SphereInput { - length_unit: "ANGSTROM".to_string(), - radius: 1000.0, - densities: vec![0.03, 0.03], - electronic_stopping_correction_factor: 1.0 - }; - - let mut material_sphere: material::Material = material::Material::::new(&material_parameters, &geometry_input_sphere); - material_sphere.geometry.energy_barrier_thickness = 10.*ANGSTROM; - - particle_1.pos.x = 500.*ANGSTROM; - particle_1.pos.y = 0.; - - particle_1.pos_old.x = -1500.*ANGSTROM; - particle_1.pos_old.y = 0.; - - let inside_sphere = material_sphere.inside(particle_1.pos.x, particle_1.pos.y, particle_1.pos.z); - let inside_old_sphere = material_sphere.inside(particle_1.pos_old.x, particle_1.pos_old.y, particle_1.pos_old.z); - - //println!("{} {}", inside, inside_old); - assert!(inside_sphere); - assert!(!inside_old_sphere); - - //Test concentration-dependent surface binding energy - let surface_binding_energy = material_sphere.actual_surface_binding_energy(&particle_1, particle_1.pos.x, particle_1.pos.y, particle_1.pos.z); - assert!(approx_eq!(f64, surface_binding_energy/EV, (2. + 4.)/2., epsilon=1E-24)); - - assert!(material_sphere.inside_energy_barrier(0., 0., 0.)); - assert!(material_sphere.inside_simulation_boundary(0., 0., 0.)); - assert!(material_sphere.inside(0., 0., 0.)); - - let ux = 1214.0*ANGSTROM; - let uy = 123123.0*ANGSTROM; - let uz = 1239.0*ANGSTROM; - let r = (ux.powi(2) + uy.powi(2) + uz.powi(2)).sqrt(); - let R = 1001.0*ANGSTROM; - let u = (ux/r*R, uy/r*R, uz/r*R); - assert!(!material_sphere.inside(u.0, u.1, u.2)); - assert!(material_sphere.inside_energy_barrier(u.0, u.1, u.2)); - assert!(material_sphere.inside_simulation_boundary(u.0, u.1, u.2)); - - assert!(material_sphere.inside_energy_barrier(0., 0., 0.)); - assert!(material_sphere.inside_simulation_boundary(0., 0., 0.)); - assert!(material_sphere.inside(0., 0., 0.)); - - assert!(material_sphere.inside_energy_barrier(-1000.0*ANGSTROM - 1.0*ANGSTROM, 0., 0.)); - assert!(!material_sphere.inside_energy_barrier(-1000.0*ANGSTROM - 11.0*ANGSTROM, 0., 0.)); - - assert!(material_sphere.inside_energy_barrier(0., -1000.0*ANGSTROM - 1.0*ANGSTROM, 0.)); - assert!(!material_sphere.inside_energy_barrier(0., -1000.0*ANGSTROM - 11.0*ANGSTROM, 0.)); - - assert!(material_sphere.inside_energy_barrier(0., 0., -1000.0*ANGSTROM - 1.0*ANGSTROM)); - assert!(!material_sphere.inside_energy_barrier(0., 0., -1000.0*ANGSTROM - 11.0*ANGSTROM)); - - assert!(material_sphere.inside(-1000.0*ANGSTROM + 1.0*ANGSTROM, 0., 0.)); - assert!(!material_sphere.inside(-1000.0*ANGSTROM - 1.0*ANGSTROM, 0., 0.)); - - assert!(material_sphere.inside(0., -1000.0*ANGSTROM + 1.0*ANGSTROM, 0.)); - assert!(!material_sphere.inside(0., -1000.0*ANGSTROM - 1.0*ANGSTROM, 0.)); - - assert!(material_sphere.inside(0., 0., -1000.0*ANGSTROM + 1.0*ANGSTROM)); - assert!(!material_sphere.inside(0., 0., -1000.0*ANGSTROM - 1.0*ANGSTROM)); - - assert!(approx_eq!(f64, material_sphere.closest_point(-2000.*ANGSTROM, 0., 0.).0, (-1000.*ANGSTROM, 0., 0.).0, epsilon=1E-12)); - assert!(approx_eq!(f64, material_sphere.closest_point(0., -2000.*ANGSTROM, 0.).1, (0., -1000.*ANGSTROM, 0.).1, epsilon=1E-12)); - assert!(approx_eq!(f64, material_sphere.closest_point(0., 0., -2000.*ANGSTROM).2, (0., 0., -1000.*ANGSTROM).2, epsilon=1E-12)); - -} - -#[test] -fn test_geometry() { - let mass = 1.; - let Z = 1.; - let E = 10.*EV; - let Ec = 1.*EV; - let Es = 5.76*EV; - let x = 0.; - let y = 0.; - let z = 0.; - let cosx = 1./(2.0_f64).sqrt(); - let cosy = 1./(2.0_f64).sqrt(); - let cosz = 0.; - let mut particle_1 = particle::Particle::new(mass, Z, E, Ec, Es, x, y, z, cosx, cosy, cosz, false, false, 0); - - let material_parameters = material::MaterialParameters{ - energy_unit: "EV".to_string(), - mass_unit: "AMU".to_string(), - Eb: vec![0.0, 0.0], - Es: vec![2.0, 4.0], - Ec: vec![1.0, 1.0], - Z: vec![29., 1.], - m: vec![63.54, 1.0008], - interaction_index: vec![0, 0], - surface_binding_model: SurfaceBindingModel::PLANAR{calculation: SurfaceBindingCalculation::TARGET}, - bulk_binding_model: BulkBindingModel::INDIVIDUAL, - }; - - let thickness: f64 = 1000.; - let depth: f64 = 1000.; - - let geometry_input_2D = geometry::Mesh2DInput { - length_unit: "ANGSTROM".to_string(), - triangles: vec![(0., depth, 0., thickness/2., thickness/2., -thickness/2.), (depth, depth, 0., thickness/2., -thickness/2., -thickness/2.)], - densities: vec![vec![0.03, 0.03], vec![0.03, 0.03]], - material_boundary_points: vec![(0., thickness/2.), (depth, thickness/2.), (depth, -thickness/2.), (0., -thickness/2.), (0., thickness/2.)], - simulation_boundary_points: vec![(0., 1.1*thickness/2.), (depth, 1.1*thickness/2.), (depth, -1.1*thickness/2.), (0., -1.1*thickness/2.), (0., 1.1*thickness/2.)], - energy_barrier_thickness: 10., - electronic_stopping_correction_factors: vec![1.0, 1.0], - }; - - let geometry_input_0D = geometry::Mesh0DInput { - length_unit: "ANGSTROM".to_string(), - densities: vec![0.03, 0.03], - electronic_stopping_correction_factor: 1.0 - }; - - let material_2D: material::Material = material::Material::::new(&material_parameters, &geometry_input_2D); - let mut material_0D: material::Material = material::Material::::new(&material_parameters, &geometry_input_0D); - material_0D.geometry.energy_barrier_thickness = 10.*ANGSTROM; - - particle_1.pos.x = 500.*ANGSTROM; - particle_1.pos.y = 0.; - - particle_1.pos_old.x = -500.*ANGSTROM; - particle_1.pos_old.y = 0.; - - assert_eq!(material_2D.geometry.get_ck(0., 0., 0.), material_0D.geometry.get_ck(0., 0., 0.)); - assert_eq!(material_2D.geometry.get_densities(0., 0., 0.), material_0D.geometry.get_densities(0., 0., 0.)); - assert_eq!(material_2D.geometry.get_total_density(0., 0., 0.), material_0D.geometry.get_total_density(0., 0., 0.)); - assert_eq!(material_2D.geometry.get_concentrations(0., 0., 0.), material_0D.geometry.get_concentrations(0., 0., 0.)); - assert_eq!(material_2D.geometry.closest_point(-10., 0., 5.), material_0D.geometry.closest_point(-10., 0., 5.)); - assert_eq!(material_2D.geometry.get_densities(-10., 0., 5.), material_0D.geometry.get_densities(-10., 0., 5.)); - assert_eq!(material_2D.geometry.get_ck(-10., 0., 5.), material_0D.geometry.get_ck(-10., 0., 5.)); -} - -#[test] -fn test_surface_binding_energy_barrier() { - let mass = 1.; - let Z = 1.; - let E = 10.*EV; - let Ec = 1.*EV; - let Es = 5.76*EV; - let x = 0.; - let y = 0.; - let z = 0.; - let cosx = 1./(2.0_f64).sqrt(); - let cosy = 1./(2.0_f64).sqrt(); - let cosz = 0.; - let mut particle_1 = particle::Particle::new(mass, Z, E, Ec, Es, x, y, z, cosx, cosy, cosz, false, false, 0); - - let material_parameters = material::MaterialParameters{ - energy_unit: "EV".to_string(), - mass_unit: "AMU".to_string(), - Eb: vec![0.0, 0.0], - Es: vec![2.0, 4.0], - Ec: vec![1.0, 1.0], - Z: vec![29., 1.], - m: vec![63.54, 1.0008], - interaction_index: vec![0, 0], - surface_binding_model: SurfaceBindingModel::PLANAR{calculation: SurfaceBindingCalculation::TARGET}, - bulk_binding_model: BulkBindingModel::INDIVIDUAL, - }; - - let thickness: f64 = 1000.; - let depth: f64 = 1000.; - - let geometry_input_2D = geometry::Mesh2DInput { - length_unit: "ANGSTROM".to_string(), - triangles: vec![(0., depth, 0., thickness/2., thickness/2., -thickness/2.), (depth, depth, 0., thickness/2., -thickness/2., -thickness/2.)], - densities: vec![vec![0.03, 0.03], vec![0.03, 0.03]], - material_boundary_points: vec![(0., thickness/2.), (depth, thickness/2.), (depth, -thickness/2.), (0., -thickness/2.), (0., thickness/2.)], - simulation_boundary_points: vec![(0., 1.1*thickness/2.), (depth, 1.1*thickness/2.), (depth, -1.1*thickness/2.), (0., -1.1*thickness/2.), (0., 1.1*thickness/2.)], - energy_barrier_thickness: 10., - electronic_stopping_correction_factors: vec![1.0, 1.0], - }; - - let geometry_input_0D = geometry::Mesh0DInput { - length_unit: "ANGSTROM".to_string(), - densities: vec![0.03, 0.03], - electronic_stopping_correction_factor: 1.0 - }; - - let material_2D: material::Material = material::Material::::new(&material_parameters, &geometry_input_2D); - let mut material_0D: material::Material = material::Material::::new(&material_parameters, &geometry_input_0D); - material_0D.geometry.energy_barrier_thickness = 10.*ANGSTROM; - - particle_1.pos.x = 500.*ANGSTROM; - particle_1.pos.y = 0.; - - particle_1.pos_old.x = -500.*ANGSTROM; - particle_1.pos_old.y = 0.; - - let inside_0D = material_0D.inside(particle_1.pos.x, particle_1.pos.y, particle_1.pos.z); - let inside_old_0D = material_0D.inside(particle_1.pos_old.x, particle_1.pos_old.y, particle_1.pos_old.z); - - let inside_2D = material_2D.inside(particle_1.pos.x, particle_1.pos.y, particle_1.pos.z); - let inside_old_2D = material_2D.inside(particle_1.pos_old.x, particle_1.pos_old.y, particle_1.pos_old.z); - - //println!("{} {}", inside, inside_old); - assert!(inside_0D); - assert!(!inside_old_0D); - - assert!(inside_2D); - assert!(!inside_old_2D); - - //Test concentration-dependent surface binding energy - let surface_binding_energy_2D = material_2D.actual_surface_binding_energy(&particle_1, particle_1.pos.x, particle_1.pos.y, particle_1.pos.z); - let surface_binding_energy_0D = material_0D.actual_surface_binding_energy(&particle_1, particle_1.pos.x, particle_1.pos.y, particle_1.pos.z); - - assert!(approx_eq!(f64, surface_binding_energy_0D/EV, (2. + 4.)/2., epsilon=1E-24)); - assert!(approx_eq!(f64, surface_binding_energy_2D/EV, (2. + 4.)/2., epsilon=1E-24)); - - println!("sbv 0D: {}", surface_binding_energy_0D/EV); - println!("sbv 2D: {}", surface_binding_energy_2D/EV); - - //Test leftmost boundary - assert!(material_2D.inside_energy_barrier(500.*ANGSTROM, 0., 0.)); - assert!(material_2D.inside_energy_barrier(-5.*ANGSTROM, 0., 0.)); - assert!(!material_2D.inside_energy_barrier(-15.*ANGSTROM, 0., 0.)); - - assert!(material_0D.inside_energy_barrier(500.*ANGSTROM, 0., 0.)); - assert!(material_0D.inside_energy_barrier(-5.*ANGSTROM, 0., 0.)); - assert!(!material_0D.inside_energy_barrier(-15.*ANGSTROM, 0., 0.)); - - assert!(material_0D.inside_energy_barrier(500.*ANGSTROM, 0., 0.)); - assert!(!material_0D.inside_energy_barrier(-500.*ANGSTROM, 0., 0.)); - assert!(material_0D.inside_energy_barrier(-9.*ANGSTROM, 0., 0.)); - assert!(!material_0D.inside_energy_barrier(-11.*ANGSTROM, 0., 0.)); - - //Test top boundary - assert!(material_2D.inside_energy_barrier(500.*ANGSTROM, 0., 0.)); - assert!(material_2D.inside_energy_barrier(500.*ANGSTROM, 505.*ANGSTROM, 0.)); - assert!(!material_2D.inside_energy_barrier(500.*ANGSTROM, 515.*ANGSTROM, 0.)); - - assert!(material_0D.inside_energy_barrier(500.*ANGSTROM, 0., 0.)); - assert!(material_0D.inside_energy_barrier(500.*ANGSTROM, 505.*ANGSTROM, 0.)); - assert!(material_0D.inside_energy_barrier(500.*ANGSTROM, 515.*ANGSTROM, 0.)); - - //Test bottom boundary - assert!(material_2D.inside_energy_barrier(500.*ANGSTROM, -505.*ANGSTROM, 0.)); - assert!(!material_2D.inside_energy_barrier(500.*ANGSTROM, -515.*ANGSTROM, 0.)); - - assert!(material_0D.inside_energy_barrier(500.*ANGSTROM, -505.*ANGSTROM, 0.)); - assert!(material_0D.inside_energy_barrier(500.*ANGSTROM, -515.*ANGSTROM, 0.)); - - //Test rightmost boundary - assert!(material_2D.inside_energy_barrier(1005.*ANGSTROM, 0., 0.)); - assert!(!material_2D.inside_energy_barrier(1015.*ANGSTROM, 0., 0.)); - - assert!(material_0D.inside_energy_barrier(1005.*ANGSTROM, 0., 0.)); - assert!(material_0D.inside_energy_barrier(1015.*ANGSTROM, 0., 0.)); -} - -#[test] -fn test_triangle_contains() { - let triangle_1 = geometry::Triangle2D::new((0., 2., 0., 2., 0., 0.)); - assert!(triangle_1.contains(0.5, 0.5)); - assert!(!triangle_1.contains(2., 2.)); - - let triangle_2 = geometry::Triangle2D::new((-2., 0., 0., 0., 0., -2.)); - assert!(triangle_2.contains(-0.5, -0.5)); - assert!(!triangle_2.contains(0.5, 0.5)); - assert!(!triangle_2.contains(-2., -2.)); -} - -#[test] -fn test_triangle_distance_to() { - let triangle_1 = geometry::Triangle2D::new((0., 2., 0., 2., 0., 0.)); - assert!(approx_eq!(f64, triangle_1.distance_to(-2., 0.), 2., epsilon=1E-12), "{}", triangle_1.distance_to(-2., 0.)); - - assert!(approx_eq!(f64, triangle_1.distance_to(2., 2.), (2.0_f64).sqrt(), epsilon=1E-12), "{}", triangle_1.distance_to(2., 2.)); - - assert!(approx_eq!(f64, triangle_1.distance_to(0., 0.), 0., epsilon=1E-12), "{}", triangle_1.distance_to(0., 0.)); - assert!(approx_eq!(f64, triangle_1.distance_to(2., 0.), 0., epsilon=1E-12), "{}", triangle_1.distance_to(2., 0.)); - assert!(approx_eq!(f64, triangle_1.distance_to(0., 2.), 0., epsilon=1E-12), "{}", triangle_1.distance_to(0., 2.)); -} - -#[test] -fn test_surface_refraction() { - let print_output = false; - - let mass = 1.; - let Z = 1.; - let E = 10.*EV; - let Ec = 1.*EV; - let Es = 5.76*EV; - let x = 0.; - let y = 0.; - let z = 0.; - let cosx = 1./(2.0_f64).sqrt(); - let cosy = 1./(2.0_f64).sqrt(); - let cosz = 0.; - let mut particle_1 = particle::Particle::new(mass, Z, E, Ec, Es, x, y, z, cosx, cosy, cosz, false, false, 0); - - //Test particle entering material and gaining energy - - //Eckstein's formulation for particle entering surface - let cosx_new = ((E*cosx*cosx + Es)/(E + Es)).sqrt(); - let sinx = (1. - cosx*cosx).sqrt(); - let sinx_new = (1. - cosx_new*cosx_new).sqrt(); - let cosy_new = cosy*sinx_new/sinx; - let cosz_new = cosz*sinx_new/sinx; - let dir_mag = (cosx_new*cosx_new + cosy_new*cosy_new + cosz_new*cosz_new).sqrt(); - let cosx_new = cosx_new/dir_mag; - let cosy_new = cosy_new/dir_mag; - let cosz_new = cosz_new/dir_mag; - - //let delta_theta = particle::refraction_angle(cosx, E, E + Es); - //particle::rotate_particle(&mut particle_1, delta_theta, 0.); - - let normal = Vector::new(1.0, 0.0, 0.0); - particle::surface_refraction(&mut particle_1, normal, Es); - - if print_output { - println!("dir_mag: {}", dir_mag); - println!("{} {} {}", cosx, cosy, cosz); - println!("{} {} {}", cosx_new, cosy_new, cosz_new); - println!("{} {} {}", particle_1.dir.x, particle_1.dir.y, particle_1.dir.z); - println!(); - } - - assert!(approx_eq!(f64, particle_1.dir.x, cosx_new, epsilon=1E-12)); - assert!(approx_eq!(f64, particle_1.dir.y, cosy_new, epsilon=1E-12)); - assert!(approx_eq!(f64, particle_1.dir.z, cosz_new, epsilon=1E-12)); - - //Test particle leaving material and losing energy - - let cosx_new = ((particle_1.E*particle_1.dir.x*particle_1.dir.x - Es)/(particle_1.E - Es)).sqrt(); - let sinx = (1. - particle_1.dir.x*particle_1.dir.x).sqrt(); - let sinx_new = (1. - cosx_new*cosx_new).sqrt(); - let cosy_new = particle_1.dir.y*sinx_new/sinx; - let cosz_new = particle_1.dir.z*sinx_new/sinx; - - if print_output { - println!("{} {} {}", particle_1.dir.x, particle_1.dir.y, particle_1.dir.z); - } - - //let delta_theta = particle::refraction_angle(particle_1.dir.x, particle_1.E, particle_1.E - Es); - //particle::rotate_particle(&mut particle_1, delta_theta, 0.); - - let normal = Vector::new(1.0, 0.0, 0.0); - particle::surface_refraction(&mut particle_1, normal, -Es); - - - if print_output { - println!("{} {} {}", cosx_new, cosy_new, cosz_new); - println!("{} {} {}", particle_1.dir.x, particle_1.dir.y, particle_1.dir.z); - } - - assert!(approx_eq!(f64, particle_1.dir.x, cosx_new, epsilon=1E-9)); - assert!(approx_eq!(f64, particle_1.dir.y, cosy_new, epsilon=1E-9)); - assert!(approx_eq!(f64, particle_1.dir.z, cosz_new, epsilon=1E-9)); - -} - -#[test] -fn test_momentum_conservation() { - - for energy_eV in vec![1., 10., 100., 1000., 1000.] { - //Aluminum - let m1 = 183.8*AMU; - let Z1 = 74.; - let E1 = energy_eV*EV; - let Ec1 = 1.*EV; - let Es1 = 1.*EV; - let x1 = 0.; - let y1 = 0.; - let z1 = 0.; - - //Aluminum - let m2 = 6.941; - let Z2 = 3.; - let Ec2 = 1.; - let Es2 = 1.; - - //Arbitrary initial angle - let theta = 0.974194583091052_f64; - let cosx = (theta).cos(); - let cosy = (theta).sin(); - let cosz = 0.; - - let material_parameters = material::MaterialParameters{ - energy_unit: "EV".to_string(), - mass_unit: "AMU".to_string(), - Eb: vec![0.0], - Es: vec![Es2], - Ec: vec![Ec2], - Z: vec![Z2], - m: vec![m2], - interaction_index: vec![0], - surface_binding_model: SurfaceBindingModel::PLANAR{calculation: SurfaceBindingCalculation::TARGET}, - bulk_binding_model: BulkBindingModel::INDIVIDUAL, - }; - - let thickness: f64 = 1000.; - let depth: f64 = 1000.; - let geometry_input = geometry::Mesh2DInput { - length_unit: "ANGSTROM".to_string(), - triangles: vec![(0., depth, 0., thickness/2., thickness/2., -thickness/2.), (depth, depth, 0., thickness/2., -thickness/2., -thickness/2.)], - densities: vec![vec![0.06306], vec![0.06306]], - material_boundary_points: vec![(0., thickness/2.), (depth, thickness/2.), (depth, -thickness/2.), (0., -thickness/2.), (0., thickness/2.)], - simulation_boundary_points: vec![(0., 1.1*thickness/2.), (depth, 1.1*thickness/2.), (depth, -1.1*thickness/2.), (0., -1.1*thickness/2.), (0., 1.1*thickness/2.)], - electronic_stopping_correction_factors: vec![0.0, 0.0], - energy_barrier_thickness: 0., - }; - - let material_1: material::Material = material::Material::::new(&material_parameters, &geometry_input); - - for high_energy_free_flight_paths in vec![true, false] { - for potential in vec![InteractionPotential::KR_C, InteractionPotential::MOLIERE, InteractionPotential::ZBL, InteractionPotential::LENZ_JENSEN] { - for scattering_integral in vec![ScatteringIntegral::MENDENHALL_WELLER, ScatteringIntegral::GAUSS_MEHLER{n_points: 10}, ScatteringIntegral::GAUSS_LEGENDRE] { - for root_finder in vec![Rootfinder::NEWTON{max_iterations: 100, tolerance: 1E-3}] { - - println!("Case: {} {} {} {}", energy_eV, high_energy_free_flight_paths, potential, scattering_integral); - - let mut particle_1 = particle::Particle::new(m1, Z1, E1, Ec1, Es1, x1, y1, z1, cosx, cosy, cosz, false, false, 0); - - #[cfg(not(feature = "distributions"))] - let options = Options { - name: "test".to_string(), - track_trajectories: false, - track_recoils: true, - track_recoil_trajectories: false, - write_buffer_size: 8000, - weak_collision_order: 0, - suppress_deep_recoils: false, - high_energy_free_flight_paths: high_energy_free_flight_paths, - electronic_stopping_mode: ElectronicStoppingMode::INTERPOLATED, - mean_free_path_model: MeanFreePathModel::LIQUID, - interaction_potential: vec![vec![potential]], - scattering_integral: vec![vec![scattering_integral]], - num_threads: 1, - num_chunks: 1, - use_hdf5: false, - root_finder: vec![vec![root_finder]], - track_displacements: false, - track_energy_losses: false, - }; - - #[cfg(feature = "distributions")] - let options = Options { - name: "test".to_string(), - track_trajectories: false, - track_recoils: true, - track_recoil_trajectories: false, - write_buffer_size: 8000, - weak_collision_order: 0, - suppress_deep_recoils: false, - high_energy_free_flight_paths: high_energy_free_flight_paths, - electronic_stopping_mode: ElectronicStoppingMode::INTERPOLATED, - mean_free_path_model: MeanFreePathModel::LIQUID, - interaction_potential: vec![vec![potential]], - scattering_integral: vec![vec![scattering_integral]], - num_threads: 1, - num_chunks: 1, - use_hdf5: false, - root_finder: vec![vec![root_finder]], - track_displacements: false, - track_energy_losses: false, - energy_min: 0.0, - energy_max: 10.0, - energy_num: 11, - angle_min: 0.0, - angle_max: 90.0, - angle_num: 11, - x_min: 0.0, - y_min: -10.0, - z_min: -10.0, - x_max: 10.0, - y_max: 10.0, - z_max: 10.0, - x_num: 11, - y_num: 11, - z_num: 11, - }; - - let binary_collision_geometries = bca::determine_mfp_phi_impact_parameter(&mut particle_1, &material_1, &options); - - println!("Phi: {} rad p: {} Angstrom mfp: {} Angstrom", binary_collision_geometries[0].phi_azimuthal, - binary_collision_geometries[0].impact_parameter/ANGSTROM, - binary_collision_geometries[0].mfp/ANGSTROM); - - let (species_index, mut particle_2) = bca::choose_collision_partner(&mut particle_1, &material_1, - &binary_collision_geometries[0], &options); - - let mom1_0 = particle_1.get_momentum(); - let mom2_0 = particle_2.get_momentum(); - - let initial_momentum = mom1_0.add(&mom2_0); - - let binary_collision_result = bca::calculate_binary_collision(&particle_1, - &particle_2, &binary_collision_geometries[0], &options).unwrap(); - - println!("E_recoil: {} eV Psi: {} rad Psi_recoil: {} rad", binary_collision_result.recoil_energy/EV, - binary_collision_result.psi, - binary_collision_result.psi_recoil); - - println!("Initial Energies: {} eV {} eV", particle_1.E/EV, particle_2.E/EV); - - //Energy transfer to recoil - 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); - - //Rotate particle 1, 2 by lab frame scattering angles - particle::rotate_particle(&mut particle_1, binary_collision_result.psi, - binary_collision_geometries[0].phi_azimuthal); - - particle::rotate_particle(&mut particle_2, -binary_collision_result.psi_recoil, - binary_collision_geometries[0].phi_azimuthal); - - //Subtract total energy from all simultaneous collisions and electronic stopping - bca::update_particle_energy(&mut particle_1, &material_1, 0., - binary_collision_result.recoil_energy, 0., particle_2.Z, species_index, &options); - - let mom1_1 = particle_1.get_momentum(); - let mom2_1 = particle_2.get_momentum(); - - let final_momentum = mom1_1.add(&mom2_1); - - println!("Final Energies: {} eV {} eV", particle_1.E/EV, particle_2.E/EV); - println!("X: {} {} {}% Error", initial_momentum.x/ANGSTROM/AMU, final_momentum.x/ANGSTROM/AMU, 100.*(final_momentum.x - initial_momentum.x)/initial_momentum.magnitude()); - println!("Y: {} {} {}% Error", initial_momentum.y/ANGSTROM/AMU, final_momentum.y/ANGSTROM/AMU, 100.*(final_momentum.y - initial_momentum.y)/initial_momentum.magnitude()); - println!("Z: {} {} {}% Error", initial_momentum.z/ANGSTROM/AMU, final_momentum.z/ANGSTROM/AMU, 100.*(final_momentum.z - initial_momentum.z)/initial_momentum.magnitude()); - println!(); - - assert!(approx_eq!(f64, initial_momentum.x, final_momentum.x, epsilon = 1E-12)); - assert!(approx_eq!(f64, initial_momentum.y, final_momentum.y, epsilon = 1E-12)); - assert!(approx_eq!(f64, initial_momentum.z, final_momentum.z, epsilon = 1E-12)); - - assert!(!particle_1.E.is_nan()); - assert!(!particle_2.E.is_nan()); - assert!(!initial_momentum.x.is_nan()); - assert!(!initial_momentum.x.is_nan()); - assert!(!initial_momentum.x.is_nan()); - } - } - } - } - } -} - -#[test] -fn test_rotate_particle() { - let mass = 1.; - let Z = 1.; - let E = 1.; - let Ec = 1.; - let Es = 1.; - let x = 0.; - let y = 0.; - let z = 0.; - let cosx = (PI/4.).cos(); - let cosy = (PI/4.).sin(); - let cosz = 0.; - let psi = -PI/4.; - let phi = 0.; - - let mut particle = particle::Particle::new(mass, Z, E, Ec, Es, x, y, z, cosx, cosy, cosz, false, false, 0); - - //Check that rotation in 2D works - particle::rotate_particle(&mut particle, psi, phi); - assert!(approx_eq!(f64, particle.dir.x, 0., epsilon = 1E-12), "particle.dir.x: {} Should be ~0.", particle.dir.x); - assert!(approx_eq!(f64, particle.dir.y, 1., epsilon = 1E-12), "particle.dir.y: {} Should be ~1.", particle.dir.y); - - //Check that rotating back by negative psi returns to the previous values - particle::rotate_particle(&mut particle, -psi, phi); - assert!(approx_eq!(f64, particle.dir.x, cosx, epsilon = 1E-12), "particle.dir.x: {} Should be ~{}", particle.dir.x, cosx); - assert!(approx_eq!(f64, particle.dir.y, cosy, epsilon = 1E-12), "particle.dir.y: {} Should be ~{}", particle.dir.y, cosy); - - //Check that azimuthal rotation by 180 degrees works correctly - let phi = PI; - particle::rotate_particle(&mut particle, psi, phi); - assert!(approx_eq!(f64, particle.dir.x, 1., epsilon = 1E-12), "particle.dir.x: {} Should be ~1.", particle.dir.x); - assert!(approx_eq!(f64, particle.dir.y, 0., epsilon = 1E-12), "particle.dir.y: {} Should be ~0.", particle.dir.y); - - //Check that particle direction vector remains normalized following rotations - assert!(approx_eq!(f64, particle.dir.x.powi(2) + particle.dir.y.powi(2) + particle.dir.z.powi(2), 1.), "Particle direction not normalized."); - -} - -#[test] -fn test_particle_advance() { - let mass = 1.; - let Z = 1.; - let E = 1.; - let Ec = 1.; - let Es = 1.; - let x = 0.; - let y = 0.; - let z = 0.; - let cosx = (PI/4.).cos(); - let cosy = (PI/4.).sin(); - let cosz = 0.; - let mfp = 1.; - let asymptotic_deflection = 0.5; - - let mut particle = particle::Particle::new(mass, Z, E, Ec, Es, x, y, z, cosx, cosy, cosz, false, false, 0); - - let distance_traveled = particle::particle_advance(&mut particle, mfp, asymptotic_deflection); - - assert_eq!(particle.pos.x, (1. - 0.5)*cosx); - assert_eq!(particle.pos.y, (1. - 0.5)*cosy); - assert_eq!(particle.pos.z, 0.); - assert_eq!(distance_traveled, mfp - asymptotic_deflection); -} - -#[test] -fn test_quadrature() { - let Za = 1.; - let Zb = 13.; - let Ma = 1.008; - let Mb = 26.9815385; - let E0 = 10.*EV; - let p = 1.*ANGSTROM; - let a = interactions::screening_length(Za, Zb, InteractionPotential::KR_C); - - #[cfg(not(feature = "distributions"))] - let options = Options { - name: "test".to_string(), - track_trajectories: false, - track_recoils: true, - track_recoil_trajectories: false, - write_buffer_size: 8000, - weak_collision_order: 0, - suppress_deep_recoils: false, - high_energy_free_flight_paths: false, - electronic_stopping_mode: ElectronicStoppingMode::INTERPOLATED, - mean_free_path_model: MeanFreePathModel::LIQUID, - interaction_potential: vec![vec![InteractionPotential::KR_C]], - scattering_integral: vec![vec![ScatteringIntegral::MENDENHALL_WELLER]], - num_threads: 1, - num_chunks: 1, - use_hdf5: false, - root_finder: vec![vec![Rootfinder::NEWTON{max_iterations: 100, tolerance: 1E-14}]], - track_displacements: false, - track_energy_losses: false, - }; - - #[cfg(feature = "distributions")] - let options = Options { - name: "test".to_string(), - track_trajectories: false, - track_recoils: true, - track_recoil_trajectories: false, - write_buffer_size: 8000, - weak_collision_order: 0, - suppress_deep_recoils: false, - high_energy_free_flight_paths: false, - electronic_stopping_mode: ElectronicStoppingMode::INTERPOLATED, - mean_free_path_model: MeanFreePathModel::LIQUID, - interaction_potential: vec![vec![InteractionPotential::KR_C]], - scattering_integral: vec![vec![ScatteringIntegral::MENDENHALL_WELLER]], - num_threads: 1, - num_chunks: 1, - use_hdf5: false, - root_finder: vec![vec![Rootfinder::NEWTON{max_iterations: 100, tolerance: 1E-14}]], - track_displacements: false, - track_energy_losses: false, - energy_min: 0.0, - energy_max: 10.0, - energy_num: 11, - angle_min: 0.0, - angle_max: 90.0, - angle_num: 11, - x_min: 0.0, - y_min: -10.0, - z_min: -10.0, - x_max: 10.0, - y_max: 10.0, - z_max: 10.0, - x_num: 11, - y_num: 11, - z_num: 11, - }; - - let x0_newton = bca::newton_rootfinder(Za, Zb, Ma, Mb, E0, p, InteractionPotential::KR_C, 100, 1E-12).unwrap(); - - //If cpr_rootfinder is enabled, compare Newton to CPR - they should be nearly identical - #[cfg(any(feature = "cpr_rootfinder_openblas", feature = "cpr_rootfinder_netlib", feature = "cpr_rootfinder_intel_mkl"))] - if let Ok(x0_cpr) = bca::cpr_rootfinder(Za, Zb, Ma, Mb, E0, p, InteractionPotential::KR_C, 2, 1000, 1E-13, 1E-16, 1E-18, 1E6, 1E-18, false) { - println!("CPR: {} Newton: {}", x0_cpr, x0_newton); - assert!(approx_eq!(f64, x0_newton, x0_cpr, epsilon=1E-3)); - }; - - //Compute center of mass deflection angle with each algorithm - let theta_gm = bca::gauss_mehler(Za, Zb, Ma, Mb, E0, p, x0_newton, InteractionPotential::KR_C, 10); - let theta_gl = bca::gauss_legendre(Za, Zb, Ma, Mb, E0, p, x0_newton, InteractionPotential::KR_C); - let theta_mw = bca::mendenhall_weller(Za, Zb, Ma, Mb, E0, p, x0_newton, InteractionPotential::KR_C); - let theta_magic = bca::magic(Za, Zb, Ma, Mb, E0, p, x0_newton, InteractionPotential::KR_C); - - //Gauss-Mehler and Gauss-Legendre should be very close to each other - assert!(approx_eq!(f64, theta_gm, theta_gl, epsilon=0.001)); - assert!(approx_eq!(f64, theta_gm, theta_mw, epsilon=0.001)); - assert!(approx_eq!(f64, theta_gm, theta_magic, epsilon=0.15)); - - println!("Gauss-Mehler: {} Gauss-Legendre: {} Mendenhall-Weller: {} MAGIC: {}", - theta_gm, theta_gl, theta_mw, theta_magic); -} +#[cfg(test)] +use super::*; +#[cfg(test)] +use float_cmp::*; + +#[test] +#[cfg(feature = "parry3d")] +fn test_parry_cuboid() { + let mass = 1.; + let Z = 1.; + let E = 10.*EV; + let Ec = 1.*EV; + let Es = 5.76*EV; + let x = 0.; + let y = 0.; + let z = 0.; + let cosx = 1./(2.0_f64).sqrt(); + let cosy = 1./(2.0_f64).sqrt(); + let cosz = 0.; + let particle_1 = particle::Particle::new(mass, Z, E, Ec, Es, x, y, z, cosx, cosy, cosz, false, false, 0); + + let material_parameters = material::MaterialParameters{ + energy_unit: "EV".to_string(), + mass_unit: "AMU".to_string(), + Eb: vec![0.0, 0.0], + Es: vec![2.0, 4.0], + Ec: vec![1.0, 1.0], + Z: vec![29., 1.], + m: vec![63.54, 1.0008], + interaction_index: vec![0, 0], + surface_binding_model: SurfaceBindingModel::PLANAR{calculation: SurfaceBindingCalculation::TARGET}, + bulk_binding_model: BulkBindingModel::INDIVIDUAL, + }; + + let radius = 1000.*ANGSTROM; + + //cuboid centered at (0, 0, 0) with l=w=h=1 Angstrom + let geometry_input = parry::ParryTriMeshInput { + length_unit: "ANGSTROM".to_string(), + densities: vec![0.03, 0.03], + electronic_stopping_correction_factor: 1.0, + vertices: vec![ + [-0.5, -0.5, 0.5], + [0.5, -0.5, 0.5], + [-0.5, 0.5, 0.5], + [0.5, 0.5, 0.5], + [-0.5, 0.5, -0.5], + [0.5, 0.5, -0.5], + [-0.5, -0.5, -0.5], + [0.5, -0.5, -0.5], + ], + indices: vec![ + [0, 1, 3], + [0, 3, 2], + [2, 3, 5], + [2, 5, 4], + [4, 5, 7], + [4, 7, 6], + [6, 7, 1], + [6, 1, 0], + [1, 7, 5], + [1, 5, 3], + [6, 0, 2], + [6, 2, 4] + ] + }; + + let mut material_cuboid: material::Material = material::Material::::new(&material_parameters, &geometry_input); + material_cuboid.geometry.energy_barrier_thickness = 10.*ANGSTROM; + + let surface_binding_energy = material_cuboid.actual_surface_binding_energy(&particle_1, particle_1.pos.x, particle_1.pos.y, particle_1.pos.z); + assert!(approx_eq!(f64, surface_binding_energy/EV, (2. + 4.)/2., epsilon=1E-24)); + + //Test origin is inside + assert!(material_cuboid.geometry.inside(0., 0., 0.)); + + //Test all outside areas for containment + assert!(!material_cuboid.geometry.inside(0., 1., 0.)); + assert!(!material_cuboid.geometry.inside(0., -1., 0.)); + assert!(!material_cuboid.geometry.inside(1., 0., 0.)); + assert!(!material_cuboid.geometry.inside(-1., 0., 0.)); + assert!(!material_cuboid.geometry.inside(0., 0., -1.)); + assert!(!material_cuboid.geometry.inside(0., 0., -1.)); + + //distance to origin + let (x, y, z) = material_cuboid.geometry.closest_point(0., 0., 0.); + assert!(approx_eq!(f64, (x.powi(2) + y.powi(2) + z.powi(2)).sqrt(), 0.5*ANGSTROM, epsilon=1E-24)); + + //distance to wall should be zero at wall + let (x, y, z) = material_cuboid.geometry.closest_point(0.5*ANGSTROM, 0., 0.); + assert!(approx_eq!(f64, ((x - 0.5*ANGSTROM).powi(2) + y.powi(2) + z.powi(2)).sqrt(), 0.0, epsilon=1E-24)); +} + +#[test] +#[cfg(feature = "parry3d")] +fn test_parry_sphere() { + let mass = 1.; + let Z = 1.; + let E = 10.*EV; + let Ec = 1.*EV; + let Es = 5.76*EV; + let x = 0.; + let y = 0.; + let z = 0.; + let cosx = 1./(2.0_f64).sqrt(); + let cosy = 1./(2.0_f64).sqrt(); + let cosz = 0.; + let mut particle_1 = particle::Particle::new(mass, Z, E, Ec, Es, x, y, z, cosx, cosy, cosz, false, false, 0); + + let material_parameters = material::MaterialParameters{ + energy_unit: "EV".to_string(), + mass_unit: "AMU".to_string(), + Eb: vec![0.0, 0.0], + Es: vec![2.0, 4.0], + Ec: vec![1.0, 1.0], + Z: vec![29., 1.], + m: vec![63.54, 1.0008], + interaction_index: vec![0, 0], + surface_binding_model: SurfaceBindingModel::PLANAR{calculation: SurfaceBindingCalculation::TARGET}, + bulk_binding_model: BulkBindingModel::INDIVIDUAL, + }; + + let radius = 1000.*ANGSTROM; + + let geometry_input_sphere = parry::ParryBallInput { + length_unit: "ANGSTROM".to_string(), + radius: 1000.0, + densities: vec![0.03, 0.03], + electronic_stopping_correction_factor: 1.0 + }; + + let mut material_sphere: material::Material = material::Material::::new(&material_parameters, &geometry_input_sphere); + material_sphere.geometry.energy_barrier_thickness = 10.*ANGSTROM; + + particle_1.pos.x = 500.*ANGSTROM; + particle_1.pos.y = 0.; + + particle_1.pos_old.x = -1500.*ANGSTROM; + particle_1.pos_old.y = 0.; + + let inside_sphere = material_sphere.inside(particle_1.pos.x, particle_1.pos.y, particle_1.pos.z); + let inside_old_sphere = material_sphere.inside(particle_1.pos_old.x, particle_1.pos_old.y, particle_1.pos_old.z); + + //println!("{} {}", inside, inside_old); + assert!(inside_sphere); + assert!(!inside_old_sphere); + + //Test concentration-dependent surface binding energy + let surface_binding_energy = material_sphere.actual_surface_binding_energy(&particle_1, particle_1.pos.x, particle_1.pos.y, particle_1.pos.z); + assert!(approx_eq!(f64, surface_binding_energy/EV, (2. + 4.)/2., epsilon=1E-24)); + + assert!(material_sphere.inside_energy_barrier(0., 0., 0.)); + assert!(material_sphere.inside_simulation_boundary(0., 0., 0.)); + assert!(material_sphere.inside(0., 0., 0.)); + + let ux = 1214.0*ANGSTROM; + let uy = 123123.0*ANGSTROM; + let uz = 1239.0*ANGSTROM; + let r = (ux.powi(2) + uy.powi(2) + uz.powi(2)).sqrt(); + let R = 1001.0*ANGSTROM; + let u = (ux/r*R, uy/r*R, uz/r*R); + assert!(!material_sphere.inside(u.0, u.1, u.2)); + assert!(material_sphere.inside_energy_barrier(u.0, u.1, u.2)); + assert!(material_sphere.inside_simulation_boundary(u.0, u.1, u.2)); + + assert!(material_sphere.inside_energy_barrier(0., 0., 0.)); + assert!(material_sphere.inside_simulation_boundary(0., 0., 0.)); + assert!(material_sphere.inside(0., 0., 0.)); + + assert!(material_sphere.inside_energy_barrier(-1000.0*ANGSTROM - 1.0*ANGSTROM, 0., 0.)); + assert!(!material_sphere.inside_energy_barrier(-1000.0*ANGSTROM - 11.0*ANGSTROM, 0., 0.)); + + assert!(material_sphere.inside_energy_barrier(0., -1000.0*ANGSTROM - 1.0*ANGSTROM, 0.)); + assert!(!material_sphere.inside_energy_barrier(0., -1000.0*ANGSTROM - 11.0*ANGSTROM, 0.)); + + assert!(material_sphere.inside_energy_barrier(0., 0., -1000.0*ANGSTROM - 1.0*ANGSTROM)); + assert!(!material_sphere.inside_energy_barrier(0., 0., -1000.0*ANGSTROM - 11.0*ANGSTROM)); + + assert!(material_sphere.inside(-1000.0*ANGSTROM + 1.0*ANGSTROM, 0., 0.)); + assert!(!material_sphere.inside(-1000.0*ANGSTROM - 1.0*ANGSTROM, 0., 0.)); + + assert!(material_sphere.inside(0., -1000.0*ANGSTROM + 1.0*ANGSTROM, 0.)); + assert!(!material_sphere.inside(0., -1000.0*ANGSTROM - 1.0*ANGSTROM, 0.)); + + assert!(material_sphere.inside(0., 0., -1000.0*ANGSTROM + 1.0*ANGSTROM)); + assert!(!material_sphere.inside(0., 0., -1000.0*ANGSTROM - 1.0*ANGSTROM)); + + assert!(approx_eq!(f64, material_sphere.closest_point(-2000.*ANGSTROM, 0., 0.).0, (-1000.*ANGSTROM, 0., 0.).0, epsilon=1E-12)); + assert!(approx_eq!(f64, material_sphere.closest_point(0., -2000.*ANGSTROM, 0.).1, (0., -1000.*ANGSTROM, 0.).1, epsilon=1E-12)); + assert!(approx_eq!(f64, material_sphere.closest_point(0., 0., -2000.*ANGSTROM).2, (0., 0., -1000.*ANGSTROM).2, epsilon=1E-12)); +} + +#[test] +#[cfg(feature = "distributions")] +fn test_distributions() { + + let options = Options { + name: "test".to_string(), + track_trajectories: false, + track_recoils: false, + track_recoil_trajectories: false, + write_buffer_size: 8000, + weak_collision_order: 0, + suppress_deep_recoils: false, + high_energy_free_flight_paths: false, + electronic_stopping_mode: ElectronicStoppingMode::INTERPOLATED, + mean_free_path_model: MeanFreePathModel::LIQUID, + interaction_potential: vec![vec![InteractionPotential::KR_C]], + scattering_integral: vec![vec![ScatteringIntegral::MENDENHALL_WELLER]], + num_threads: 1, + num_chunks: 1, + use_hdf5: false, + root_finder: vec![vec![Rootfinder::NEWTON{max_iterations: 100, tolerance: 1E-3}]], + track_displacements: false, + track_energy_losses: true, + energy_min: 0.0, + energy_max: 10.0, + energy_num: 11, + angle_min: 0.0, + angle_max: 90.0, + angle_num: 11, + x_min: 0.0, + y_min: -10.0, + z_min: -10.0, + x_max: 10.0, + y_max: 10.0, + z_max: 10.0, + x_num: 11, + y_num: 11, + z_num: 11, + }; + + let output_units = OutputUnits { + length_unit: 1.0, + energy_unit: 1.0, + mass_unit: 1.0, + }; + + let mass = 1.0; + let Z = 1.0; + let E = 1.5; + let Ec = 0.0; + let Es = 0.0; + let x = 0.0; + let y = 0.0; + let z = 0.0; + let cosx = 0.0; + let cosy = 0.0; + let cosz = 0.0; + let mut particle = particle::Particle::new(mass, Z, E, Ec, Es, x, y, z, cosx, cosy, cosz, false, false, 0); + + + + let mut distributions = output::Distributions::new(&options); + assert_eq!(distributions.x_range[0], 0.); + assert_eq!(distributions.x_range[distributions.x_range.len() - 1], 10.); + assert_eq!(distributions.y_range[0], -10.); + assert_eq!(distributions.y_range[distributions.y_range.len() - 1], 10.); + assert_eq!(distributions.z_range[0], -10.); + assert_eq!(distributions.z_range[distributions.z_range.len() - 1], 10.); + assert_eq!(distributions.x_range.len(), 11); + assert_eq!(distributions.y_range.len(), 11); + assert_eq!(distributions.z_range.len(), 11); + + particle.incident = true; + particle.stopped = true; + particle.pos.x = 0.0; + distributions.update(&particle, &output_units, &options, 1); + assert_eq!(distributions.implanted_x[0], 1); + + particle.pos.x = 10.0; + distributions.update(&particle, &output_units, &options, 1); + assert_eq!(distributions.implanted_x[10], 1); + + distributions.update(&particle, &output_units, &options, 1); + assert_eq!(distributions.implanted_y[5], 3); + assert_eq!(distributions.implanted_z[5], 3); + + particle.incident = false; + particle.stopped = false; + particle.left = true; + particle.E = 0.0; + particle.dir.x = -0.999; + particle.dir.y = 0.001; + particle.dir.z = 0.0; + particle.dir.normalize(); + + distributions.update(&particle, &output_units, &options, 1); + assert_eq!(distributions.sputtered_ead[[0, 0]], 1); + + particle.E = -0.1; + distributions.update(&particle, &output_units, &options, 1); + assert_eq!(distributions.sputtered_ead[[0, 0]], 1); + + particle.E = 10.0; + distributions.update(&particle, &output_units, &options, 1); + assert_eq!(distributions.sputtered_ead[[10, 0]], 1); + + particle.dir = Vector::new(-0.707, 0.707, 0.0); + particle.dir.normalize(); + distributions.update(&particle, &output_units, &options, 1); + assert_eq!(distributions.sputtered_ead[[10, 5]], 1); + + particle.incident = true; + particle.E = 0.0; + particle.dir.x = -0.999; + particle.dir.y = 0.001; + particle.dir.z = 0.0; + particle.dir.normalize(); + + distributions.update(&particle, &output_units, &options, 1); + assert_eq!(distributions.reflected_ead[[0, 0]], 1); + + particle.E = -0.1; + distributions.update(&particle, &output_units, &options, 1); + assert_eq!(distributions.reflected_ead[[0, 0]], 1); + + particle.E = 10.0; + distributions.update(&particle, &output_units, &options, 1); + assert_eq!(distributions.reflected_ead[[10, 0]], 1); + + particle.dir = Vector::new(-0.707, 0.707, 0.0); + particle.dir.normalize(); + distributions.update(&particle, &output_units, &options, 1); + assert_eq!(distributions.reflected_ead[[10, 5]], 1); +} + +#[test] +fn test_spherical_geometry() { + let mass = 1.; + let Z = 1.; + let E = 10.*EV; + let Ec = 1.*EV; + let Es = 5.76*EV; + let x = 0.; + let y = 0.; + let z = 0.; + let cosx = 1./(2.0_f64).sqrt(); + let cosy = 1./(2.0_f64).sqrt(); + let cosz = 0.; + let mut particle_1 = particle::Particle::new(mass, Z, E, Ec, Es, x, y, z, cosx, cosy, cosz, false, false, 0); + + let material_parameters = material::MaterialParameters{ + energy_unit: "EV".to_string(), + mass_unit: "AMU".to_string(), + Eb: vec![0.0, 0.0], + Es: vec![2.0, 4.0], + Ec: vec![1.0, 1.0], + Z: vec![29., 1.], + m: vec![63.54, 1.0008], + interaction_index: vec![0, 0], + surface_binding_model: SurfaceBindingModel::PLANAR{calculation: SurfaceBindingCalculation::TARGET}, + bulk_binding_model: BulkBindingModel::INDIVIDUAL, + }; + + let radius = 1000.*ANGSTROM; + + let geometry_input_sphere = sphere::SphereInput { + length_unit: "ANGSTROM".to_string(), + radius: 1000.0, + densities: vec![0.03, 0.03], + electronic_stopping_correction_factor: 1.0 + }; + + let mut material_sphere: material::Material = material::Material::::new(&material_parameters, &geometry_input_sphere); + material_sphere.geometry.energy_barrier_thickness = 10.*ANGSTROM; + + particle_1.pos.x = 500.*ANGSTROM; + particle_1.pos.y = 0.; + + particle_1.pos_old.x = -1500.*ANGSTROM; + particle_1.pos_old.y = 0.; + + let inside_sphere = material_sphere.inside(particle_1.pos.x, particle_1.pos.y, particle_1.pos.z); + let inside_old_sphere = material_sphere.inside(particle_1.pos_old.x, particle_1.pos_old.y, particle_1.pos_old.z); + + //println!("{} {}", inside, inside_old); + assert!(inside_sphere); + assert!(!inside_old_sphere); + + //Test concentration-dependent surface binding energy + let surface_binding_energy = material_sphere.actual_surface_binding_energy(&particle_1, particle_1.pos.x, particle_1.pos.y, particle_1.pos.z); + assert!(approx_eq!(f64, surface_binding_energy/EV, (2. + 4.)/2., epsilon=1E-24)); + + assert!(material_sphere.inside_energy_barrier(0., 0., 0.)); + assert!(material_sphere.inside_simulation_boundary(0., 0., 0.)); + assert!(material_sphere.inside(0., 0., 0.)); + + let ux = 1214.0*ANGSTROM; + let uy = 123123.0*ANGSTROM; + let uz = 1239.0*ANGSTROM; + let r = (ux.powi(2) + uy.powi(2) + uz.powi(2)).sqrt(); + let R = 1001.0*ANGSTROM; + let u = (ux/r*R, uy/r*R, uz/r*R); + assert!(!material_sphere.inside(u.0, u.1, u.2)); + assert!(material_sphere.inside_energy_barrier(u.0, u.1, u.2)); + assert!(material_sphere.inside_simulation_boundary(u.0, u.1, u.2)); + + assert!(material_sphere.inside_energy_barrier(0., 0., 0.)); + assert!(material_sphere.inside_simulation_boundary(0., 0., 0.)); + assert!(material_sphere.inside(0., 0., 0.)); + + assert!(material_sphere.inside_energy_barrier(-1000.0*ANGSTROM - 1.0*ANGSTROM, 0., 0.)); + assert!(!material_sphere.inside_energy_barrier(-1000.0*ANGSTROM - 11.0*ANGSTROM, 0., 0.)); + + assert!(material_sphere.inside_energy_barrier(0., -1000.0*ANGSTROM - 1.0*ANGSTROM, 0.)); + assert!(!material_sphere.inside_energy_barrier(0., -1000.0*ANGSTROM - 11.0*ANGSTROM, 0.)); + + assert!(material_sphere.inside_energy_barrier(0., 0., -1000.0*ANGSTROM - 1.0*ANGSTROM)); + assert!(!material_sphere.inside_energy_barrier(0., 0., -1000.0*ANGSTROM - 11.0*ANGSTROM)); + + assert!(material_sphere.inside(-1000.0*ANGSTROM + 1.0*ANGSTROM, 0., 0.)); + assert!(!material_sphere.inside(-1000.0*ANGSTROM - 1.0*ANGSTROM, 0., 0.)); + + assert!(material_sphere.inside(0., -1000.0*ANGSTROM + 1.0*ANGSTROM, 0.)); + assert!(!material_sphere.inside(0., -1000.0*ANGSTROM - 1.0*ANGSTROM, 0.)); + + assert!(material_sphere.inside(0., 0., -1000.0*ANGSTROM + 1.0*ANGSTROM)); + assert!(!material_sphere.inside(0., 0., -1000.0*ANGSTROM - 1.0*ANGSTROM)); + + assert!(approx_eq!(f64, material_sphere.closest_point(-2000.*ANGSTROM, 0., 0.).0, (-1000.*ANGSTROM, 0., 0.).0, epsilon=1E-12)); + assert!(approx_eq!(f64, material_sphere.closest_point(0., -2000.*ANGSTROM, 0.).1, (0., -1000.*ANGSTROM, 0.).1, epsilon=1E-12)); + assert!(approx_eq!(f64, material_sphere.closest_point(0., 0., -2000.*ANGSTROM).2, (0., 0., -1000.*ANGSTROM).2, epsilon=1E-12)); + +} + +#[test] +fn test_geometry() { + let mass = 1.; + let Z = 1.; + let E = 10.*EV; + let Ec = 1.*EV; + let Es = 5.76*EV; + let x = 0.; + let y = 0.; + let z = 0.; + let cosx = 1./(2.0_f64).sqrt(); + let cosy = 1./(2.0_f64).sqrt(); + let cosz = 0.; + let mut particle_1 = particle::Particle::new(mass, Z, E, Ec, Es, x, y, z, cosx, cosy, cosz, false, false, 0); + + let material_parameters = material::MaterialParameters{ + energy_unit: "EV".to_string(), + mass_unit: "AMU".to_string(), + Eb: vec![0.0, 0.0], + Es: vec![2.0, 4.0], + Ec: vec![1.0, 1.0], + Z: vec![29., 1.], + m: vec![63.54, 1.0008], + interaction_index: vec![0, 0], + surface_binding_model: SurfaceBindingModel::PLANAR{calculation: SurfaceBindingCalculation::TARGET}, + bulk_binding_model: BulkBindingModel::INDIVIDUAL, + }; + + let thickness: f64 = 1000.; + let depth: f64 = 1000.; + + let geometry_input_2D = geometry::Mesh2DInput { + length_unit: "ANGSTROM".to_string(), + triangles: vec![(0., depth, 0., thickness/2., thickness/2., -thickness/2.), (depth, depth, 0., thickness/2., -thickness/2., -thickness/2.)], + densities: vec![vec![0.03, 0.03], vec![0.03, 0.03]], + material_boundary_points: vec![(0., thickness/2.), (depth, thickness/2.), (depth, -thickness/2.), (0., -thickness/2.), (0., thickness/2.)], + simulation_boundary_points: vec![(0., 1.1*thickness/2.), (depth, 1.1*thickness/2.), (depth, -1.1*thickness/2.), (0., -1.1*thickness/2.), (0., 1.1*thickness/2.)], + energy_barrier_thickness: 10., + electronic_stopping_correction_factors: vec![1.0, 1.0], + }; + + let geometry_input_0D = geometry::Mesh0DInput { + length_unit: "ANGSTROM".to_string(), + densities: vec![0.03, 0.03], + electronic_stopping_correction_factor: 1.0 + }; + + let material_2D: material::Material = material::Material::::new(&material_parameters, &geometry_input_2D); + let mut material_0D: material::Material = material::Material::::new(&material_parameters, &geometry_input_0D); + material_0D.geometry.energy_barrier_thickness = 10.*ANGSTROM; + + particle_1.pos.x = 500.*ANGSTROM; + particle_1.pos.y = 0.; + + particle_1.pos_old.x = -500.*ANGSTROM; + particle_1.pos_old.y = 0.; + + assert_eq!(material_2D.geometry.get_ck(0., 0., 0.), material_0D.geometry.get_ck(0., 0., 0.)); + assert_eq!(material_2D.geometry.get_densities(0., 0., 0.), material_0D.geometry.get_densities(0., 0., 0.)); + assert_eq!(material_2D.geometry.get_total_density(0., 0., 0.), material_0D.geometry.get_total_density(0., 0., 0.)); + assert_eq!(material_2D.geometry.get_concentrations(0., 0., 0.), material_0D.geometry.get_concentrations(0., 0., 0.)); + assert_eq!(material_2D.geometry.closest_point(-10., 0., 5.), material_0D.geometry.closest_point(-10., 0., 5.)); + assert_eq!(material_2D.geometry.get_densities(-10., 0., 5.), material_0D.geometry.get_densities(-10., 0., 5.)); + assert_eq!(material_2D.geometry.get_ck(-10., 0., 5.), material_0D.geometry.get_ck(-10., 0., 5.)); +} + +#[test] +fn test_surface_binding_energy_barrier() { + let mass = 1.; + let Z = 1.; + let E = 10.*EV; + let Ec = 1.*EV; + let Es = 5.76*EV; + let x = 0.; + let y = 0.; + let z = 0.; + let cosx = 1./(2.0_f64).sqrt(); + let cosy = 1./(2.0_f64).sqrt(); + let cosz = 0.; + let mut particle_1 = particle::Particle::new(mass, Z, E, Ec, Es, x, y, z, cosx, cosy, cosz, false, false, 0); + + let material_parameters = material::MaterialParameters{ + energy_unit: "EV".to_string(), + mass_unit: "AMU".to_string(), + Eb: vec![0.0, 0.0], + Es: vec![2.0, 4.0], + Ec: vec![1.0, 1.0], + Z: vec![29., 1.], + m: vec![63.54, 1.0008], + interaction_index: vec![0, 0], + surface_binding_model: SurfaceBindingModel::PLANAR{calculation: SurfaceBindingCalculation::TARGET}, + bulk_binding_model: BulkBindingModel::INDIVIDUAL, + }; + + let thickness: f64 = 1000.; + let depth: f64 = 1000.; + + let geometry_input_2D = geometry::Mesh2DInput { + length_unit: "ANGSTROM".to_string(), + triangles: vec![(0., depth, 0., thickness/2., thickness/2., -thickness/2.), (depth, depth, 0., thickness/2., -thickness/2., -thickness/2.)], + densities: vec![vec![0.03, 0.03], vec![0.03, 0.03]], + material_boundary_points: vec![(0., thickness/2.), (depth, thickness/2.), (depth, -thickness/2.), (0., -thickness/2.), (0., thickness/2.)], + simulation_boundary_points: vec![(0., 1.1*thickness/2.), (depth, 1.1*thickness/2.), (depth, -1.1*thickness/2.), (0., -1.1*thickness/2.), (0., 1.1*thickness/2.)], + energy_barrier_thickness: 10., + electronic_stopping_correction_factors: vec![1.0, 1.0], + }; + + let geometry_input_0D = geometry::Mesh0DInput { + length_unit: "ANGSTROM".to_string(), + densities: vec![0.03, 0.03], + electronic_stopping_correction_factor: 1.0 + }; + + let material_2D: material::Material = material::Material::::new(&material_parameters, &geometry_input_2D); + let mut material_0D: material::Material = material::Material::::new(&material_parameters, &geometry_input_0D); + material_0D.geometry.energy_barrier_thickness = 10.*ANGSTROM; + + particle_1.pos.x = 500.*ANGSTROM; + particle_1.pos.y = 0.; + + particle_1.pos_old.x = -500.*ANGSTROM; + particle_1.pos_old.y = 0.; + + let inside_0D = material_0D.inside(particle_1.pos.x, particle_1.pos.y, particle_1.pos.z); + let inside_old_0D = material_0D.inside(particle_1.pos_old.x, particle_1.pos_old.y, particle_1.pos_old.z); + + let inside_2D = material_2D.inside(particle_1.pos.x, particle_1.pos.y, particle_1.pos.z); + let inside_old_2D = material_2D.inside(particle_1.pos_old.x, particle_1.pos_old.y, particle_1.pos_old.z); + + //println!("{} {}", inside, inside_old); + assert!(inside_0D); + assert!(!inside_old_0D); + + assert!(inside_2D); + assert!(!inside_old_2D); + + //Test concentration-dependent surface binding energy + let surface_binding_energy_2D = material_2D.actual_surface_binding_energy(&particle_1, particle_1.pos.x, particle_1.pos.y, particle_1.pos.z); + let surface_binding_energy_0D = material_0D.actual_surface_binding_energy(&particle_1, particle_1.pos.x, particle_1.pos.y, particle_1.pos.z); + + assert!(approx_eq!(f64, surface_binding_energy_0D/EV, (2. + 4.)/2., epsilon=1E-24)); + assert!(approx_eq!(f64, surface_binding_energy_2D/EV, (2. + 4.)/2., epsilon=1E-24)); + + println!("sbv 0D: {}", surface_binding_energy_0D/EV); + println!("sbv 2D: {}", surface_binding_energy_2D/EV); + + //Test leftmost boundary + assert!(material_2D.inside_energy_barrier(500.*ANGSTROM, 0., 0.)); + assert!(material_2D.inside_energy_barrier(-5.*ANGSTROM, 0., 0.)); + assert!(!material_2D.inside_energy_barrier(-15.*ANGSTROM, 0., 0.)); + + assert!(material_0D.inside_energy_barrier(500.*ANGSTROM, 0., 0.)); + assert!(material_0D.inside_energy_barrier(-5.*ANGSTROM, 0., 0.)); + assert!(!material_0D.inside_energy_barrier(-15.*ANGSTROM, 0., 0.)); + + assert!(material_0D.inside_energy_barrier(500.*ANGSTROM, 0., 0.)); + assert!(!material_0D.inside_energy_barrier(-500.*ANGSTROM, 0., 0.)); + assert!(material_0D.inside_energy_barrier(-9.*ANGSTROM, 0., 0.)); + assert!(!material_0D.inside_energy_barrier(-11.*ANGSTROM, 0., 0.)); + + //Test top boundary + assert!(material_2D.inside_energy_barrier(500.*ANGSTROM, 0., 0.)); + assert!(material_2D.inside_energy_barrier(500.*ANGSTROM, 505.*ANGSTROM, 0.)); + assert!(!material_2D.inside_energy_barrier(500.*ANGSTROM, 515.*ANGSTROM, 0.)); + + assert!(material_0D.inside_energy_barrier(500.*ANGSTROM, 0., 0.)); + assert!(material_0D.inside_energy_barrier(500.*ANGSTROM, 505.*ANGSTROM, 0.)); + assert!(material_0D.inside_energy_barrier(500.*ANGSTROM, 515.*ANGSTROM, 0.)); + + //Test bottom boundary + assert!(material_2D.inside_energy_barrier(500.*ANGSTROM, -505.*ANGSTROM, 0.)); + assert!(!material_2D.inside_energy_barrier(500.*ANGSTROM, -515.*ANGSTROM, 0.)); + + assert!(material_0D.inside_energy_barrier(500.*ANGSTROM, -505.*ANGSTROM, 0.)); + assert!(material_0D.inside_energy_barrier(500.*ANGSTROM, -515.*ANGSTROM, 0.)); + + //Test rightmost boundary + assert!(material_2D.inside_energy_barrier(1005.*ANGSTROM, 0., 0.)); + assert!(!material_2D.inside_energy_barrier(1015.*ANGSTROM, 0., 0.)); + + assert!(material_0D.inside_energy_barrier(1005.*ANGSTROM, 0., 0.)); + assert!(material_0D.inside_energy_barrier(1015.*ANGSTROM, 0., 0.)); +} + +#[test] +fn test_triangle_contains() { + let triangle_1 = geometry::Triangle2D::new((0., 2., 0., 2., 0., 0.)); + assert!(triangle_1.contains(0.5, 0.5)); + assert!(!triangle_1.contains(2., 2.)); + + let triangle_2 = geometry::Triangle2D::new((-2., 0., 0., 0., 0., -2.)); + assert!(triangle_2.contains(-0.5, -0.5)); + assert!(!triangle_2.contains(0.5, 0.5)); + assert!(!triangle_2.contains(-2., -2.)); +} + +#[test] +fn test_triangle_distance_to() { + let triangle_1 = geometry::Triangle2D::new((0., 2., 0., 2., 0., 0.)); + assert!(approx_eq!(f64, triangle_1.distance_to(-2., 0.), 2., epsilon=1E-12), "{}", triangle_1.distance_to(-2., 0.)); + + assert!(approx_eq!(f64, triangle_1.distance_to(2., 2.), (2.0_f64).sqrt(), epsilon=1E-12), "{}", triangle_1.distance_to(2., 2.)); + + assert!(approx_eq!(f64, triangle_1.distance_to(0., 0.), 0., epsilon=1E-12), "{}", triangle_1.distance_to(0., 0.)); + assert!(approx_eq!(f64, triangle_1.distance_to(2., 0.), 0., epsilon=1E-12), "{}", triangle_1.distance_to(2., 0.)); + assert!(approx_eq!(f64, triangle_1.distance_to(0., 2.), 0., epsilon=1E-12), "{}", triangle_1.distance_to(0., 2.)); +} + +#[test] +fn test_surface_refraction() { + let print_output = false; + + let mass = 1.; + let Z = 1.; + let E = 10.*EV; + let Ec = 1.*EV; + let Es = 5.76*EV; + let x = 0.; + let y = 0.; + let z = 0.; + let cosx = 1./(2.0_f64).sqrt(); + let cosy = 1./(2.0_f64).sqrt(); + let cosz = 0.; + let mut particle_1 = particle::Particle::new(mass, Z, E, Ec, Es, x, y, z, cosx, cosy, cosz, false, false, 0); + + //Test particle entering material and gaining energy + + //Eckstein's formulation for particle entering surface + let cosx_new = ((E*cosx*cosx + Es)/(E + Es)).sqrt(); + let sinx = (1. - cosx*cosx).sqrt(); + let sinx_new = (1. - cosx_new*cosx_new).sqrt(); + let cosy_new = cosy*sinx_new/sinx; + let cosz_new = cosz*sinx_new/sinx; + let dir_mag = (cosx_new*cosx_new + cosy_new*cosy_new + cosz_new*cosz_new).sqrt(); + let cosx_new = cosx_new/dir_mag; + let cosy_new = cosy_new/dir_mag; + let cosz_new = cosz_new/dir_mag; + + //let delta_theta = particle::refraction_angle(cosx, E, E + Es); + //particle::rotate_particle(&mut particle_1, delta_theta, 0.); + + let normal = Vector::new(1.0, 0.0, 0.0); + particle::surface_refraction(&mut particle_1, normal, Es); + + if print_output { + println!("dir_mag: {}", dir_mag); + println!("{} {} {}", cosx, cosy, cosz); + println!("{} {} {}", cosx_new, cosy_new, cosz_new); + println!("{} {} {}", particle_1.dir.x, particle_1.dir.y, particle_1.dir.z); + println!(); + } + + assert!(approx_eq!(f64, particle_1.dir.x, cosx_new, epsilon=1E-12)); + assert!(approx_eq!(f64, particle_1.dir.y, cosy_new, epsilon=1E-12)); + assert!(approx_eq!(f64, particle_1.dir.z, cosz_new, epsilon=1E-12)); + + //Test particle leaving material and losing energy + + let cosx_new = ((particle_1.E*particle_1.dir.x*particle_1.dir.x - Es)/(particle_1.E - Es)).sqrt(); + let sinx = (1. - particle_1.dir.x*particle_1.dir.x).sqrt(); + let sinx_new = (1. - cosx_new*cosx_new).sqrt(); + let cosy_new = particle_1.dir.y*sinx_new/sinx; + let cosz_new = particle_1.dir.z*sinx_new/sinx; + + if print_output { + println!("{} {} {}", particle_1.dir.x, particle_1.dir.y, particle_1.dir.z); + } + + //let delta_theta = particle::refraction_angle(particle_1.dir.x, particle_1.E, particle_1.E - Es); + //particle::rotate_particle(&mut particle_1, delta_theta, 0.); + + let normal = Vector::new(1.0, 0.0, 0.0); + particle::surface_refraction(&mut particle_1, normal, -Es); + + + if print_output { + println!("{} {} {}", cosx_new, cosy_new, cosz_new); + println!("{} {} {}", particle_1.dir.x, particle_1.dir.y, particle_1.dir.z); + } + + assert!(approx_eq!(f64, particle_1.dir.x, cosx_new, epsilon=1E-9)); + assert!(approx_eq!(f64, particle_1.dir.y, cosy_new, epsilon=1E-9)); + assert!(approx_eq!(f64, particle_1.dir.z, cosz_new, epsilon=1E-9)); + +} + +#[test] +fn test_momentum_conservation() { + + for energy_eV in vec![1., 10., 100., 1000., 1000.] { + //Aluminum + let m1 = 183.8*AMU; + let Z1 = 74.; + let E1 = energy_eV*EV; + let Ec1 = 1.*EV; + let Es1 = 1.*EV; + let x1 = 0.; + let y1 = 0.; + let z1 = 0.; + + //Aluminum + let m2 = 6.941; + let Z2 = 3.; + let Ec2 = 1.; + let Es2 = 1.; + + //Arbitrary initial angle + let theta = 0.974194583091052_f64; + let cosx = (theta).cos(); + let cosy = (theta).sin(); + let cosz = 0.; + + let material_parameters = material::MaterialParameters{ + energy_unit: "EV".to_string(), + mass_unit: "AMU".to_string(), + Eb: vec![0.0], + Es: vec![Es2], + Ec: vec![Ec2], + Z: vec![Z2], + m: vec![m2], + interaction_index: vec![0], + surface_binding_model: SurfaceBindingModel::PLANAR{calculation: SurfaceBindingCalculation::TARGET}, + bulk_binding_model: BulkBindingModel::INDIVIDUAL, + }; + + let thickness: f64 = 1000.; + let depth: f64 = 1000.; + let geometry_input = geometry::Mesh2DInput { + length_unit: "ANGSTROM".to_string(), + triangles: vec![(0., depth, 0., thickness/2., thickness/2., -thickness/2.), (depth, depth, 0., thickness/2., -thickness/2., -thickness/2.)], + densities: vec![vec![0.06306], vec![0.06306]], + material_boundary_points: vec![(0., thickness/2.), (depth, thickness/2.), (depth, -thickness/2.), (0., -thickness/2.), (0., thickness/2.)], + simulation_boundary_points: vec![(0., 1.1*thickness/2.), (depth, 1.1*thickness/2.), (depth, -1.1*thickness/2.), (0., -1.1*thickness/2.), (0., 1.1*thickness/2.)], + electronic_stopping_correction_factors: vec![0.0, 0.0], + energy_barrier_thickness: 0., + }; + + let material_1: material::Material = material::Material::::new(&material_parameters, &geometry_input); + + for high_energy_free_flight_paths in vec![true, false] { + for potential in vec![InteractionPotential::KR_C, InteractionPotential::MOLIERE, InteractionPotential::ZBL, InteractionPotential::LENZ_JENSEN] { + for scattering_integral in vec![ScatteringIntegral::MENDENHALL_WELLER, ScatteringIntegral::GAUSS_MEHLER{n_points: 10}, ScatteringIntegral::GAUSS_LEGENDRE] { + for root_finder in vec![Rootfinder::NEWTON{max_iterations: 100, tolerance: 1E-3}] { + + println!("Case: {} {} {} {}", energy_eV, high_energy_free_flight_paths, potential, scattering_integral); + + let mut particle_1 = particle::Particle::new(m1, Z1, E1, Ec1, Es1, x1, y1, z1, cosx, cosy, cosz, false, false, 0); + + #[cfg(not(feature = "distributions"))] + let options = Options { + name: "test".to_string(), + track_trajectories: false, + track_recoils: true, + track_recoil_trajectories: false, + write_buffer_size: 8000, + weak_collision_order: 0, + suppress_deep_recoils: false, + high_energy_free_flight_paths: high_energy_free_flight_paths, + electronic_stopping_mode: ElectronicStoppingMode::INTERPOLATED, + mean_free_path_model: MeanFreePathModel::LIQUID, + interaction_potential: vec![vec![potential]], + scattering_integral: vec![vec![scattering_integral]], + num_threads: 1, + num_chunks: 1, + use_hdf5: false, + root_finder: vec![vec![root_finder]], + track_displacements: false, + track_energy_losses: false, + }; + + #[cfg(feature = "distributions")] + let options = Options { + name: "test".to_string(), + track_trajectories: false, + track_recoils: true, + track_recoil_trajectories: false, + write_buffer_size: 8000, + weak_collision_order: 0, + suppress_deep_recoils: false, + high_energy_free_flight_paths: high_energy_free_flight_paths, + electronic_stopping_mode: ElectronicStoppingMode::INTERPOLATED, + mean_free_path_model: MeanFreePathModel::LIQUID, + interaction_potential: vec![vec![potential]], + scattering_integral: vec![vec![scattering_integral]], + num_threads: 1, + num_chunks: 1, + use_hdf5: false, + root_finder: vec![vec![root_finder]], + track_displacements: false, + track_energy_losses: false, + energy_min: 0.0, + energy_max: 10.0, + energy_num: 11, + angle_min: 0.0, + angle_max: 90.0, + angle_num: 11, + x_min: 0.0, + y_min: -10.0, + z_min: -10.0, + x_max: 10.0, + y_max: 10.0, + z_max: 10.0, + x_num: 11, + y_num: 11, + z_num: 11, + }; + + let binary_collision_geometries = bca::determine_mfp_phi_impact_parameter(&mut particle_1, &material_1, &options); + + println!("Phi: {} rad p: {} Angstrom mfp: {} Angstrom", binary_collision_geometries[0].phi_azimuthal, + binary_collision_geometries[0].impact_parameter/ANGSTROM, + binary_collision_geometries[0].mfp/ANGSTROM); + + let (species_index, mut particle_2) = bca::choose_collision_partner(&mut particle_1, &material_1, + &binary_collision_geometries[0], &options); + + let mom1_0 = particle_1.get_momentum(); + let mom2_0 = particle_2.get_momentum(); + + let initial_momentum = mom1_0.add(&mom2_0); + + let binary_collision_result = bca::calculate_binary_collision(&particle_1, + &particle_2, &binary_collision_geometries[0], &options).unwrap(); + + println!("E_recoil: {} eV Psi: {} rad Psi_recoil: {} rad", binary_collision_result.recoil_energy/EV, + binary_collision_result.psi, + binary_collision_result.psi_recoil); + + println!("Initial Energies: {} eV {} eV", particle_1.E/EV, particle_2.E/EV); + + //Energy transfer to recoil + 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); + + //Rotate particle 1, 2 by lab frame scattering angles + particle::rotate_particle(&mut particle_1, binary_collision_result.psi, + binary_collision_geometries[0].phi_azimuthal); + + particle::rotate_particle(&mut particle_2, -binary_collision_result.psi_recoil, + binary_collision_geometries[0].phi_azimuthal); + + //Subtract total energy from all simultaneous collisions and electronic stopping + bca::update_particle_energy(&mut particle_1, &material_1, 0., + binary_collision_result.recoil_energy, 0., particle_2.Z, species_index, &options); + + let mom1_1 = particle_1.get_momentum(); + let mom2_1 = particle_2.get_momentum(); + + let final_momentum = mom1_1.add(&mom2_1); + + println!("Final Energies: {} eV {} eV", particle_1.E/EV, particle_2.E/EV); + println!("X: {} {} {}% Error", initial_momentum.x/ANGSTROM/AMU, final_momentum.x/ANGSTROM/AMU, 100.*(final_momentum.x - initial_momentum.x)/initial_momentum.magnitude()); + println!("Y: {} {} {}% Error", initial_momentum.y/ANGSTROM/AMU, final_momentum.y/ANGSTROM/AMU, 100.*(final_momentum.y - initial_momentum.y)/initial_momentum.magnitude()); + println!("Z: {} {} {}% Error", initial_momentum.z/ANGSTROM/AMU, final_momentum.z/ANGSTROM/AMU, 100.*(final_momentum.z - initial_momentum.z)/initial_momentum.magnitude()); + println!(); + + assert!(approx_eq!(f64, initial_momentum.x, final_momentum.x, epsilon = 1E-12)); + assert!(approx_eq!(f64, initial_momentum.y, final_momentum.y, epsilon = 1E-12)); + assert!(approx_eq!(f64, initial_momentum.z, final_momentum.z, epsilon = 1E-12)); + + assert!(!particle_1.E.is_nan()); + assert!(!particle_2.E.is_nan()); + assert!(!initial_momentum.x.is_nan()); + assert!(!initial_momentum.x.is_nan()); + assert!(!initial_momentum.x.is_nan()); + } + } + } + } + } +} + +#[test] +fn test_rotate_particle() { + let mass = 1.; + let Z = 1.; + let E = 1.; + let Ec = 1.; + let Es = 1.; + let x = 0.; + let y = 0.; + let z = 0.; + let cosx = (PI/4.).cos(); + let cosy = (PI/4.).sin(); + let cosz = 0.; + let psi = -PI/4.; + let phi = 0.; + + let mut particle = particle::Particle::new(mass, Z, E, Ec, Es, x, y, z, cosx, cosy, cosz, false, false, 0); + + //Check that rotation in 2D works + particle::rotate_particle(&mut particle, psi, phi); + assert!(approx_eq!(f64, particle.dir.x, 0., epsilon = 1E-12), "particle.dir.x: {} Should be ~0.", particle.dir.x); + assert!(approx_eq!(f64, particle.dir.y, 1., epsilon = 1E-12), "particle.dir.y: {} Should be ~1.", particle.dir.y); + + //Check that rotating back by negative psi returns to the previous values + particle::rotate_particle(&mut particle, -psi, phi); + assert!(approx_eq!(f64, particle.dir.x, cosx, epsilon = 1E-12), "particle.dir.x: {} Should be ~{}", particle.dir.x, cosx); + assert!(approx_eq!(f64, particle.dir.y, cosy, epsilon = 1E-12), "particle.dir.y: {} Should be ~{}", particle.dir.y, cosy); + + //Check that azimuthal rotation by 180 degrees works correctly + let phi = PI; + particle::rotate_particle(&mut particle, psi, phi); + assert!(approx_eq!(f64, particle.dir.x, 1., epsilon = 1E-12), "particle.dir.x: {} Should be ~1.", particle.dir.x); + assert!(approx_eq!(f64, particle.dir.y, 0., epsilon = 1E-12), "particle.dir.y: {} Should be ~0.", particle.dir.y); + + //Check that particle direction vector remains normalized following rotations + assert!(approx_eq!(f64, particle.dir.x.powi(2) + particle.dir.y.powi(2) + particle.dir.z.powi(2), 1.), "Particle direction not normalized."); + +} + +#[test] +fn test_particle_advance() { + let mass = 1.; + let Z = 1.; + let E = 1.; + let Ec = 1.; + let Es = 1.; + let x = 0.; + let y = 0.; + let z = 0.; + let cosx = (PI/4.).cos(); + let cosy = (PI/4.).sin(); + let cosz = 0.; + let mfp = 1.; + let asymptotic_deflection = 0.5; + + let mut particle = particle::Particle::new(mass, Z, E, Ec, Es, x, y, z, cosx, cosy, cosz, false, false, 0); + + let distance_traveled = particle::particle_advance(&mut particle, mfp, asymptotic_deflection); + + assert_eq!(particle.pos.x, (1. - 0.5)*cosx); + assert_eq!(particle.pos.y, (1. - 0.5)*cosy); + assert_eq!(particle.pos.z, 0.); + assert_eq!(distance_traveled, mfp - asymptotic_deflection); +} + +#[test] +fn test_quadrature() { + let Za = 1.; + let Zb = 13.; + let Ma = 1.008; + let Mb = 26.9815385; + let E0 = 10.*EV; + let p = 1.*ANGSTROM; + let a = interactions::screening_length(Za, Zb, InteractionPotential::KR_C); + + #[cfg(not(feature = "distributions"))] + let options = Options { + name: "test".to_string(), + track_trajectories: false, + track_recoils: true, + track_recoil_trajectories: false, + write_buffer_size: 8000, + weak_collision_order: 0, + suppress_deep_recoils: false, + high_energy_free_flight_paths: false, + electronic_stopping_mode: ElectronicStoppingMode::INTERPOLATED, + mean_free_path_model: MeanFreePathModel::LIQUID, + interaction_potential: vec![vec![InteractionPotential::KR_C]], + scattering_integral: vec![vec![ScatteringIntegral::MENDENHALL_WELLER]], + num_threads: 1, + num_chunks: 1, + use_hdf5: false, + root_finder: vec![vec![Rootfinder::NEWTON{max_iterations: 100, tolerance: 1E-14}]], + track_displacements: false, + track_energy_losses: false, + }; + + #[cfg(feature = "distributions")] + let options = Options { + name: "test".to_string(), + track_trajectories: false, + track_recoils: true, + track_recoil_trajectories: false, + write_buffer_size: 8000, + weak_collision_order: 0, + suppress_deep_recoils: false, + high_energy_free_flight_paths: false, + electronic_stopping_mode: ElectronicStoppingMode::INTERPOLATED, + mean_free_path_model: MeanFreePathModel::LIQUID, + interaction_potential: vec![vec![InteractionPotential::KR_C]], + scattering_integral: vec![vec![ScatteringIntegral::MENDENHALL_WELLER]], + num_threads: 1, + num_chunks: 1, + use_hdf5: false, + root_finder: vec![vec![Rootfinder::NEWTON{max_iterations: 100, tolerance: 1E-14}]], + track_displacements: false, + track_energy_losses: false, + energy_min: 0.0, + energy_max: 10.0, + energy_num: 11, + angle_min: 0.0, + angle_max: 90.0, + angle_num: 11, + x_min: 0.0, + y_min: -10.0, + z_min: -10.0, + x_max: 10.0, + y_max: 10.0, + z_max: 10.0, + x_num: 11, + y_num: 11, + z_num: 11, + }; + + let x0_newton = bca::newton_rootfinder(Za, Zb, Ma, Mb, E0, p, InteractionPotential::KR_C, 100, 1E-12).unwrap(); + + //If cpr_rootfinder is enabled, compare Newton to CPR - they should be nearly identical + #[cfg(any(feature = "cpr_rootfinder_openblas", feature = "cpr_rootfinder_netlib", feature = "cpr_rootfinder_intel_mkl"))] + if let Ok(x0_cpr) = bca::cpr_rootfinder(Za, Zb, Ma, Mb, E0, p, InteractionPotential::KR_C, 2, 1000, 1E-13, 1E-16, 1E-18, 1E6, 1E-18, false) { + println!("CPR: {} Newton: {}", x0_cpr, x0_newton); + assert!(approx_eq!(f64, x0_newton, x0_cpr, epsilon=1E-3)); + }; + + //Compute center of mass deflection angle with each algorithm + let theta_gm = bca::gauss_mehler(Za, Zb, Ma, Mb, E0, p, x0_newton, InteractionPotential::KR_C, 10); + let theta_gl = bca::gauss_legendre(Za, Zb, Ma, Mb, E0, p, x0_newton, InteractionPotential::KR_C); + let theta_mw = bca::mendenhall_weller(Za, Zb, Ma, Mb, E0, p, x0_newton, InteractionPotential::KR_C); + let theta_magic = bca::magic(Za, Zb, Ma, Mb, E0, p, x0_newton, InteractionPotential::KR_C); + + //Gauss-Mehler and Gauss-Legendre should be very close to each other + assert!(approx_eq!(f64, theta_gm, theta_gl, epsilon=0.001)); + assert!(approx_eq!(f64, theta_gm, theta_mw, epsilon=0.001)); + assert!(approx_eq!(f64, theta_gm, theta_magic, epsilon=0.15)); + + println!("Gauss-Mehler: {} Gauss-Legendre: {} Mendenhall-Weller: {} MAGIC: {}", + theta_gm, theta_gl, theta_mw, theta_magic); +} From b0a899f0f6a66637c108a056200045e066a5126c Mon Sep 17 00:00:00 2001 From: Jon Drobny Date: Fri, 19 Mar 2021 17:10:24 -0500 Subject: [PATCH 18/33] Added unneeded but necessary-to-compile options. --- src/lib.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index db34be8..eda1ebc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -118,6 +118,21 @@ pub fn simple_bca(x: f64, y: f64, z: f64, ux: f64, uy: f64, uz: f64, E1: f64, Z1 root_finder: vec![vec![Rootfinder::DEFAULTNEWTON]], track_displacements: false, track_energy_losses: false, + energy_min: 0.0, + energy_max: 10.0, + energy_num: 11, + angle_min: 0.0, + angle_max: 90.0, + angle_num: 11, + x_min: 0.0, + y_min: -10.0, + z_min: -10.0, + x_max: 10.0, + y_max: 10.0, + z_max: 10.0, + x_num: 11, + y_num: 11, + z_num: 11, }; let p = particle::Particle { From 1c3a3f367f0816e6e990810b00dd623227ce8c8e Mon Sep 17 00:00:00 2001 From: Jon Drobny Date: Fri, 19 Mar 2021 17:17:46 -0500 Subject: [PATCH 19/33] More files necessary for bindings to work. --- MANIFEST.in | 2 ++ cbindgen.toml | 0 pyproject.toml | 2 ++ setup.py | 9 +++++++++ 4 files changed, 13 insertions(+) create mode 100644 MANIFEST.in create mode 100644 cbindgen.toml create mode 100644 pyproject.toml create mode 100644 setup.py diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..7c68298 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include Cargo.toml +recursive-include src * diff --git a/cbindgen.toml b/cbindgen.toml new file mode 100644 index 0000000..e69de29 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9203538 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,2 @@ +[build-system] +requires = ["setuptools", "setuptools-rust"] diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..6e08107 --- /dev/null +++ b/setup.py @@ -0,0 +1,9 @@ +from setuptools import setup +from setuptools_rust import Binding, RustExtension + +setup( + name="RustBCA", + rust_extensions=[RustExtension("libRustBCA.pybca", binding=Binding.PyO3)], + # rust extensions are not zip safe, just like C-extensions. + zip_safe=False, +) From 01a8a00d6c59efca053d3fce885260567643934c Mon Sep 17 00:00:00 2001 From: Jon Drobny Date: Fri, 19 Mar 2021 17:24:44 -0500 Subject: [PATCH 20/33] Fixes for when built with distributions. --- src/lib.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index eda1ebc..a851fea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,6 +99,7 @@ pub fn simple_bca_py(x: f64, y: f64, z: f64, ux: f64, uy: f64, uz: f64, E1: f64, pub fn simple_bca(x: f64, y: f64, z: f64, ux: f64, uy: f64, uz: f64, E1: f64, Z1: f64, m1: f64, Ec1: f64, Es1: f64, Z2: f64, m2: f64, Ec2: f64, Es2: f64, n2: f64, Eb2: f64) -> Vec<[f64; 9]> { + #[cfg(feature = "distributions")] let options = Options { name: "test".to_string(), track_trajectories: false, @@ -135,6 +136,28 @@ pub fn simple_bca(x: f64, y: f64, z: f64, ux: f64, uy: f64, uz: f64, E1: f64, Z1 z_num: 11, }; + #[cfg(not(feature = "distributions"))] + let options = Options { + name: "test".to_string(), + track_trajectories: false, + track_recoils: true, + track_recoil_trajectories: false, + write_buffer_size: 8000, + weak_collision_order: 3, + suppress_deep_recoils: false, + high_energy_free_flight_paths: false, + electronic_stopping_mode: ElectronicStoppingMode::INTERPOLATED, + mean_free_path_model: MeanFreePathModel::LIQUID, + interaction_potential: vec![vec![InteractionPotential::KR_C]], + scattering_integral: vec![vec![ScatteringIntegral::MENDENHALL_WELLER]], + num_threads: 1, + num_chunks: 1, + use_hdf5: false, + root_finder: vec![vec![Rootfinder::DEFAULTNEWTON]], + track_displacements: false, + track_energy_losses: false, + }; + let p = particle::Particle { m: m1*AMU, Z: Z1, From 4ab8cfc1bc39244839b0be8bd26663f4b1e1fc8f Mon Sep 17 00:00:00 2001 From: Jon Drobny Date: Fri, 19 Mar 2021 17:37:12 -0500 Subject: [PATCH 21/33] Workflow fix --- .github/workflows/rustbca_compile_check.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/rustbca_compile_check.yml b/.github/workflows/rustbca_compile_check.yml index bec6e7f..5cd3005 100644 --- a/.github/workflows/rustbca_compile_check.yml +++ b/.github/workflows/rustbca_compile_check.yml @@ -1,4 +1,4 @@ -name: rustBCA Compile check +name: RustBCA Compile check on: push: @@ -41,11 +41,11 @@ jobs: - name: Run Examples run: | cargo run --release 0D examples/boron_nitride_0D.toml - ./target/release/rustBCA 0D examples/titanium_dioxide_0D.toml - ./target/release/rustBCA 1D examples/layered_geometry_1D.toml + ./target/release/RustBCA 0D examples/titanium_dioxide_0D.toml + ./target/release/RustBCA 1D examples/layered_geometry_1D.toml cat 2000.0eV_0.0001deg_He_TiO2_Al_Sisummary.output - ./target/release/rustBCA examples/boron_nitride.toml - ./target/release/rustBCA examples/layered_geometry.toml + ./target/release/RustBCA examples/boron_nitride.toml + ./target/release/RustBCA examples/layered_geometry.toml cat 2000.0eV_0.0001deg_He_TiO2_Al_Sisummary.output - ./target/release/rustBCA SPHERE examples/boron_nitride_sphere.toml + ./target/release/RustBCA SPHERE examples/boron_nitride_sphere.toml cargo run --release --features parry3d TRIMESH examples/tungsten_twist_trimesh.toml From f2b7dc77fdf1ae20fcd55bbe95d3a5b0cdd6cc9e Mon Sep 17 00:00:00 2001 From: Jon Drobny Date: Sat, 20 Mar 2021 00:23:52 -0500 Subject: [PATCH 22/33] Added ability to run lists of energy-angle coords for simple_bca. --- src/lib.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index a851fea..1bd1397 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,6 +70,7 @@ pub use crate::parry::{ParryBall, ParryBallInput, InputParryBall, ParryTriMesh, #[pymodule] pub fn pybca(py: Python, m: &PyModule) -> PyResult<()> { m.add_function(wrap_pyfunction!(simple_bca_py, m)?)?; + m.add_function(wrap_pyfunction!(simple_bca_list_py, m)?)?; Ok(()) } @@ -97,6 +98,27 @@ pub fn simple_bca_py(x: f64, y: f64, z: f64, ux: f64, uy: f64, uz: f64, E1: f64, simple_bca(x, y, z, ux, uy, uz, E1, Z1, m1, Ec1, Es1, Z2, m2, Ec2, Es2, n2, Eb2) } +#[pyfunction] +pub fn simple_bca_list_py(energies: Vec, usx: Vec, usy: Vec, usz: Vec, Z1: f64, m1: f64, Ec1: f64, Es1: f64, Z2: f64, m2: f64, Ec2: f64, Es2: f64, n2: f64, Eb2: f64) -> Vec<[f64; 9]> { + + assert_eq!(energies.len(), usx.len()); + assert_eq!(energies.len(), usy.len()); + assert_eq!(energies.len(), usz.len()); + + let x = -2.*(n2*10E30).powf(-1./3.)/SQRTPI*2.; + let y = 0.0; + let z = 0.0; + + let mut total_output = vec![]; + for (((E1, ux), uy), uz) in energies.iter().zip(usx).zip(usy).zip(usz) { + let output = simple_bca(x, y, z, ux, uy, uz, *E1, Z1, m1, Ec1, Es1, Z2, m2, Ec2, Es2, n2, Eb2); + for particle in output { + total_output.push(particle); + } + } + total_output +} + pub fn simple_bca(x: f64, y: f64, z: f64, ux: f64, uy: f64, uz: f64, E1: f64, Z1: f64, m1: f64, Ec1: f64, Es1: f64, Z2: f64, m2: f64, Ec2: f64, Es2: f64, n2: f64, Eb2: f64) -> Vec<[f64; 9]> { #[cfg(feature = "distributions")] From 8d1d93f34393f540604d7a214653caca488af82c Mon Sep 17 00:00:00 2001 From: Jon Drobny Date: Mon, 22 Mar 2021 09:42:49 -0500 Subject: [PATCH 23/33] Updates to bindings. --- RustBCA.c | 14 ++++++++++++++ src/lib.rs | 1 - 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 RustBCA.c diff --git a/RustBCA.c b/RustBCA.c new file mode 100644 index 0000000..dee3b1e --- /dev/null +++ b/RustBCA.c @@ -0,0 +1,14 @@ +#include "RustBCA.h" +#include +#include + +int main() { + OutputBCA output; + + output = simple_bca_c(0., 0., 0., 0.5, 0.5, 0.00, 2000.0, 2.0, 4.0, 1.0, 0.0, 74.0, 184.0, 1.0, 8.79, 0.06306, 0.0); + + std::cout << output.len; + std::cout << output.particles[1][0]; + + return 0.; +} diff --git a/src/lib.rs b/src/lib.rs index 1bd1397..6a54c37 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,7 +79,6 @@ pub struct OutputBCA { len: usize, pub particles: *mut [f64; 9], } - #[no_mangle] pub extern "C" fn simple_bca_c(x: f64, y: f64, z: f64, ux: f64, uy: f64, uz: f64, E1: f64, Z1: f64, m1: f64, Ec1: f64, Es1: f64, Z2: f64, m2: f64, Ec2: f64, Es2: f64, n2: f64, Eb2: f64) -> OutputBCA { let mut output = simple_bca(x, y, z, ux, uy, uz, E1, Z1, m1, Ec1, Es1, Z2, m2, Ec2, Es2, n2, Eb2); From bd7dca14718d848c8cfa5deb7e33e7032bce2f0d Mon Sep 17 00:00:00 2001 From: Jon Drobny <37962344+drobnyjt@users.noreply.github.com> Date: Mon, 22 Mar 2021 09:43:22 -0500 Subject: [PATCH 24/33] Delete RustBCA.c --- RustBCA.c | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 RustBCA.c diff --git a/RustBCA.c b/RustBCA.c deleted file mode 100644 index dee3b1e..0000000 --- a/RustBCA.c +++ /dev/null @@ -1,14 +0,0 @@ -#include "RustBCA.h" -#include -#include - -int main() { - OutputBCA output; - - output = simple_bca_c(0., 0., 0., 0.5, 0.5, 0.00, 2000.0, 2.0, 4.0, 1.0, 0.0, 74.0, 184.0, 1.0, 8.79, 0.06306, 0.0); - - std::cout << output.len; - std::cout << output.particles[1][0]; - - return 0.; -} From 8511c652c326d711bc6d05d957e57d90aa739c0b Mon Sep 17 00:00:00 2001 From: Jon Drobny Date: Tue, 23 Mar 2021 11:29:50 -0500 Subject: [PATCH 25/33] First pass of library error messages. --- src/bca.rs | 2 +- src/lib.rs | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/bca.rs b/src/bca.rs index 23b4be0..7ceaa15 100644 --- a/src/bca.rs +++ b/src/bca.rs @@ -189,7 +189,7 @@ pub fn single_ion_bca(particle: particle::Particle, material: &mate #[cfg(feature = "accelerated_ions")] let distance_traveled = particle::particle_advance(&mut particle_1, - binary_collision_geometries[0].mfp + distance_to_target, total_asymptotic_deflection); + binary_collision_geometries[0].mfp + distance_to_target - material.geometry.get_energy_barrier_thickness(), total_asymptotic_deflection); //Subtract total energy from all simultaneous collisions and electronic stopping bca::update_particle_energy(&mut particle_1, &material, distance_traveled, diff --git a/src/lib.rs b/src/lib.rs index 6a54c37..9fed4c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,6 +79,7 @@ pub struct OutputBCA { len: usize, pub particles: *mut [f64; 9], } + #[no_mangle] pub extern "C" fn simple_bca_c(x: f64, y: f64, z: f64, ux: f64, uy: f64, uz: f64, E1: f64, Z1: f64, m1: f64, Ec1: f64, Es1: f64, Z2: f64, m2: f64, Ec2: f64, Es2: f64, n2: f64, Eb2: f64) -> OutputBCA { let mut output = simple_bca(x, y, z, ux, uy, uz, E1, Z1, m1, Ec1, Es1, Z2, m2, Ec2, Es2, n2, Eb2); @@ -104,7 +105,7 @@ pub fn simple_bca_list_py(energies: Vec, usx: Vec, usy: Vec, usz: assert_eq!(energies.len(), usy.len()); assert_eq!(energies.len(), usz.len()); - let x = -2.*(n2*10E30).powf(-1./3.)/SQRTPI*2.; + let x = -2.*(n2*10E30).powf(-1./3.); let y = 0.0; let z = 0.0; @@ -120,6 +121,10 @@ pub fn simple_bca_list_py(energies: Vec, usx: Vec, usy: Vec, usz: pub fn simple_bca(x: f64, y: f64, z: f64, ux: f64, uy: f64, uz: f64, E1: f64, Z1: f64, m1: f64, Ec1: f64, Es1: f64, Z2: f64, m2: f64, Ec2: f64, Es2: f64, n2: f64, Eb2: f64) -> Vec<[f64; 9]> { + assert!(E1 > 0.0, "Error: Incident energy cannot be less than or equal to 0."); + assert!(Ec1 > 0.0, "Error: Cutoff energy cannot be less than or equal to 0."); + assert!(Ec2 > 0.0, "Error: Cutoff energy cannot be less than or equal to 0."); + #[cfg(feature = "distributions")] let options = Options { name: "test".to_string(), From 2811710b313cc8bdeb65e33b33f4ef92c181aa9a Mon Sep 17 00:00:00 2001 From: Jon Drobny Date: Fri, 26 Mar 2021 09:47:37 -0500 Subject: [PATCH 26/33] Added context to toml unpack that hints to the user they may have forgotten the geometry command line argument (0D, 1D, 2D, SPHERE, BALL, TRIMESH) --- src/input.rs | 6 +++--- src/parry.rs | 4 ++-- src/sphere.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/input.rs b/src/input.rs index c682d58..42a6779 100644 --- a/src/input.rs +++ b/src/input.rs @@ -28,7 +28,7 @@ impl GeometryInput for Input2D { impl InputFile for Input2D { fn new(string: &str) -> Input2D { - toml::from_str(string).unwrap() + toml::from_str(string).context("Could not parse TOML file. Be sure you are using the correct input file mode (e.g., ./RustBCA SPHERE sphere.toml or RustBCA.exe 0D mesh_0d.toml).").unwrap() } fn get_options(&self) -> &Options{ @@ -60,7 +60,7 @@ impl GeometryInput for Input0D { impl InputFile for Input0D { fn new(string: &str) -> Input0D { - toml::from_str(string).expect("Could not parse TOML file.") + toml::from_str(string).context("Could not parse TOML file. Be sure you are using the correct input file mode (e.g., ./RustBCA SPHERE sphere.toml or RustBCA.exe 0D mesh_0d.toml).").unwrap() } fn get_options(&self) -> &Options{ @@ -92,7 +92,7 @@ impl GeometryInput for Input1D { impl InputFile for Input1D { fn new(string: &str) -> Input1D { - toml::from_str(string).expect("Could not parse TOML file.") + toml::from_str(string).context("Could not parse TOML file. Be sure you are using the correct input file mode (e.g., ./RustBCA SPHERE sphere.toml or RustBCA.exe 0D mesh_0d.toml).").unwrap() } fn get_options(&self) -> &Options{ diff --git a/src/parry.rs b/src/parry.rs index ac99ced..75d9a24 100644 --- a/src/parry.rs +++ b/src/parry.rs @@ -15,7 +15,7 @@ pub struct InputParryBall { impl InputFile for InputParryBall { fn new(string: &str) -> InputParryBall { - toml::from_str(string).expect("Could not parse TOML file.") + toml::from_str(string).context("Could not parse TOML file. Be sure you are using the correct input file mode (e.g., ./RustBCA SPHERE sphere.toml or RustBCA.exe 0D mesh_0d.toml).").unwrap() } fn get_options(&self) -> &Options{ @@ -137,7 +137,7 @@ pub struct InputParryTriMesh { impl InputFile for InputParryTriMesh { fn new(string: &str) -> InputParryTriMesh { - toml::from_str(string).expect("Could not parse TOML file.") + toml::from_str(string).context("Could not parse TOML file. Be sure you are using the correct input file mode (e.g., ./RustBCA SPHERE sphere.toml or RustBCA.exe 0D mesh_0d.toml).").unwrap() } fn get_options(&self) -> &Options{ diff --git a/src/sphere.rs b/src/sphere.rs index 80034cb..c4994f3 100644 --- a/src/sphere.rs +++ b/src/sphere.rs @@ -11,7 +11,7 @@ pub struct InputSphere { impl InputFile for InputSphere { fn new(string: &str) -> InputSphere { - toml::from_str(string).expect("Could not parse TOML file.") + toml::from_str(string).context("Could not parse TOML file. Be sure you are using the correct input file mode (e.g., ./RustBCA SPHERE sphere.toml or RustBCA.exe 0D mesh_0d.toml).").unwrap() } fn get_options(&self) -> &Options{ From 5d81514236288fa2c14db652341e9884d6cffca5 Mon Sep 17 00:00:00 2001 From: Jon Drobny Date: Fri, 26 Mar 2021 13:10:46 -0500 Subject: [PATCH 27/33] Progress bar .finish() call was not in the right place. --- src/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index e8b6d5a..5eeb94a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -125,7 +125,6 @@ fn physics_loop(particle_input_array: Vec(particle_input_array: Vec Date: Mon, 29 Mar 2021 17:22:49 -0500 Subject: [PATCH 28/33] Added C list version. --- src/lib.rs | 188 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 185 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9fed4c5..2b32635 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,9 @@ use std::fs::OpenOptions; use std::io::prelude::*; use std::io::BufWriter; +//standard slice +use std::slice; + //itertools use itertools::izip; @@ -76,13 +79,192 @@ pub fn pybca(py: Python, m: &PyModule) -> PyResult<()> { #[repr(C)] pub struct OutputBCA { - len: usize, + pub len: usize, pub particles: *mut [f64; 9], } +#[derive(Debug)] +#[repr(C)] +pub struct InputSimpleBCA { + pub len: usize, + /// x, y, z, vx, vy, vz + pub velocities: *mut [f64; 6], + pub Z1: f64, + pub m1: f64, + pub Ec1: f64, + pub Es1: f64, + pub Z2: f64, + pub m2: f64, + pub n2: f64, + pub Ec2: f64, + pub Es2: f64, + pub Eb2: f64, +} + +#[no_mangle] +pub extern "C" fn simple_bca_list_c(input: InputSimpleBCA) -> OutputBCA { + + let x = -2.*(input.n2*10E30).powf(-1./3.); + let y = 0.0; + let z = 0.0; + + let mut total_output = vec![]; + + #[cfg(feature = "distributions")] + let options = Options { + name: "test".to_string(), + track_trajectories: false, + track_recoils: true, + track_recoil_trajectories: false, + write_buffer_size: 8000, + weak_collision_order: 3, + suppress_deep_recoils: false, + high_energy_free_flight_paths: false, + electronic_stopping_mode: ElectronicStoppingMode::INTERPOLATED, + mean_free_path_model: MeanFreePathModel::LIQUID, + interaction_potential: vec![vec![InteractionPotential::KR_C]], + scattering_integral: vec![vec![ScatteringIntegral::MENDENHALL_WELLER]], + num_threads: 1, + num_chunks: 1, + use_hdf5: false, + root_finder: vec![vec![Rootfinder::DEFAULTNEWTON]], + track_displacements: false, + track_energy_losses: false, + energy_min: 0.0, + energy_max: 10.0, + energy_num: 11, + angle_min: 0.0, + angle_max: 90.0, + angle_num: 11, + x_min: 0.0, + y_min: -10.0, + z_min: -10.0, + x_max: 10.0, + y_max: 10.0, + z_max: 10.0, + x_num: 11, + y_num: 11, + z_num: 11, + }; + + #[cfg(not(feature = "distributions"))] + let options = Options { + name: "test".to_string(), + track_trajectories: false, + track_recoils: true, + track_recoil_trajectories: false, + write_buffer_size: 8000, + weak_collision_order: 3, + suppress_deep_recoils: false, + high_energy_free_flight_paths: false, + electronic_stopping_mode: ElectronicStoppingMode::INTERPOLATED, + mean_free_path_model: MeanFreePathModel::LIQUID, + interaction_potential: vec![vec![InteractionPotential::KR_C]], + scattering_integral: vec![vec![ScatteringIntegral::MENDENHALL_WELLER]], + num_threads: 1, + num_chunks: 1, + use_hdf5: false, + root_finder: vec![vec![Rootfinder::DEFAULTNEWTON]], + track_displacements: false, + track_energy_losses: false, + }; + + let material_parameters = material::MaterialParameters { + energy_unit: "EV".to_string(), + mass_unit: "AMU".to_string(), + Eb: vec![input.Eb2], + Es: vec![input.Es2], + Ec: vec![input.Ec2], + Z: vec![input.Z2], + m: vec![input.m2], + interaction_index: vec![0], + surface_binding_model: SurfaceBindingModel::AVERAGE, + bulk_binding_model: BulkBindingModel::INDIVIDUAL, + }; + + let geometry_input = geometry::Mesh0DInput { + length_unit: "ANGSTROM".to_string(), + densities: vec![input.n2], + electronic_stopping_correction_factor: 1.0 + }; + + let m = material::Material::::new(&material_parameters, &geometry_input); + + let velocities = unsafe { slice::from_raw_parts(input.velocities, input.len) }; + + for velocity in velocities { + + let vx = velocity[3]; + let vy = velocity[4]; + let vz = velocity[5]; + + let v = (vx*vx + vy*vy + vz*vz).sqrt(); + + let E1 = 0.5*input.m1*AMU*v*v; + + let ux = vx/v; + let uy = vy/v; + let uz = vz/v; + + let p = particle::Particle { + m: input.m1*AMU, + Z: input.Z1, + E: E1, + Ec: input.Ec1*EV, + Es: input.Es1*EV, + pos: Vector::new(x, y, z), + dir: Vector::new(ux, uy, uz), + pos_origin: Vector::new(x, y, z), + pos_old: Vector::new(x, y, z), + dir_old: Vector::new(ux, uy, uz), + energy_origin: E1, + asymptotic_deflection: 0.0, + stopped: false, + left: false, + incident: true, + first_step: true, + trajectory: vec![], + energies: vec![], + track_trajectories: false, + number_collision_events: 0, + backreflected: false, + interaction_index : 0 + }; + + + let output = bca::single_ion_bca(p, &m, &options); + + for particle in output { + total_output.push( + [ + particle.Z, + particle.m/AMU, + particle.E/EV, + particle.pos.x/ANGSTROM, + particle.pos.y/ANGSTROM, + particle.pos.z/ANGSTROM, + particle.dir.x, + particle.dir.y, + particle.dir.z + ] + ); + } + } + + let len = total_output.len(); + let particles = total_output.as_mut_ptr(); + + std::mem::forget(total_output); + OutputBCA { + len, + particles + } +} + #[no_mangle] pub extern "C" fn simple_bca_c(x: f64, y: f64, z: f64, ux: f64, uy: f64, uz: f64, E1: f64, Z1: f64, m1: f64, Ec1: f64, Es1: f64, Z2: f64, m2: f64, Ec2: f64, Es2: f64, n2: f64, Eb2: f64) -> OutputBCA { let mut output = simple_bca(x, y, z, ux, uy, uz, E1, Z1, m1, Ec1, Es1, Z2, m2, Ec2, Es2, n2, Eb2); + let len = output.len(); let particles = output.as_mut_ptr(); @@ -122,8 +304,8 @@ pub fn simple_bca_list_py(energies: Vec, usx: Vec, usy: Vec, usz: pub fn simple_bca(x: f64, y: f64, z: f64, ux: f64, uy: f64, uz: f64, E1: f64, Z1: f64, m1: f64, Ec1: f64, Es1: f64, Z2: f64, m2: f64, Ec2: f64, Es2: f64, n2: f64, Eb2: f64) -> Vec<[f64; 9]> { assert!(E1 > 0.0, "Error: Incident energy cannot be less than or equal to 0."); - assert!(Ec1 > 0.0, "Error: Cutoff energy cannot be less than or equal to 0."); - assert!(Ec2 > 0.0, "Error: Cutoff energy cannot be less than or equal to 0."); + assert!(Ec1 > 0.0, "Error: Cutoff energy Ec1 cannot be less than or equal to 0."); + assert!(Ec2 > 0.0, "Error: Cutoff energy Ec2 cannot be less than or equal to 0."); #[cfg(feature = "distributions")] let options = Options { From 347bc24185df7351ee1b1691e6cf03241626d963 Mon Sep 17 00:00:00 2001 From: Jon Drobny Date: Mon, 29 Mar 2021 17:23:35 -0500 Subject: [PATCH 29/33] Updates to C bindings. --- RustBCA.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/RustBCA.h b/RustBCA.h index 72ea4ae..1f04ea4 100644 --- a/RustBCA.h +++ b/RustBCA.h @@ -64,8 +64,26 @@ struct OutputBCA { double (*particles)[9]; }; +struct InputSimpleBCA { + uintptr_t len; + // vx vy vz [m/s] + double (*velocities)[3]; + double Z1; + double m1; + double Ec1; + double Es1; + double Z2; + double m2; + double n2; + double Ec2; + double Es2; + double Eb2; +}; + extern "C" { +OutputBCA simple_bca_list_c(InputSimpleBCA input); + OutputBCA simple_bca_c(double x, double y, double z, From 7634de31f61f320056b3f03da20f32a2516bb643 Mon Sep 17 00:00:00 2001 From: Jon Drobny Date: Tue, 30 Mar 2021 11:52:00 -0500 Subject: [PATCH 30/33] Fix to filter out stopped, non-incident ions. --- RustBCA.h | 210 ++++++++++++++++++++++++++--------------------------- src/lib.rs | 39 +++++----- 2 files changed, 126 insertions(+), 123 deletions(-) diff --git a/RustBCA.h b/RustBCA.h index 1f04ea4..563a7cc 100644 --- a/RustBCA.h +++ b/RustBCA.h @@ -1,105 +1,105 @@ -#include -#include -#include -#include -#include -#include - -static const double PI = M_PI; - -static const double FRAC_2_SQRT_PI = M_2_PI; - -static const double SQRT_2 = M_SQRT2; - -///Fundamental charge in Coulombs. -static const double Q = 1.602176634e-19; - -/// One electron-volt in Joules. -static const double EV = Q; - -/// One atomic mass unit in kilograms. -static const double AMU = 1.66053906660e-27; - -/// One Angstrom in meters. -static const double ANGSTROM = 1e-10; - -/// One micron in meters. -static const double MICRON = 1e-6; - -/// One nanometer in meters. -static const double NM = 1e-9; - -/// One centimeter in meters. -static const double CM = 1e-2; - -/// Vacuum permitivity in Farads/meter. -static const double EPS0 = 8.8541878128e-12; - -/// Bohr radius in meters. -static const double A0 = 5.29177210903e-11; - -/// Electron mass in kilograms. -static const double ME = 9.1093837015e-31; - -/// sqrt(pi). -static const double SQRTPI = (2. / FRAC_2_SQRT_PI); - -/// sqrt(2 * pi). -static const double SQRT2PI = ((2. * SQRT_2) / FRAC_2_SQRT_PI); - -/// Speed of light in meters/second. -static const double C = 299792458.; - -/// Bethe-Bloch electronic stopping prefactor, in SI units. -static const double BETHE_BLOCH_PREFACTOR = ((((((4. * PI) * ((Q * Q) / ((4. * PI) * EPS0))) * ((Q * Q) / ((4. * PI) * EPS0))) / ME) / C) / C); - -/// Lindhard-Scharff electronic stopping prefactor, in SI units. -static const double LINDHARD_SCHARFF_PREFACTOR = (((1.212 * ANGSTROM) * ANGSTROM) * Q); - -/// Lindhard reduced energy prefactor, in SI units. -static const double LINDHARD_REDUCED_ENERGY_PREFACTOR = ((((4. * PI) * EPS0) / Q) / Q); - -struct OutputBCA { - uintptr_t len; - double (*particles)[9]; -}; - -struct InputSimpleBCA { - uintptr_t len; - // vx vy vz [m/s] - double (*velocities)[3]; - double Z1; - double m1; - double Ec1; - double Es1; - double Z2; - double m2; - double n2; - double Ec2; - double Es2; - double Eb2; -}; - -extern "C" { - -OutputBCA simple_bca_list_c(InputSimpleBCA input); - -OutputBCA simple_bca_c(double x, - double y, - double z, - double ux, - double uy, - double uz, - double E1, - double Z1, - double m1, - double Ec1, - double Es1, - double Z2, - double m2, - double Ec2, - double Es2, - double n2, - double Eb2); - -} // extern "C" +#include +#include +#include +#include +#include +#include + +static const double PI = M_PI; + +static const double FRAC_2_SQRT_PI = M_2_PI; + +static const double SQRT_2 = M_SQRT2; + +///Fundamental charge in Coulombs. +static const double Q = 1.602176634e-19; + +/// One electron-volt in Joules. +static const double EV = Q; + +/// One atomic mass unit in kilograms. +static const double AMU = 1.66053906660e-27; + +/// One Angstrom in meters. +static const double ANGSTROM = 1e-10; + +/// One micron in meters. +static const double MICRON = 1e-6; + +/// One nanometer in meters. +static const double NM = 1e-9; + +/// One centimeter in meters. +static const double CM = 1e-2; + +/// Vacuum permitivity in Farads/meter. +static const double EPS0 = 8.8541878128e-12; + +/// Bohr radius in meters. +static const double A0 = 5.29177210903e-11; + +/// Electron mass in kilograms. +static const double ME = 9.1093837015e-31; + +/// sqrt(pi). +static const double SQRTPI = (2. / FRAC_2_SQRT_PI); + +/// sqrt(2 * pi). +static const double SQRT2PI = ((2. * SQRT_2) / FRAC_2_SQRT_PI); + +/// Speed of light in meters/second. +static const double C = 299792458.; + +/// Bethe-Bloch electronic stopping prefactor, in SI units. +static const double BETHE_BLOCH_PREFACTOR = ((((((4. * PI) * ((Q * Q) / ((4. * PI) * EPS0))) * ((Q * Q) / ((4. * PI) * EPS0))) / ME) / C) / C); + +/// Lindhard-Scharff electronic stopping prefactor, in SI units. +static const double LINDHARD_SCHARFF_PREFACTOR = (((1.212 * ANGSTROM) * ANGSTROM) * Q); + +/// Lindhard reduced energy prefactor, in SI units. +static const double LINDHARD_REDUCED_ENERGY_PREFACTOR = ((((4. * PI) * EPS0) / Q) / Q); + +struct OutputBCA { + uintptr_t len; + double (*particles)[9]; +}; + +struct InputSimpleBCA { + uintptr_t len; + // vx vy vz [m/s] + double (*velocities)[3]; + double Z1; + double m1; + double Ec1; + double Es1; + double Z2; + double m2; + double n2; + double Ec2; + double Es2; + double Eb2; +}; + +extern "C" { + +OutputBCA simple_bca_list_c(InputSimpleBCA input); + +OutputBCA simple_bca_c(double x, + double y, + double z, + double ux, + double uy, + double uz, + double E1, + double Z1, + double m1, + double Ec1, + double Es1, + double Z2, + double m2, + double Ec2, + double Es2, + double n2, + double Eb2); + +} // extern "C" diff --git a/src/lib.rs b/src/lib.rs index 2b32635..c17ab9a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,8 +87,8 @@ pub struct OutputBCA { #[repr(C)] pub struct InputSimpleBCA { pub len: usize, - /// x, y, z, vx, vy, vz - pub velocities: *mut [f64; 6], + /// vx, vy, vz + pub velocities: *mut [f64; 3], pub Z1: f64, pub m1: f64, pub Ec1: f64, @@ -194,9 +194,9 @@ pub extern "C" fn simple_bca_list_c(input: InputSimpleBCA) -> OutputBCA { for velocity in velocities { - let vx = velocity[3]; - let vy = velocity[4]; - let vz = velocity[5]; + let vx = velocity[0]; + let vy = velocity[1]; + let vz = velocity[2]; let v = (vx*vx + vy*vy + vz*vz).sqrt(); @@ -235,19 +235,22 @@ pub extern "C" fn simple_bca_list_c(input: InputSimpleBCA) -> OutputBCA { let output = bca::single_ion_bca(p, &m, &options); for particle in output { - total_output.push( - [ - particle.Z, - particle.m/AMU, - particle.E/EV, - particle.pos.x/ANGSTROM, - particle.pos.y/ANGSTROM, - particle.pos.z/ANGSTROM, - particle.dir.x, - particle.dir.y, - particle.dir.z - ] - ); + + if (particle.left) | (particle.incident) { + total_output.push( + [ + particle.Z, + particle.m/AMU, + particle.E/EV, + particle.pos.x/ANGSTROM, + particle.pos.y/ANGSTROM, + particle.pos.z/ANGSTROM, + particle.dir.x, + particle.dir.y, + particle.dir.z + ] + ); + } } } From 322aad75502ea5bee09371633d64d6c14472ae86 Mon Sep 17 00:00:00 2001 From: Jon Drobny Date: Fri, 11 Jun 2021 11:05:22 -0500 Subject: [PATCH 31/33] Added package.py for spack compatibility. --- package.py | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 package.py diff --git a/package.py b/package.py new file mode 100644 index 0000000..71a8171 --- /dev/null +++ b/package.py @@ -0,0 +1,47 @@ +# Copyright 2013-2021 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) + +# ---------------------------------------------------------------------------- +# If you submit this package back to Spack as a pull request, +# please first remove this boilerplate and all FIXME comments. +# +# This is a template package file for Spack. We've put "FIXME" +# next to all the things you'll want to change. Once you've handled +# them, you can save this file and test your package like this: +# +# spack install rustbca +# +# You can edit this file again by typing: +# +# spack edit rustbca +# +# See the Spack documentation for more information on packaging. +# ---------------------------------------------------------------------------- + +from spack import * + + +class Rustbca(Package): + """FIXME: Put a proper description of your package here.""" + + # FIXME: Add a proper url for your package's homepage here. + homepage = "https://www.github.com/lcpp-org/RustBCA/wiki" + url = "https://github.com/lcpp-org/RustBCA/archive/refs/tags/v1.0.0.tar.gz" + git = "https://www.github.com/lcpp-org/RustBCA.git" + + # FIXME: Add a list of GitHub accounts to + # notify when the package is updated. + # maintainers = ['github_user1', 'github_user2'] + + version('dev', branch='dev') + version('1.0.0', sha256='99dcac7c7a78e6cd17da63a0dcbb3c36bca523ffafbb0425128b0c971b1a6829') + depends_on('rust') + + # FIXME: Add dependencies if required. + # depends_on('foo') + + def install(self, spec, prefix): + cargo = which('cargo') + cargo('build', '--release', '--lib', '--target-dir', prefix) From bb36666eb2578abfad8282977e2ee37ab0c7a947 Mon Sep 17 00:00:00 2001 From: Jon Drobny Date: Thu, 22 Jul 2021 13:31:30 -0700 Subject: [PATCH 32/33] Minor updates to scripts. --- scripts/materials.py | 11 ++ scripts/rustbca.py | 236 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 222 insertions(+), 25 deletions(-) diff --git a/scripts/materials.py b/scripts/materials.py index 0e5074c..d572d66 100644 --- a/scripts/materials.py +++ b/scripts/materials.py @@ -21,6 +21,17 @@ 'Es': 1.5, } +lithium = { + 'symbol': 'Li', + 'name': 'lithium', + 'Z': 3, + 'm': 6.941, + 'Ec': 1.0, + 'Es': 1.64, + 'Eb': 0.0, + 'n': 4.63E28 +} + nitrogen = { 'symbol': 'N', 'name': 'nitrogen', diff --git a/scripts/rustbca.py b/scripts/rustbca.py index 2654c97..b740952 100644 --- a/scripts/rustbca.py +++ b/scripts/rustbca.py @@ -10,8 +10,8 @@ import matplotlib as mpl import matplotlib.colors as colors -from .materials import * -from .formulas import * +from materials import * +from formulas import * #from generate_ftridyn_input import * @@ -170,7 +170,7 @@ def do_trajectory_plot_3d(name, thickness=None, depth=None, boundary=None, plot_ index = 0 x_max = 0 min_length = 1 - scale_factor = 200.0 + scale_factor = 1.0 if np.size(trajectories) > 0: for trajectory_length in trajectory_data: @@ -276,9 +276,9 @@ def generate_rustbca_input(Zb, Mb, n, Eca, Ecb, Esa, Esb, Eb, Ma, Za, E0, N, N_, track_displacements=True, track_energy_losses=True, electronic_stopping_mode=LOW_ENERGY_NONLOCAL, weak_collision_order=3, ck=1., mean_free_path_model=LIQUID, - interaction_potential='"KR_C"', high_energy=False, energy_barrier_thickness=(6.306E10)**(-1/3.), - initial_particle_position = -1*ANGSTROM/MICRON, integral='"MENDENHALL_WELLER"', - root_finder = '{"NEWTON"={max_iterations=100, tolerance=1e-3}}', + interaction_potential="KR_C", high_energy=False, energy_barrier_thickness=(6.306E10)**(-1/3.), + initial_particle_position = -1*ANGSTROM/MICRON, integral="MENDENHALL_WELLER", + root_finder = "DEFAULTNEWTON", delta_x_angstrom=5., uniformly_distributed_ions=False): ''' @@ -295,7 +295,7 @@ def generate_rustbca_input(Zb, Mb, n, Eca, Ecb, Esa, Esb, Eb, Ma, Za, E0, N, N_, 'suppress_deep_recoils': False, 'high_energy_free_flight_paths': high_energy, 'num_threads': 4, - 'num_chunks': 100, + 'num_chunks': 10, 'use_hdf5': False, 'electronic_stopping_mode': electronic_stopping_mode, 'mean_free_path_model': mean_free_path_model, @@ -374,7 +374,7 @@ def plot_distributions_rustbca(name, beam, target, incident_energy=1, incident_angle=0, max_collision_contours=4, plot_2d_reflected_contours=False, collision_contour_significance_threshold=0.1, plot_garrison_contours=False, - plot_reflected_energies_by_number_collisions=False, + plot_reflected_energies_by_number_collisions=True, plot_scattering_energy_curve=False): ''' @@ -389,7 +389,7 @@ def plot_distributions_rustbca(name, beam, target, max_collision_contours (int): number of collision event contours to plot on reflected EAD; default 4 plot_2d_reflected_contours (bool): whether to plot collision event contours on reflected EAD; default False collision_contour_significance_threshold (int): threshold of significance for contour plotting; default 0.1 - plot_reflected_energies_by_number_collisions (bool): separte reflected energy spectrum into collision number distributions; default False + RustBCA.exe (bool): separte reflected energy spectrum into collision number distributions; default False plot_scattering_energy_curve (bool): plot bold, translucent white curve showing theoretical single-collision reflected energies; default False ''' @@ -866,7 +866,7 @@ def run_iead(ions, target, energies, angles, iead, name="default_", N=1): 'suppress_deep_recoils': False, 'high_energy_free_flight_paths': False, 'num_threads': 8, - 'num_chunks': 100, + 'num_chunks': 10, 'use_hdf5': False, 'electronic_stopping_mode': LOW_ENERGY_LOCAL, 'mean_free_path_model': LIQUID, @@ -936,7 +936,7 @@ def run_iead(ions, target, energies, angles, iead, name="default_", N=1): toml.dump(input_file, file, encoder=toml.TomlNumpyEncoder()) with open(f'{name}.toml', 'a') as file: file.write(r'root_finder = [[{"NEWTON"={max_iterations = 100, tolerance=1E-3}}]]') - os.system(f'rustBCA.exe {name}.toml') + os.system(f'RustBCA.exe {name}.toml') plot_distributions_rustbca(name, ions, target, incident_energy=np.max(energies)) @@ -953,8 +953,8 @@ def run_iead(ions, target, energies, angles, iead, name="default_", N=1): def beam_target(ions, target, energy, angle, N_=10000, N=1, run_sim=True, high_energy=False, integral='"GAUSS_LEGENDRE"', - interaction_potential='"ZBL"', - root_finder='{"NEWTON"={max_iterations=100, tolerance=1e-6}}', tag=None, + interaction_potential='"KR_C"', + root_finder='"DEFAULTNEWTON"', tag=None, track_trajectories = False, plot_trajectories = False, plot_distributions = False, @@ -968,7 +968,7 @@ def beam_target(ions, target, energy, angle, N_=10000, N=1, run_sim=True, weak_collision_order=3, electronic_stopping_mode=LOW_ENERGY_NONLOCAL, uniformly_distributed_ions=False, - mean_free_path_model='"LIQUID"'): + mean_free_path_model="LIQUID"): ''' Simplified generation of a monoenergetic, mono-angular beam on target simulation using rustbca. @@ -1001,7 +1001,7 @@ def beam_target(ions, target, energy, angle, N_=10000, N=1, run_sim=True, initial_particle_position = -2.01*pmax, weak_collision_order=weak_collision_order, ck=ck, uniformly_distributed_ions=uniformly_distributed_ions, mean_free_path_model=mean_free_path_model) - if run_sim: os.system(f'rustBCA.exe {name}.toml') + if run_sim: os.system(f'RustBCA.exe {name}.toml') if do_plots: if track_energy_losses: plot_energy_loss(name, N*N_, num_bins=100) @@ -1077,7 +1077,7 @@ def sputtering(ions, target, energies, angle, N_=10000, run_sim=True): electronic_stopping_mode="LOW_ENERGY_EQUIPARTITION", integral=integral, interaction_potential = option) - if run_sim: os.system(f'rustBCA.exe {name+str(index)+"_"+str(option_index)}.toml') + if run_sim: os.system(f'RustBCA.exe {name+str(index)+"_"+str(option_index)}.toml') sputtered = np.atleast_2d(np.genfromtxt(name+str(index)+'_'+str(option_index)+'sputtered.output', delimiter=',')) reflected = np.atleast_2d(np.genfromtxt(name+str(index)+'_'+str(option_index)+'reflected.output', delimiter=',')) @@ -1447,23 +1447,209 @@ def helium_on_tungsten_oxide_layer(): plt.axis([0., 1.5*np.max(d[:,2]), 0., 1.2*np.max(heights)]) plt.savefig(f'dep_west_1_wo2_w_{j}.png') -def main(): +def generate_rustbca_input_sphere(Zb, Mb, n, Eca, Ecb, Esa, Esb, Eb, Ma, Za, E0, N, N_, theta, + radius, displacement, track_trajectories=True, track_recoils=True, + track_recoil_trajectories=True, name='test_', + track_displacements=True, track_energy_losses=True, + electronic_stopping_mode=LOW_ENERGY_NONLOCAL, + weak_collision_order=3, ck=1., mean_free_path_model=LIQUID, + interaction_potential="KR_C", high_energy=False, + integral="MENDENHALL_WELLER", + root_finder = "DEFAULTNEWTON", + delta_x_angstrom=1., uniformly_distributed_ions=False): + + ''' + Generates a rustbca input file. Assumes eV, amu, and microns for units. + ''' + + options = { + 'name': name, + 'track_trajectories': track_trajectories, + 'track_recoils': track_recoils, + 'track_recoil_trajectories': track_recoil_trajectories, + 'write_buffer_size': 8000, + 'weak_collision_order': weak_collision_order, + 'suppress_deep_recoils': False, + 'high_energy_free_flight_paths': high_energy, + 'num_threads': 4, + 'num_chunks': 10, + 'use_hdf5': False, + 'electronic_stopping_mode': electronic_stopping_mode, + 'mean_free_path_model': mean_free_path_model, + 'track_displacements': track_displacements, + 'track_energy_losses': track_energy_losses, + } + + material_parameters = { + 'energy_unit': 'EV', + 'mass_unit': 'AMU', + 'Eb': Eb, + 'Es': Esb, + 'Ec': Ecb, + 'Z': Zb, + 'm': Mb, + 'interaction_index': [0], + 'surface_binding_model': "AVERAGE", + 'bulk_binding_model': "AVERAGE" + } + + geometry_input = { + 'length_unit': 'ANGSTROM', + 'densities': [n*(ANGSTROM)**3], + 'electronic_stopping_correction_factor': ck, + 'radius': radius, + } + + cosx = np.cos(theta*np.pi/180.) + sinx = np.sin(theta*np.pi/180.) + x0 = -np.sqrt(radius**2 - displacement**2) + y0 = displacement + positions = [(x0, y0, 0.0) for _ in range(N)] + + particle_parameters = { + 'length_unit': 'ANGSTROM', + 'energy_unit': 'EV', + 'mass_unit': 'AMU', + 'N': [N_ for _ in range(N)], + 'm': [Ma for _ in range(N)], + 'Z': [Za for _ in range(N)], + 'E': [E0 for _ in range(N)], + 'Ec': [Eca for _ in range(N)], + 'Es': [Esa for _ in range(N)], + 'interaction_index': np.zeros(N, dtype=int), + 'pos': positions, + 'dir': [(cosx, sinx, 0.) for _ in range(N)], + 'particle_input_filename': '' + } + + input_file = { + 'material_parameters': material_parameters, + 'particle_parameters': particle_parameters, + 'geometry_input': geometry_input, + 'options': options, + } + + with open(f'{name}.toml', 'w') as file: + toml.dump(input_file, file, encoder=toml.TomlNumpyEncoder()) + + with open(f'{name}.toml', 'a') as file: + file.write(f'root_finder = [[{root_finder}]]\n') + file.write(f'interaction_potential = [[{interaction_potential}]]\n') + file.write(f'scattering_integral = [[{integral}]]\n') + +def test(): ''' Here an example usage of beam_target is shown. This code runs rustbca and produces plots. For helium on copper at 1 keV and 0 degrees angle of incidence, all the distributions and trajectories are plotted. ''' + ion = uranium - energy = 2000 - angle = 0.00001 - N = 10000 + target = uranium + Zb = [target['Z']] + Mb = [target['m']] + n = target['n'] + Eca = 0.1 + Ecb = [0.1] + Esa = ion['Es'] + Esb = [target['Es']] + Eb = [3.0] + Ma = ion['m'] + Za = ion['Z'] + + s = [] + r = [] + energies = np.logspace(1.0, 2.0, 25) + + for E0 in energies: + + N = 1 + N_ = 10000 + theta = 0.001 + radius = 1000.0 #angstrom + displacement = 0.0 + + generate_rustbca_input_sphere(Zb, Mb, n, Eca, Ecb, Esa, Esb, Eb, Ma, Za, E0, N, N_, theta, + radius, displacement, track_trajectories=False, track_recoils=True, + track_recoil_trajectories=False, name='test_', + track_displacements=True, track_energy_losses=True, + electronic_stopping_mode=LOW_ENERGY_NONLOCAL, + weak_collision_order=3, ck=1., mean_free_path_model=LIQUID, + interaction_potential='"KR_C"', high_energy=False, + integral='"MENDENHALL_WELLER"', + root_finder = '{"NEWTON"={max_iterations=100, tolerance=1e-3}}', + delta_x_angstrom=1., uniformly_distributed_ions=False) + + os.system('cargo run --release SPHERE test_.toml') + + #do_trajectory_plot('test_') + + sputtered = np.atleast_2d(np.genfromtxt('test_sputtered.output', delimiter=',')) + deposited = np.atleast_2d(np.genfromtxt('test_deposited.output', delimiter=',')) + reflected = np.atleast_2d(np.genfromtxt('test_reflected.output', delimiter=',')) + + breakpoint() + + try: + s.append(np.size(sputtered[:,0])/N_*N) + except: + s.append(0.0) + + try: + r.append(np.size(reflected[:,0])/N_*N) + except: + r.append(0.0) + + plt.figure(2) + plt.title(f'Reflected/Transmitted Energies @ {np.round(E0,1)}') + plt.xlabel('E [eV]') + plt.ylabel('f(E)') + plt.hist(reflected[:, 2], bins=25, histtype='step', color='black') + plt.show() + + plt.figure(1) + plt.plot(energies, s) + plt.plot(energies, 1.0 - np.array(r)) + plt.title(f'{radius} Å Sphere') + plt.xlabel('E [eV]') + plt.ylabel('Y/R') + plt.legend(['Sputtering Yield', 'Sticking Coefficient']) + plt.show() + +def main(): + ions = [hydrogen, deuterium, lithium] + target = lithium + energies = np.logspace(-1, 3, 10) + angle = 0.0001 + number_ions = 100000 + + legends = [] + for ion in ions: + sputtering_yield = [] + reflection_coefficient = [] + + for energy in energies: + s, r, _ = beam_target(ion, target, energy, angle, N_=number_ions) + + if np.shape(s)[1] > 0: + sputtering_yield.append(len(s)/number_ions) + else: + sputtering_yield.append(0) + + if np.shape(r)[1] > 0: + reflection_coefficient.append(len(r)/number_ions) + else: + reflection_coefficient.append(0.0) + + legends.append(f"{ion['symbol']} on Li Y") + plt.loglog(energies, sputtering_yield) + legends.append(f"{ion['symbol']} on Li R") + plt.loglog(energies, reflection_coefficient, linestyle='--') + + plt.legend(legends) + plt.show() - beam_target(helium, copper, energy, angle, N_=N, N=1, do_plots=True, run_sim=True, - plot_trajectories=True, track_trajectories=False, thickness=50, depth=1000., - interaction_potential=r"'KR_C'", track_energy_losses=True, track_displacements=False, - uniformly_distributed_ions=True, high_energy=False, electronic_stopping_mode=INTERPOLATED, - weak_collision_order=0, plot_distributions=True, mean_free_path_model='LIQUID') if __name__ == '__main__': main() From e19a54f882a9013b5ab2e5603fa8aed9d8d2f023 Mon Sep 17 00:00:00 2001 From: Jon Drobny Date: Thu, 22 Jul 2021 13:34:26 -0700 Subject: [PATCH 33/33] Fix issuen #139 --- src/material.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/material.rs b/src/material.rs index ae12ee9..6c2ef17 100644 --- a/src/material.rs +++ b/src/material.rs @@ -352,7 +352,7 @@ pub fn surface_binding_energy(particle_1: &mut particle::Particle, if leaving_energy > Es { match material.surface_binding_model { - SurfaceBindingModel::PLANAR{..} => { + SurfaceBindingModel::PLANAR{..} | SurfaceBindingModel::TARGET | SurfaceBindingModel::INDIVIDUAL | SurfaceBindingModel::AVERAGE => { particle::surface_refraction(particle_1, Vector::new(-dx/mag, -dy/mag, -dz/mag), -Es); } _ => particle_1.E += -Es,