Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python compound bca; minor refactors #188

Merged
merged 5 commits into from
Mar 12, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/ISSUE_TEMPLATE/benchmark_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
name: Benchmark request
about: Suggest a benchmark for validation
title: "[benchmark]"
labels: benchmark
assignees: ''

---

**Description**
A clear and concise description of the requested benchmark.

**Source**
Source of data for benchmark; a paper, code, or database with sputtering yields, reflection coefficients, etc.
22 changes: 5 additions & 17 deletions src/bca.rs
Original file line number Diff line number Diff line change
@@ -75,9 +75,6 @@ pub fn single_ion_bca<T: Geometry>(particle: particle::Particle, material: &mate

//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 {

@@ -114,8 +111,6 @@ pub fn single_ion_bca<T: Geometry>(particle: particle::Particle, material: &mate
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;
@@ -127,17 +122,14 @@ pub fn single_ion_bca<T: Geometry>(particle: particle::Particle, material: &mate
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
//Accumulate energy losses and 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,
particle_1.rotate(binary_collision_result.psi,
binary_collision_geometry.phi_azimuthal);

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

particle_2.dir_old.x = particle_2.dir.x;
@@ -184,28 +176,24 @@ pub fn single_ion_bca<T: Geometry>(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,
let distance_traveled = particle_1.advance(
binary_collision_geometries[0].mfp, total_asymptotic_deflection);

#[cfg(feature = "accelerated_ions")]
let distance_traveled = particle::particle_advance(&mut particle_1,
let distance_traveled = particle_1.advance(
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,
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
1 change: 0 additions & 1 deletion src/geometry.rs
Original file line number Diff line number Diff line change
@@ -97,7 +97,6 @@ impl Geometry for Mesh0D {
}

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
}

1 change: 0 additions & 1 deletion src/interactions.rs
Original file line number Diff line number Diff line change
@@ -8,7 +8,6 @@ pub fn crossing_point_doca(interaction_potential: InteractionPotential) -> f64 {
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.")
}

}
193 changes: 193 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -80,6 +80,7 @@ pub use crate::parry::{ParryBall, ParryBallInput, InputParryBall, ParryTriMesh,
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)?)?;
m.add_function(wrap_pyfunction!(compound_bca_list_py, m)?)?;
Ok(())
}

@@ -980,6 +981,198 @@ pub extern "C" fn simple_bca_c(x: f64, y: f64, z: f64, ux: f64, uy: f64, uz: f64
}
}

#[cfg(feature = "python")]
///compound_tagged_bca_list_py(ux, uy, uz, energy, Z1, m1, Ec1, Es1, Z2, m2, Ec2, Es2, n2, Eb2)
/// runs a BCA simulation for a list of particles and outputs a list of sputtered, reflected, and implanted particles.
/// Args:
/// energies (list(f64)): initial ion energies in eV.
/// ux (list(f64)): initial ion directions x. ux != 0.0 to avoid gimbal lock
/// uy (list(f64)): initial ion directions y.
/// uz (list(f64)): initial ion directions z.
/// Z1 (list(f64)): initial ion atomic numbers.
/// m1 (list(f64)): initial ion masses in amu.
/// Ec1 (list(f64)): ion cutoff energies in eV. If ion energy < Ec1, it stops in the material.
/// Es1 (list(f64)): ion surface binding energies. Assumed planar.
/// Z2 (list(f64)): target material species atomic numbers.
/// m2 (list(f64)): target material species masses in amu.
/// Ec2 (list(f64)): target material species cutoff energies in eV. If recoil energy < Ec2, it stops in the material.
/// Es2 (list(f64)): target species surface binding energies. Assumed planar.
/// n2 (list(f64)): target material species atomic number densities in inverse cubic Angstroms.
/// Eb2 (list(f64)): target material species bulk binding energies in eV.
/// Returns:
/// output (NX9 list of f64): each row in the list represents an output particle (implanted,
/// sputtered, or reflected). Each row consists of:
/// [Z, m (amu), E (eV), x, y, z, (angstrom), ux, uy, uz]
/// incident (list(bool)): whether each row of output was an incident ion or originated in the target
#[pyfunction]
pub fn compound_bca_list_py(energies: Vec<f64>, ux: Vec<f64>, uy: Vec<f64>, uz: Vec<f64>, Z1: Vec<f64>, m1: Vec<f64>, Ec1: Vec<f64>, Es1: Vec<f64>, Z2: Vec<f64>, m2: Vec<f64>, Ec2: Vec<f64>, Es2: Vec<f64>, n2: Vec<f64>, Eb2: Vec<f64>) -> (Vec<[f64; 9]>, Vec<bool>) {
let mut total_output = vec![];
let mut incident = vec![];
let num_species_target = Z2.len();
let num_incident_ions = energies.len();

assert_eq!(ux.len(), num_incident_ions, "Input error: list of x-directions is not the same length as list of incident energies.");
assert_eq!(uy.len(), num_incident_ions, "Input error: list of y-directions is not the same length as list of incident energies.");
assert_eq!(uz.len(), num_incident_ions, "Input error: list of z-directions is not the same length as list of incident energies.");
assert_eq!(Z1.len(), num_incident_ions, "Input error: list of incident atomic numbers is not the same length as list of incident energies.");
assert_eq!(m1.len(), num_incident_ions, "Input error: list of incident atomic masses is not the same length as list of incident energies.");
assert_eq!(Es1.len(), num_incident_ions, "Input error: list of incident surface binding energies is not the same length as list of incident energies.");
assert_eq!(Ec1.len(), num_incident_ions, "Input error: list of incident cutoff energies is not the same length as list of incident energies.");

assert_eq!(m2.len(), num_species_target, "Input error: list of target atomic masses is not the same length as atomic numbers.");
assert_eq!(Ec2.len(), num_species_target, "Input error: list of target cutoff energies is not the same length as atomic numbers.");
assert_eq!(Es2.len(), num_species_target, "Input error: list of target surface binding energies is not the same length as atomic numbers.");
assert_eq!(Eb2.len(), num_species_target, "Input error: list of target bulk binding energies is not the same length as atomic numbers.");
assert_eq!(n2.len(), num_species_target, "Input error: list of target number densities is not the same length as atomic numbers.");

#[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: true,
high_energy_free_flight_paths: true,
electronic_stopping_mode: ElectronicStoppingMode::LOW_ENERGY_NONLOCAL,
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: true,
high_energy_free_flight_paths: false,
electronic_stopping_mode: ElectronicStoppingMode::LOW_ENERGY_NONLOCAL,
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 x = -2.*(n2.iter().sum::<f64>()*10E30).powf(-1./3.);
let y = 0.0;
let z = 0.0;

let material_parameters = material::MaterialParameters {
energy_unit: "EV".to_string(),
mass_unit: "AMU".to_string(),
Eb: Eb2,
Es: Es2,
Ec: Ec2,
Z: Z2,
m: m2,
interaction_index: vec![0; num_species_target],
surface_binding_model: SurfaceBindingModel::INDIVIDUAL,
bulk_binding_model: BulkBindingModel::INDIVIDUAL,
};

let geometry_input = geometry::Mesh0DInput {
length_unit: "ANGSTROM".to_string(),
densities: n2,
electronic_stopping_correction_factor: 1.0
};

let m = material::Material::<Mesh0D>::new(&material_parameters, &geometry_input);

let mut index: usize = 0;
for (((((((E1_, ux_), uy_), uz_), Z1_), Ec1_), Es1_), m1_) in energies.iter().zip(ux).zip(uy).zip(uz).zip(Z1).zip(Ec1).zip(Es1).zip(m1) {

let mut energy_out;
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,
weight: 0.0,
tag: 0,
tracked_vector: Vector::new(0., 0., 0.),
};

let output = bca::single_ion_bca(p, &m, &options);

for particle in output {
if (particle.left) | (particle.incident) {

incident.push(particle.incident);

if particle.stopped {
energy_out = 0.
} else {
energy_out = particle.E/EV
}
total_output.push(
[
particle.Z,
particle.m/AMU,
energy_out,
particle.pos.x,
particle.pos.y,
particle.pos.z,
particle.dir.x,
particle.dir.y,
particle.dir.z,
]
);
}
}
index += 1;
}
(total_output, incident)
}

