You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Electron table grid: the default 10 points per decade under-resolves the inelastic rate just above the Fermi level on the band-bottom axis #291
Found while working on #241 (inelastic tables of materials with a band built on the band-bottom axis, rows at T = E - E_F; implemented in PR #289). Distinct from #287, which re-runs the committed δ tables: this is about the table grid those runs use.
What is the case
On the band-bottom axis the inelastic rate of a row rises from zero at the Fermi level over the first few eV above it (only losses below E - E_F count, so for Al most of the plasmon weight is excluded there). The shared table grid ([electron.tables] points_per_decade, default 10; min_energy_ev = 5 in validation/experiments/se_yield.py) has points about 3 to 6 eV apart at 15 to 30 eV, and the transport interpolates the rate linearly between them (electron::transport::rate). The secondary yield is sensitive to that interpolation.
Measured (diagnostic, not a gate)
lindhard-cli/examples/inelastic_low_energy.rs, mode clamp (the table lindhard run builds after #241), on the Al 400 eV default input that se_yield.py writes, 200 histories, seed 1, release build of a #241 implementation equivalent to #289 for Al (a metal, so E_F is the same under either choice of insulator offset):
points_per_decade
δ
10 (default)
6.44
20
6.85
40
7.01
80
7.00
The same input with the pre-#241 table (model axis, Fermi energy 0) moves less: 6.89 at 10, 7.13 at 40. So on the new axis the default grid leaves a grid error of about 8 % in δ at this point, larger than the statistical error of the committed δ tables (200 histories here, about ±0.1 to 0.2).
Acceptance criteria
Decide how the inelastic table grid resolves the region just above the Fermi level for a material with a band (for example a denser default, or extra inelastic grid points from E_F upward), and document the choice in docs/cli.md.
A convergence check (δ, or the interpolation error of the rate) showing the default within a stated tolerance.
Sweep (sweep-20261010T000141Z-35105-6f5a1405): parked loom:blocked on #241 (park record appended to the body). Concerns the table grid on the band-bottom axis introduced by #241, which is not on main yet. The prerequisite is in PR #289, which is approved but held by Champion for a human merge (loom:operator). Once it merges and #241 closes, this issue is ready to curate and build.
Cited blocker cleared: #241 just closed, and this issue's loom:blocked cites it. The block now reads as stale:
a cited blocker is no longer open: 241:CLOSED
Re-check whether the block still applies and remove loom:blocked if not. Posted at merge time by loom-daemon notify-cleared-blockers (#9102), rather than waiting for the next check-stale-blocked sweep pass (#8927).
Unblocked by the daemon's deterministic release pass (#10556): every blocker declared in this issue's park record has resolved (#241). Removed loom:blocked. loom:issue was not restored: this issue never carried it before the block, so it re-enters triage/approval rather than the Builder queue.
This issue is being worked by an in-session/loom:sweep run sweep-20261010T004216Z-6564-14875022 on host host-d9142cf3, published at 2026-10-10T09:27:43Z. This comment is a lease record (Issue #6320, Epic #6165) in the identical format loom-daemon's dispatch-time write uses (#6179) — its liveness signal is this comment's own forge-assigned updated_at, never the timestamp in this text. It is renewed by sweep-lease-renew.sh (#6180) for as long as this sweep's own process is alive, and simply ages out afterwards. Readers: the daemon's reclamation gate (#6286) refuses to reclaim this claim while the lease is fresh, dispatch-time ordering (#6287) yields to it, and sweep-lease-fence.sh (#6309) fences this sweep's own push/PR-open against it. See defaults/docs/lease-record.md.
Premise check on current origin/main (read-only; #241/PR #289 is merged).
Mechanism holds. EnergyAxis::BandBottom (lindhard/src/electron/inelastic/table.rs, model_energies_ev, loss_rows) stores the row at E as the model at T = E - E_F. Rows with T <= 0 are empty, and losses stop at E - E_F. So the rate starts at 0 at E_F and climbs steeply over the first few eV, and the transport interpolates it linearly in energy between grid rows (lindhard/src/electron/transport.rs, module docs line ~20). Chords of a convex, rising curve overestimate nothing but under-resolve the knee, so a coarse grid biases the rate low there. That fits the table in the body (δ 6.44 at 10 pts/decade, 7.00 at 80).
Correction to the body: the default is not 10. The library default is DEFAULT_POINTS_PER_DECADE = 20 (docs/cli.md[electron.tables], min_energy_ev 10). The 10 comes from validation/experiments/se_yield.py (min_energy_ev = 5.0, points_per_decade = 10.0, ~line 328). By the body's own numbers, 20 gives 6.85 vs 7.01 at 40, about 2 %, inside the stated ±0.1 to 0.2 noise. The ~8 % error belongs to the validation input, not the library default.
The grid is one log grid shared by the elastic and inelastic tables of every material (log_energy_grid in lindhard/src/electron/elastic/table.rs; built in lindhard/src/input/electron.rs ~line 1300). Any inelastic-only refinement has to respect that sharing and the table-cache grid check, so a code change is the more invasive option.
Bounded deliverable (no full Penn, no transport re-runs of δ(E), minutes of compute).
Add a rate mode to lindhard-cli/examples/inelastic_low_energy.rs. For Al and Si (single-pole and Mermin of the input's ELF only), build the band-bottom inelastic table on a dense reference grid (for example 200 pts/decade over E_F to E_F + 40 eV) and on the grids of 10, 20 and 40 pts/decade. Report the linear-interpolation error of 1/λ against the dense table at E - E_F = 0.5, 1, 2, 3, 5, 10, 20 eV. One table build per grid, no transport.
Decide from the numbers, stating the tolerance first (for example max relative rate error <= 2 % for E - E_F >= 2 eV; the region below that is dominated by one grid cell and is reported, not gated).
If 20 meets it: no engine change. Set se_yield.py to the default grid (or 40) and document in docs/cli.md that a material with a band wants >= 20 pts/decade, naming the measured error. Bandless tables are untouched by construction.
If 20 fails and 40 passes: raise the validation-input grid to 40 and say so in docs/cli.md. Do not change the library default unless the table build time at 40 is shown acceptable (one timing line).
Only if neither passes: propose, in a follow-up issue, extra inelastic rows from E_F upward on the band-bottom axis. Not part of this issue, since it touches the shared-grid and cache contract.
Stop rule. No tuned constants and no per-material grid fits; the grid is chosen from the stated tolerance, not to match an experimental δ. No δ(E) or yield re-runs here (that is #287). No full Penn. Any engine change must leave a bandless material's tables byte-identical and keep results identical across thread counts.
Relation to #287.#287 re-runs the committed δ tables with these tables, so this issue must land first if it changes the validation grid. If it concludes "no change", #287 proceeds unblocked, and the conclusion should say so on #287.
Acceptance criteria
Example mode prints the interpolation error of 1/λ on the band-bottom axis for Al and Si at 10, 20 and 40 pts/decade against a dense reference (single-pole, and Mermin if cheap).
A tolerance is stated and the default (library and se_yield.py) is shown within it, or the validation input is raised until it is.
docs/cli.md states the grid guidance for materials with a band, and corrects "default 10" where it appears.
Test plan. Run the new example mode locally once (release build, single table builds). If se_yield.py changes its grid, update test_se_yield.py for the written input. No engine change expected, so existing table and determinism tests (lindhard/tests/electron_bench_determinism.rs) are unaffected.
Re-parked by the daemon's deterministic release pass (#10556): declared blocker(s) #241 resolved, so their park records were removed from the body. Still blocked by #339, #340; loom:blocked stays.
This issue is being worked by an in-session/loom:sweep run sweep-insession-s34613-FriOct91741382026 on host host-d9142cf3, published at 2026-10-10T10:55:31Z. This comment is a lease record (Issue #6320, Epic #6165) in the identical format loom-daemon's dispatch-time write uses (#6179) — its liveness signal is this comment's own forge-assigned updated_at, never the timestamp in this text. It is renewed by sweep-lease-renew.sh (#6180) for as long as this sweep's own process is alive, and simply ages out afterwards. Readers: the daemon's reclamation gate (#6286) refuses to reclaim this claim while the lease is fresh, dispatch-time ordering (#6287) yields to it, and sweep-lease-fence.sh (#6309) fences this sweep's own push/PR-open against it. See defaults/docs/lease-record.md.
Cited blocker cleared: #340 just closed, and this issue's loom:blocked cites it. The block now reads as stale:
a cited blocker is no longer open: 241:CLOSED, 339:OPEN, 340:CLOSED
Re-check whether the block still applies and remove loom:blocked if not. Posted at merge time by loom-daemon notify-cleared-blockers (#9102), rather than waiting for the next check-stale-blocked sweep pass (#8927).
Re-parked by the daemon's deterministic release pass (#10556): declared blocker(s) #241, #340 resolved, so their park records were removed from the body. Still blocked by #339; loom:blocked stays.
Cited blocker cleared: #339 just closed, and this issue's loom:blocked cites it. The block now reads as stale:
a cited blocker is no longer open: 241:CLOSED, 339:CLOSED, 340:CLOSED
Re-check whether the block still applies and remove loom:blocked if not. Posted at merge time by loom-daemon notify-cleared-blockers (#9102), rather than waiting for the next check-stale-blocked sweep pass (#8927).
Unblocked by the daemon's deterministic release pass (#10556): every blocker declared in this issue's park record has resolved (#241, #339, #340). Removed loom:blocked. Restored loom:issue.
Quality standards: the title still calls 10 points per decade the default, while the earlier Curator correction identifies the library default as 20 and 10 as the validation input.
Curator should reconcile the acceptance criteria with current main and #339/#341, then record a terminal disposition if the work is complete or define the specific remaining hand-off to #287. Keep the curated marker; route to Curator rather than queue duplicate engine work.
Sweep: resolved by PR #341 (diagnosis, Part of #291) + PR #355 (closes #339): band-bottom inelastic tables are now refined in lindhard run until linear interpolation of the rate stays within the 1 % tolerance over E−E_F ∈ [2, 20] eV; every acceptance cell passes at the default grid (10 eV, 20 ppd) and the se_yield.py grid (5 eV, 10 ppd) for Al and Si, single-pole and Mermin (table in docs/validation.md, 'Table grid above the Fermi level'). The Si Mermin refusals (#340) are fixed. The δ(E) re-run on these tables is #287.
Activity
rjwalters commentedon Oct 10, 2026
Sweep (
sweep-20261010T000141Z-35105-6f5a1405): parkedloom:blockedon #241 (park record appended to the body). Concerns the table grid on the band-bottom axis introduced by #241, which is not on main yet. The prerequisite is in PR #289, which is approved but held by Champion for a human merge (loom:operator). Once it merges and #241 closes, this issue is ready to curate and build.rjwalters commentedon Oct 10, 2026
Cited blocker cleared: #241 just closed, and this issue's
loom:blockedcites it. The block now reads as stale:Re-check whether the block still applies and remove
loom:blockedif not. Posted at merge time byloom-daemon notify-cleared-blockers(#9102), rather than waiting for the nextcheck-stale-blockedsweep pass (#8927).loom-fleet-dispatch commentedon Oct 10, 2026
Unblocked by the daemon's deterministic release pass (#10556): every blocker declared in this issue's park record has resolved (#241). Removed
loom:blocked.loom:issuewas not restored: this issue never carried it before the block, so it re-enters triage/approval rather than the Builder queue.loom dashboard
rjwalters commentedon Oct 10, 2026
Sweep (
sweep-20261010T004216Z-6564-14875022): blocker #241 closed (PR #289 merged). Removedloom:blocked; back toloom:triagefor curation.rjwalters commentedon Oct 10, 2026
This issue is being worked by an in-session
/loom:sweeprunsweep-20261010T004216Z-6564-14875022on hosthost-d9142cf3, published at 2026-10-10T09:27:43Z. This comment is a lease record (Issue #6320, Epic #6165) in the identical formatloom-daemon's dispatch-time write uses (#6179) — its liveness signal is this comment's own forge-assignedupdated_at, never the timestamp in this text. It is renewed bysweep-lease-renew.sh(#6180) for as long as this sweep's own process is alive, and simply ages out afterwards. Readers: the daemon's reclamation gate (#6286) refuses to reclaim this claim while the lease is fresh, dispatch-time ordering (#6287) yields to it, andsweep-lease-fence.sh(#6309) fences this sweep's own push/PR-open against it. Seedefaults/docs/lease-record.md.rjwalters commentedon Oct 10, 2026
Curator Enhancement (2026-10-10)
Premise check on current
origin/main(read-only; #241/PR #289 is merged).EnergyAxis::BandBottom(lindhard/src/electron/inelastic/table.rs,model_energies_ev,loss_rows) stores the row atEas the model atT = E - E_F. Rows withT <= 0are empty, and losses stop atE - E_F. So the rate starts at 0 atE_Fand climbs steeply over the first few eV, and the transport interpolates it linearly in energy between grid rows (lindhard/src/electron/transport.rs, module docs line ~20). Chords of a convex, rising curve overestimate nothing but under-resolve the knee, so a coarse grid biases the rate low there. That fits the table in the body (δ 6.44 at 10 pts/decade, 7.00 at 80).DEFAULT_POINTS_PER_DECADE= 20 (docs/cli.md[electron.tables],min_energy_ev10). The 10 comes fromvalidation/experiments/se_yield.py(min_energy_ev = 5.0,points_per_decade = 10.0, ~line 328). By the body's own numbers, 20 gives 6.85 vs 7.01 at 40, about 2 %, inside the stated ±0.1 to 0.2 noise. The ~8 % error belongs to the validation input, not the library default.log_energy_gridinlindhard/src/electron/elastic/table.rs; built inlindhard/src/input/electron.rs~line 1300). Any inelastic-only refinement has to respect that sharing and the table-cache grid check, so a code change is the more invasive option.Bounded deliverable (no full Penn, no transport re-runs of δ(E), minutes of compute).
ratemode tolindhard-cli/examples/inelastic_low_energy.rs. For Al and Si (single-pole and Mermin of the input's ELF only), build the band-bottom inelastic table on a dense reference grid (for example 200 pts/decade overE_FtoE_F + 40 eV) and on the grids of 10, 20 and 40 pts/decade. Report the linear-interpolation error of1/λagainst the dense table atE - E_F= 0.5, 1, 2, 3, 5, 10, 20 eV. One table build per grid, no transport.E - E_F>= 2 eV; the region below that is dominated by one grid cell and is reported, not gated).se_yield.pyto the default grid (or 40) and document indocs/cli.mdthat a material with a band wants >= 20 pts/decade, naming the measured error. Bandless tables are untouched by construction.docs/cli.md. Do not change the library default unless the table build time at 40 is shown acceptable (one timing line).E_Fupward on the band-bottom axis. Not part of this issue, since it touches the shared-grid and cache contract.docs/validation.mdnext to the Re-run the secondary-yield tables and the electron oracle summaries with the band-bottom inelastic tables of #241 #287 note (~line 1947).Stop rule. No tuned constants and no per-material grid fits; the grid is chosen from the stated tolerance, not to match an experimental δ. No δ(E) or yield re-runs here (that is #287). No full Penn. Any engine change must leave a bandless material's tables byte-identical and keep results identical across thread counts.
Relation to #287. #287 re-runs the committed δ tables with these tables, so this issue must land first if it changes the validation grid. If it concludes "no change", #287 proceeds unblocked, and the conclusion should say so on #287.
Acceptance criteria
1/λon the band-bottom axis for Al and Si at 10, 20 and 40 pts/decade against a dense reference (single-pole, and Mermin if cheap).se_yield.py) is shown within it, or the validation input is raised until it is.docs/cli.mdstates the grid guidance for materials with a band, and corrects "default 10" where it appears.docs/validation.mdrecords the numbers and the hand-off to Re-run the secondary-yield tables and the electron oracle summaries with the band-bottom inelastic tables of #241 #287.Affected Files
lindhard-cli/examples/inelastic_low_energy.rsvalidation/experiments/se_yield.pyvalidation/experiments/test_se_yield.pydocs/cli.mddocs/validation.mdTest plan. Run the new example mode locally once (release build, single table builds). If
se_yield.pychanges its grid, updatetest_se_yield.pyfor the written input. No engine change expected, so existing table and determinism tests (lindhard/tests/electron_bench_determinism.rs) are unaffected.9 remaining items
loom-fleet-dispatch commentedon Oct 10, 2026
Re-parked by the daemon's deterministic release pass (#10556): declared blocker(s) #241 resolved, so their park records were removed from the body. Still blocked by #339, #340;
loom:blockedstays.loom dashboard
rjwalters commentedon Oct 10, 2026
This issue is being worked by an in-session
/loom:sweeprunsweep-insession-s34613-FriOct91741382026on hosthost-d9142cf3, published at 2026-10-10T10:55:31Z. This comment is a lease record (Issue #6320, Epic #6165) in the identical formatloom-daemon's dispatch-time write uses (#6179) — its liveness signal is this comment's own forge-assignedupdated_at, never the timestamp in this text. It is renewed bysweep-lease-renew.sh(#6180) for as long as this sweep's own process is alive, and simply ages out afterwards. Readers: the daemon's reclamation gate (#6286) refuses to reclaim this claim while the lease is fresh, dispatch-time ordering (#6287) yields to it, andsweep-lease-fence.sh(#6309) fences this sweep's own push/PR-open against it. Seedefaults/docs/lease-record.md.rjwalters commentedon Oct 10, 2026
Cited blocker cleared: #340 just closed, and this issue's
loom:blockedcites it. The block now reads as stale:Re-check whether the block still applies and remove
loom:blockedif not. Posted at merge time byloom-daemon notify-cleared-blockers(#9102), rather than waiting for the nextcheck-stale-blockedsweep pass (#8927).loom-fleet-dispatch commentedon Oct 10, 2026
Re-parked by the daemon's deterministic release pass (#10556): declared blocker(s) #241, #340 resolved, so their park records were removed from the body. Still blocked by #339;
loom:blockedstays.loom dashboard
rjwalters commentedon Oct 10, 2026
Cited blocker cleared: #339 just closed, and this issue's
loom:blockedcites it. The block now reads as stale:Re-check whether the block still applies and remove
loom:blockedif not. Posted at merge time byloom-daemon notify-cleared-blockers(#9102), rather than waiting for the nextcheck-stale-blockedsweep pass (#8927).loom-fleet-dispatch commentedon Oct 10, 2026
Unblocked by the daemon's deterministic release pass (#10556): every blocker declared in this issue's park record has resolved (#241, #339, #340). Removed
loom:blocked. Restoredloom:issue.loom dashboard
rjwalters commentedon Oct 10, 2026
Sweep: blocker #339 closed (PR #355 merged — band-bottom rate refinement now on in
lindhard run). Removedloom:blocked; back to triage.loom-fleet-dispatch commentedon Oct 10, 2026
Champion Review: NEEDS REVISION
Curator should reconcile the acceptance criteria with current main and #339/#341, then record a terminal disposition if the work is complete or define the specific remaining hand-off to #287. Keep the curated marker; route to Curator rather than queue duplicate engine work.
Automated by Champion role
loom dashboard
rjwalters commentedon Oct 10, 2026
Sweep: resolved by PR #341 (diagnosis, Part of #291) + PR #355 (closes #339): band-bottom inelastic tables are now refined in
lindhard rununtil linear interpolation of the rate stays within the 1 % tolerance over E−E_F ∈ [2, 20] eV; every acceptance cell passes at the default grid (10 eV, 20 ppd) and these_yield.pygrid (5 eV, 10 ppd) for Al and Si, single-pole and Mermin (table indocs/validation.md, 'Table grid above the Fermi level'). The Si Mermin refusals (#340) are fixed. The δ(E) re-run on these tables is #287.