SpectraRust/src/tlusty/math/continuum/opacfl.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

470 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.

//! 吸收、发射和散射系数计算(频率和深度相关)。
//!
//! 重构自 TLUSTY `OPACFL.f`
//!
//! 计算给定频率点在所有深度的不透明度和发射率:
//! - 束缚-自由贡献(含/不含双电子复合)
//! - 自由-自由贡献
//! - 额外连续谱不透明度 (OPADD)
//! - 谱线贡献
// ============================================================================
// 常量
// ============================================================================
/// 光速 (cm/s) × 1e14
const C14: f64 = 2.99793e14;
/// 单位常数
const UN: f64 = 1.0;
// f2r_depends: DWNFR1, OPADD, SGMER1
/// Absorption, emission, and scattering coefficients wrapper (matches Fortran OPACFL subroutine signature).
pub fn opacfl(
ij: usize,
nd: usize,
freq: &[f64],
bnue: &[f64],
hkt1: &[f64],
elscat: &[f64],
) -> OpacflOutput {
opacfl_init(ij, nd, freq, bnue, hkt1, elscat)
}
// ============================================================================
// 输出结构体
// ============================================================================
/// OPACFL 输出结构体。
#[derive(Debug, Clone, Default)]
pub struct OpacflOutput {
/// 吸收系数 (MDEPTH)
pub abso1: Vec<f64>,
/// 发射系数 (MDEPTH)
pub emis1: Vec<f64>,
/// 散射系数 (MDEPTH)
pub scat1: Vec<f64>,
/// 谱线吸收系数 (MDEPTH)
pub abso1l: Vec<f64>,
/// 谱线发射系数 (MDEPTH)
pub emis1l: Vec<f64>,
/// 辐射权重 XKF (MDEPTH)
pub xkf: Vec<f64>,
/// 1 - XKF (MDEPTH)
pub xkf1: Vec<f64>,
/// 普朗克函数 × XKF (MDEPTH)
pub xkfb: Vec<f64>,
}
// ============================================================================
// 主函数 - 简化版本
// ============================================================================
/// 计算给定频率点的不透明度、发射率和散射系数。
///
/// 这是简化版本,用于测试和验证核心逻辑。
/// 完整版本需要传入完整的模型状态。
///
/// # 参数
/// - `ij`: 频率索引 (0-indexed)
/// - `nd`: 深度点数
/// - `freq`: 频率数组
/// - `bnue`: 普朗克函数数组
/// - `hkt1`: h/kT 数组
/// - `elscat`: 电子散射不透明度数组
///
/// # 返回值
/// 包含 ABSO1, EMIS1, SCAT1, ABSO1L, EMIS1L, XKF, XKF1, XKFB 的结构体
///
/// # Fortran 原始代码
///
/// ```fortran
/// SUBROUTINE OPACFL(IJ)
/// DO ID=1,ND
/// ABSO1(ID)=ELSCAT(ID)
/// EMIS1(ID)=0.
/// SCAT1(ID)=ELSCAT(ID)
/// ABSO1L(ID)=0.
/// EMIS1L(ID)=0.
/// END DO
///
/// FR=FREQ(IJ)
/// FRINV=UN/FR
/// FR3INV=FRINV*FRINV*FRINV
/// DO ID=1,ND
/// XKF(ID)=EXP(-HKT1(ID)*FR)
/// XKF1(ID)=UN-XKF(ID)
/// XKFB(ID)=XKF(ID)*BNUE(IJ)
/// END DO
/// ```
pub fn opacfl_init(
ij: usize,
nd: usize,
freq: &[f64],
bnue: &[f64],
hkt1: &[f64],
elscat: &[f64],
) -> OpacflOutput {
// 初始化输出
let mut output = OpacflOutput {
abso1: vec![0.0; nd],
emis1: vec![0.0; nd],
scat1: vec![0.0; nd],
abso1l: vec![0.0; nd],
emis1l: vec![0.0; nd],
xkf: vec![0.0; nd],
xkf1: vec![0.0; nd],
xkfb: vec![0.0; nd],
};
// 初始化基本量
for id in 0..nd {
output.abso1[id] = elscat[id];
output.emis1[id] = 0.0;
output.scat1[id] = elscat[id];
output.abso1l[id] = 0.0;
output.emis1l[id] = 0.0;
}
// 基本频率和深度相关量
let fr = freq[ij];
let _frinv = UN / fr;
let _fr3inv = _frinv * _frinv * _frinv;
for id in 0..nd {
output.xkf[id] = (-hkt1[id] * fr).exp();
output.xkf1[id] = UN - output.xkf[id];
output.xkfb[id] = output.xkf[id] * bnue[ij];
}
output
}
/// 完成不透明度计算的最后步骤。
///
/// # 参数
/// - `output`: 部分计算的输出(会被修改)
///
/// # Fortran 原始代码
///
/// ```fortran
/// DO ID=1,ND
/// ABSO1(ID)=ABSO1(ID)-EMIS1(ID)*XKF(ID)
/// EMIS1(ID)=EMIS1(ID)*XKFB(ID)
/// ABSO1L(ID)=ABSO1L(ID)-EMIS1L(ID)*XKF(ID)
/// EMIS1L(ID)=EMIS1L(ID)*XKFB(ID)
/// ABSO1L(ID)=ABSO1(ID)-ABSO1L(ID)
/// EMIS1L(ID)=EMIS1(ID)-EMIS1L(ID)
/// END DO
/// ```
pub fn opacfl_finalize(output: &mut OpacflOutput) {
let nd = output.abso1.len();
for id in 0..nd {
output.abso1[id] -= output.emis1[id] * output.xkf[id];
output.emis1[id] *= output.xkfb[id];
output.abso1l[id] -= output.emis1l[id] * output.xkf[id];
output.emis1l[id] *= output.xkfb[id];
output.abso1l[id] = output.abso1[id] - output.abso1l[id];
output.emis1l[id] = output.emis1[id] - output.emis1l[id];
}
}
// ============================================================================
// 辅助函数
// ============================================================================
/// 计算自由-自由不透明度氢型Gaunt 因子 = 1
///
/// # Fortran 原始代码
///
/// ```fortran
/// IF(IT.EQ.1) THEN
/// DO ID=1,ND
/// SF1=SFF3(ION,ID)*FR3INV
/// SF2=SFF2(ION,ID)
/// IF(FR.LT.FF(ION)) SF2=UN/XKF(ID)
/// ABSOFF=SF1*SF2
/// ABSO1(ID)=ABSO1(ID)+ABSOFF
/// EMIS1(ID)=EMIS1(ID)+ABSOFF
/// END DO
/// ```
pub fn free_free_hydrogenic(
nd: usize,
sff2: &[f64],
sff3: &[f64],
fr: f64,
fr3inv: f64,
ff_ion: f64,
xkf: &[f64],
abso1: &mut [f64],
emis1: &mut [f64],
) {
for id in 0..nd {
let sf1 = sff3[id] * fr3inv;
let mut sf2 = sff2[id];
if fr < ff_ion {
sf2 = UN / xkf[id];
}
let absoff = sf1 * sf2;
abso1[id] += absoff;
emis1[id] += absoff;
}
}
/// 计算自由-自由不透明度(氢型,精确 Gaunt 因子)。
///
/// # Fortran 原始代码
///
/// ```fortran
/// ELSE IF(IT.EQ.2) THEN
/// DO ID=1,ND
/// SF1=SFF3(ION,ID)*FR3INV
/// SF2=SFF2(ION,ID)
/// IF(FR.LT.FF(ION)) SF2=UN/XKF(ID)
/// X=C14*CHARG2(ION)/FR
/// SF2=SF2-UN+GFREE1(ID,X)
/// ABSOFF=SF1*SF2
/// ABSO1(ID)=ABSO1(ID)+ABSOFF
/// EMIS1(ID)=EMIS1(ID)+ABSOFF
/// END DO
/// ```
pub fn free_free_hydrogenic_gaunt(
nd: usize,
sff2: &[f64],
sff3: &[f64],
fr: f64,
fr3inv: f64,
ff_ion: f64,
charg2_ion: f64,
xkf: &[f64],
gfree1_values: &[f64],
abso1: &mut [f64],
emis1: &mut [f64],
) {
let _x = C14 * charg2_ion / fr;
for id in 0..nd {
let sf1 = sff3[id] * fr3inv;
let mut sf2 = sff2[id];
if fr < ff_ion {
sf2 = UN / xkf[id];
}
sf2 = sf2 - UN + gfree1_values[id];
let absoff = sf1 * sf2;
abso1[id] += absoff;
emis1[id] += absoff;
}
}
/// 计算束缚-自由贡献(简化版)。
///
/// # 参数
/// - `sg`: 截面
/// - `abtra`: 吸收跃迁矩阵值
/// - `emtra`: 发射跃迁矩阵值
/// - `iluctr`: 跃迁控制标志
/// - `output`: 输出结构体
///
/// # Fortran 原始代码
///
/// ```fortran
/// EMISBF=SGD*EMTRA(ITR,ID)
/// ABSO1(ID)=ABSO1(ID)+SGD*ABTRA(ITR,ID)
/// EMIS1(ID)=EMIS1(ID)+EMISBF
/// if(iluctr(itr).gt.0) then
/// ABSO1L(ID)=ABSO1L(ID)+SGD*ABTRA(ITR,ID)
/// EMIS1L(ID)=EMIS1L(ID)+EMISBF
/// end if
/// ```
pub fn bound_free_contribution(
nd: usize,
sgd: f64,
abtra: &[f64],
emtra: &[f64],
iluctr: i32,
output: &mut OpacflOutput,
) {
for id in 0..nd {
let emisbf = sgd * emtra[id];
output.abso1[id] += sgd * abtra[id];
output.emis1[id] += emisbf;
if iluctr > 0 {
output.abso1l[id] += sgd * abtra[id];
output.emis1l[id] += emisbf;
}
}
}
/// 计算谱线贡献(主线索引)。
///
/// # Fortran 原始代码
///
/// ```fortran
/// IF(IJLIN(IJ).GT.0) THEN
/// ITR=IJLIN(IJ)
/// ...
/// DO ID=1,ND
/// SG=PRFLIN(ID,IJ)
/// ABSO1(ID)=ABSO1(ID)+SG*ABTRA(ITR,ID)
/// EMIS1(ID)=EMIS1(ID)+SG*EMTRA(ITR,ID)
/// END DO
/// ```
pub fn line_contribution_primary(
nd: usize,
prflin_ij: &[f64],
abtra_itr: &[f64],
emtra_itr: &[f64],
output: &mut OpacflOutput,
) {
for id in 0..nd {
let sg = prflin_ij[id];
output.abso1[id] += sg * abtra_itr[id];
output.emis1[id] += sg * emtra_itr[id];
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_opacfl_output_init() {
let output = OpacflOutput::default();
assert!(output.abso1.is_empty());
assert!(output.emis1.is_empty());
assert!(output.scat1.is_empty());
}
#[test]
fn test_opacfl_init_basic() {
let nd = 3;
let freq = vec![3.0e15; 100];
let bnue = vec![1e-10; 100];
let hkt1 = vec![4.8e-11; nd];
let elscat = vec![0.1, 0.2, 0.3];
let output = opacfl_init(50, nd, &freq, &bnue, &hkt1, &elscat);
assert_eq!(output.abso1.len(), nd);
assert_eq!(output.emis1.len(), nd);
assert_eq!(output.scat1.len(), nd);
// 检查初始化
assert!((output.abso1[0] - 0.1).abs() < 1e-10);
assert!((output.scat1[0] - 0.1).abs() < 1e-10);
assert!((output.emis1[0]).abs() < 1e-10);
// 检查频率相关量
let fr = freq[50];
for id in 0..nd {
let expected_xkf = (-hkt1[id] * fr).exp();
assert!((output.xkf[id] - expected_xkf).abs() < 1e-10);
assert!((output.xkf1[id] - (1.0 - expected_xkf)).abs() < 1e-10);
assert!((output.xkfb[id] - expected_xkf * bnue[50]).abs() < 1e-15);
}
}
#[test]
fn test_opacfl_finalize() {
let mut output = OpacflOutput {
abso1: vec![1.0, 2.0, 3.0],
emis1: vec![0.5, 1.0, 1.5],
scat1: vec![0.1, 0.2, 0.3],
abso1l: vec![0.2, 0.4, 0.6],
emis1l: vec![0.1, 0.2, 0.3],
xkf: vec![0.5, 0.5, 0.5],
xkf1: vec![0.5, 0.5, 0.5],
xkfb: vec![1e-10, 1e-10, 1e-10],
};
opacfl_finalize(&mut output);
// 检查计算结果
// abso1 = abso1 - emis1 * xkf
assert!((output.abso1[0] - (1.0 - 0.5 * 0.5)).abs() < 1e-10);
// emis1 = emis1 * xkfb
assert!((output.emis1[0] - 0.5 * 1e-10).abs() < 1e-15);
}
#[test]
fn test_free_free_hydrogenic() {
let nd = 3;
let sff2 = vec![1.0, 1.0, 1.0];
let sff3 = vec![1e-25, 1e-25, 1e-25];
let fr = 3.0e15;
let fr3inv = 1.0 / (fr * fr * fr);
let ff_ion = 4.0e15; // fr < ff_ion
let xkf = vec![0.5, 0.5, 0.5];
let mut abso1 = vec![0.0; nd];
let mut emis1 = vec![0.0; nd];
free_free_hydrogenic(
nd, &sff2, &sff3, fr, fr3inv, ff_ion, &xkf, &mut abso1, &mut emis1,
);
// 由于 fr < ff_ion, sf2 = 1/xkf = 2.0
let expected = sff3[0] * fr3inv * 2.0;
assert!((abso1[0] - expected).abs() < 1e-35);
assert!((emis1[0] - expected).abs() < 1e-35);
}
#[test]
fn test_bound_free_contribution() {
let nd = 3;
let sgd = 1e-18;
let abtra = vec![1e10, 2e10, 3e10];
let emtra = vec![0.5e10, 1e10, 1.5e10];
let mut output = OpacflOutput {
abso1: vec![1.0; nd],
emis1: vec![0.5; nd],
scat1: vec![0.1; nd],
abso1l: vec![0.0; nd],
emis1l: vec![0.0; nd],
xkf: vec![0.5; nd],
xkf1: vec![0.5; nd],
xkfb: vec![1e-10; nd],
};
bound_free_contribution(nd, sgd, &abtra, &emtra, 1, &mut output);
// 检查吸收和发射系数增加
assert!((output.abso1[0] - (1.0 + sgd * abtra[0])).abs() < 1e-10);
assert!((output.emis1[0] - (0.5 + sgd * emtra[0])).abs() < 1e-10);
// 由于 iluctr > 0, 应该更新 abso1l 和 emis1l
assert!((output.abso1l[0] - sgd * abtra[0]).abs() < 1e-10);
assert!((output.emis1l[0] - sgd * emtra[0]).abs() < 1e-10);
}
#[test]
fn test_line_contribution_primary() {
let nd = 3;
let prflin_ij = vec![1e-15, 2e-15, 3e-15];
let abtra_itr = vec![1e10, 2e10, 3e10];
let emtra_itr = vec![0.5e10, 1e10, 1.5e10];
let mut output = OpacflOutput {
abso1: vec![1.0; nd],
emis1: vec![0.5; nd],
scat1: vec![0.1; nd],
abso1l: vec![0.0; nd],
emis1l: vec![0.0; nd],
xkf: vec![0.5; nd],
xkf1: vec![0.5; nd],
xkfb: vec![1e-10; nd],
};
line_contribution_primary(nd, &prflin_ij, &abtra_itr, &emtra_itr, &mut output);
// 检查谱线贡献
for id in 0..nd {
let expected_abso = 1.0 + prflin_ij[id] * abtra_itr[id];
let expected_emis = 0.5 + prflin_ij[id] * emtra_itr[id];
assert!((output.abso1[id] - expected_abso).abs() < 1e-10);
assert!((output.emis1[id] - expected_emis).abs() < 1e-10);
}
}
}