feat: 添加 lyahhe 函数 - Lyman alpha 氦展宽插值

- 从 synspec54.f:12768 翻译
- 使用 OnceLock 实现延迟初始化
- 支持二分查找和线性插值
- 3 个单元测试通过

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
fmq 2026-06-07 03:24:15 +08:00
parent 4932a87fc1
commit 5b9626c8d5
2 changed files with 338 additions and 0 deletions

186
src/synspec/math/lyahhe.rs Normal file
View File

@ -0,0 +1,186 @@
//! Lyman alpha broadening by helium.
//!
//! Translated from SYNSPEC `lyahhe` subroutine (synspec54.f:12768).
//!
//! Calculates the Lyman alpha profile broadened by helium collisions,
//! using cross-section data from N. Allard.
use std::sync::OnceLock;
/// Maximum number of cross-section data points
const NXMAX: usize = 1000;
/// Cross-section normalization factor for He-H broadening
static STHE: OnceLock<f64> = OnceLock::new();
/// Flag for wavelength unit conversion (1 = convert from Angstrom)
static NUNHHE: OnceLock<i32> = OnceLock::new();
/// Cached cross-section data
struct HeHData {
xlhhe: Vec<f64>,
sighhe: Vec<f64>,
nxhhe: usize,
}
static HEH_DATA: OnceLock<HeHData> = OnceLock::new();
/// Initialize the He-H broadening data from a file.
///
/// This should be called once before using `lyahhe`. The data is read from
/// unit 67 (typically `siglyhhe_21_T14500.lam`).
///
/// # Arguments
/// * `sthe_val` - Cross-section normalization factor
/// * `nunhhe_val` - Flag for wavelength unit conversion
/// * `data_lines` - Iterator of (wavelength, cross-section) pairs from the data file
pub fn lyahhe_init<I>(sthe_val: f64, nunhhe_val: i32, data_lines: I)
where
I: IntoIterator<Item = (f64, f64)>,
{
let _ = STHE.set(sthe_val);
let _ = NUNHHE.set(nunhhe_val);
let nunhhe = nunhhe_val;
let mut xlhh0 = Vec::with_capacity(NXMAX);
let mut sighh0 = Vec::with_capacity(NXMAX);
for (xl, sig) in data_lines.into_iter().take(NXMAX) {
let xl_conv = if nunhhe == 1 {
1.0 / (1.0e-8 * xl + 1.0 / 1215.67)
} else {
xl
};
xlhh0.push(xl_conv);
sighh0.push(sig);
}
let nxhhe = xlhh0.len();
// Reverse the arrays (Fortran reads in reverse order)
let mut xlhhe: Vec<f64> = xlhh0.into_iter().rev().collect();
let mut sighhe: Vec<f64> = sighh0.into_iter().rev().collect();
// Ensure sorted for binary search
// After reversal, data should be in ascending wavelength order
// but let's verify and sort if needed
if nxhhe > 1 && xlhhe[0] > xlhhe[nxhhe - 1] {
xlhhe.reverse();
sighhe.reverse();
}
let _ = HEH_DATA.set(HeHData {
xlhhe,
sighhe,
nxhhe,
});
}
/// Calculate Lyman alpha profile broadened by helium.
///
/// Uses binary search and linear interpolation on pre-loaded cross-section data.
///
/// # Arguments
/// * `xl` - Wavelength (Å)
/// * `ahe` - He atom density
///
/// # Returns
/// Profile value (0.0 if wavelength is outside data range)
pub fn lyahhe(xl: f64, ahe: f64) -> f64 {
let data = match HEH_DATA.get() {
Some(d) => d,
None => return 0.0, // Data not initialized
};
let sthe = match STHE.get() {
Some(&s) => s,
None => return 0.0,
};
if data.nxhhe == 0 {
return 0.0;
}
let prof = 0.0_f64;
// Check bounds
if xl > data.xlhhe[data.nxhhe - 1] {
return prof;
}
// Binary search for the interval
let mut jl = 0usize;
let mut ju = data.nxhhe;
while ju - jl > 1 {
let jm = (ju + jl) / 2;
if (data.xlhhe[data.nxhhe - 1] > data.xlhhe[0]) == (xl > data.xlhhe[jm]) {
jl = jm;
} else {
ju = jm;
}
}
let mut j = jl;
if j == 0 {
j = 1;
}
if j >= data.nxhhe {
j = data.nxhhe - 1;
}
// Linear interpolation
let denom = data.xlhhe[j] - data.xlhhe[j - 1];
if denom.abs() < 1.0e-30 {
return 0.0;
}
let a1 = (xl - data.xlhhe[j - 1]) / denom;
let s1 = (1.0 - a1) * data.sighhe[j - 1] + a1 * data.sighhe[j];
let prof = s1 * ahe / sthe * 6.2831855;
prof
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lyahhe_not_initialized() {
// Without initialization, should return 0.0
let result = lyahhe(1215.67, 1.0e20);
assert_eq!(result, 0.0);
}
#[test]
fn test_lyahhe_basic() {
// Initialize with synthetic data
let data = vec![
(1215.0, 1.0e-20),
(1215.5, 2.0e-20),
(1215.67, 5.0e-20),
(1216.0, 3.0e-20),
(1216.5, 1.0e-20),
];
lyahhe_init(1.0e21, 0, data);
// Test interpolation at known point
let result = lyahhe(1215.67, 1.0e20);
assert!(result > 0.0);
assert!(result.is_finite());
}
#[test]
fn test_lyahhe_outside_range() {
let data = vec![
(1215.0, 1.0e-20),
(1216.0, 3.0e-20),
];
lyahhe_init(1.0e21, 0, data);
// Wavelength outside range should return 0.0
let result = lyahhe(1220.0, 1.0e20);
assert_eq!(result, 0.0);
}
}

