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>
461 lines
13 KiB
Rust
461 lines
13 KiB
Rust
//! He II 线不透明度和发射率计算。
|
||
//!
|
||
//! 重构自 SYNSPEC `phe2.f`。
|
||
//!
|
||
//! 使用 Schoening 和 Butler 计算的轮廓系数评估给定 He II 线的不透明度和发射率。
|
||
|
||
// ============================================================================
|
||
// 物理常数
|
||
// ============================================================================
|
||
|
||
/// 普朗克常数 × 光速 (erg·cm)
|
||
#[allow(dead_code)]
|
||
const HC: f64 = 1.986477e-16;
|
||
|
||
/// He II 电离能 (cm⁻¹)
|
||
#[allow(dead_code)]
|
||
const HE2_IONIZATION_ENERGY: f64 = 438916.146;
|
||
|
||
/// He II 线波长因子 (Å·cm⁻¹)
|
||
/// WLIN = 227.838 / (1/II - 1/JJ) for n <= 2
|
||
/// WLIN = 227.7776 / (1/II - 1/JJ) for n > 2
|
||
const WLIN_FACTOR_1: f64 = 227.838;
|
||
const WLIN_FACTOR_2: f64 = 227.7776;
|
||
|
||
/// 玻尔兹曼常数相关的激发能因子
|
||
/// 631479 = HC * HE2_IONIZATION_ENERGY / k (简化形式)
|
||
const EXCITATION_FACTOR: f64 = 631479.0;
|
||
|
||
/// 萨哈因子常数
|
||
/// 4.1412E-16 / (k * sqrt(T)) 的前置因子
|
||
const SAHA_PREFACTOR: f64 = 4.1412e-16;
|
||
|
||
/// 转换因子 (2.3025851 = ln(10))
|
||
const LN10: f64 = std::f64::consts::LN_10;
|
||
|
||
/// 发射率转换因子 (1.4747E-2)
|
||
const EMIS_FACTOR: f64 = 1.4747e-2;
|
||
|
||
/// 频率转换因子 (1E-15)
|
||
const FREQ_SCALE: f64 = 1e-15;
|
||
|
||
/// 频率到波长转换因子 (Å·Hz)
|
||
#[allow(dead_code)]
|
||
const FREQ_TO_WAVE: f64 = 2.997925e17;
|
||
|
||
// ============================================================================
|
||
// He II 振子强度数据
|
||
// ============================================================================
|
||
|
||
/// He II 线的振子强度 (OSCHE2)
|
||
///
|
||
/// 对应 Fortran DATA 语句中的 19 个值。
|
||
/// 这些是 Schoening 和 Butler 计算的振子强度。
|
||
const OSCHE2: [f64; 19] = [
|
||
6.407e-1, 1.506e-1, 5.584e-2, 2.768e-2,
|
||
1.604e-2, 1.023e-2, 6.980e-3,
|
||
8.421e-1, 3.230e-2, 1.870e-2, 1.196e-2, 8.187e-3,
|
||
5.886e-3, 4.393e-3, 3.375e-3, 2.656e-3,
|
||
1.038, 1.793e-1, 6.549e-2,
|
||
];
|
||
|
||
// ============================================================================
|
||
// 参数结构体
|
||
// ============================================================================
|
||
|
||
/// PHE2 输入参数。
|
||
///
|
||
/// 包含计算 He II 线不透明度所需的所有数据。
|
||
#[derive(Debug, Clone)]
|
||
pub struct Phe2Params<'a> {
|
||
/// 谱线索引 (ISPEC)
|
||
/// 定义在 HE2INI 中,He II 线从 ISPEC=6 开始
|
||
pub ispec: i32,
|
||
|
||
/// 深度索引 (ID)
|
||
pub id: i32,
|
||
|
||
/// He II 元素索引 (IELHE2)
|
||
/// 0 表示不处理 He II NLTE
|
||
pub ielhe2: i32,
|
||
|
||
/// NLTE 模式标志 (INLTE)
|
||
/// > 0 表示使用 NLTE 布居数
|
||
pub inlte: i32,
|
||
|
||
/// 频率点数 (NFREQ)
|
||
pub nfreq: i32,
|
||
|
||
/// 频率数组 (FREQ, Hz)
|
||
pub freq: &'a [f64],
|
||
|
||
/// 波长数组 (WLAM, Å)
|
||
pub wlam: &'a [f64],
|
||
|
||
/// 温度 (TEMP, K)
|
||
pub temp: f64,
|
||
|
||
/// 电子密度 (ELEC)
|
||
pub elec: f64,
|
||
|
||
/// He III 布居数 (来自 POPUL 或 RRR)
|
||
pub he3_pop: f64,
|
||
|
||
/// He II NLTE 能级数 (NLHE2 = NLAST - NFIRST + 1)
|
||
pub nlhe2: i32,
|
||
|
||
/// He II 能级起始索引 (NFIRST)
|
||
pub nfirst_he2: i32,
|
||
|
||
/// 各能级布居数 (POPUL)
|
||
pub popul: &'a [f64],
|
||
|
||
/// He II 线轮廓数据 (PRFHE2)
|
||
/// PRFHE2(ILINE, ID, IWL) - 线索引 × 深度 × 波长点
|
||
pub prfhe2: &'a [f64],
|
||
|
||
/// He II 线波长表 (WLHE2)
|
||
/// WLHE2(ILINE, IWL) - log10(|波长 - 线心波长|)
|
||
pub wlhe2: &'a [f64],
|
||
|
||
/// He II 线波长点数 (NWLHE2)
|
||
pub nwlhe2: i32,
|
||
|
||
/// He II 线下能级主量子数 (ILHE2)
|
||
pub ilhe2: i32,
|
||
|
||
/// He II 线上能级主量子数 (IUHE2)
|
||
pub iuhe2: i32,
|
||
|
||
/// 激光 delta 标志 (lasdel)
|
||
/// 当 lasdel=true 且 popi <= popj 时跳过该频率点
|
||
pub lasdel: bool,
|
||
}
|
||
|
||
/// PHE2 输出结果。
|
||
#[derive(Debug, Clone)]
|
||
pub struct Phe2Output {
|
||
/// 吸收系数数组 (ABLIN)
|
||
pub ablin: Vec<f64>,
|
||
|
||
/// 发射系数数组 (EMLIN)
|
||
pub emlin: Vec<f64>,
|
||
}
|
||
|
||
// ============================================================================
|
||
// PHE2 函数
|
||
// ============================================================================
|
||
|
||
/// 计算 He II 线不透明度和发射率。
|
||
///
|
||
/// 使用 Schoening 和 Butler 预计算的轮廓系数表,
|
||
/// 通过插值获得给定频率点的不透明度。
|
||
///
|
||
/// # 参数
|
||
///
|
||
/// * `params` - 输入参数结构体
|
||
///
|
||
/// # 返回
|
||
///
|
||
/// 返回包含吸收系数和发射系数数组的输出结构体
|
||
///
|
||
/// # Fortran 原始代码
|
||
///
|
||
/// ```fortran
|
||
/// SUBROUTINE PHE2(ISPEC,ID,ABLIN,EMLIN)
|
||
/// ```
|
||
pub fn phe2(params: &Phe2Params) -> Phe2Output {
|
||
let nfreq = params.nfreq as usize;
|
||
|
||
// 初始化输出数组
|
||
let mut ablin = vec![0.0; nfreq];
|
||
let mut emlin = vec![0.0; nfreq];
|
||
|
||
// 计算线索引 (He II 线从 ISPEC=6 开始)
|
||
let iline = (params.ispec - 5) as usize;
|
||
|
||
// 检查线索引有效性
|
||
if iline == 0 || iline > 19 {
|
||
return Phe2Output { ablin, emlin };
|
||
}
|
||
|
||
// 提取轮廓数据到局部数组
|
||
let nwl = params.nwlhe2 as usize;
|
||
let mut prf0 = vec![0.0; nwl];
|
||
let mut wll = vec![0.0; nwl];
|
||
|
||
for iwl in 0..nwl {
|
||
// PRFHE2(ILINE, ID, IWL) 存储为 [iline][id][iwl] 或线性化
|
||
// 假设存储顺序为 [iline * nd * nwl + id * nwl + iwl]
|
||
let id_idx = (params.id - 1) as usize; // Fortran 1-indexed
|
||
let prf_idx = (iline - 1) * 100 * 36 + id_idx * 36 + iwl; // 假设 MDEPTH=100, 36 wavelength points max
|
||
let wl_idx = (iline - 1) * 36 + iwl;
|
||
|
||
if prf_idx < params.prfhe2.len() {
|
||
prf0[iwl] = params.prfhe2[prf_idx];
|
||
}
|
||
if wl_idx < params.wlhe2.len() {
|
||
wll[iwl] = params.wlhe2[wl_idx];
|
||
}
|
||
}
|
||
|
||
// 计算线心波长
|
||
let i = params.ilhe2;
|
||
let j = params.iuhe2;
|
||
let ii = (i * i) as f64;
|
||
let jj = (j * j) as f64;
|
||
|
||
let wlin = if i <= 2 {
|
||
WLIN_FACTOR_1 / (1.0 / ii - 1.0 / jj)
|
||
} else {
|
||
WLIN_FACTOR_2 / (1.0 / ii - 1.0 / jj)
|
||
};
|
||
|
||
let t = params.temp;
|
||
|
||
// He III 布居数 (LTE 或 NLTE)
|
||
let (pp, nlhe2) = if params.ielhe2 > 0 && params.inlte > 0 {
|
||
(params.he3_pop, params.nlhe2)
|
||
} else {
|
||
(params.he3_pop, 0)
|
||
};
|
||
|
||
// 计算下能级布居数
|
||
let pp_scaled = pp * params.elec * SAHA_PREFACTOR / t / t.sqrt() * ii;
|
||
|
||
let popi = if i <= nlhe2 && params.inlte > 0 {
|
||
// NLTE 布居数
|
||
let level_idx = (params.nfirst_he2 + i - 2) as usize; // 转换为 0-indexed
|
||
if level_idx < params.popul.len() {
|
||
params.popul[level_idx]
|
||
} else {
|
||
0.0
|
||
}
|
||
} else {
|
||
// LTE 布居数
|
||
pp_scaled * (EXCITATION_FACTOR / t / ii).exp()
|
||
};
|
||
|
||
// 计算上能级布居数
|
||
let popj = if j <= nlhe2 {
|
||
// NLTE 布居数 (归一化到下能级)
|
||
let level_idx = (params.nfirst_he2 + j - 2) as usize;
|
||
if level_idx < params.popul.len() {
|
||
params.popul[level_idx] * ii / jj
|
||
} else {
|
||
0.0
|
||
}
|
||
} else {
|
||
// LTE 布居数
|
||
pp_scaled * (EXCITATION_FACTOR / t / jj).exp()
|
||
};
|
||
|
||
// 振子强度因子
|
||
let fid = 0.02654 * OSCHE2[iline - 1];
|
||
|
||
// 遍历频率点
|
||
for ij in 2..nfreq {
|
||
// 计算与线心的波长差
|
||
let al = (params.wlam[ij] - wlin).abs();
|
||
let al = if al < 1e-4 { 1e-4 } else { al };
|
||
let al = al.log10();
|
||
|
||
// 在轮廓表中插值
|
||
let mut iw0 = 0;
|
||
for iwl in 0..(nwl - 1) {
|
||
iw0 = iwl;
|
||
if al <= wll[iwl + 1] {
|
||
break;
|
||
}
|
||
}
|
||
|
||
let iw1 = iw0 + 1;
|
||
if iw1 >= nwl {
|
||
continue;
|
||
}
|
||
|
||
// 线性插值
|
||
let denom = wll[iw1] - wll[iw0];
|
||
let prh = if denom.abs() > 1e-10 {
|
||
(prf0[iw0] * (wll[iw1] - al) + prf0[iw1] * (al - wll[iw0])) / denom
|
||
} else {
|
||
prf0[iw0]
|
||
};
|
||
|
||
// 计算线强度
|
||
let sg = (prh * LN10).exp() * fid;
|
||
|
||
// 检查布居数反转
|
||
if (popi - popj) <= 0.0 && params.lasdel {
|
||
continue;
|
||
}
|
||
|
||
// 累加不透明度和发射率
|
||
ablin[ij] += sg * (popi - popj);
|
||
emlin[ij] += sg * popj * EMIS_FACTOR * (params.freq[ij] * FREQ_SCALE).powi(3);
|
||
}
|
||
|
||
Phe2Output { ablin, emlin }
|
||
}
|
||
|
||
// ============================================================================
|
||
// 测试
|
||
// ============================================================================
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
use approx::assert_relative_eq;
|
||
|
||
/// 创建测试用的参数
|
||
fn create_test_params() -> Phe2Params<'static> {
|
||
// 创建简单的测试数据
|
||
static FREQ: [f64; 10] = [
|
||
1e15, 1.1e15, 1.2e15, 1.3e15, 1.4e15,
|
||
1.5e15, 1.6e15, 1.7e15, 1.8e15, 1.9e15,
|
||
];
|
||
static WLAM: [f64; 10] = [
|
||
2997.9, 2725.4, 2498.3, 2306.1, 2141.4,
|
||
1998.6, 1873.7, 1763.5, 1665.5, 1577.9,
|
||
];
|
||
static POPUL: [f64; 20] = [
|
||
1e10, 5e9, 2e9, 1e9, 5e8,
|
||
2e8, 1e8, 5e7, 2e7, 1e7,
|
||
0.0, 0.0, 0.0, 0.0, 0.0,
|
||
0.0, 0.0, 0.0, 0.0, 0.0,
|
||
];
|
||
static PRFHE2: [f64; 100 * 36 * 19] = [0.0; 100 * 36 * 19];
|
||
static WLHE2: [f64; 36 * 19] = [0.0; 36 * 19];
|
||
|
||
Phe2Params {
|
||
ispec: 6, // 第一条 He II 线
|
||
id: 1,
|
||
ielhe2: 3,
|
||
inlte: 0,
|
||
nfreq: 10,
|
||
freq: &FREQ,
|
||
wlam: &WLAM,
|
||
temp: 20000.0,
|
||
elec: 1e13,
|
||
he3_pop: 1e10,
|
||
nlhe2: 0,
|
||
nfirst_he2: 11,
|
||
popul: &POPUL,
|
||
prfhe2: &PRFHE2,
|
||
wlhe2: &WLHE2,
|
||
nwlhe2: 10,
|
||
ilhe2: 2,
|
||
iuhe2: 3,
|
||
lasdel: false,
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_phe2_basic() {
|
||
let params = create_test_params();
|
||
let result = phe2(¶ms);
|
||
|
||
// 验证输出数组长度
|
||
assert_eq!(result.ablin.len(), 10);
|
||
assert_eq!(result.emlin.len(), 10);
|
||
}
|
||
|
||
#[test]
|
||
fn test_phe2_invalid_line_index() {
|
||
let mut params = create_test_params();
|
||
params.ispec = 1; // 不是 He II 线 (ISPEC < 6)
|
||
|
||
let result = phe2(¶ms);
|
||
|
||
// 应该返回全零数组
|
||
assert!(result.ablin.iter().all(|&x| x == 0.0));
|
||
assert!(result.emlin.iter().all(|&x| x == 0.0));
|
||
}
|
||
|
||
#[test]
|
||
fn test_phe2_line_index_calculation() {
|
||
// 验证线索引计算
|
||
// ISPEC=6 -> iline=1
|
||
// ISPEC=7 -> iline=2
|
||
assert_eq!(6 - 5, 1);
|
||
assert_eq!(7 - 5, 2);
|
||
}
|
||
|
||
#[test]
|
||
fn test_wlin_calculation() {
|
||
// 测试线心波长计算
|
||
// He II Lyman-alpha: n=1 -> n=2 (lower -> upper)
|
||
// I = lower level, J = upper level
|
||
let i = 1; // lower level
|
||
let j = 2; // upper level
|
||
let ii = (i * i) as f64;
|
||
let jj = (j * j) as f64;
|
||
let wlin = WLIN_FACTOR_1 / (1.0 / ii - 1.0 / jj);
|
||
|
||
// He II Lyman-alpha 约为 303.8 Å
|
||
assert_relative_eq!(wlin, 303.784, epsilon = 0.1);
|
||
}
|
||
|
||
#[test]
|
||
fn test_constants() {
|
||
// 验证常数
|
||
assert_relative_eq!(LN10, std::f64::consts::LN_10, epsilon = 1e-7);
|
||
assert_relative_eq!(EMIS_FACTOR, 1.4747e-2, epsilon = 1e-6);
|
||
assert_relative_eq!(EXCITATION_FACTOR, 631479.0, epsilon = 1.0);
|
||
}
|
||
|
||
#[test]
|
||
fn test_osche2_data() {
|
||
// 验证振子强度数组
|
||
assert_eq!(OSCHE2.len(), 19);
|
||
assert_relative_eq!(OSCHE2[0], 6.407e-1, epsilon = 1e-4);
|
||
assert_relative_eq!(OSCHE2[18], 6.549e-2, epsilon = 1e-5);
|
||
}
|
||
|
||
#[test]
|
||
fn test_population_inversion_skip() {
|
||
// 当 lasdel=true 且 popi <= popj 时应该跳过
|
||
let mut params = create_test_params();
|
||
params.lasdel = true;
|
||
|
||
// 设置使得 popi <= popj 的情况
|
||
// 这需要特定的温度和布居数设置
|
||
let result = phe2(¶ms);
|
||
|
||
// 由于轮廓数据为零,结果应该很小或为零
|
||
// 这是一个边界情况测试
|
||
assert!(result.ablin.iter().all(|&x| x >= 0.0));
|
||
}
|
||
|
||
#[test]
|
||
fn test_lte_population() {
|
||
// 测试 LTE 布居数计算
|
||
let params = create_test_params();
|
||
|
||
// 使用 LTE 模式 (inlte = 0)
|
||
let t = params.temp;
|
||
let pp = params.he3_pop;
|
||
let elec = params.elec;
|
||
let ii = 4.0; // n=2
|
||
|
||
let pp_scaled = pp * elec * SAHA_PREFACTOR / t / t.sqrt() * ii;
|
||
let popi_lte = pp_scaled * (EXCITATION_FACTOR / t / ii).exp();
|
||
|
||
// 验证 LTE 布居数为正
|
||
assert!(popi_lte > 0.0);
|
||
}
|
||
|
||
#[test]
|
||
fn test_nlte_population() {
|
||
// 测试 NLTE 布居数模式
|
||
let mut params = create_test_params();
|
||
params.inlte = 1; // NLTE 模式
|
||
params.nlhe2 = 5; // 有 5 个 NLTE 能级
|
||
|
||
let result = phe2(¶ms);
|
||
|
||
// 验证输出
|
||
assert_eq!(result.ablin.len(), 10);
|
||
}
|
||
}
|