1use std::collections::BTreeMap;
5
6use serde::{Deserialize, Serialize};
7
8use crate::electron::elastic::table::{DEFAULT_MIN_ENERGY_EV, DEFAULT_POINTS_PER_DECADE};
9use crate::electron::inelastic::{FitWeighting, MerminFitOptions, PennAlgorithm};
10use crate::electron::transport::TransportConfig;
11use crate::input::{DivergenceSpec, TargetSpec, VoxelTallySpec};
12use crate::material::MaterialSpec;
13use crate::tally::{PsfModel, PsfNormalization, SE_BSE_SPLIT_EV};
14
15#[cfg(doc)]
17use crate::electron::{
18 boundary::{BandModel, BandStructure},
19 data::OpticalElf,
20 phonon::{FrohlichPhonon, PolaronTrapping},
21 secondary::SecondaryModel,
22 transport::{BoundaryModel, CutoffReference, EscapeRule},
23};
24
25#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
27#[serde(deny_unknown_fields)]
28pub struct ElectronInput {
29 pub electron: ElectronSpec,
31 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
33 pub materials: BTreeMap<String, MaterialSpec>,
34 pub target: TargetSpec,
36 pub run: ElectronRunSpec,
38}
39
40#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
42#[serde(deny_unknown_fields)]
43pub struct ElectronSpec {
44 pub beam: ElectronBeamSpec,
46 pub transport: TransportSpec,
48 pub elastic: ElasticSpec,
50 #[serde(default)]
52 pub inelastic: InelasticSpec,
53 #[serde(default)]
55 pub tables: TableGridSpec,
56 pub materials: BTreeMap<String, ElectronMaterialSpec>,
59 #[serde(default)]
61 pub tally: ElectronTallySpec,
62}
63
64#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
66#[serde(deny_unknown_fields)]
67pub struct ElectronBeamSpec {
68 pub energy_ev: f64,
72 #[serde(default)]
74 pub tilt_deg: f64,
75 #[serde(default)]
77 pub azimuth_deg: f64,
78 #[serde(default, skip_serializing_if = "is_origin")]
81 pub landing_nm: [f64; 2],
82 #[serde(default, skip_serializing_if = "Option::is_none")]
84 pub profile: Option<BeamProfileSpec>,
85 #[serde(default, skip_serializing_if = "Option::is_none")]
89 pub divergence: Option<DivergenceSpec>,
90}
91
92fn is_origin(v: &[f64; 2]) -> bool {
93 *v == [0.0; 2]
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
109#[serde(tag = "model", rename_all = "kebab-case", deny_unknown_fields)]
110pub enum BeamProfileSpec {
111 Point,
113 Gaussian {
115 sigma_nm: f64,
117 },
118 Disc {
120 radius_nm: f64,
122 },
123 Rectangle {
125 width_y_nm: f64,
127 width_z_nm: f64,
129 },
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
134#[serde(rename_all = "kebab-case")]
135pub enum CutoffReferenceChoice {
136 #[default]
138 BandBottom,
139 VacuumLevel,
141}
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
145#[serde(rename_all = "kebab-case")]
146pub enum EscapeRuleChoice {
147 #[default]
149 BothFaces,
150 FrontOnly,
152}
153
154#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
156#[serde(rename_all = "kebab-case")]
157pub enum SecondaryChoice {
158 #[default]
160 Off,
161 KieftBosch,
163}
164
165#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
167#[serde(rename_all = "kebab-case")]
168pub enum BoundaryChoice {
169 #[default]
171 Transparent,
172 StepBarrier,
174}
175
176fn default_max_events() -> u64 {
177 TransportConfig::new(1.0).max_events
178}
179
180#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
182#[serde(deny_unknown_fields)]
183pub struct TransportSpec {
184 pub cutoff_ev: f64,
186 #[serde(default)]
188 pub cutoff_reference: CutoffReferenceChoice,
189 #[serde(default)]
191 pub escape_rule: EscapeRuleChoice,
192 #[serde(default = "default_max_events")]
194 pub max_events: u64,
195 #[serde(default)]
197 pub secondaries: SecondaryChoice,
198 #[serde(default, skip_serializing_if = "Option::is_none")]
201 pub instantaneous_momentum: Option<bool>,
202 #[serde(default, skip_serializing_if = "Option::is_none")]
204 pub momentum_conservation: Option<bool>,
205 #[serde(default)]
207 pub boundary: BoundaryChoice,
208 #[serde(default, skip_serializing_if = "Option::is_none")]
211 pub quantum_transmission: Option<bool>,
212 #[serde(default, skip_serializing_if = "Option::is_none")]
214 pub refraction: Option<bool>,
215}
216
217#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
220#[serde(rename_all = "kebab-case")]
221pub enum ElasticModelChoice {
222 #[default]
224 Mott,
225}
226
227impl ElasticModelChoice {
228 pub fn label(self) -> &'static str {
230 match self {
231 Self::Mott => "mott",
232 }
233 }
234}
235
236#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
238#[serde(rename_all = "kebab-case")]
239pub enum PotentialChoice {
240 ThomasFermiYukawa,
243 SalvatDhfs,
247}
248
249impl PotentialChoice {
250 pub fn label(self) -> &'static str {
252 match self {
253 Self::ThomasFermiYukawa => "thomas-fermi-yukawa",
254 Self::SalvatDhfs => "salvat-dhfs",
255 }
256 }
257}
258
259#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
261#[serde(deny_unknown_fields)]
262pub struct ElasticSpec {
263 #[serde(default)]
265 pub model: ElasticModelChoice,
266 pub potential: PotentialChoice,
269 #[serde(default)]
271 pub exchange: bool,
272 #[serde(default, skip_serializing_if = "Option::is_none")]
274 pub correlation_polarization: Option<CorrelationPolarizationSpec>,
275}
276
277#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
279#[serde(deny_unknown_fields)]
280pub struct PolarizabilitySpec {
281 pub bohr3: f64,
283 pub source: String,
285}
286
287fn default_outer_radius() -> f64 {
288 crate::electron::elastic::corrections::DEFAULT_OUTER_RADIUS
289}
290
291#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
293#[serde(deny_unknown_fields)]
294pub struct CorrelationPolarizationSpec {
295 #[serde(default, skip_serializing_if = "Option::is_none")]
298 pub b_pol_squared: Option<f64>,
299 #[serde(default = "default_outer_radius")]
301 pub outer_radius_bohr: f64,
302 pub polarizability: BTreeMap<String, PolarizabilitySpec>,
304}
305
306#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
308#[serde(deny_unknown_fields)]
309pub struct InelasticSpec {
310 #[serde(default = "default_inelastic_model")]
312 pub model: String,
313 #[serde(default)]
317 pub fermi_energy_ev: f64,
318}
319
320fn default_inelastic_model() -> String {
321 PennAlgorithm::SinglePole.label().to_string()
322}
323
324impl Default for InelasticSpec {
325 fn default() -> Self {
326 Self {
327 model: default_inelastic_model(),
328 fermi_energy_ev: 0.0,
329 }
330 }
331}
332
333fn default_min_energy() -> f64 {
334 DEFAULT_MIN_ENERGY_EV
335}
336
337fn default_points_per_decade() -> f64 {
338 DEFAULT_POINTS_PER_DECADE
339}
340
341#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
343#[serde(deny_unknown_fields)]
344pub struct TableGridSpec {
345 #[serde(default = "default_min_energy")]
347 pub min_energy_ev: f64,
348 #[serde(default, skip_serializing_if = "Option::is_none")]
351 pub max_energy_ev: Option<f64>,
352 #[serde(default = "default_points_per_decade")]
354 pub points_per_decade: f64,
355}
356
357impl Default for TableGridSpec {
358 fn default() -> Self {
359 Self {
360 min_energy_ev: default_min_energy(),
361 max_energy_ev: None,
362 points_per_decade: default_points_per_decade(),
363 }
364 }
365}
366
367#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
369#[serde(tag = "kind", rename_all = "kebab-case", deny_unknown_fields)]
370pub enum BandSpec {
371 Metal {
373 fermi_ev: f64,
375 work_function_ev: f64,
377 #[serde(default, skip_serializing_if = "Option::is_none")]
381 valence_binding_ev: Option<f64>,
382 provenance: String,
384 },
385 Insulator {
387 valence_band_width_ev: f64,
389 band_gap_ev: f64,
391 affinity_ev: f64,
393 provenance: String,
395 },
396 FreeElectronMetal {
399 valence_electrons_per_atom: f64,
401 work_function_ev: f64,
403 #[serde(default, skip_serializing_if = "Option::is_none")]
405 valence_binding_ev: Option<f64>,
406 provenance: String,
409 },
410}
411
412#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
415#[serde(deny_unknown_fields)]
416pub struct PhononSpec {
417 #[serde(default, skip_serializing_if = "Option::is_none")]
420 pub preset: Option<String>,
421 #[serde(default, skip_serializing_if = "Option::is_none")]
423 pub hbar_omega_ev: Option<f64>,
424 #[serde(default, skip_serializing_if = "Option::is_none")]
426 pub eps_static: Option<f64>,
427 #[serde(default, skip_serializing_if = "Option::is_none")]
429 pub eps_high_frequency: Option<f64>,
430 pub temperature_k: f64,
432 #[serde(default, skip_serializing_if = "Option::is_none")]
434 pub provenance: Option<String>,
435}
436
437#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
439#[serde(deny_unknown_fields)]
440pub struct PolaronSpec {
441 pub c_per_nm: f64,
443 pub gamma_per_ev: f64,
445 pub provenance: String,
447}
448
449fn default_mermin_oscillators() -> usize {
450 MerminFitOptions::default().n_oscillators
451}
452
453pub(super) fn default_relative_floor() -> f64 {
456 match MerminFitOptions::default().weighting {
457 FitWeighting::Relative { floor } => floor,
458 FitWeighting::Uniform => unreachable!("the default fit weighting is relative"),
459 }
460}
461
462#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
465#[serde(rename_all = "kebab-case")]
466pub enum MerminWeightingChoice {
467 #[default]
469 Relative,
470 Uniform,
472}
473
474#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
481#[serde(deny_unknown_fields)]
482pub struct MerminFitSpec {
483 #[serde(default = "default_mermin_oscillators")]
485 pub oscillators: usize,
486 #[serde(default)]
488 pub weighting: MerminWeightingChoice,
489 #[serde(default, skip_serializing_if = "Option::is_none")]
492 pub relative_floor: Option<f64>,
493}
494
495#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
501#[serde(deny_unknown_fields)]
502pub struct InnerShellsSpec {
503 pub subshells: Vec<String>,
506 #[serde(default, skip_serializing_if = "Option::is_none")]
511 pub binding: Option<String>,
512}
513
514#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
516#[serde(deny_unknown_fields)]
517pub struct ElectronMaterialSpec {
518 pub optical_elf: String,
521 #[serde(default, skip_serializing_if = "Option::is_none")]
524 pub band: Option<BandSpec>,
525 #[serde(default, skip_serializing_if = "Option::is_none")]
527 pub phonon: Option<PhononSpec>,
528 #[serde(default, skip_serializing_if = "Option::is_none")]
530 pub polaron: Option<PolaronSpec>,
531 #[serde(default, skip_serializing_if = "Option::is_none")]
534 pub mermin_fit: Option<MerminFitSpec>,
535 #[serde(default, skip_serializing_if = "Option::is_none")]
537 pub inner_shells: Option<InnerShellsSpec>,
538 #[serde(default, skip_serializing_if = "Option::is_none")]
542 pub e_pair: Option<EPairSpec>,
543}
544
545#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
551#[serde(deny_unknown_fields)]
552pub struct EPairSpec {
553 pub ev: f64,
555 pub source: String,
557}
558
559#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
561#[serde(deny_unknown_fields)]
562pub struct BinsNm {
563 pub lo_nm: f64,
565 pub hi_nm: f64,
567 pub bins: usize,
569}
570
571#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
573#[serde(deny_unknown_fields)]
574pub struct CartesianSpec {
575 pub x: BinsNm,
577 pub y: BinsNm,
579 pub z: BinsNm,
581}
582
583#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
585#[serde(deny_unknown_fields)]
586pub struct CylindricalSpec {
587 pub r: BinsNm,
589 pub depth: BinsNm,
591}
592
593#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
595#[serde(rename_all = "kebab-case")]
596pub enum PsfFitSpec {
597 Double,
599 Triple,
601}
602
603impl From<PsfFitSpec> for PsfModel {
604 fn from(f: PsfFitSpec) -> Self {
605 match f {
606 PsfFitSpec::Double => PsfModel::DoubleGaussian,
607 PsfFitSpec::Triple => PsfModel::TripleGaussian,
608 }
609 }
610}
611
612#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
614#[serde(rename_all = "kebab-case")]
615pub enum PsfNormalizationSpec {
616 #[default]
618 SlabTotal,
619 Free,
621}
622
623impl From<PsfNormalizationSpec> for PsfNormalization {
624 fn from(n: PsfNormalizationSpec) -> Self {
625 match n {
626 PsfNormalizationSpec::SlabTotal => PsfNormalization::SlabTotal,
627 PsfNormalizationSpec::Free => PsfNormalization::Free,
628 }
629 }
630}
631
632pub(super) fn default_psf_fits() -> Vec<PsfFitSpec> {
633 vec![PsfFitSpec::Double, PsfFitSpec::Triple]
634}
635
636#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
640#[serde(deny_unknown_fields)]
641pub struct PsfSpec {
642 pub depth_lo_nm: f64,
644 pub depth_hi_nm: f64,
646 pub r_min_nm: f64,
648 pub r_max_nm: f64,
650 pub bins: usize,
652 #[serde(default = "default_psf_fits")]
654 pub fits: Vec<PsfFitSpec>,
655 #[serde(default)]
657 pub normalization: PsfNormalizationSpec,
658}
659
660fn default_exposure_bins() -> usize {
661 200
662}
663fn default_exposure_quantiles() -> Vec<f64> {
664 vec![0.05, 0.5, 0.95]
665}
666fn default_exposure_tail() -> f64 {
667 1e-12
668}
669
670#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
672#[serde(tag = "kind", rename_all = "kebab-case", deny_unknown_fields)]
673pub enum ExposureBeamSpec {
674 Point,
676 Gaussian {
678 sigma_nm: f64,
680 },
681 UniformDisc {
683 radius_nm: f64,
685 },
686}
687
688#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
690#[serde(rename_all = "kebab-case")]
691pub enum ExposureShapeSpec {
692 Cylinder,
694 Box,
696}
697
698#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
700#[serde(deny_unknown_fields)]
701pub struct ExposureVolumeSpec {
702 pub name: String,
704 pub shape: ExposureShapeSpec,
706 #[serde(default, skip_serializing_if = "Option::is_none")]
708 pub radius_nm: Option<f64>,
709 #[serde(default, skip_serializing_if = "Option::is_none")]
711 pub half_y_nm: Option<f64>,
712 #[serde(default, skip_serializing_if = "Option::is_none")]
714 pub half_z_nm: Option<f64>,
715 pub depth_lo_nm: f64,
717 pub depth_hi_nm: f64,
719 #[serde(default)]
721 pub offset_y_nm: f64,
722 #[serde(default)]
724 pub offset_z_nm: f64,
725}
726
727#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
731#[serde(deny_unknown_fields)]
732pub struct ExposureSpec {
733 pub lambda: f64,
735 pub beam: ExposureBeamSpec,
737 pub volumes: Vec<ExposureVolumeSpec>,
739 #[serde(default = "default_exposure_bins")]
741 pub bins: usize,
742 #[serde(default, skip_serializing_if = "Option::is_none")]
744 pub x_max_ev: Option<f64>,
745 #[serde(default = "default_exposure_quantiles")]
747 pub quantiles: Vec<f64>,
748 #[serde(default)]
750 pub below_ev: Vec<f64>,
751 #[serde(default)]
753 pub above_ev: Vec<f64>,
754 #[serde(default = "default_exposure_tail")]
756 pub tail_tolerance: f64,
757}
758
759fn default_split() -> f64 {
760 SE_BSE_SPLIT_EV
761}
762fn default_energy_bins() -> usize {
763 100
764}
765fn default_polar_max() -> f64 {
766 90.0
767}
768fn default_polar_bins() -> usize {
769 18
770}
771
772#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
774#[serde(deny_unknown_fields)]
775pub struct ElectronTallySpec {
776 #[serde(default = "default_split")]
778 pub se_bse_split_ev: f64,
779 #[serde(default, skip_serializing_if = "Option::is_none")]
782 pub escape_energy_max_ev: Option<f64>,
783 #[serde(default = "default_energy_bins")]
785 pub escape_energy_bins: usize,
786 #[serde(default = "default_polar_max")]
789 pub escape_polar_max_deg: f64,
790 #[serde(default = "default_polar_bins")]
792 pub escape_polar_bins: usize,
793 #[serde(default, skip_serializing_if = "Option::is_none")]
795 pub cartesian: Option<CartesianSpec>,
796 #[serde(default, skip_serializing_if = "Option::is_none")]
798 pub cylindrical: Option<CylindricalSpec>,
799 #[serde(default, skip_serializing_if = "Option::is_none")]
801 pub psf: Option<PsfSpec>,
802 #[serde(default, skip_serializing_if = "Option::is_none")]
804 pub dose: Option<DoseSpec>,
805 #[serde(default, skip_serializing_if = "Option::is_none")]
807 pub exposure: Option<ExposureSpec>,
808 #[serde(default, skip_serializing_if = "Option::is_none")]
812 pub voxel: Option<VoxelTallySpec>,
813}
814
815#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
819#[serde(deny_unknown_fields)]
820pub struct DoseSpec {
821 #[serde(default, skip_serializing_if = "Option::is_none")]
823 pub depth_bins_per_layer: Option<usize>,
824 #[serde(default, skip_serializing_if = "Option::is_none")]
826 pub refine_width_nm: Option<f64>,
827 #[serde(default, skip_serializing_if = "Option::is_none")]
829 pub refine_bins: Option<usize>,
830 #[serde(default, skip_serializing_if = "Option::is_none")]
832 pub substrate_depth_nm: Option<f64>,
833 #[serde(default)]
835 pub transmission: bool,
836}
837
838impl Default for ElectronTallySpec {
839 fn default() -> Self {
840 Self {
841 se_bse_split_ev: default_split(),
842 escape_energy_max_ev: None,
843 escape_energy_bins: default_energy_bins(),
844 escape_polar_max_deg: default_polar_max(),
845 escape_polar_bins: default_polar_bins(),
846 cartesian: None,
847 cylindrical: None,
848 psf: None,
849 dose: None,
850 exposure: None,
851 voxel: None,
852 }
853 }
854}
855
856#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
858#[serde(deny_unknown_fields)]
859pub struct ElectronRunSpec {
860 pub histories: u64,
862 pub seed: u64,
864 #[serde(default, skip_serializing_if = "Option::is_none")]
866 pub threads: Option<usize>,
867}