Skip to main content

lindhard/input/
resolved.rs

1//! The resolved run: [`Resolved`], its parts and the shared material resolver.
2
3use crate::elements::element_by_symbol;
4use crate::geometry::Stack;
5use crate::ion::bca::{BcaConfig, Beam, CrystalTarget, MeanFreePath};
6use crate::ion::potential::{Screening, ScreeningLength};
7use crate::ion::scattering::TableSpec;
8use crate::ion::stopping::bethe::BetheBloch;
9use crate::ion::stopping::lindhard_scharff::LindhardScharff;
10use crate::ion::stopping::none::NoStopping;
11use crate::ion::stopping::table::TableOverride;
12use crate::ion::stopping::ElectronicStopping;
13use crate::material::{Material, MaterialSpec};
14use serde::Serialize;
15use std::borrow::Cow;
16use std::collections::BTreeMap;
17
18use super::schema::*;
19use super::{invalid, Input, InputError};
20
21/// One model in use, for the output metadata.
22#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
23pub struct ModelInfo {
24    /// What the model is for, e.g. `"screening function"`.
25    pub role: &'static str,
26    /// Stable model name.
27    pub name: &'static str,
28    /// Published source (for a user table: its path and provenance).
29    pub citation: Cow<'static, str>,
30}
31
32/// One target layer after resolution.
33#[derive(Debug, Clone, PartialEq)]
34pub struct ResolvedLayer {
35    /// How the input referred to the material (`[materials]` key, element
36    /// symbol, or `"inline"`).
37    pub source: String,
38    /// The material with every energy set.
39    pub material: Material,
40}
41
42/// One crystal assignment after resolution.
43#[derive(Debug, Clone)]
44pub struct ResolvedCrystal {
45    /// Stack layers (engine regions) the crystal fills.
46    pub regions: Vec<usize>,
47    /// Lattice, orientation (with the beam's tilt and azimuth) and search
48    /// parameters, ready for [`crate::ion::bca::Bca::with_crystal`].
49    pub target: CrystalTarget,
50}
51
52/// A validated input, in the engine's types.
53#[derive(Debug, Clone)]
54pub struct Resolved {
55    /// The input with defaults filled in (including the screening length).
56    pub input: Input,
57    /// The beam.
58    pub beam: Beam,
59    /// Beam divergence about the nominal direction ([`crate::ion::crystal::Divergence::None`]
60    /// without `[beam.divergence]`).
61    pub divergence: crate::ion::crystal::Divergence,
62    /// The target.
63    pub stack: Stack,
64    /// Layers in stack order, with where each material came from.
65    pub layers: Vec<ResolvedLayer>,
66    /// Engine configuration (including the seed).
67    pub config: BcaConfig,
68    /// Screening function.
69    pub screening: Screening,
70    /// Screening length.
71    pub screening_length: ScreeningLength,
72    /// Scattering-table grid.
73    pub table_spec: TableSpec,
74    /// User stopping tables, in input order (empty without `[stopping]`).
75    pub stopping_tables: Vec<LoadedStoppingTable>,
76    /// Crystal assignments, in input order (empty: amorphous run).
77    pub crystals: Vec<ResolvedCrystal>,
78    /// Non-fatal advice (e.g. beam energy outside a model's validity range).
79    pub warnings: Vec<String>,
80    /// The tuning applied, if `[physics] tuning` named a set.
81    pub tuning: Option<TuningReport>,
82}
83
84/// Scattering-table grid used for every run: `per_decade` points per decade
85/// of reduced energy and impact parameter. Angles outside the energy range
86/// fall back to direct quadrature in the engine; impact parameters above
87/// `beta_max` give no deflection (the constant-free-path `p_max` is about 20
88/// screening lengths or less for solid densities). The measured interpolation
89/// error is reported in the output metadata.
90pub const TABLE_SPEC: TableSpec = TableSpec {
91    eps_min: 1e-6,
92    eps_max: 1e4,
93    beta_min: 1e-5,
94    beta_max: 1e2,
95    per_decade: 32,
96};
97
98/// Resolve a [`MaterialRef`] against the `[materials]` table: a key of
99/// `materials`, else an element symbol (the pure element at its tabulated
100/// density), or an inline table. `field` names the key in errors. Shared by
101/// the ion input and the electron input ([`electron`]).
102pub(crate) fn resolve_material(
103    materials: &BTreeMap<String, MaterialSpec>,
104    field: &str,
105    r: &MaterialRef,
106) -> Result<ResolvedLayer, InputError> {
107    match r {
108        MaterialRef::Inline(spec) => Material::try_from(spec.clone())
109            .map(|material| ResolvedLayer {
110                source: "inline".into(),
111                material,
112            })
113            .map_err(|e| invalid(field, e.to_string())),
114        MaterialRef::Name(name) => {
115            if let Some(spec) = materials.get(name) {
116                let mut material = Material::try_from(spec.clone())
117                    .map_err(|e| invalid(format!("materials.{name}"), e.to_string()))?;
118                if material.name().is_none() {
119                    material = material.with_name(name.clone());
120                }
121                return Ok(ResolvedLayer {
122                    source: name.clone(),
123                    material,
124                });
125            }
126            let el = element_by_symbol(name).ok_or_else(|| {
127                invalid(
128                    field,
129                    format!(
130                        "unknown material {name:?}: not a key of [materials] \
131                         and not an element symbol"
132                    ),
133                )
134            })?;
135            let material = Material::from_atom_fractions(&[(el.z, 1.0)], None)
136                .map_err(|e| {
137                    invalid(
138                        field,
139                        format!("element {name}: {e}; define it in [materials] with a density"),
140                    )
141                })?
142                .with_name(name.clone());
143            Ok(ResolvedLayer {
144                source: name.clone(),
145                material,
146            })
147        }
148    }
149}
150
151/// The electronic stopping model for a choice. For
152/// [`StoppingChoice::EquipartitionLsOr`] the engine does not use this model
153/// (it carries its own Lindhard-Scharff/Oen-Robinson mix); Lindhard-Scharff
154/// is returned so the validity range can still be checked. For
155/// [`StoppingChoice::None`] it is [`NoStopping`], whose validity range is
156/// unbounded, so no out-of-range warning is raised.
157pub fn stopping_model(choice: StoppingChoice) -> Box<dyn ElectronicStopping + Send + Sync> {
158    match choice {
159        StoppingChoice::LindhardScharff | StoppingChoice::EquipartitionLsOr => {
160            Box::new(LindhardScharff::new())
161        }
162        StoppingChoice::BetheBloch => Box::new(BetheBloch::new()),
163        StoppingChoice::None => Box::new(NoStopping::new()),
164    }
165}
166
167impl Resolved {
168    /// The electronic stopping model of the run: the `[physics] stopping`
169    /// choice, with any `[stopping]` tables layered over it for the pairs
170    /// they declare. This is what to pass to the engine.
171    pub fn stopping_model(&self) -> Box<dyn ElectronicStopping + Send + Sync> {
172        let base = stopping_model(self.input.physics.stopping);
173        if self.stopping_tables.is_empty() {
174            base
175        } else {
176            Box::new(TableOverride::new(
177                self.stopping_tables
178                    .iter()
179                    .map(|l| l.table.clone())
180                    .collect(),
181                base,
182            ))
183        }
184    }
185
186    /// Every model in use, with its published source, in a fixed order.
187    pub fn models(&self) -> Vec<ModelInfo> {
188        let mut v = vec![ModelInfo {
189            role: "transport",
190            name: "amorphous-bca",
191            citation: Cow::Borrowed(
192                "J. P. Biersack and L. G. Haggmark, Nucl. Instrum. Methods 174 (1980) 257; \
193                       M. T. Robinson and I. M. Torrens, Phys. Rev. B 9 (1974) 5008; \
194                       W. Eckstein, Computer Simulation of Ion-Solid Interactions (Springer, 1991)",
195            ),
196        }];
197        if !self.crystals.is_empty() {
198            v.push(ModelInfo {
199                role: "crystal transport",
200                name: "lattice-site-bca",
201                citation: Cow::Borrowed(
202                    "collision partners from explicit lattice sites after DISPLATH \
203                     (https://github.com/permissionx/DISPLATH, MIT); cubic lattices: \
204                     M. J. Mehl et al., Comput. Mater. Sci. 136 (2017) S1; thermal \
205                     displacements from the Debye model (lindhard::ion::crystal::debye); \
206                     the electronic-constants-unverified flag and the known channeling \
207                     deviations are stated in docs/cli.md, [[crystal]]",
208                ),
209            });
210        }
211        v.push(match self.screening {
212            Screening::ZblUniversal => ModelInfo {
213                role: "screening function",
214                name: "zbl-universal",
215                citation: Cow::Borrowed(
216                    "J. F. Ziegler, J. P. Biersack, U. Littmark, The Stopping and Range \
217                           of Ions in Solids (Pergamon, 1985), ch. 2",
218                ),
219            },
220            Screening::KrC => ModelInfo {
221                role: "screening function",
222                name: "kr-c",
223                citation: Cow::Borrowed(
224                    "W. D. Wilson, L. G. Haggmark, J. P. Biersack, Phys. Rev. B 15 (1977) 2458",
225                ),
226            },
227            Screening::Moliere => ModelInfo {
228                role: "screening function",
229                name: "moliere",
230                citation: Cow::Borrowed("G. Moliere, Z. Naturforsch. A 2 (1947) 133"),
231            },
232            Screening::LenzJensen => ModelInfo {
233                role: "screening function",
234                name: "lenz-jensen",
235                citation: Cow::Borrowed(
236                    "W. Lenz, Z. Phys. 77 (1932) 713; H. Jensen, Z. Phys. 77 (1932) 722",
237                ),
238            },
239        });
240        v.push(match self.screening_length {
241            ScreeningLength::Universal => ModelInfo {
242                role: "screening length",
243                name: "universal",
244                citation: Cow::Borrowed(
245                    "J. F. Ziegler, J. P. Biersack, U. Littmark, The Stopping and Range \
246                           of Ions in Solids (Pergamon, 1985), ch. 2",
247                ),
248            },
249            ScreeningLength::Firsov => ModelInfo {
250                role: "screening length",
251                name: "firsov",
252                citation: Cow::Borrowed("O. B. Firsov, Sov. Phys. JETP 6 (1958) 534"),
253            },
254            ScreeningLength::Lindhard => ModelInfo {
255                role: "screening length",
256                name: "lindhard",
257                citation: Cow::Borrowed(
258                    "J. Lindhard, M. Scharff, H. E. Schiott, Mat. Fys. Medd. Dan. Vid. \
259                           Selsk. 33 (14) (1963)",
260                ),
261            },
262        });
263        v.push(ModelInfo {
264            role: "scattering angle",
265            name: "gauss-mehler-quadrature-table",
266            citation: Cow::Borrowed(
267                "scattering integral by Gauss-Mehler quadrature, tabulated in \
268                       (reduced energy, reduced impact parameter); see lindhard::ion::scattering",
269            ),
270        });
271        v.extend(match self.input.physics.stopping {
272            StoppingChoice::LindhardScharff => vec![ModelInfo {
273                role: "electronic stopping",
274                name: "lindhard-scharff",
275                citation: Cow::Borrowed("J. Lindhard and M. Scharff, Phys. Rev. 124 (1961) 128"),
276            }],
277            StoppingChoice::BetheBloch => vec![ModelInfo {
278                role: "electronic stopping",
279                name: "bethe-bloch",
280                citation: Cow::Borrowed(
281                    "H. Bethe, Ann. Phys. 5 (1930) 325; F. Bloch, Ann. Phys. 408 (1933) 285; \
282                           mean excitation energy by the Bloch rule I = 10 eV Z2",
283                ),
284            }],
285            StoppingChoice::EquipartitionLsOr => vec![
286                ModelInfo {
287                    role: "electronic stopping",
288                    name: "lindhard-scharff",
289                    citation: Cow::Borrowed(
290                        "J. Lindhard and M. Scharff, Phys. Rev. 124 (1961) 128",
291                    ),
292                },
293                ModelInfo {
294                    role: "electronic loss partition",
295                    name: "equipartition-ls-or",
296                    citation: Cow::Borrowed(
297                        "half nonlocal Lindhard-Scharff, half local Oen-Robinson: \
298                               O. S. Oen and M. T. Robinson, Nucl. Instrum. Methods 132 (1976) 647",
299                    ),
300                },
301            ],
302            StoppingChoice::None => vec![ModelInfo {
303                role: "electronic stopping",
304                name: "none",
305                citation: Cow::Borrowed(
306                    "no electronic stopping (nuclear loss only; the k = 0 limit of \
307                           J. Lindhard, M. Scharff, H. E. Schiott, Mat. Fys. Medd. Dan. Vid. \
308                           Selsk. 33 (14) (1963)); for comparisons and nuclear-only studies, \
309                           not a physical model",
310                ),
311            }],
312        });
313        for l in &self.stopping_tables {
314            v.push(ModelInfo {
315                role: "electronic stopping (user table)",
316                name: "user-table",
317                citation: Cow::Owned(format!("{}: {}", l.path, l.table.provenance())),
318            });
319        }
320        // With no electronic stopping and no tables there is nothing to add up.
321        if self.input.physics.stopping != StoppingChoice::None || !self.stopping_tables.is_empty() {
322            v.push(ModelInfo {
323                role: "compound stopping",
324                name: "bragg-additivity",
325                citation: Cow::Borrowed(
326                    "W. H. Bragg and R. Kleeman, Phil. Mag. 10 (1905) 318; no compound correction",
327                ),
328            });
329        }
330        v.push(match self.config.mean_free_path {
331            MeanFreePath::Constant => ModelInfo {
332                role: "free path",
333                name: "constant",
334                citation: Cow::Borrowed(
335                    "J. P. Biersack and L. G. Haggmark, Nucl. Instrum. Methods 174 (1980) 257",
336                ),
337            },
338            MeanFreePath::EnergyDependent { .. } => ModelInfo {
339                role: "free path",
340                name: "energy-dependent",
341                citation: Cow::Borrowed(
342                    "W. Eckstein, Computer Simulation of Ion-Solid Interactions \
343                           (Springer, 1991)",
344                ),
345            },
346        });
347        v.push(ModelInfo {
348            role: "displacement criterion",
349            name: "e_d-e_b",
350            citation: Cow::Borrowed(
351                "J. P. Biersack and L. G. Haggmark, Nucl. Instrum. Methods 174 (1980) 257; \
352                       W. Eckstein (1991)",
353            ),
354        });
355        v.push(ModelInfo {
356            role: "surface barrier",
357            name: "planar",
358            citation: Cow::Borrowed(
359                "W. Eckstein, Computer Simulation of Ion-Solid Interactions (Springer, 1991)",
360            ),
361        });
362        v.push(ModelInfo {
363            role: "random numbers",
364            name: "chacha8-per-history-stream",
365            citation: Cow::Borrowed(
366                "D. J. Bernstein, ChaCha, a variant of Salsa20 (2008); \
367                       J. K. Salmon et al., Proc. SC'11 (2011)",
368            ),
369        });
370        v
371    }
372}