Skip to main content

lindhard/ion/stopping/
mix.rs

1//! Equipartition mix of Lindhard-Scharff (nonlocal) and Oen-Robinson (local).
2//!
3//! Each collision loses half of its electronic energy by the nonlocal LS
4//! (continuous, along the free flight) channel and half by the local OR
5//! channel (at the collision, depending on `r_min`). This "equipartition"
6//! split is the combination described in the BCA literature that builds on
7//! Oen and Robinson (1976). Averaged over impact parameter the mix equals the
8//! LS stopping.
9
10use super::lindhard_scharff::LindhardScharff;
11use super::oen_robinson::OenRobinson;
12use super::{ElectronicStopping, Ion, StoppingError, ValidityRange};
13
14/// Equipartition LS/OR model.
15#[derive(Debug, Clone, Default)]
16pub struct EquipartitionMix {
17    ls: LindhardScharff,
18    or: OenRobinson,
19}
20
21impl EquipartitionMix {
22    /// New mix with plain LS coefficient.
23    pub fn new() -> Self {
24        Self::default()
25    }
26
27    /// Nonlocal (free-flight) loss per unit areal density, J m²: half of LS.
28    pub fn nonlocal_stopping(
29        &self,
30        ion: &Ion,
31        target_z: u8,
32        energy_ev: f64,
33    ) -> Result<f64, StoppingError> {
34        Ok(0.5 * self.ls.stopping(ion, target_z, energy_ev)?)
35    }
36
37    /// `c` with `nonlocal_stopping(E) = c sqrt(E)`: half the Lindhard-Scharff
38    /// coefficient (see [`ElectronicStopping::sqrt_energy_coefficient`]).
39    pub fn nonlocal_sqrt_energy_coefficient(&self, ion: &Ion, target_z: u8) -> Option<f64> {
40        Some(0.5 * self.ls.sqrt_energy_coefficient(ion, target_z)?)
41    }
42
43    /// Local loss at one collision, J: half of the OR local loss.
44    pub fn local_loss(
45        &self,
46        ion: &Ion,
47        target_z: u8,
48        energy_ev: f64,
49        r_min_m: f64,
50    ) -> Result<f64, StoppingError> {
51        Ok(0.5 * self.or.local_loss(ion, target_z, energy_ev, r_min_m)?)
52    }
53}
54
55impl ElectronicStopping for EquipartitionMix {
56    fn name(&self) -> &'static str {
57        "equipartition-ls-or"
58    }
59
60    fn stopping(&self, ion: &Ion, target_z: u8, energy_ev: f64) -> Result<f64, StoppingError> {
61        // Half nonlocal + half local averaged over impact parameter.
62        Ok(self.nonlocal_stopping(ion, target_z, energy_ev)?
63            + 0.5 * self.or.stopping(ion, target_z, energy_ev)?)
64    }
65
66    fn validity(&self, ion: &Ion) -> ValidityRange {
67        self.ls.validity(ion)
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn average_equals_ls() {
77        let ion = Ion::new(31).unwrap();
78        let a = EquipartitionMix::new().stopping(&ion, 14, 5e4).unwrap();
79        let b = LindhardScharff::new().stopping(&ion, 14, 5e4).unwrap();
80        assert!((a / b - 1.0).abs() < 1e-14);
81    }
82
83    #[test]
84    fn local_loss_rejects_bad_closest_approach() {
85        let ion = Ion::proton();
86        let m = EquipartitionMix::new();
87        for bad in [-1e-10, f64::NAN, f64::INFINITY] {
88            assert!(matches!(
89                m.local_loss(&ion, 14, 1e4, bad),
90                Err(StoppingError::InvalidParameter { .. })
91            ));
92        }
93        assert!(m.local_loss(&ion, 14, 1e4, 0.0).unwrap() > 0.0);
94    }
95}