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

322 lines
9.8 KiB
Rust

//! Continuous opacity, emissivity, and scattering coefficients.
//!
//! Translated from SYNSPEC54.FOR subroutine OPACON(ID,CROSS,ABSOC,EMISC,SCATC)
//! at line 12661.
//!
//! Computes absorption, emission, and scattering coefficients at a given
//! depth point for continuum frequencies.
use super::dwnfr1::{dwnfr1, Dwnfr1Params};
use super::gfree::gfree;
use super::sffhmi::sffhmi;
/// Parameters for continuous opacity calculation.
pub struct OpaconParams<'a> {
/// Depth index
pub id: usize,
/// Temperature at each depth (K)
pub temp: &'a [f64],
/// Electron density at each depth (cm^-3)
pub elec: &'a [f64],
/// Continuum frequencies
pub freqc: &'a [f64],
/// Number of continuum frequencies
pub nfreqc: usize,
/// Number of ions
pub nion: usize,
/// First level index for each ion
pub nfirst: &'a [usize],
/// Last level index for each ion
pub nlast: &'a [usize],
/// Next ion index for each ion
pub nnext: &'a [usize],
/// Free-free flag for each ion
pub ifree: &'a [i32],
/// IELHM index (H- element)
pub ielhm: usize,
/// Ionic charge for each element
pub iz: &'a [i32],
/// Element index for each level
pub iel: &'a [usize],
/// Occupation probability flag for each level
pub ifwop: &'a [i32],
/// Population of each level at current depth
pub popul_lev: &'a [f64],
/// Statistical weight of each level
pub g: &'a [f64],
/// Ionization energy of each level (erg)
pub enion: &'a [f64],
/// Occupation probability for each level
pub wop: &'a [f64],
/// Dissolved fraction frequency for each level
pub fropc: &'a [f64],
/// Dissolved fraction index for each level
pub indexp: &'a [i32],
/// Z3 array for dissolved fraction
pub z3: &'a [f64],
/// ELEC23 array for dissolved fraction
pub elec23: &'a [f64],
/// DWC1 array for dissolved fraction
pub dwc1: &'a [f64],
/// DWC2 array for dissolved fraction
pub dwc2: &'a [f64],
/// DWC1 number of depth points
pub ndepth: usize,
/// Bergmann factor
pub bergfc: f64,
/// Planck function constant
pub bn: f64,
/// h/k constant
pub hk: f64,
/// Electron scattering cross-section
pub sige: f64,
/// Stimulated emission correction at current depth
pub stim: f64,
/// Planck function at current depth
pub plan: f64,
/// Photoionization cross-sections (nion x nfreqc)
pub cross: &'a [&'a [f64]],
/// Saha-Boltzmann factors for merged levels
pub sgmerg_fn: Option<Box<dyn Fn(usize, usize, f64) -> f64>>,
/// Additional opacity function
pub opadd_fn: Option<Box<dyn Fn(usize, f64) -> (f64, f64, f64)>>,
/// Lyman line function
pub lymlin_fn: Option<Box<dyn Fn(usize, f64) -> (f64, f64, f64)>>,
/// Photoionization function
pub phtion_fn: Option<Box<dyn Fn(usize, &mut [f64], &mut [f64], &[f64], usize)>>,
/// Photoionization function (extended)
pub phtx_fn: Option<Box<dyn Fn(usize, &mut [f64], &mut [f64], &[f64], i32)>>,
}
/// Result of continuous opacity calculation.
pub struct OpaconResult {
/// Absorption coefficient array
pub absoc: Vec<f64>,
/// Emission coefficient array
pub emisc: Vec<f64>,
/// Scattering coefficient array
pub scatc: Vec<f64>,
}
/// Continuous opacity, emissivity, and scattering coefficients.
///
/// Computes absorption, emission, and scattering coefficients at a given
/// depth point for continuum frequencies. Includes bound-free, free-free,
/// and additional opacity contributions.
///
/// # Arguments
/// * `params` - Calculation parameters
///
/// # Returns
/// Arrays of absorption, emission, and scattering coefficients.
pub fn opacon(params: &OpaconParams) -> OpaconResult {
let id = params.id;
let t = params.temp[id];
let ane = params.elec[id];
let t1 = 1.0 / t;
let hkt = params.hk * t1;
let tk = hkt / params.bn;
let srt = 1.0 / t.sqrt();
let sgff = 3.694e8 * srt;
let con = 2.0706e-16 * t1 * srt;
let sce = ane * params.sige;
let mut absoc = vec![0.0; params.nfreqc];
let mut emisc = vec![0.0; params.nfreqc];
let mut scatc = vec![0.0; params.nfreqc];
for ij in 0..params.nfreqc {
let fr = params.freqc[ij];
let fr15 = fr * 1e-15;
let bnu = params.bn * fr15 * fr15 * fr15;
let hkf = hkt * fr;
let mut abf = 0.0;
let mut ebf = 0.0;
let mut aff = 0.0;
for il in 0..params.nion {
let n0i = params.nfirst[il];
let n1i = params.nlast[il];
let nke = params.nnext[il];
let xn = params.popul_lev[nke];
// Bound-free contribution
for ii in n0i..=n1i {
let mut sg = 0.0;
if params.ifwop[ii] < 0 {
// Merged level
if let Some(ref f) = params.sgmerg_fn {
sg = f(ii, id, fr);
}
} else {
sg = params.cross[il][ij];
if sg <= 0.0 {
continue;
}
// Dissolved fraction correction
if params.indexp[ii] == 5 {
let izz = params.iz[params.iel[ii]] as usize;
let fr0 = params.enion[ii] / 6.6256e-27;
let dw1 = dwnfr1(&Dwnfr1Params {
fr,
fr0,
id,
izz,
z3: params.z3,
elec23: params.elec23,
dwc1: params.dwc1,
ndepth: params.ndepth,
dwc2: params.dwc2,
bergfc: params.bergfc,
});
sg *= dw1;
}
}
if params.popul_lev[ii] < 1e-20 || xn < 1e-20 {
continue;
}
abf += sg * params.popul_lev[ii];
let xx = sg * xn * (params.enion[ii] * tk - hkf).exp() * params.wop[ii];
let _ee = (params.enion[ii] * tk - hkf).exp();
ebf += xx * con * params.g[ii] / params.g[nke];
}
let it = params.ifree[il];
if it == 0 {
continue;
}
// Free-free contribution
let ie = il;
if ie == params.ielhm {
let sff = sffhmi(xn, fr, t);
aff += sff;
} else {
let ch = params.iz[il] as f64 * params.iz[il] as f64;
let sf1 = ch * xn * sgff / (fr * fr * fr);
let sff = if it == 2 {
let sg = gfree(t, fr / ch);
sf1 * sg
} else {
sf1
};
aff += sff;
}
}
// Additional opacities
let (abad, emad, scad) = if let Some(ref f) = params.opadd_fn {
f(id, fr)
} else {
(0.0, 0.0, 0.0)
};
// Lyman lines
let (ably, emly, scly) = if let Some(ref f) = params.lymlin_fn {
f(id, fr)
} else {
(0.0, 0.0, 0.0)
};
// Total opacity and emissivity
let x = (-hkf).exp();
let x1 = 1.0 - x;
let bne = bnu * x * ane;
absoc[ij] = abf + ane * (x1 * aff - ebf) + abad + ably;
emisc[ij] = bne * aff + bnu * ane * ebf + emad + emly;
scatc[ij] = scad + scly + sce;
}
// Photoionization contributions
if let Some(ref f) = params.phtion_fn {
f(id, &mut absoc, &mut emisc, params.freqc, params.nfreqc);
}
if let Some(ref f) = params.phtx_fn {
f(id, &mut absoc, &mut emisc, params.freqc, 1);
}
OpaconResult {
absoc,
emisc,
scatc,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_opacon_basic() {
let temp = [10000.0];
let elec = [1e13];
let freqc = [1e14, 2e14, 3e14];
let nfirst = [0];
let nlast = [0];
let nnext = [1];
let ifree = [1];
let iz = [1];
let iel = [0];
let ifwop = [1];
let popul_lev = [1e12, 1e10];
let g = [1.0, 2.0];
let enion = [0.0, 1e-11];
let wop = [1.0, 1.0];
let fropc = [0.0, 0.0];
let indexp = [0, 0];
let z3 = [1.0];
let elec23 = [2.15e-9];
let dwc1 = [0.0];
let dwc2 = [0.0];
let cross_row: &[f64] = &[1e-18, 2e-18, 3e-18];
let cross: &[&[f64]] = &[cross_row];
let params = OpaconParams {
id: 0,
temp: &temp,
elec: &elec,
freqc: &freqc,
nfreqc: 3,
nion: 1,
nfirst: &nfirst,
nlast: &nlast,
nnext: &nnext,
ifree: &ifree,
ielhm: 99,
iz: &iz,
iel: &iel,
ifwop: &ifwop,
popul_lev: &popul_lev,
g: &g,
enion: &enion,
wop: &wop,
fropc: &fropc,
indexp: &indexp,
z3: &z3,
elec23: &elec23,
dwc1: &dwc1,
dwc2: &dwc2,
ndepth: 1,
bergfc: 1.0,
bn: 1.0,
hk: 4.79928144e-11,
sige: 6.65e-25,
stim: 1.0,
plan: 1.0,
cross,
sgmerg_fn: None,
opadd_fn: None,
lymlin_fn: None,
phtion_fn: None,
phtx_fn: None,
};
let result = opacon(&params);
assert_eq!(result.absoc.len(), 3);
assert_eq!(result.emisc.len(), 3);
assert_eq!(result.scatc.len(), 3);
assert!(result.absoc[0].is_finite());
assert!(result.scatc[0].is_finite());
}
}