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>
322 lines
9.8 KiB
Rust
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(¶ms);
|
|
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());
|
|
}
|
|
}
|