Skip to main content

lindhard/input/electron/
resolved.rs

1//! The resolved electron run: [`ResolvedElectron`] and its parts.
2
3use std::borrow::Cow;
4use std::collections::BTreeMap;
5use std::path::PathBuf;
6
7use serde::Serialize;
8
9use crate::electron::boundary::BandStructure;
10use crate::electron::data::{OpticalElf, Subshell, SubshellBindingTable};
11use crate::electron::elastic::corrections::{
12    Corrections, CorrelationPolarization, PolarizationCutoff, CORRELATION_POLARIZATION_MODEL,
13    EXCHANGE_MODEL,
14};
15use crate::electron::inelastic::{MerminFitOptions, PennAlgorithm};
16use crate::electron::phonon::InsulatorChannels;
17use crate::electron::secondary::SecondaryModel;
18use crate::electron::transport::{BeamSpread, BoundaryModel, Primary, TransportConfig};
19use crate::geometry::Stack;
20use crate::input::{ModelInfo, ResolvedLayer};
21use crate::material::Material;
22use crate::tally::electron::SE_BSE_SPLIT_SOURCE;
23use crate::tally::{ElectronTallyConfig, PsfModel, PsfNormalization, VoxelTallyGrid};
24
25use super::schema::*;
26
27/// A data file a run read, identified for provenance.
28#[derive(Debug, Clone, PartialEq, Serialize)]
29pub struct DataFile {
30    /// The path as written in the input.
31    pub path: String,
32    /// Where it was read from (absolute where possible).
33    pub resolved_path: PathBuf,
34    /// SHA-256 of the file's bytes, lowercase hex.
35    pub sha256: String,
36}
37
38/// The elastic choices after validation.
39#[derive(Debug, Clone, PartialEq)]
40pub struct ElasticChoice {
41    /// The elastic model.
42    pub model: ElasticModelChoice,
43    /// The atomic potential.
44    pub potential: PotentialChoice,
45    /// Furness-McCarthy exchange on or off.
46    pub exchange: bool,
47    /// Per-element correlation-polarization inputs (empty when off).
48    pub correlation_polarization: BTreeMap<u8, CorrelationPolarization>,
49}
50
51impl ElasticChoice {
52    /// The corrections for element `z`.
53    pub fn corrections(&self, z: u8) -> Corrections {
54        Corrections {
55            exchange: self.exchange,
56            correlation_polarization: self.correlation_polarization.get(&z).cloned(),
57        }
58    }
59
60    /// Whether any correction is on.
61    pub fn any_correction(&self) -> bool {
62        self.exchange || !self.correlation_polarization.is_empty()
63    }
64
65    /// One line naming the corrections and their inputs, appended to the
66    /// potential description of every element's table (empty when none is
67    /// on).
68    pub fn corrections_description(&self) -> String {
69        if !self.any_correction() {
70            return String::new();
71        }
72        let mut parts = Vec::new();
73        if self.exchange {
74            parts.push(format!("exchange: {EXCHANGE_MODEL}"));
75        }
76        if !self.correlation_polarization.is_empty() {
77            let each: Vec<String> = self
78                .correlation_polarization
79                .iter()
80                .map(|(z, c)| {
81                    let sym = crate::elements::element(*z).map_or("?", |e| e.symbol);
82                    let cut = match c.cutoff {
83                        PolarizationCutoff::Seltzer => "Seltzer b_pol^2".to_string(),
84                        PolarizationCutoff::BPolSquared(b) => format!("b_pol^2 = {b}"),
85                    };
86                    format!(
87                        "{sym}: alpha_p = {} bohr^3 ({}), {cut}, outer radius {} bohr",
88                        c.polarizability, c.polarizability_source, c.outer_radius
89                    )
90                })
91                .collect();
92            parts.push(format!(
93                "correlation-polarization: {CORRELATION_POLARIZATION_MODEL}; {}",
94                each.join("; ")
95            ));
96        }
97        format!("; corrections: {}", parts.join("; "))
98    }
99}
100
101/// The inner-shell channels of one material after loading and validation.
102#[derive(Debug, Clone, PartialEq)]
103pub struct ResolvedInnerShells {
104    /// The subshells of every element of the material that become channels,
105    /// in input order.
106    pub subshells: Vec<Subshell>,
107    /// The binding energies and occupancies (checked to cover every element
108    /// and subshell above).
109    pub binding: SubshellBindingTable,
110    /// The `binding` file; `None` for the embedded EADL2017 table.
111    pub binding_file: Option<DataFile>,
112}
113
114/// The electron data of one material after loading and validation.
115#[derive(Debug, Clone, PartialEq)]
116pub struct ResolvedElectronMaterial {
117    /// The name the target uses.
118    pub name: String,
119    /// The material (composition and density).
120    pub material: Material,
121    /// The optical ELF, validated (with its provenance).
122    pub optical_elf: OpticalElf,
123    /// The ELF file.
124    pub optical_elf_file: DataFile,
125    /// Band parameters, if given.
126    pub band: Option<BandStructure>,
127    /// Insulator channels (none by default).
128    pub channels: InsulatorChannels,
129    /// Options of the Mermin oscillator fit of the `mermin-melf` table:
130    /// [`MerminFitOptions::default`] unless `mermin_fit` is given.
131    pub mermin_fit: MerminFitOptions,
132    /// Inner-shell channels, if given.
133    pub inner_shells: Option<ResolvedInnerShells>,
134    /// Energy per electron-hole pair, eV, and its source; `None` when the
135    /// input gives none (no pair count is then reported).
136    pub e_pair: Option<ResolvedEPair>,
137}
138
139/// `E_pair` of a material, validated.
140#[derive(Debug, Clone, PartialEq)]
141pub struct ResolvedEPair {
142    /// Energy per electron-hole pair, eV.
143    pub ev: f64,
144    /// The citation, trimmed.
145    pub source: String,
146}
147
148/// A validated electron input, in the engine's types.
149#[derive(Debug, Clone)]
150pub struct ResolvedElectron {
151    /// The input as echoed: defaults filled in, `run.threads` removed.
152    pub input: ElectronInput,
153    /// The target.
154    pub stack: Stack,
155    /// Layers in stack order, with where each material came from.
156    pub layers: Vec<ResolvedLayer>,
157    /// For each layer, the index of its material in `materials`.
158    pub layer_material: Vec<usize>,
159    /// The materials the target uses, by name, with their electron data.
160    pub materials: Vec<ResolvedElectronMaterial>,
161    /// Transport configuration.
162    pub config: TransportConfig,
163    /// The primary electron.
164    pub primary: Primary,
165    /// Landing profile and divergence of the beam (a point beam by default);
166    /// run with [`Transport::run_beam`](crate::electron::transport::Transport::run_beam).
167    pub beam: BeamSpread,
168    /// Elastic choices.
169    pub elastic: ElasticChoice,
170    /// Inelastic model.
171    pub inelastic: PennAlgorithm,
172    /// Inelastic Fermi energy, eV, of the tables of materials without a band
173    /// (0 if any material has one).
174    pub inelastic_fermi_ev: f64,
175    /// Energy grid of every table, eV.
176    pub table_energy_ev: Vec<f64>,
177    /// Tally configuration (SI units).
178    pub tally: ElectronTallyConfig,
179    /// The grid of `electron.tally.voxel` (SI units), if configured.
180    pub voxel_grid: Option<VoxelTallyGrid>,
181    /// The PSF forms to fit to the profile of `tally.psf`, in input order
182    /// (duplicates removed).
183    pub psf_fits: Vec<PsfModel>,
184    /// How the PSF fits set their energy scale.
185    pub psf_normalization: PsfNormalization,
186    /// Non-fatal advice.
187    pub warnings: Vec<String>,
188}
189
190impl ResolvedElectron {
191    /// Every model in use, with its published source, for the output
192    /// metadata. Data provenance (the ELF files, band, phonon and polaron
193    /// parameters, and the tables built from them) is recorded separately.
194    pub fn models(&self) -> Vec<ModelInfo> {
195        let mut m = vec![
196            ModelInfo {
197                role: "electron transport",
198                name: "event-by-event",
199                citation: Cow::Borrowed(
200                    "Kieft and Bosch, J. Phys. D: Appl. Phys. 41, 215310 (2008), \
201                     doi:10.1088/0022-3727/41/21/215310",
202                ),
203            },
204            ModelInfo {
205                role: "elastic scattering",
206                name: "mott-partial-waves",
207                citation: Cow::Borrowed(
208                    "Mott cross section from radial-Dirac phase shifts (electron::elastic), \
209                     independent-atom additivity (electron::elastic::table)",
210                ),
211            },
212        ];
213        m.push(match self.elastic.potential {
214            PotentialChoice::ThomasFermiYukawa => ModelInfo {
215                role: "elastic potential",
216                name: "thomas-fermi-yukawa",
217                citation: Cow::Borrowed(
218                    "STAND-IN Yukawa potential with the Thomas-Fermi screening length \
219                     a = 0.8853 a0 Z^(-1/3) (Firsov 1958; Lindhard, Scharff and Schiott 1963); \
220                     not a DHFS atomic potential",
221                ),
222            },
223            PotentialChoice::SalvatDhfs => ModelInfo {
224                role: "elastic potential",
225                name: "salvat-dhfs",
226                citation: Cow::Borrowed(
227                    "Salvat, Martinez, Mayol and Parellada, Phys. Rev. A 36, 467 (1987), \
228                     doi:10.1103/PhysRevA.36.467",
229                ),
230            },
231        });
232        if self.elastic.exchange {
233            m.push(ModelInfo {
234                role: "elastic exchange correction",
235                name: "furness-mccarthy",
236                citation: Cow::Borrowed(EXCHANGE_MODEL),
237            });
238        }
239        if !self.elastic.correlation_polarization.is_empty() {
240            m.push(ModelInfo {
241                role: "elastic correlation-polarization correction",
242                name: "salvat-2003",
243                citation: Cow::Borrowed(CORRELATION_POLARIZATION_MODEL),
244            });
245        }
246        m.push(ModelInfo {
247            role: "inelastic scattering",
248            name: self.inelastic.label(),
249            citation: Cow::Borrowed(match self.inelastic {
250                PennAlgorithm::SinglePole => {
251                    "single-pole Penn algorithm, Penn, Phys. Rev. B 35, 482 (1987), as written \
252                     in Shinotsuka et al., Surf. Interface Anal. 49, 238 (2017), \
253                     doi:10.1002/sia.6123, eqs. (7)-(13)"
254                }
255                PennAlgorithm::Full => {
256                    "full Penn algorithm, Penn, Phys. Rev. B 35, 482 (1987), as written in \
257                     Shinotsuka et al., Surf. Interface Anal. 49, 238 (2017), \
258                     doi:10.1002/sia.6123, eqs. (4)-(6)"
259                }
260                PennAlgorithm::Mermin => {
261                    "Mermin-ELF (MELF-GOS) oscillator fit, Mermin, Phys. Rev. B 1, 2362 (1970), \
262                     as written in de Vera et al., Int. J. Mol. Sci. 23, 6121 (2022), \
263                     doi:10.3390/ijms23116121, eqs. (3)-(5)"
264                }
265            }),
266        });
267        if self.config.secondaries != SecondaryModel::Off {
268            m.push(ModelInfo {
269                role: "secondary electrons",
270                name: "kieft-bosch",
271                citation: Cow::Borrowed(
272                    "Kieft and Bosch, J. Phys. D 41, 215310 (2008), as written out in Verduin, \
273                     PhD thesis, TU Delft (2017), doi:10.4233/uuid:f214f594-a21f-4318-9f29-9776d60ab06c, \
274                     Eqs. 3.86, 3.100-3.111; ported from Nebula (BSD-3-Clause)",
275                ),
276            });
277        }
278        if self.config.boundary != BoundaryModel::Transparent {
279            m.push(ModelInfo {
280                role: "surface and interface barrier",
281                name: "step-barrier",
282                citation: Cow::Borrowed(
283                    "inner-potential step, Verduin, PhD thesis, TU Delft (2017), Eqs. 3.136, \
284                     3.139, 3.145; ported from Nebula (BSD-3-Clause)",
285                ),
286            });
287        }
288        if self.materials.iter().any(|x| x.channels.phonon.is_some()) {
289            m.push(ModelInfo {
290                role: "LO-phonon scattering",
291                name: "frohlich",
292                citation: Cow::Borrowed(
293                    "Frohlich, Adv. Phys. 3, 325 (1954), as written in Ding, Li, Da, Liu, Sci. \
294                     Technol. Adv. Mater. 22, 932 (2021), Eqs. (14)-(16), and Taioli and Dapor, \
295                     arXiv:2404.07521v5, Eqs. (33)-(34)",
296                ),
297            });
298        }
299        if self.materials.iter().any(|x| x.channels.polaron.is_some()) {
300            m.push(ModelInfo {
301                role: "polaron trapping",
302                name: "ganachaud-mokrani",
303                citation: Cow::Borrowed(
304                    "C exp(-gamma E), Ganachaud and Mokrani, Surf. Sci. 334, 329 (1995), as \
305                     written in Taioli and Dapor, arXiv:2404.07521v5, Eq. (37)",
306                ),
307            });
308        }
309        if self.materials.iter().any(|x| x.inner_shells.is_some()) {
310            m.push(ModelInfo {
311                role: "inner-shell ionisation",
312                name: "hydrogenic-shell-single-pole-penn",
313                citation: Cow::Borrowed(
314                    "one channel per inner subshell, each from that shell's hydrogenic optical \
315                     ELF (electron::inelastic::shell_elf) with single-pole Penn, as the inner \
316                     part of the ELF in de Vera et al., Int. J. Mol. Sci. 23, 6121 (2022), \
317                     doi:10.3390/ijms23116121, eqs. (1)-(2), (32), (35); secondary energy \
318                     E_F + W - B (Verduin 2017, Eq. 3.86); no exchange correction",
319                ),
320            });
321        }
322        m.push(ModelInfo {
323            role: "SE/BSE split",
324            name: "energy-split",
325            citation: Cow::Borrowed(SE_BSE_SPLIT_SOURCE),
326        });
327        m
328    }
329}