層構造¶
エンジンは層の積み重ね (スタック) です。L5 より下の層は共有の道具箱で、どの出口も それらから組み立てられます (ただし、どの出口も全部の層を使うわけではありません — 散乱因子は L1 で止まり、弾性の出口は L2 で止まります)。そして、何を報告しているのかを 知っているのは最上層だけです。出口 は作者の用語で、「同じエンジンに何を報告させるか」 を指します — イオン化形状因子 F(s, E₀)、EELS のエッジ、位相シフトなど。チャネル は 1 つの元素と 1 つの副殻の組です (Fe K、あるいは Au L3)。
L5 Exits electron impact | elastic | photon | transport
─────────────────────────────────────────────
L4 Angular 3j and 6j symbols, Legendre recursion, MDFF (mixed dynamic
form factor) assembly
L3 Radial ME ∫ u_a(r) · j_λ(Qr) · u_b(r) dr (multipole integrals)
L2 Continuum distorted waves, energy normalization, asymptotic Coulomb
matching, orthogonalization; non-relativistic (v2),
scalar-relativistic (v3, retired) or κ-resolved Dirac (v4)
solution
L1 Atomic SCF (HFS / DHFS, Xα or KLI exchange), bound states
(Schrödinger / Dirac), neutral and relaxed core-hole potentials
L0 Numerics spherical Bessel, Coulomb functions, splines, quadrature,
ODE integrators
下の 2 つの軸の表 にある演算子の行のうち、まだ空いているのは
双極子 (光子) 演算子だけです。輸送 の列には、阻止能への縮約 (edge) と σ_tr (mott)
がすでに載っています。
層の置き場所¶
src/ionization.jl は薄いローダとコマンドラインで、層のファイルを依存順に include
します。Julia の module はありません — 名前空間はフラットのままなので、
src/ionization.jl を include したものからはすべての名前が見えますし、ファイルを
include 順に連結すれば単一ファイル版が再現できます。
| ファイル | 層 | 内容 |
|---|---|---|
l0_numerics.jl |
L0 | 定数、精度のつまみ、スプライン、Gauss–Legendre、球 Bessel (スカラー版と 8 レーン版)、Coulomb 関数、Numerov |
l0_json.jl |
L0 | 存在しない標準ライブラリの代わりを務める最小限の JSON reader/writer |
l1_atomic.jl |
L1 | HFS の自己無撞着場 (SCF) 計算 — 非相対論版、または κ で分解し小成分を密度に含める完全 Dirac SCF (DHFS) — 交換は局所 Xα か KLI 交換 (OEP に対する exchange-only の KLI 近似。Latter 補正なし)。束縛 Schrödinger 状態と Dirac 状態、緩和 core-hole (内殻空孔を開けて再収束したイオン) のポテンシャル |
l2_continuum.jl |
L2 | ContinuumSet — 歪曲波、エネルギー規格化、Coulomb マッチング、直交化、非相対論解 (v2 の連続状態。今も refcheck / --no-kdirac の基準) とスカラー相対論の選択肢 (v3 の連続状態。v3 の再現と T8 のために残している)。DiracContinuumSet — 両成分を保持する κ 分解の連立動径 Dirac 解 (v4 の既定) |
l3_radial.jl |
L3 | RlTable — 多重極積分とその PCHIP (単調 3 次) 補間。どちらの連続状態からも作れる (Dirac 版は積分の前に \(G_aG_b+F_aF_b\) をまとめるので、同じ struct を返す) |
l4_angular.jl |
L4 | 3j と 6j 記号、Legendre 漸化、MDFF の組み立て、相互作用核 — Coulomb (縦) 項のみ、または横断的 (Møller) 項を足したもの |
l5_channel.jl |
L5 | 出口が共有するものすべて: チャネル表、SCF/Dirac のキャッシュ、prepare_channel (:relaxed / :frozen / :frozen_static の終状態処方を含む)、ε 求積、ε ごとのドライバ、N(K) への縮約、Bote–Salvat の絶対断面積 (Bote et al., 2009 の解析係数表。Bote & Salvat, 2008 の歪曲波計算にフィットしたもの) |
l5_exit_edx.jl |
L5 | F(s, E₀) の出口 — s グリッド上の K で、N(K)/N(0) として報告 |
l5_exit_eels.jl |
L5 | dσ/dΔE の出口 — K = 0 のみ。エッジ形状と阻止能への縮約を報告 |
l5_exit_phase.jl |
L5 | δ_l の出口 — 中性原子の静的場での弾性位相シフト (既定は純静電。--fm で Furness–McCarthy の局所交換を足す。Furness & McCarthy, 1973) |
l5_exit_mott.jl |
L5 | Mott 弾性の出口 (P4) — κ 分解 Dirac 位相シフトからの dσ/dΩ、Sherman 関数、σ_el、σ_tr |
l5_exit_gos.jl |
L5 | GOS の出口 — df/dΔE(Q)、Bethe 面。E₀ はどこにも入らない |
l5_exit_fx.jl |
L5 | 散乱因子の出口 — SCF 密度からの f_x(s)、Mott–Bethe を通した f_e(s)。演算子 が異なる最初の出口なので、L0 と L1 しか使わない |
selftest.jl |
— | selftest の梯子 (T0–T24 と T26–T27。T6b、T11b、T23a–e のような英字付きの下位テストを含む。T25 は欠番) と refcheck (v2 の非相対論処方を Python の参照値に対して再実行する) |
何を報告しているのかを知っているのは l5_exit_*.jl のファイルだけです。2 つ目の出口は、
それらの隣に置くファイルであって、下の層に対する変更ではありません:
l5_exit_eels.jl は L0–L4 に一切触れずに追加されました。
スタックを 1 コマンドで通す
既定のコマンドは 1 チャネル — 鉄、K 殻、200 keV — の形状因子を、出荷テーブルの
物理処方で計算し、既定の s グリッド上の F(s) を印字します (テーブル自体は
gen_production.jl 経由で、より厳しい HIGH_SETTINGS 求積プリセットで生成されて
いるので、印字される値は出荷値に近いものの、出荷値そのものではありません):
julia -t auto src/ionization.jl 26 K 200
このコマンドが触るものを、層ごとに追います。関数名はソースにあるままなので、 各ステップを grep で探せます。
- CLI (
src/ionization.jl、main_)。Z = 26、タグK、E₀ = 200 keV を 解釈します。CLI の既定求積プリセット (PROD_SETTINGS。--highでHIGH_SETTINGS、--quickで粗いもの) を選び、1 行目に model id (DHFS-KS23-DiracB-KDIRAC2C-jsplit-fullrange-sym-v4-DSCF— Dirac SCF を 伴う κ 分解 Dirac 連続状態) を印字して、l5_exit_edx.jlのcompute_channelを呼びます。 - 出口 (
compute_channel、L5)。既定の s グリッド、0 から 4 Å⁻¹ まで 0.25 刻み (17 節点) を取り、エンジンが扱う運動量移行 K = 4πs·a₀ に 換算します (a₀ = 0.529 Å なので、s = 0.5 Å⁻¹ は K ≈ 3.32 a₀⁻¹ です)。 そのあと L5 の共有コードに渡します。 - チャネルの準備 (
prepare_channel、L5 共有)。タグをCHANNELSで 引きます: K = 1s 軌道、κ = −1、占有数 2。しきい値 E_th は同梱の Bote–Salvat 表にある K 端 (鉄では 7083.48 eV) なので、ここでの過電圧は u = 200/7.083 ≈ 28 です。続いて L1 から:- 中性原子の SCF —
ensure_converged/get_neutralがSCFAtom(Dirac SCF、Xα 交換) を組みます。これが最初の遅いステップで、処方と L0/L1 ソースの指紋をキーにしてatom_cache/にディスクキャッシュされる ので、Fe のどのチャネルでも 2 回目の実行では飛ばされます。初回の実行では[SCF/Dirac] neutral Z=26の行として見えます。 - 束縛 1s 状態 — 中性の Kohn–Sham ポテンシャル (
V_bound_callable、 Latter の尾を含む) でのsolve_dirac_boundが E_b、動径グリッドr_b、 大成分u_bを与えます。v4 の経路ではさらに、Dirac の行列要素が必要とする 2 成分の組 (G, F) のためにsolve_dirac_bound_2cを呼びます。どちらも SCF と同じようにキャッシュされます。 - 緩和したイオンの場 — 1s 電子を 1 個抜いたイオンを同じ方法で解き
(
ensure_converged→build_ion。キャッシュされ、get_ionで読み戻し ます。[SCF/Dirac] ion Z=26 hole@(1, 0))、IonPotentialが放出電子の 感じる場を組みます: −Z/r + V_H[ρ_ion] に (2/3)·Slater 交換を足したもので、 1 価イオンの −1/r の尾を持ちます。
- 中性原子の SCF —
- N(K) のドライバ (
compute_NK、L5 共有)。eps_nodesで ε 求積を 敷きます — 3 区間、既定プリセットでは 16 + 40 + 16 = 72 節点 (--quickプリセットでは 8 + 16 + 8 = 32 で、これが はじめに のページで見えるeps 32/32です) — そして入射波数 k_i を作ります。 各 ε 節点は独立なので、スレッドに分配されます (Threads.@threads :greedy、 重いものから順)。各節点はeps_workerを走らせます。 - ε ごとの運動学 (
eps_worker) と準備 (eps_setup)。eps_workerは ビームの運動学から Q の範囲を選び — 出口が決めるのはこれだけです —eps_setupを呼びます。ここで E₀ は効かなくなります:eps_setupが 受け取るのは、その Q の範囲 (とr_tail診断に使う運動学的な Q の上限) と 原子だけです:- L2 —
DiracContinuumSetが、イオンの場の中の放出電子について連立動径 Dirac 方程式を κ ごとに l_max まで解き、Coulomb 漸近形にマッチさせ、 エネルギー規格化し、orthogonalize_dirac!が束縛 (G, F) との重なりを 取り除きます。 - L3 —
RlTableが \(R_{l'\lambda}(Q) = \int [G_aG_b + F_aF_b]\, j_\lambda(Qr)\, dr\) を対数 Q グリッド上で積分し、PCHIP 補間つきで保存します。eps_setupに 戻ると、有意性フィルタが寄与しない部分波を落とし、あとでコンソールに 出る診断 — マッチ残差 (match_resid)、打ち切り診断 (r_tail)、badL— が記録されます。 - L4 —
eps_workerに戻って、AngWSが (k_i, k_f) の角度ワークスペースを 組み、precompute_RaTが Q₊ 側を ε 節点ごとに 1 回評価し、angular_integralが MDFF を組み立てて、対称な Ewald 対 (Q₊, Q₋) についての 二重角度積分を K 節点ごとに行い、(k_f/k_i)·∫dΩ S/(Q₊²Q₋²) を 1 行として 返します。
- L2 —
- 縮約 (
compute_NK)。各行がdNde(ε 節点 × K 節点) を埋め、N = dNde' * weが ε の重みで足し合わせて N(K) にします。 - 報告 (
compute_channel、次いで CLI)。F(s) = N(K)/N(0)、健全性の目安として エンジン自身の σ = 4γ²a₀²N(0)、bote_sigma_nm2からの出荷 σ、そして診断値 — これらをs [1/Å] F(s)の表、2 本の σ の行、診断の行として印字するか、--jsonで JSON に書き出します。
ステップ 3–6 は EELS の出口 (edge。K = 0 だけを求め、縮約するだけでなく dNde
の ε 行を報告します) と共有です。GOS の出口 (gos) はステップ 3 と、ステップ 5 の
L2/L3 の半分を共有します — ビームエネルギー無しの prepare_channel、独自の
ε ループ、ユーザーの Q グリッドでの eps_setup — そして eps_worker にも L4 にも
決して入りません。l5_exit_edx.jl に属するのはステップ 2 とステップ 7 だけです。
変わる 2 つの軸¶
ロードマップにあるどの量も、独立な 2 つのものの選び方で決まります。
演算子 — 始状態と終状態を結ぶもの。
| 演算子 | プローブ | 与えるもの | 状態 |
|---|---|---|---|
| 遮蔽 Coulomb、第一 Born | 高速電子 | イオン化 F(s)、GOS、EELS | 実装済 |
| 静的ポテンシャル (遷移なし) | 弾性電子 | 位相シフト δ_l、Mott DCS | 実装済 — δ_l (phase、スカラー) と、スピンを含む Mott DCS (mott。κ 分解 Dirac 位相シフトから、l5_exit_mott.jl) |
| 電荷密度の Fourier 変換 | X 線 / 弾性電子 | f_x(s)、f_e(s) | 実装済 |
| 双極子 (長さ形式または速度形式) | 光子 | 光イオン化 σ_nl、β_nl、f′f″ | 未実装 |
出口 — 何について積分し、何を報告するか。
| 出口 | 積分する変数 | 報告する形 | 状態 |
|---|---|---|---|
| F(s, E₀) | ε と全立体角 | 規格化された形状、F(0)=1 | 実装済 (既定 — サブコマンド無しの Z tag E0) |
| GOS | なし (Q と ΔE を残す) | df/dΔE(Q, ΔE) | 実装済 (gos) |
| dσ/dΔE | 全角度 | エッジ形状 | 実装済 (edge) |
| d²σ/dΩdΔE | なし | 角度・エネルギー分解 | 未実装 |
| σ(β, Δ) | θ < β と ΔE 窓 | EELS 定量の k 因子 | 未実装 |
| δ_l | — | 部分波ごとの位相シフト | 実装済 (phase。mott はその上に断面積を組む) |
演算子の 4 行のうち 3 行が埋まり、遮蔽 Coulomb の行はそれ自身で 4 つの報告量 —
F(s, E₀)、dσ/dΔE、阻止能への縮約、GOS — を持っています。後から足したものを安く
したのは eps_setup です: 1 つの ε 節点について入射の運動学に依存しないものすべてを
eps_worker から括り出したので、出口は自分の Q の範囲を選ぶだけで済みます。これは
本物の継ぎ目ですが、狭い継ぎ目でもあります — 分けているのは 運動学 と 報告 で
あって、演算子と出口ではありません。L5 は今も L4 のルーチンを名前で呼んでいます。
散乱因子の出口はこの問題を丸ごと迂回します: その演算子は遷移を必要としないので、
L2–L4 を飛び越えて L1 の密度を直接読みます。
すでに計算していて捨てていたもの¶
層を最初に描いたとき、3 つの量が呼び出しグラフの内部に存在していながら、返す前に 捨てられていました。それらを外に出すのは出力の配管の変更であって物理ではなく、 3 つとも今では外に出ています:
diag.dNde— (ε 節点 × K 節点) の行列。その K = 0 の列に 4γ²a₀² を掛けたものが 平行照明の EELS dσ/dε です。ほとんど手間なしにエッジ形状が得られます。edgeサブコマンド (l5_exit_eels.jl) として公開済。- 漸近フィットの係数 — 連続状態ソルバは尾を u ≈ a·F_l + b·G_l に最小二乗
フィットします。弾性位相シフトは δ_l = atan2(b, a) です。
phaseサブコマンド (l5_exit_phase.jl) として公開済。それまでは振幅 √(a²+b²) しか残していません でした。外に出したことで注意点が 2 つ出てきて、どちらもl2_continuum.jlに 記録してあります: Coulomb の参照対には全体の符号の固定が無いので、それに対する δ_l は π を法としてしか定義されません (中性原子に使う Riccati–Bessel の参照に対して なら曖昧さはありません)。また、報告される値は主値なので、|δ| > π となる低い部分波は 折り返します。κ 分解 Dirac 連続状態ができると、同じフィットからスピン込みの δ_κ が 得られ、Mott 断面積がmottサブコマンド (l5_exit_mott.jl) として続きました。 dNdeと並ぶ ε 求積の重み — 1 回の縮約で内殻の阻止能への寄与が得られます。 同じedgeサブコマンドで公開済。エッジ形状の隣に ∫ΔE dσ/dΔE dΔE を報告します。
GOS の出口が構造的に安い理由¶
連続状態ソルバも動径行列要素の表も、入射・終状態の波数を物理として参照して いません — 使うのはメッシュ密度を決めるためだけです。一般化振動子強度は定義により ビームエネルギー E₀ に依存しません。
つまり GOS の表では E₀ の次元が丸ごと消えます: (チャネル, E₀) の組ごとに 1 回ではなく、 チャネルごとに 1 回の実行で済みます。出荷テーブルについて言えば、コストで約 22 倍の 差です。
gos サブコマンドとして実装済。それを可能にした継ぎ目は l5_channel.jl の
eps_setup です: 1 つの ε 節点について入射の運動学に依存しないものすべて —
部分波の上限、マッチング半径、メッシュ密度、連続状態の解、R の表、有意性フィルタ —
を eps_worker から括り出しました。出口がまだ自分で選ぶのは Q の範囲だけです。
F(s) の出口はそれを k_i と k_f から導き、GOS の出口はユーザーが求めた Q グリッドから
導いて、k_i を作ることがそもそもありません。したがって prepare_channel は
このモードではビームエネルギーを受け取りません。
GOS そのものはその上の 1 行です: df/dΔE(Q) = 2ΔE·S(Q)/Q²。ここで S は F(s) の出口が ずっと組み立ててきたのと同じ量です。
最適化に対する再現性の制約¶
痛い目を見て学んだ 2 つの規則です:
- 総和の順序は保証の一部です。タイルを同じスカラーへ添字の昇順に累積する
キャッシュブロッキングはビット同一で、常に許されます。
@simdの縮約、 結合順序の変更、fast-math は許されません — テーブルの全再生成と一緒にしか 導入できず、データセットのマニフェストで宣言しなければなりません。
知っておく価値のある帰結が 1 つあります: 最後の縮約 N = dNde' * we は BLAS の
gemv で、その縮約順序は行列の形に依存します。K 節点を 1 つだけ求めると、
同じ実行で 17 個 (上の既定 s グリッド) を求めたときの値から 1 ULP 離れた N(0) が
得られます — ε ごとの物理 (dNde[:, 1]) はどちらでもビット同一で、その同じ数を
手で足せばどちらの値も再現できます。これは決定論的な形状依存であって、
再現性の規律のページの E8 で論じている負荷依存の
非決定性ではありません。出荷テーブルは常に同じ s グリッドで
生成されるので、影響はありません。
2. 測る、決めつけない。もっともらしい最適化のいくつかは、この負荷では
1.0 倍かその近くと測定されました: 漸化式から逆数を括り出す (レイテンシ律速の
依存連鎖)、--heap-size-hint (ライブセットが小さいので、GC は割り当て速度で
決まる)、BigInt の階乗をルックアップ表に置き換える (もともと割り当ての大きな
割合を占めていなかった)。
大きく出たものもあります: 動径積分のキャッシュブロッキング (そのループで 2.4 倍) と、 スレッド数を減らしてプロセス数を増やすこと (全体の実行時間で 2.26 倍。エネルギー節点に 対するスレッド並列は 8 スレッドよりずっと手前で飽和するため)。
プラットフォームに関する注意¶
Windows では、割り当ての多いマルチスレッド負荷を長時間かけると、Julia がガベージ
コレクション中にクラッシュしたことがあります — 1.12 (gc_mark_objarray、マーク中) と
1.11 (sweep_malloced_memory、スイープ中) で観測され、どちらも
EXCEPTION_ACCESS_VIOLATION で、どちらの場合もプロセスが終了せずに wedged する
(生きたままログだけ凍る) のが見られました。コードには unsafe な操作も、ユーザー
ライブラリへの ccall も、ポインタも無く、スレッド化したループは互いに重ならない添字
にしか書き込まないので、証拠が指しているのは物理側のデータ競合ではなく Julia
ランタイムのガベージコレクタです。
したがって長いバッチ実行には次が要ります: チャネルごとのアトミックな出力 (再開時に
失うのは最大でも 1 つの途中単位)、プロセスの終了ではなくログの mtime の停滞で kill する
監視 (watchdog。tools/lane_watchdog.sh: 15 分の mtime 規則に加えて、opt-in の高速
wedged 検知 — WATCHDOG_FAST_WEDGE=1、既定は off。ログが 180 s 以上停滞し、かつ
CPU が 2 回連続の標本で凍っていれば、約 3 分で動く)、そして割り当て圧を抑えるための
スレッドごとの事前確保ワークスペースです。
関連ページ¶
- 物理 (処方) — 各 L5 出口が実際に計算しているもの
- ロードマップ — L0–L4 の上に計画している出口とその順序
- 再現性の規律 — 上の最適化の制約がなぜ絶対なのか
- 性能 — 「測る、決めつけない」の裏にある測定
参考文献¶
- Bote, D. & Salvat, F. (2008). Calculations of inner-shell ionization by electron impact with the distorted-wave and plane-wave Born approximations. Physical Review A 77, 042701.
- Bote, D., Salvat, F., Jablonski, A. & Powell, C. J. (2009). Cross sections for ionization of K, L and M shells of atoms by impact of electrons and positrons with energies up to 1 GeV: Analytical formulas. Atomic Data and Nuclear Data Tables 95, 871–909. Erratum: 97 (2011), 186.
- Furness, J. B. & McCarthy, I. E. (1973). Semiphenomenological optical model for electron scattering on atoms. Journal of Physics B 6, 2280–2291.