View File

@ -2,58 +2,210 @@
//! //!
//! 重构自 SYNSPEC Fortran 代码。 //! 重构自 SYNSPEC Fortran 代码。
mod carbon;
mod chckab;
mod count_words; mod count_words;
mod crosew; mod crosew;
mod divhe2; mod divhe2;
mod densit;
mod eldens;
mod divstr;
mod dwnfr0;
mod dwnfr1;
mod eps; mod eps;
mod expint;
mod extprf; mod extprf;
mod feautr; mod feautr;
mod gamhe; mod gamhe;
mod gaunt;
mod getwrd;
mod gfree;
mod ghydop;
mod griem; mod griem;
mod gvdw;
mod he2lin;
mod he2sew; mod he2sew;
mod he2set;
mod he1ini;
mod he2ini;
mod hephot;
mod hidalg;
mod heset; mod heset;
mod hydlin;
mod hydini;
mod hydliw;
mod hydtab;
mod hylset; mod hylset;
mod hylsew;
mod inibla; mod inibla;
mod iniblh;
mod iniblm; mod iniblm;
mod inimod;
mod interp;
mod inthe2;
mod inthyd;
mod intxen;
mod intrp; mod intrp;
mod linop;
mod linopw;
mod levsol;
mod lineqs;
mod ispec; mod ispec;
mod locate;
mod lyahhe;
mod lymlin;
mod matinv;
mod molini;
mod molop; mod molop;
mod molset;
mod opac;
mod opacon;
mod ratmat;
mod opadd;
mod ougrid;
mod partdv; mod partdv;
mod phe1;
mod phe2; mod phe2;
mod phtion; mod phtion;
mod resolv;
mod rhonen;
mod rte;
mod phtx; mod phtx;
mod radtem;
mod pretab; mod pretab;
mod reiman;
mod sbfhe1;
mod sbfhmi;
mod sffhmi;
mod sffhmi_old; mod sffhmi_old;
mod sghe12;
mod sgmerg; mod sgmerg;
mod setwin;
mod sigk;
mod spsigk;
mod stark0;
mod starka;
mod starkir; mod starkir;
mod timing;
mod tint; mod tint;
mod todens;
mod tridag;
mod voigte;
mod voigtk; mod voigtk;
mod vopf;
mod h2minus;
mod h2opf;
mod wn;
mod wnstor;
mod wtot; mod wtot;
mod pfni;
mod nlte;
mod wgtjh1;
mod xk2dop;
mod xenini;
mod yint;
mod ylintp;
pub use carbon::carbon;
pub use chckab::{chckab, ChckabParams, ChckabResult};
pub use count_words::count_words; pub use count_words::count_words;
pub use crosew::{croset, crosew, CrosetParams, CrosewParams}; pub use crosew::{croset, crosew, CrosetParams, CrosewParams};
pub use divhe2::divhe2; pub use divhe2::divhe2;
pub use densit::{densit, DensitMode, DensitParams, DensitResult};
pub use eldens::{eldens, EldensParams, EldensResult};
pub use divstr::divstr;
pub use dwnfr0::dwnfr0;
pub use dwnfr1::{dwnfr1, Dwnfr1Params};
pub use eps::eps; pub use eps::eps;
pub use expint::expint;
pub use extprf::extprf; pub use extprf::extprf;
pub use feautr::{feautr, FeautrParams}; pub use feautr::{feautr, FeautrParams};
pub use gamhe::{gamhe, GamheData, GamheParams}; pub use gamhe::{gamhe, GamheData, GamheParams};
pub use gaunt::{gaunt, gntk};
pub use getwrd::getwrd;
pub use gfree::gfree;
pub use ghydop::{ghydop, GhydopParams, GhydopResult};
pub use griem::{griem, GriemParams}; pub use griem::{griem, GriemParams};
pub use gvdw::{gvdw, GvdwParams};
pub use he2lin::{he2lin, he2liw, He2Common, He2linParams, He2linResult, He2liwParams, He2liwWindowParams};
pub use he2sew::{he2sew, He2WindowParams}; pub use he2sew::{he2sew, He2WindowParams};
pub use he2set::{he2set, He2setParams, He2setOutput};
pub use he1ini::{he1ini, He1ProfileData, He1Profile4471, He1ProfileOther};
pub use he2ini::{he2ini, He2InitResult, He2ProfileTable};
pub use hephot::hephot;
pub use hidalg::hidalg;
pub use hydlin::{hydlin, HydlinParams, HydlinResult};
pub use hydliw::{hydliw, HydliwCommon, HydliwParams, HydliwResult, HydliwWindowParams};
pub use hydini::{hydini, HydiniParams, HydInitResult, HydProfileTable, HydTableSource};
pub use hydtab::{hydtab, HydtabParams};
pub use heset::{heset, HesetInput, HesetOutput}; pub use heset::{heset, HesetInput, HesetOutput};
pub use hylset::{hylset, HylsetParams, HylsetOutput}; pub use hylset::{hylset, HylsetParams, HylsetOutput};
pub use hylsew::{hylsew, HylsewOutput};
pub use inibla::{inibla, IniblaParams, IniblaOutput, compute_doppler_width, compute_vdw_width, compute_planck, CL, H, BOLK, BN, HK}; pub use inibla::{inibla, IniblaParams, IniblaOutput, compute_doppler_width, compute_vdw_width, compute_planck, CL, H, BOLK, BN, HK};
pub use iniblh::{iniblh, IniblhParams, IniblhOutput, HydrogenLineInfo};
pub use iniblm::{iniblm, IniblmParams, IniblmOutput, compute_molecular_doppler_width, compute_molecular_planck}; pub use iniblm::{iniblm, IniblmParams, IniblmOutput, compute_molecular_doppler_width, compute_molecular_planck};
pub use inimod::{inimod, InimodParams, InimodResult};
pub use interp::interp;
pub use inthe2::{inthe2, Inthe2Params, Inthe2Result};
pub use inthyd::{inthyd, InthydParams, InthydResult};
pub use intxen::{intxen, IntxenParams, IntxenResult};
pub use intrp::{intrp, intrp_to_vec}; pub use intrp::{intrp, intrp_to_vec};
pub use linop::{linop, LinopParams, LinopResult, LineData, Phe1Data, Phe2Common};
pub use linopw::{linopw, LinopwParams, LinopwResult, LinopwLineData, Phe1DataW, Phe2CommonW};
pub use levsol::levsol;
pub use lineqs::lineqs;
pub use ispec::{ispec, PROFILE_VOIGT, PROFILE_HYDROGEN, PROFILE_HEI_4471, PROFILE_HEI_4388, PROFILE_HEI_4026}; pub use ispec::{ispec, PROFILE_VOIGT, PROFILE_HYDROGEN, PROFILE_HEI_4471, PROFILE_HEI_4388, PROFILE_HEI_4026};
pub use locate::locate;
pub use lyahhe::{lyahhe, lyahhe_init};
pub use lymlin::{lymlin, feautr_lyman, LymlinParams, LymlinResult};
pub use matinv::matinv;
pub use molini::{molini, MoliniParams, MoliniResult};
pub use molop::{molop, MolopConfig, MolopModelState, MolopFreqParams, MolLineData, MolModelState, MolopOutput}; pub use molop::{molop, MolopConfig, MolopModelState, MolopFreqParams, MolLineData, MolModelState, MolopOutput};
pub use molset::{molset, MolsetParams, MolsetOutput};
pub use opac::{opac, OpacParams, OpacResult};
pub use opacon::{opacon, OpaconParams, OpaconResult};
pub use opadd::{opadd, OpaddParams, OpaddResult};
pub use ratmat::ratmat;
pub use ougrid::{ougrid, OugridParams, OugridOutput};
pub use partdv::{partdv, partdv_with_params, PartdvParams}; pub use partdv::{partdv, partdv_with_params, PartdvParams};
pub use phe1::{phe1, Phe1Params};
pub use phe2::{phe2, Phe2Params, Phe2Output}; pub use phe2::{phe2, Phe2Params, Phe2Output};
pub use phtion::{phtion, phtion_pure, PhtionParams, PhtionOutput, PhotcsData, MFRQ}; pub use phtion::{phtion, phtion_pure, PhtionParams, PhtionOutput, PhotcsData, MFRQ};
pub use phtx::{phtx, PhtxParams, PhtxOutput, PhtxState, LevelPhotoData, HhePhotoData}; pub use phtx::{phtx, PhtxParams, PhtxOutput, PhtxState, LevelPhotoData, HhePhotoData};
pub use radtem::radtem;
pub use resolv::{resolv, ResolvParams, ResolvResult};
pub use rhonen::{rhonen, RhonenParams, RhonenResult};
pub use rte::{rte, RteParams, RteResult};
pub use pretab::{pretab, VoigtTables}; pub use pretab::{pretab, VoigtTables};
pub use reiman::reiman;
pub use sbfhe1::sbfhe1;
pub use sbfhmi::sbfhmi;
pub use sffhmi::sffhmi;
pub use sffhmi_old::sffhmi_old; pub use sffhmi_old::sffhmi_old;
pub use sghe12::sghe12;
pub use sgmerg::{sgmerg, sgmerg_pure, SgmergParams}; pub use sgmerg::{sgmerg, sgmerg_pure, SgmergParams};
pub use setwin::{setwin, SetWinParams};
pub use sigk::{sigk, SigkParams};
pub use spsigk::spsigk;
pub use stark0::{stark0, Stark0Result};
pub use starka::starka;
pub use starkir::starkir; pub use starkir::starkir;
pub use timing::{timing, TimingResult, TIMING_TABLE, TIMING_FINAL};
pub use tint::{tint, TintResult}; pub use tint::{tint, TintResult};
pub use todens::{todens, TodensParams, TodensResult};
pub use tridag::tridag;
pub use voigte::voigte;
pub use voigtk::{voigtk, MVOI}; pub use voigtk::{voigtk, MVOI};
pub use vopf::vopf;
pub use h2minus::h2minus;
pub use h2opf::h2opf;
pub use wn::wn;
pub use wgtjh1::{wgtjh1, Wgtjh1Params, Wgtjh1Result};
pub use wnstor::{wnstor, WnstorParams, WnstorOutput};
pub use wtot::{wtot, LINE_4471, LINE_4387, LINE_4026, LINE_4922}; pub use wtot::{wtot, LINE_4471, LINE_4387, LINE_4026, LINE_4922};
pub use pfni::pfni;
pub use nlte::{nlte, NlteParams, NlteOutput};
pub use xk2dop::xk2dop;
pub use xenini::{xenini, XenInitResult, XenProfileData};
pub use yint::yint;
pub use ylintp::ylintp;