Expand description
Inelastic sampling tables from the single-pole Penn DIIMFP (issue #94):
an electron::data::CrossSectionTable
with SamplingAxis::InelasticEnergyLoss (the inverse mean free path and
the inverse CDF of the energy loss W at every energy of a caller-chosen
grid), and a sampler of the momentum transfer q given (E, W), from
which the deflection angle follows.
Both are built from SinglePolePenn only; no data and no equation beyond
the model of super::penn enter here (the elementary kinematics of a
binary collision aside, see below). build_inelastic_table_for_model
runs the same energy-loss table procedure on any of the three models of
super::model::PennInelastic (single pole, full Penn, Mermin-ELF),
using that model’s own DIIMFP and inverse IMFP; nothing else changes.
§Energy-loss table
At incident energy E the loss distribution is the DIIMFP at fixed E,
p(E, W) (SinglePolePenn::diimfp_per_m_ev), normalised:
F(W) = ∫_0^W p dW' / ∫_0^E p dW'. The table stores
λ⁻¹(E)fromSinglePolePenn::imfp_and_stopping(the closed-form reduction of #18, not the integral ofpcomputed here; the two agree to the model’s tolerance, 1e-5 in the #18 tests), and- the quantiles
W(u_j)withF(W(u_j)) = u_jon a probability grid shared by all rows.
Density model. W spans decades, so the integrand is handled in
s = ln W, where p dW = p W ds. g(s) = p W is sampled on adaptively
chosen nodes between the lowest loss of the model’s rate (the lowest
tabulated ELF energy, below which p = 0, for the single-pole model; see
“Rows below the ELF table” for the Mermin and full Penn models) and E,
and taken to be
linear between nodes. Starting from
INITIAL_LOSS_PANELS equal panels, a panel is split at its midpoint
while the Simpson-minus-trapezoid estimate of its error in either
∫ g ds or ∫ W g ds exceeds tol · I · h/H (I the whole integral, h
the panel width, H the whole width; tol =
InelasticTableOptions::density_tolerance). The quantiles are then the
exact inverse of this piecewise-quadratic CDF (a quadratic per panel,
solved without cancellation), so the density model is the only
approximation of the loss distribution and its error is controlled by that
tolerance.
Full Penn rows. For the full Penn model the density is built on the
same nodes from the DIIMFP of a DiimfpGrid tabulated once for the
table’s energies (FullPenn::diimfp_grid, #256), not from the direct
nested integrals: its interpolation error, held to the model tolerance
(1e-4, relative or relative to the row’s mean density), is a second
approximation of the loss distribution, next to the density tolerance.
It is also smooth in W, where the direct DIIMFP carries quadrature
noise at the model tolerance, which is above the default density
tolerance and would drive the panel refinement to MAX_NODES.
Unresolved grid cells. Where the grid could not hold its
interpolation to the model tolerance even at its narrowest cell (1e-4 in
ln ω), it leaves the cell to the direct model
(DiimfpGrid::unresolved_cells). The row integrand then switches model
at each edge of such a cell, a step of the order of the model tolerance,
and inside it carries the direct model’s quadrature noise. The split test
above does not depend on the panel width for a step (both sides scale
with h), so a step above about 3 · tol of the row’s mean density
would be bisected down to floating-point resolution and then padded with
duplicate nodes up to MAX_NODES, each a direct evaluation. So a panel
within one grid-cell width (1e-4) of an unresolved cell is not split
once it is no wider than that width: that is the finest scale at which
the grid tested the DIIMFP, and below it the split test measures the
noise and the step, not the shape. What such a panel can still miss is
bounded by its width times the step, 1e-4 · (model tolerance) · g,
per cell edge, far below the model tolerance of the row. Rows that do
not come near an unresolved cell, and every model without a grid, are
refined exactly as before. loss_density_node_count reports the node
count of a row.
Floating-point resolution (#345). Every model is a numerical integral
at its own tolerance, so its DIIMFP carries small jumps where the
adaptive quadrature changes its subdivision between two adjacent W.
The Mermin DIIMFP (super::mermin, integration tolerance 1e-5, the
same as the default density tolerance) has them: the Si row at
T = 1000 eV of the committed Yang et al. (2019) ELF (model Fermi energy
13.46 eV) has 18, of 0.4 to 7 times 1e-5 of the local density, between
W = 20 and 976 eV. A jump is not a shape the density can resolve: as
for the model switch at an unresolved cell, the split test of a panel
across it does not fall with the width, so the panel is bisected until
its midpoint rounds onto one of its ends. Such a panel (one ulp of
ln W wide) is not split: its midpoint is not a new node, and splitting
it would only add zero-width panels, up to MAX_NODES of them. This is
this project’s choice of floor, with no tuned width: it is the point
where bisection can no longer place a node. A jump bisected to it costs
about 50 nodes (the levels from the starting panel to one ulp), and
leaves nothing of the density unresolved: the panel holding it has no
interior. Before #345 the zero-width panels were added until the row
reached MAX_NODES; they carry no integral (h = 0), so a row whose
other panels had converged is stored the same, with fewer evaluations
(the Si row above: 20 099 nodes before, 5 792 now).
Node bound. A row whose refinement still has a failing panel when it
reaches MAX_NODES nodes is refused,
InelasticTableError::DensityNodeLimit, rather than stored with that
panel unresolved (the convention of the rate refinement’s row bound,
below).
Probability grid. The stored inverse CDF is interpolated linearly in
u (CrossSectionTable::inverse_cdf). Its first moment, which is what
sets the stopping power S = λ⁻¹ ⟨W⟩ with ⟨W⟩ = ∫_0^1 W(u) du, is
dominated by the upper tail of W, so the starting grid
(logistic_probability_grid) is uniform in t = ln(u/(1-u)), and it is
refined adaptively, shared by all rows: a segment is split at its
logistic midpoint when δ Δu > tol μ max(Δu, 1e-4) at any energy, with δ
the difference between the exact quantile there and the linear
interpolation and μ the row’s mean loss (the criterion of the elastic
tables, crate::electron::elastic::table). The test
stopping_power_from_the_loss_cdf_matches_the_model holds
stopping_power_ev_per_m to 1e-3 of S(E) of #18 at every grid
energy.
Rows where λ⁻¹ = 0 (no loss allowed) are stored empty, and so are rows
with T at or below the lowest loss of the model’s rate (module docs,
“Energy axis”). The table covers
the direct single-pole model only: no exchange and no surface or band-gap
structure (see super::penn). Inner shells get one table each, beside
the valence table, from build_shell_channel_tables. A model with the
exchange correction enabled (SinglePolePenn::with_exchange) is
rejected by build_inelastic_table and MomentumTransferSampler.
§Energy axis
The models take T, the kinetic energy above the model’s Fermi level,
with kinematics on T' = T + E_F and losses up to T (S2017 eqs.
(2)-(3), super::penn). By default (EnergyAxis::ModelFermiLevel)
a table is stored on that axis: the row at E is the model’s at T = E.
EnergyAxis::BandBottom stores it on the band-bottom axis instead: the
row at E is the model’s at T = E - E_F, so its kinematics are on
T' = E and its losses stop at E - E_F; rows with T <= 0 are stored
empty. With E_F the band’s, this is the convention of the table
compiler of Nebula, compile_full_imfp_icdf in
cstool/dielectric_function/compile.py (Nebula-simulator/cstool commit
0c739eb3fcc3fe5297e74c601ac4a9546db596cf, BSD-3-Clause): it evaluates
every row at the electron’s kinetic energy K (the momenta of the
kinematic limits are those of K and K - ω) and keeps only the losses
ω < K - F (its “Fermi correction”), F the energy its caller
compile_full_penn (apps/cstool.py, same commit) passes, the band’s
get_min_excitation() (cstool/input_data/band_structure.py): the Fermi
energy of a metal, the conduction-band bottom W_v + E_g of an
insulator or semiconductor. The model’s own E_F is the F of the axis,
so there is one Fermi energy, not two. The table builder knows no band;
the caller sets the model’s Fermi energy (lindhard run does so per
material, docs/cli.md).
Rows below the ELF table. The sampled loss window of a row is
(W_min, T], W_min the lowest loss the model’s rate λ⁻¹(T) counts,
so that the stored rate and the sampled losses cover the same window:
- single-pole Penn:
W_min = elf_min, the lowest tabulated ELF energy. Its DIIMFP is zero below it (super::penn), so its rate counts no loss there; - full Penn:
W_min = 1e-6 T(super::full_penn, itsLOWEST_LOSS_FRACTION). The DIIMFP of the full model is not zero belowelf_min: each Lindhard gas of the expansion has an electron-hole continuum down toω -> 0atq > 0(S2017 eq. (4)), and the model’s rate and stopping power integrate every loss from 0. A window opened atelf_min(until #361) left the losses below it out of the sampled distribution while the stored rate counted them: on the Si ELF tabulated from 0.5 eV the share ofλ⁻¹below 0.5 eV is 4.7e-3 atT= 10 eV, and the table’sS = λ⁻¹ ⟨W⟩was biased high by the same amount. The lower limit is a numerical floor, not a model cutoff (a literal zero cannot bound theln Waxis):p ∝ ωat smallω, so the rate belowWfalls asW²and the stopping power asW³, and the omitted share of both is below the model tolerance by orders of magnitude (tests/penn_full.rsbounds it against the direct DIIMFP). The row’s rate is the model’s own; the sampled distribution and⟨W⟩cover(1e-6 T, T], which the model’s0 < ω <= Tequals up to that omitted share. The DIIMFP grid reaches down to the floor of the lowest row energy, so these losses are read from it, not computed directly; - Mermin-ELF:
W_min = 1e-8 T, the lower limit of theωintegral of its rate and stopping power (super::mermin, the same constant). Its fitted Drude-Lorentz ELF has no cutoff atelf_min(the omission of the threshold step is a documented choice of that model), so the DIIMFP belowelf_minis the fit’s extension below the tabulated range and is part of both the rate and the sampled loss distribution (#342). Before #342 the window opened atelf_minfor every model, and a Mermin row drew the losses its rate counts belowelf_minas losses above it: for the Si ELF tabulated from 0.5 eV (default fit, model Fermi energy 13.46 eV) the window(0.5 eV, T]held 28 % of the rate atT= 0.6 eV, 72 % at 1 eV, 92 % at 2 eV and 98.4 % at 5 eV.
A row with 0 < T <= W_min has an empty window, so no loss can be drawn
there, and it is stored empty (inverse mean free path 0, no quantiles),
as a row with T <= 0 is, on either axis (#340): without this the ln W
window [ln W_min, ln T] is reversed (or of zero width at T = W_min)
and the density integrates to a negative value (or 0). For the Penn
single-pole model these are the rows with 0 < T <= elf_min. A Mermin
or full Penn row has W_min < T at every T > 0, so it is never empty
for this reason: its rows with 0 < T <= elf_min, stored empty from #340
to #342 (Mermin) or #361 (full Penn), carry the model’s rate and losses
in (W_min, T].
§Rate refinement
On the band-bottom axis the rate λ⁻¹(E) is zero at E_F and climbs
by two orders of magnitude within about 20 eV above it, and the transport
interpolates it linearly between rows (crate::electron::transport,
“Free flight”). A log grid fine enough there is far finer than needed
everywhere else (#291, #339). InelasticTableOptions::rate_refinement
(RateRefinement, off by default) adds rows to this one table instead:
every cell of the grid that overlaps E - E_F in
RateRefinement::window_ev (RATE_WINDOW_EV by default) is bisected
at its midpoint in E while the linear interpolation of the exact row
rate between the cell’s ends differs from the exact rate at any of the
cell’s test points by more than RateRefinement::tolerance
(RATE_TOLERANCE) of it. The test points are the cell’s quartiles and
midpoint (rate_test_points) and, where those pass, the estimated
position of the largest error: the vertex of the parabola through the
largest of the sampled errors and its two neighbours (rate_error_peak).
Children that no longer overlap the window are not tested, so the
bisection never approaches the zero of the rate at E_F, and cells
outside the window are never split. Only the scalar rate is evaluated
while refining (the row rule of the table: zero for an empty row); each
level’s test points are evaluated in parallel and merged in sorted order,
so the grid is the same on any thread count. The rows are then built as
any other, from the merged grid, which contains the given grid. A
refinement that would add more than RateRefinement::max_rows rows is
an error naming the cell that still fails (a bound against a rate the
bisection cannot resolve, not a tuned value), and so is a cell still to
test that can no longer be bisected because its midpoint rounds to an
end. A step in the rate splits one cell per level, so it reaches
rounding (about 50 levels) before any useful row bound: that second
error, not the row bound, is what stops it.
Rate onset (#357). The single-pole rate is
identically zero for E - E_F <= elf_min (“Rows below the ELF table”,
W_min = elf_min) and positive above it: a step at the onset
E_F + elf_min, which the transport’s linear interpolation from the
zero row below it cannot follow at any row density. Where the onset lies
at or above the window start (an ELF tabulated from 2 eV or more, at the
default window), a cell is tested only if it overlaps the window and its
lower end is at or above the onset: the cell straddling the onset and
the cells below it are left as given, the interpolation across the
straddling cell being the model’s own step. Testing them would bisect
towards the step until its cell is exhausted to rounding, the error
above, and so refuse an ELF that builds without the option. Where the
onset lies below the window start nothing changes: the bisection’s
children that leave the window are not tested, so it never reaches the
step, as it never reaches the zero at E_F. Every committed ELF starts
at or below 0.5 eV (validation/data/optical/), so for none of them
does the onset change the refined grid. The Mermin rate is positive for
every T > 0 (it counts losses down to 1e-8 T), and so is the full
Penn rate (down to 1e-6 T, #361: it was grouped with the single-pole
onset before), so those models have no onset and keep the full window.
The test is a criterion, not a bound. A test of the midpoint alone
(#351) left cells whose error peaked off the midpoint above the
tolerance, and so did one of the quartiles and midpoint (the Al single
pole from 5 eV at 10 per decade, 1.0008 % at 6.1 eV, between the first
quartile and the midpoint of its cell); the parabolic vertex locates such
a peak without a tuned number of samples. The acceptance check is the dense
evaluation of the rate mode of
lindhard-cli/examples/inelastic_low_energy.rs (every 0.1 eV over the
window, docs/validation.md). The table’s other grid (the elastic table
of a run, or a table without the option) is unchanged: the transport
reads each table on its own CrossSectionTable::energy_ev.
§Momentum-transfer sampler
Given (E, W), the DIIMFP integrand in q is, in u = ln q
(S2017 eq. (2), as in super::penn), Im[-1/ε(q, W)] on
q- <= q <= q+, q± = k ± k', k = sqrt(2T'), k' = sqrt(2(T' - W))
(Hartree units, T' = E + E_F), cut to the q where the plasma frequency
of the pole lies in the tabulated ELF range. MomentumTransferSampler is
a direct sampler built per (E, W): the integrand is sampled on
adaptive nodes (breaks at the kinematic limits and at every ELF knot, 8
panels per smooth segment to start, the same refinement rule as above with
the zeroth moment only and tolerance DEFAULT_Q_TOLERANCE), linear in
ln q between nodes, and inverted exactly. The density used is therefore
the model’s to within that tolerance, and the histogram test compares it
with an independent quadrature of SinglePolePenn::loss_function at 1e6
samples. It is not a table: building one costs the integrand evaluations of
one DIIMFP slice, so a transport loop should keep the sampler for repeated
draws at one (E, W) and expect that cost for each new pair. A tabulated
(E, W, u) form is left for later work.
The deflection angle follows from momentum conservation of the primary,
q² = k² + k'² - 2 k k' cos θ, i.e.
cos θ = (k² + k'² - q²)/(2 k k') (MomentumTransferSampler::cos_theta),
clamped to [-1, 1]; q = q- is θ = 0 and q = q+ is θ = π. This is
the elementary kinematics of the loss W (the final wavenumber k' fixed
by energy conservation) with the model’s own q±.
§Determinism
Rows (and the profiles of a full-Penn DiimfpGrid) are built in
parallel with rayon and collected in grid order; each
row, and the probability refinement (a parallel map over rows followed by
a serial split decision), is a fixed sequence of floating-point
operations, so tables are bit-identical on any thread count (tested).
Samplers consume uniforms u supplied by the caller (for example from
crate::rng::stream) and keep no state of their own.
Structs§
- Inelastic
Table Options - Grids and tolerances of a table build.
- Momentum
Transfer Sampler - Samples the momentum transfer
qof an inelastic collision with incident kinetic energyEand lossW(module docs). Built once per(E, W). - Rate
Refinement - Extra rows that resolve the inelastic rate
λ⁻¹(E)near the Fermi level on the band-bottom axis (module docs, “Rate refinement”; #339). - Shell
Resolved Tables - The tables of a
ShellResolvedChannels: the valence channel’s and one per inner shell, in the order ofShellResolvedChannels::inner_shells(build_shell_channel_tables).
Enums§
- Energy
Axis - The energy axis a table is stored on (module docs, “Energy axis”).
- Inelastic
Table Error - Errors from the table builder and the samplers.
Constants§
- DEFAULT_
DENSITY_ TOLERANCE - Default tolerance of the loss-density model (see the module docs).
- DEFAULT_
LOGISTIC_ STEP - Default logistic step of the starting probability grid.
- DEFAULT_
PROBABILITY_ TAIL - Default probability tail of the starting grid (see
logistic_probability_grid). - DEFAULT_
Q_ TOLERANCE - Default tolerance of the momentum-transfer density model.
- DEFAULT_
RATE_ REFINEMENT_ MAX_ ROWS - The default bound on the rows the rate refinement may add (a safety
bound against a rate that fails the test in many cells at once;
not a tuned value): 1000 rows, so cells of 18 meV on average across the
18 eV of
RATE_WINDOW_EV, far below the 0.1 eV step of therategate. A step in the rate splits one cell per level and is stopped earlier, when its cell is exhausted to rounding (module docs, “Rate refinement”). - DEFAULT_
REFINE_ TOLERANCE - Default target of the probability-grid refinement, relative to the mean loss of each row.
- INITIAL_
LOSS_ PANELS - Number of equal panels (in
ln W) the loss density starts from. - MAX_
NODES - Upper bound on the number of nodes of one density model and on the size of the refined probability grid.
- RATE_
TOLERANCE - The tolerance of the rate refinement (#339) and of the
rategate oflindhard-cli/examples/inelastic_low_energy.rs(#291), fixed there before any value was measured: the largest relative error of the interpolated1/λoverRATE_WINDOW_EVabove the Fermi level must not exceed 1 %. It is the statistical error of the most precise committeddefaultδ entries, rounded up: invalidation/experiments/se_yield_results.jsonthe Poisson floor of Aldefaultis 0.0585 on δ = 6.85 at 600 eV (0.85 %) and 0.0571 on δ = 6.52 at 400 eV (0.88 %). Other configurations are more precise: Albarrier-offreaches 0.60 % (600 eV), and at 400 eV the Albarrier-off,cutoff-band-bottom,phi-lowandphi-highentries are 0.63 to 0.90 %; the Au, Cu and Sidefaultentries at 400 eV are 1.4, 1.7 and 2.8 %. A grid error below the tolerance cannot be told from the noise of those tables if δ responds at most in proportion to the rate in that window; that response is not measured here. - RATE_
WINDOW_ EV - The window of the rate refinement and of the
rategate (#291, #339):E - E_Ffrom 2 to 20 eV. The upper end covers the steep rise of the rate above the Fermi level, which for the Al single pole peaks in error at 15 to 17 eV. The lower end lies below the transport cutoff of everydefaultδ run ofvalidation/experiments/se_yield.py(1 eV above the vacuum level, at least 5 eV aboveE_F); thecutoff-band-bottomruns read the rate down to 1 eV aboveE_F. Below 2 eV the rate starts from zero atE_F, and a relative tolerance there would bisect towards that node without end.
Functions§
- build_
inelastic_ table - Build the inelastic energy-loss table of
pennon the grid ofoptions. - build_
inelastic_ table_ for_ model - Build the inelastic energy-loss table of any of the three ELF-extension
models (
PennInelastic: single-pole Penn, full Penn or Mermin-ELF) on the grid ofoptions, by the same procedure asbuild_inelastic_table(module docs), with that model’s DIIMFP and inverse IMFP in place of the single pole’s. ForPennInelastic::SinglePolethe rows are those ofbuild_inelastic_table; only themodelandprovenancestrings differ, and they carryPennInelastic::model_identity, so a table names the algorithm it was built with. - build_
shell_ channel_ tables - Build the energy-loss tables of every channel of
channelson the grid ofoptions: the valence table, and for inner shellithe table ofShellResolvedChannels::shell_model(that shell’s own optical ELF with the valence model’s settings), wrapped with the shell’s identity, binding energy andbinding_provenance(the origin of the binding energies, for examplecrate::electron::data::SubshellBindingTable::provenance). - default_
probability_ grid - The default starting probability grid:
logistic_probability_gridwithDEFAULT_PROBABILITY_TAILandDEFAULT_LOGISTIC_STEP. - loss_
density_ node_ count - The number of nodes of the loss-density model of the row at
energy_ev(eV, on the model’s own axis) of a table ofmodelbuilt with density tolerancedensity_tolerance, reading the DIIMFP fromgridwhere it covers the loss as a table build does (pass the grid ofFullPenn::diimfp_gridfor the table’s energies, orNone). The count is bounded byMAX_NODES(a few more: the last refinement pass splits every failing panel); a row that reaches it is refused by the table build (InelasticTableError::DensityNodeLimit). Each node not covered by the grid costs one direct DIIMFP evaluation. - mean_
loss_ ev ∫_0^1 W(u) duby the trapezoid rule of the table’s linear interpolation.- stopping_
power_ ev_ per_ m - The stopping power
λ⁻¹ ⟨W⟩(eV/m) of rowiof an energy-loss table, with⟨W⟩frommean_loss_ev; zero for a zero-rate row.Noneifiis out of range.