lindhard/electron/inelastic/
model.rs1use super::full_penn::FullPenn;
16use super::mermin::MerminPenn;
17use super::mermin_fit::MerminFitOptions;
18use super::penn::{InelasticPoint, SinglePolePenn};
19use crate::electron::data::{ElectronDataError, OpticalElf};
20
21type Result<T> = std::result::Result<T, ElectronDataError>;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25pub enum PennAlgorithm {
26 SinglePole,
28 Full,
30 Mermin,
34}
35
36impl PennAlgorithm {
37 pub fn label(self) -> &'static str {
40 match self {
41 Self::SinglePole => "penn-single-pole",
42 Self::Full => "penn-full",
43 Self::Mermin => "mermin-melf",
44 }
45 }
46
47 pub fn from_label(label: &str) -> Option<Self> {
49 [Self::SinglePole, Self::Full, Self::Mermin]
50 .into_iter()
51 .find(|a| a.label() == label)
52 }
53}
54
55impl std::str::FromStr for PennAlgorithm {
56 type Err = ElectronDataError;
57
58 fn from_str(s: &str) -> Result<Self> {
59 Self::from_label(s).ok_or_else(|| ElectronDataError::Invalid {
60 what: "Penn algorithm",
61 reason: format!("unknown label {s:?}; expected \"penn-single-pole\", \"penn-full\" or \"mermin-melf\""),
62 })
63 }
64}
65
66#[derive(Debug, Clone)]
68pub enum PennInelastic {
69 SinglePole(SinglePolePenn),
71 Full(FullPenn),
73 Mermin(MerminPenn),
75}
76
77impl PennInelastic {
78 pub fn new(algorithm: PennAlgorithm, elf: OpticalElf) -> Self {
88 Self::try_new(algorithm, elf).expect("the default Mermin fit failed; use try_new")
89 }
90
91 pub fn try_new(algorithm: PennAlgorithm, elf: OpticalElf) -> Result<Self> {
93 Ok(match algorithm {
94 PennAlgorithm::SinglePole => Self::SinglePole(SinglePolePenn::new(elf)),
95 PennAlgorithm::Full => Self::Full(FullPenn::new(elf)),
96 PennAlgorithm::Mermin => Self::mermin(elf, &MerminFitOptions::default())?,
97 })
98 }
99
100 pub fn mermin(elf: OpticalElf, options: &MerminFitOptions) -> Result<Self> {
102 Ok(Self::Mermin(MerminPenn::fit(elf, options)?))
103 }
104
105 pub fn algorithm(&self) -> PennAlgorithm {
107 match self {
108 Self::SinglePole(_) => PennAlgorithm::SinglePole,
109 Self::Full(_) => PennAlgorithm::Full,
110 Self::Mermin(_) => PennAlgorithm::Mermin,
111 }
112 }
113
114 pub fn optical_elf(&self) -> &OpticalElf {
116 match self {
117 Self::SinglePole(m) => m.optical_elf(),
118 Self::Full(m) => m.optical_elf(),
119 Self::Mermin(m) => m.optical_elf(),
120 }
121 }
122
123 pub fn with_fermi_energy_ev(self, fermi_ev: f64) -> Result<Self> {
125 Ok(match self {
126 Self::SinglePole(m) => Self::SinglePole(m.with_fermi_energy_ev(fermi_ev)?),
127 Self::Full(m) => Self::Full(m.with_fermi_energy_ev(fermi_ev)?),
128 Self::Mermin(m) => Self::Mermin(m.with_fermi_energy_ev(fermi_ev)?),
129 })
130 }
131
132 pub fn fermi_energy_ev(&self) -> f64 {
134 match self {
135 Self::SinglePole(m) => m.fermi_energy_ev(),
136 Self::Full(m) => m.fermi_energy_ev(),
137 Self::Mermin(m) => m.fermi_energy_ev(),
138 }
139 }
140
141 pub fn model_identity(&self) -> String {
148 let (tol, fermi) = match self {
149 Self::SinglePole(m) => (m.relative_tolerance(), m.fermi_energy_ev()),
150 Self::Full(m) => (m.relative_tolerance(), m.fermi_energy_ev()),
151 Self::Mermin(m) => (m.relative_tolerance(), m.fermi_energy_ev()),
152 };
153 let label = match self {
154 Self::Mermin(m) => format!(
155 "{} ({})",
156 self.algorithm().label(),
157 m.fit_report().summary()
158 ),
159 _ => self.algorithm().label().to_string(),
160 };
161 format!("{label}; nonrelativistic; E_F = {fermi} eV; rel tol {tol:e}")
162 }
163
164 pub fn diimfp_per_m_ev(&self, energy_ev: f64, loss_ev: f64) -> Result<f64> {
166 match self {
167 Self::SinglePole(m) => m.diimfp_per_m_ev(energy_ev, loss_ev),
168 Self::Full(m) => m.diimfp_per_m_ev(energy_ev, loss_ev),
169 Self::Mermin(m) => m.diimfp_per_m_ev(energy_ev, loss_ev),
170 }
171 }
172
173 pub fn imfp_and_stopping(&self, energy_ev: f64) -> Result<InelasticPoint> {
175 match self {
176 Self::SinglePole(m) => m.imfp_and_stopping(energy_ev),
177 Self::Full(m) => m.imfp_and_stopping(energy_ev),
178 Self::Mermin(m) => m.imfp_and_stopping(energy_ev),
179 }
180 }
181
182 pub fn tabulate(&self, energies_ev: &[f64]) -> Result<Vec<InelasticPoint>> {
184 match self {
185 Self::SinglePole(m) => m.tabulate(energies_ev),
186 Self::Full(m) => m.tabulate(energies_ev),
187 Self::Mermin(m) => m.tabulate(energies_ev),
188 }
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn labels_round_trip_and_unknown_labels_are_rejected() {
198 for a in [
199 PennAlgorithm::SinglePole,
200 PennAlgorithm::Full,
201 PennAlgorithm::Mermin,
202 ] {
203 assert_eq!(PennAlgorithm::from_label(a.label()), Some(a));
204 assert_eq!(a.label().parse::<PennAlgorithm>().unwrap(), a);
205 }
206 assert!(PennAlgorithm::from_label("penn").is_none());
207 assert!("penn".parse::<PennAlgorithm>().is_err());
208 }
209}