Phase 1 翻译 (完成): - TLUSTY 350 函数 100% 翻译 - SYNSPEC 168 函数 100% 翻译 - ~495 Rust 模块 Phase 2 集成 (完成): - TLUSTY RESOLV 7 个 TODO 全部清除 - TLUSTY Runner IJALI 频率选择实现 - OPFRAC ioniz.dat 解析完整实现 - SYNSPEC Runner 编排流程连接完成 - SYNSPEC RESOLV OPAC→RTE→OUTPRI 调用链完整 Phase 3 验证 (完成, 修复 8 处 bug): - INITIA: compute_hydrogen_level_bounds 索引混合修复 - INILIN: GAMR0/GS0/GW0 展宽公式修复, 经典 VdW 公式修复 - INIBL0: CNM 常数 2.997925e18→e17 修复 - OPAC: Lyman IJ=2 修正缺失修复 - RTE: minv3 矩阵求逆符号错误修复 自动化脚本改进: - specf2r.sh: 添加 429 限流退避、完成检测、同步等待 - SKILL.md: 三阶段工作流 + 状态文件系统 - references/: Phase 1/2/3 独立参考文档 新增: - src/bin/synspec.rs: SYNSPEC 可执行文件入口 - .f2r_phase/.f2r_tasks/.f2r_complete: 状态管理文件 编译: 0 错误 | Clippy: 0 错误 | 测试: voigt 28 + eldens 5 通过 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
412 lines
14 KiB
Rust
412 lines
14 KiB
Rust
//! He I 线吸收轮廓计算。
|
||
//!
|
||
//! 翻译自 SYNSPEC `PHE1` 函数 (synspec54.f:7372)。
|
||
//!
|
||
//! 计算四条 He I 线的吸收轮廓系数:
|
||
//! - 4471 Å (Barnard, Cooper, Smith 1974 JQSRT 14, 1025)
|
||
//! - 4387 Å (Shamey 1969 PhD thesis)
|
||
//! - 4026 Å
|
||
//! - 4922 Å
|
||
//!
|
||
//! 返回频率单位的轮廓系数,归一化到 sqrt(pi)(不是 1)。
|
||
|
||
use super::extprf::extprf;
|
||
use super::voigtk::{voigtk, MVOI};
|
||
use super::wtot;
|
||
use super::yint::yint;
|
||
|
||
/// 温度标准值的对数 (log10)
|
||
/// 对应 5000K, 10000K, 20000K, 40000K
|
||
const TT: [f64; 4] = [3.699, 4.000, 4.301, 4.602];
|
||
|
||
/// 四条 He I 线的线心波长 (Å)
|
||
const WLAM0: [f64; 4] = [4471.50, 4387.93, 4026.20, 4921.93];
|
||
|
||
/// 温度插值区间边界 (log10(T))
|
||
const XT0: [f64; 4] = [3.699, 4.000, 4.301, 4.602];
|
||
|
||
/// He I 线吸收轮廓参数。
|
||
///
|
||
/// 包含计算 PHE1 所需的所有数据,
|
||
/// 包括 COMMON/PROHE1 和 COMMON/PRO447 中的轮廓表。
|
||
#[derive(Debug, Clone)]
|
||
pub struct Phe1Params<'a> {
|
||
/// 深度索引 (1-indexed,Fortran 风格)
|
||
pub id: usize,
|
||
/// 频率 (Hz)
|
||
pub freq: f64,
|
||
/// 谱线索引 (1=4471, 2=4387, 3=4026, 4=4922)
|
||
pub iline: usize,
|
||
/// 温度 (K)
|
||
pub temp: f64,
|
||
/// 电子密度
|
||
pub elec: f64,
|
||
/// 湍流速度 (cm/s)
|
||
pub vturb: f64,
|
||
|
||
// --- PRO447 COMMON 块数据 (4471 Å 专用) ---
|
||
/// 4471 Å 轮廓数据: PRF447(wavelength, temp, elec_dens)
|
||
/// 维度: [nwlam_447, NT=4, NE_447=7]
|
||
pub prf447: &'a [f64],
|
||
/// 4471 Å 波长偏移表: DLM447(wavelength, elec_dens)
|
||
/// 维度: [nwlam_447, NE_447=7]
|
||
pub dlm447: &'a [f64],
|
||
/// 4471 Å 电子密度网格 (log10): XNE447(7)
|
||
pub xne447: &'a [f64],
|
||
/// 4471 Å 各温度/电子密度组合的波长点数
|
||
/// NWLAM(elec_dens_idx, temp_idx) — 注意 Fortran 维度为 [8,4]
|
||
/// 这里只用于 iline=1, 所以取第一列
|
||
pub nwlam_447: &'a [usize],
|
||
|
||
// --- PROHE1 COMMON 块数据 (其他三条线) ---
|
||
/// He I 轮廓数据: PRFHE1(wavelength, temp, elec_dens, line-1)
|
||
/// 维度: [nwlam_he1, NT=4, NE_HE1=8, 3]
|
||
pub prfhe1: &'a [f64],
|
||
/// He I 波长偏移表: DLMHE1(wavelength, elec_dens, line-1)
|
||
/// 维度: [nwlam_he1, NE_HE1=8, 3]
|
||
pub dlmhe1: &'a [f64],
|
||
/// He I 电子密度网格 (log10): XNEHE1(8)
|
||
pub xnehe1: &'a [f64],
|
||
/// He I 各温度/电子密度组合的波长点数
|
||
/// NWLAM(elec_dens_idx, iline) — iline=2,3,4 对应索引 1,2,3
|
||
pub nwlam_he1: &'a [usize],
|
||
|
||
/// 最大波长点数 (用于数组维度)
|
||
pub max_wlam_447: usize,
|
||
/// 最大波长点数 (He I)
|
||
pub max_wlam_he1: usize,
|
||
|
||
/// Voigt 函数预计算表 H0
|
||
pub h0tab: &'a [f64; MVOI],
|
||
/// Voigt 函数预计算表 H1
|
||
pub h1tab: &'a [f64; MVOI],
|
||
/// Voigt 函数预计算表 H2
|
||
pub h2tab: &'a [f64; MVOI],
|
||
}
|
||
|
||
/// 计算单个深度点的温度插值系数。
|
||
///
|
||
/// 等价于 Fortran TINT 对单点的计算。
|
||
fn compute_tint_coeffs(t: f64) -> (i32, f64, f64, f64) {
|
||
let tl = t.log10();
|
||
let j: usize = if tl > TT[2] { 4 } else { 3 };
|
||
let tt_jm2 = TT[j - 3];
|
||
let tt_jm1 = TT[j - 2];
|
||
let tt_j = TT[j - 1];
|
||
let x = (tt_j - tt_jm1) * (tt_j - tt_jm2) * (tt_jm1 - tt_jm2);
|
||
let ti0 = (tl - tt_jm2) * (tl - tt_jm1) * (tt_jm1 - tt_jm2) / x;
|
||
let ti1 = (tl - tt_jm2) * (tt_j - tl) * (tt_j - tt_jm2) / x;
|
||
let ti2 = (tl - tt_jm1) * (tl - tt_j) * (tt_j - tt_jm1) / x;
|
||
(j as i32, ti0, ti1, ti2)
|
||
}
|
||
|
||
/// 计算 He I 线吸收轮廓系数。
|
||
///
|
||
/// # 参数
|
||
///
|
||
/// * `params` - PHE1 参数结构体
|
||
///
|
||
/// # 返回值
|
||
///
|
||
/// 轮廓系数 (频率单位,归一化到 sqrt(pi))
|
||
pub fn phe1(params: &Phe1Params) -> f64 {
|
||
let _id = params.id; // 1-indexed
|
||
let freq = params.freq;
|
||
let iline = params.iline; // 1-4
|
||
|
||
// 温度修正:考虑湍流速度对 Doppler 宽度的影响
|
||
let t = params.temp + 2.42e-8 * params.vturb;
|
||
let tl = t.log10();
|
||
let ane = params.elec;
|
||
let anel = ane.log10();
|
||
|
||
// 波长 (Å)
|
||
let alam = 2.997925e18 / freq;
|
||
// 与线心的波长偏移
|
||
let dlam = alam - WLAM0[iline - 1];
|
||
// Doppler 宽度
|
||
let dopl = (4.125e7 * t).sqrt() * WLAM0[iline - 1] / 2.997925e10;
|
||
|
||
// 判断是否使用孤立线近似
|
||
let use_isolated = if tl > XT0[3] + 0.1 {
|
||
true
|
||
} else if iline == 1 && anel >= params.xne447[0] {
|
||
false
|
||
} else { !(iline != 1 && anel >= params.xnehe1[0]) };
|
||
|
||
if use_isolated {
|
||
// 孤立线近似:低电子密度情况
|
||
let (jt, ti0, ti1, ti2) = compute_tint_coeffs(t);
|
||
let a = wtot::wtot(t, ane, jt, ti0, ti1, ti2, iline - 1) / dopl;
|
||
let v = dlam.abs() / dopl;
|
||
let v1 = (alam - 4471.682).abs() / dopl;
|
||
let mut result = voigtk(a, v, params.h0tab, params.h1tab, params.h2tab);
|
||
if iline == 1 {
|
||
result = (8.0 * result + voigtk(a, v1, params.h0tab, params.h1tab, params.h2tab)) / 9.0;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
// 表插值:高电子密度情况
|
||
let (nx, nz, ny) = (3usize, 3usize, 2usize);
|
||
let (ne, ilne) = if iline == 1 {
|
||
(7usize, 0usize)
|
||
} else {
|
||
(8usize, iline - 1)
|
||
};
|
||
|
||
// 电子密度插值:找到位置
|
||
let mut ipz = 1usize;
|
||
for jz in 0..ne - 1 {
|
||
ipz = jz + 1;
|
||
let xne = if iline == 1 {
|
||
params.xne447
|
||
} else {
|
||
params.xnehe1
|
||
};
|
||
if anel <= xne[jz + 1] {
|
||
break;
|
||
}
|
||
}
|
||
|
||
let n0z = if ipz < nz / 2 + 1 {
|
||
1
|
||
} else if ipz > ne - nz + 1 {
|
||
ne - nz + 1
|
||
} else {
|
||
ipz - nz / 2
|
||
};
|
||
let n1z = n0z + nz - 1;
|
||
|
||
let mut zz = [0.0f64; 3];
|
||
let mut wz = [0.0f64; 3];
|
||
|
||
for (i0z_idx, jz) in (n0z..=n1z).enumerate() {
|
||
let xne = if iline == 1 {
|
||
params.xne447
|
||
} else {
|
||
params.xnehe1
|
||
};
|
||
zz[i0z_idx] = xne[jz - 1]; // Fortran 1-indexed
|
||
|
||
// 温度插值
|
||
let mut ipx = 1usize;
|
||
for ix in 0..3 {
|
||
ipx = ix + 1;
|
||
if tl <= XT0[ix + 1] {
|
||
break;
|
||
}
|
||
}
|
||
|
||
let n0x = if ipx < nx / 2 + 1 {
|
||
1
|
||
} else if ipx > 4 - nx + 1 {
|
||
4 - nx + 1
|
||
} else {
|
||
ipx - nx / 2
|
||
};
|
||
let n1x = n0x + nx - 1;
|
||
|
||
let mut xx = [0.0f64; 3];
|
||
let mut wx = [0.0f64; 3];
|
||
|
||
for (i0x_idx, ix) in (n0x..=n1x).enumerate() {
|
||
xx[i0x_idx] = XT0[ix - 1];
|
||
|
||
// 波长插值
|
||
let nwlst = if iline == 1 {
|
||
params.nwlam_447[(jz - 1) * 4 + (ix - 1)]
|
||
} else {
|
||
params.nwlam_he1[(jz - 1) * 4 + (iline - 1)]
|
||
};
|
||
|
||
// 检查是否需要外推
|
||
let (d1, d2, _prf_data, _dlm_data) = if iline == 1 {
|
||
let d1 = params.dlm447[(jz - 1) * params.max_wlam_447];
|
||
let d2 = params.dlm447[(jz - 1) * params.max_wlam_447 + nwlst - 1];
|
||
let _prf_base = (ix - 1) * params.max_wlam_447 * 7 + (jz - 1) * params.max_wlam_447;
|
||
(d1, d2, params.prf447, params.dlm447)
|
||
} else {
|
||
let d1 = params.dlmhe1[(jz - 1) * params.max_wlam_he1 * 3 + ilne * params.max_wlam_he1];
|
||
let d2 = params.dlmhe1[(jz - 1) * params.max_wlam_he1 * 3 + ilne * params.max_wlam_he1 + nwlst - 1];
|
||
(d1, d2, params.prfhe1, params.dlmhe1)
|
||
};
|
||
|
||
if dlam < d1 {
|
||
let plast = if iline == 1 {
|
||
params.prf447[(ix - 1) * params.max_wlam_447 * 7 + (jz - 1) * params.max_wlam_447]
|
||
} else {
|
||
params.prfhe1[(ix - 1) * params.max_wlam_he1 * 8 * 3
|
||
+ (jz - 1) * params.max_wlam_he1 * 3
|
||
+ ilne * params.max_wlam_he1]
|
||
};
|
||
wx[i0x_idx] = extprf(dlam, ix, iline, zz[i0z_idx], d1, plast);
|
||
} else if dlam > d2 {
|
||
let plast = if iline == 1 {
|
||
params.prf447[(ix - 1) * params.max_wlam_447 * 7
|
||
+ (jz - 1) * params.max_wlam_447
|
||
+ nwlst - 1]
|
||
} else {
|
||
params.prfhe1[(ix - 1) * params.max_wlam_he1 * 8 * 3
|
||
+ (jz - 1) * params.max_wlam_he1 * 3
|
||
+ ilne * params.max_wlam_he1
|
||
+ nwlst - 1]
|
||
};
|
||
wx[i0x_idx] = extprf(dlam, ix, iline, zz[i0z_idx], d2, plast);
|
||
} else {
|
||
// 波长线性插值
|
||
let mut ipy = 1usize;
|
||
for iy in 0..nwlst - 1 {
|
||
ipy = iy + 1;
|
||
let dlm_next = if iline == 1 {
|
||
params.dlm447[(jz - 1) * params.max_wlam_447 + iy + 1]
|
||
} else {
|
||
params.dlmhe1[(jz - 1) * params.max_wlam_he1 * 3
|
||
+ ilne * params.max_wlam_he1
|
||
+ iy + 1]
|
||
};
|
||
if dlam <= dlm_next {
|
||
break;
|
||
}
|
||
}
|
||
|
||
let n0y = if ipy < ny / 2 + 1 {
|
||
1
|
||
} else if ipy > nwlst - ny + 1 {
|
||
nwlst - ny + 1
|
||
} else {
|
||
ipy - ny / 2
|
||
};
|
||
let n1y = n0y + ny - 1;
|
||
|
||
let mut yy = [0.0f64; 2];
|
||
let mut pp = [0.0f64; 2];
|
||
|
||
for (i0_idx, iy) in (n0y..=n1y).enumerate() {
|
||
if iline == 1 {
|
||
yy[i0_idx] = params.dlm447[(jz - 1) * params.max_wlam_447 + iy - 1];
|
||
pp[i0_idx] = params.prf447[(ix - 1) * params.max_wlam_447 * 7
|
||
+ (jz - 1) * params.max_wlam_447
|
||
+ iy - 1]
|
||
.ln();
|
||
} else {
|
||
yy[i0_idx] = params.dlmhe1[(jz - 1) * params.max_wlam_he1 * 3
|
||
+ ilne * params.max_wlam_he1
|
||
+ iy - 1];
|
||
pp[i0_idx] = params.prfhe1[(ix - 1) * params.max_wlam_he1 * 8 * 3
|
||
+ (jz - 1) * params.max_wlam_he1 * 3
|
||
+ ilne * params.max_wlam_he1
|
||
+ iy - 1];
|
||
}
|
||
}
|
||
|
||
let interp_val = (pp[1] * (dlam - yy[0]) + pp[0] * (yy[1] - dlam))
|
||
/ (yy[1] - yy[0]);
|
||
|
||
if iline != 1 {
|
||
wx[i0x_idx] = interp_val;
|
||
} else {
|
||
wx[i0x_idx] = interp_val.exp();
|
||
}
|
||
}
|
||
}
|
||
|
||
wz[i0z_idx] = yint(&xx, &wx, tl);
|
||
}
|
||
|
||
let w0 = yint(&zz, &wz, anel);
|
||
w0 * dopl * 1.772454 // sqrt(pi) ≈ 1.7724539
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn test_compute_tint_coeffs() {
|
||
// 在 10000K (log10=4.000), JT=3
|
||
let (jt, ti0, ti1, ti2) = compute_tint_coeffs(10000.0);
|
||
assert_eq!(jt, 3);
|
||
assert!(ti0.is_finite());
|
||
assert!(ti1.is_finite());
|
||
assert!(ti2.is_finite());
|
||
|
||
// 在 30000K (log10≈4.477), JT=4
|
||
let (jt, _, _, _) = compute_tint_coeffs(30000.0);
|
||
assert_eq!(jt, 4);
|
||
}
|
||
|
||
#[test]
|
||
fn test_phe1_wlam0() {
|
||
// 验证线心波长常量
|
||
assert!((WLAM0[0] - 4471.50).abs() < 0.01);
|
||
assert!((WLAM0[1] - 4387.93).abs() < 0.01);
|
||
assert!((WLAM0[2] - 4026.20).abs() < 0.01);
|
||
assert!((WLAM0[3] - 4921.93).abs() < 0.01);
|
||
}
|
||
|
||
#[test]
|
||
fn test_phe1_xt0() {
|
||
// 验证温度插值边界
|
||
for i in 0..4 {
|
||
assert!((XT0[i] - TT[i]).abs() < 1e-10);
|
||
}
|
||
}
|
||
|
||
/// 创建测试参数(低电子密度 → 孤立线近似路径)
|
||
fn make_test_params(eline: usize, elec: f64) -> Phe1Params<'static> {
|
||
static PRF447: [f64; 80 * 4 * 7] = [0.0; 80 * 4 * 7];
|
||
static DLM447: [f64; 80 * 7] = [0.0; 80 * 7];
|
||
static XNE447: [f64; 7] = [6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0];
|
||
static NWLAM_447: [usize; 28] = [80; 28];
|
||
static PRFHE1: [f64; 50 * 4 * 8 * 3] = [0.0; 50 * 4 * 8 * 3];
|
||
static DLMHE1: [f64; 50 * 8 * 3] = [0.0; 50 * 8 * 3];
|
||
static XNEHE1: [f64; 8] = [6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0];
|
||
static NWLAM_HE1: [usize; 32] = [50; 32];
|
||
static H0TAB: [f64; MVOI] = [0.0; MVOI];
|
||
static H1TAB: [f64; MVOI] = [0.0; MVOI];
|
||
static H2TAB: [f64; MVOI] = [0.0; MVOI];
|
||
|
||
Phe1Params {
|
||
id: 1,
|
||
freq: 6.7e14,
|
||
iline: eline,
|
||
temp: 10000.0,
|
||
elec,
|
||
vturb: 0.0,
|
||
prf447: &PRF447,
|
||
dlm447: &DLM447,
|
||
xne447: &XNE447,
|
||
nwlam_447: &NWLAM_447,
|
||
prfhe1: &PRFHE1,
|
||
dlmhe1: &DLMHE1,
|
||
xnehe1: &XNEHE1,
|
||
nwlam_he1: &NWLAM_HE1,
|
||
max_wlam_447: 80,
|
||
max_wlam_he1: 50,
|
||
h0tab: &H0TAB,
|
||
h1tab: &H1TAB,
|
||
h2tab: &H2TAB,
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_phe1_isolated_line_approx() {
|
||
// 低电子密度 (anel < xne447[0]=6.0) → 孤立线近似路径
|
||
let params = make_test_params(1, 1e5); // anel = 5.0 < 6.0
|
||
let result = phe1(¶ms);
|
||
assert!(result.is_finite(), "PHE1 should return finite value in isolated line path");
|
||
assert!(result >= 0.0, "PHE1 profile should be non-negative");
|
||
}
|
||
|
||
#[test]
|
||
fn test_phe1_all_lines_isolated() {
|
||
// 测试四条线在孤立线近似路径下都能工作
|
||
for iline in 1..=4 {
|
||
let params = make_test_params(iline, 1e5);
|
||
let result = phe1(¶ms);
|
||
assert!(result.is_finite(), "PHE1 line {} should be finite", iline);
|
||
}
|
||
}
|
||
}
|