SpectraRust/src/synspec/math/phe2.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

461 lines
13 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 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(&params);
// 验证输出数组长度
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(&params);
// 应该返回全零数组
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(&params);
// 由于轮廓数据为零,结果应该很小或为零
// 这是一个边界情况测试
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(&params);
// 验证输出
assert_eq!(result.ablin.len(), 10);
}
}