SpectraRust/src/synspec/math/phe1.rs
fmq e2c1a4580a feat: F2R 重构全部完成 + 自动化脚本改进
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>
2026-06-08 14:54:53 +08:00

412 lines
14 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 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-indexedFortran 风格)
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(&params);
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(&params);
assert!(result.is_finite(), "PHE1 line {} should be finite", iline);
}
}
}