#[cfg(feature = "python")]
/// simple_bca_py( x, y, z, ux, uy, uz, energy, Z1, m1, Ec1, Es1, Z2, m2, Ec2, Es2, n2, Eb2)
/// --
2 changes: 0 additions & 2 deletions src/material.rs
Original file line number Diff line number Diff line change
@@ -323,7 +323,6 @@ pub fn surface_binding_energy<T: Geometry>(particle_1: &mut particle::Particle,

//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);
@@ -380,7 +379,6 @@ pub fn surface_binding_energy<T: Geometry>(particle_1: &mut particle::Particle,
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;
}
}
91 changes: 43 additions & 48 deletions src/particle.rs
Original file line number Diff line number Diff line change
@@ -83,7 +83,7 @@ pub struct Particle {
pub left: bool,
pub incident: bool,
pub first_step: bool,
pub trajectory: Vec<Vector4>,
pub trajectory: Vec<TrajectoryElement>,
pub energies: Vec<EnergyLoss>,
pub track_trajectories: bool,
pub number_collision_events: usize,
@@ -122,7 +122,7 @@ impl Particle {
left: false,
incident: true,
first_step: true,
trajectory: vec![Vector4::new(input.E, input.x, input.y, input.z)],
trajectory: vec![TrajectoryElement::new(input.E, input.x, input.y, input.z)],
energies: vec![],
track_trajectories: options.track_trajectories,
number_collision_events: 0,
@@ -170,7 +170,7 @@ impl Particle {
/// 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});
self.trajectory.push(TrajectoryElement {E: self.E, x: self.pos.x, y: self.pos.y, z: self.pos.z});
}
}

@@ -190,69 +190,64 @@ impl Particle {
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();
/// Rotate a particle by a deflection psi at an azimuthal angle phi.
pub fn rotate(&mut self, psi: f64, phi: f64) {
let cosx: f64 = self.dir.x;
let cosy: f64 = self.dir.y;
let cosz: f64 = self.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);
//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};
let dir_new = Vector {x: cosx_new, y: cosy_new, z: cosz_new};

particle_1.dir.assign(&dir_new);
particle_1.dir.normalize();
}
self.dir.assign(&dir_new);
self.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 {
/// Push particle in space according to previous direction and return the distance traveled.
pub fn advance(&mut self, mfp: f64, asymptotic_deflection: f64) -> f64 {

if particle_1.E > particle_1.Ec {
particle_1.add_trajectory();
}
if self.E > self.Ec {
self.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;
//Update previous position
self.pos_old.x = self.pos.x;
self.pos_old.y = self.pos.y;
self.pos_old.z = self.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;
//In order to keep average denisty constant, must add back previous asymptotic deflection
let distance_traveled = mfp + self.asymptotic_deflection - 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;
//dir has been updated, so use previous direction to advance in space
self.pos.x += self.dir_old.x*distance_traveled;
self.pos.y += self.dir_old.y*distance_traveled;
self.pos.z += self.dir_old.z*distance_traveled;
self.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;
//Update previous direction
self.dir_old.x = self.dir.x;
self.dir_old.y = self.dir.y;
self.dir_old.z = self.dir.z;

return distance_traveled;
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;
10 changes: 5 additions & 5 deletions src/structs.rs
Original file line number Diff line number Diff line change
@@ -44,18 +44,18 @@ impl Vector {
}
}

/// Vector4 is a trajectory-tracking object that includes x, y, z, and the current energy.
/// TrajectoryElement is a trajectory-tracking object that includes x, y, z, and the current energy.
#[derive(Clone)]
pub struct Vector4 {
pub struct TrajectoryElement {
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 {
impl TrajectoryElement {
pub fn new(E: f64, x: f64, y: f64, z: f64) -> TrajectoryElement {
TrajectoryElement {
E,
x,
y,
20 changes: 7 additions & 13 deletions src/tests.rs
Original file line number Diff line number Diff line change
@@ -660,9 +660,6 @@ fn test_surface_refraction() {
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);

@@ -690,9 +687,6 @@ fn test_surface_refraction() {
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);

@@ -856,10 +850,10 @@ fn test_momentum_conservation() {
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,
particle_1.rotate(binary_collision_result.psi,
binary_collision_geometries[0].phi_azimuthal);

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

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

#[test]
fn test_rotate_particle() {
fn test_rotate() {
let mass = 1.;
let Z = 1.;
let E = 1.;
@@ -912,18 +906,18 @@ fn test_rotate_particle() {
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);
particle.rotate(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);
particle.rotate(-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);
particle.rotate(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);

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

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);
let distance_traveled = particle.advance(mfp, asymptotic_deflection);

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