SpectraRust/src/synspec/math/idmtab.rs
fmq 0f97c0b05b feat: 添加 TLUSTY 新模块 + 修复编译错误
新增 TLUSTY 模块:
- crossd: 光电离截面评估 (bound-free cross section)
- sgmer0: 合并能级光电离截面初始化
- sgmerd: 合并能级光电离截面计算
- dwnfr0: 频率网格下载 (continuum)
- convc1: 对流收敛控制 (radiative)
- chckse: 统计平衡检查 (rates)

扩展 RESOLV 编排器:
- 添加 Feautrier 形式解
- 添加 Lucy 温度修正
- 添加 ROSSTD/PZEVAL/CONOUT 调用
- 添加 IFPOPR=2 占据数更新
- 添加 HESOL6 流体静力平衡修正

修复:
- sgmer0.rs: 修复 config 未声明为 mut 的编译错误
- crossd.rs: 修复测试中使用错误字段路径的问题
  (frqall.ijbf/phoexp.aijbf/phoexp.bfcs 而非 obfpar)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 12:35:09 +08:00

249 lines
6.4 KiB
Rust

//! Output of selected molecular line parameters (identification table).
//!
//! Translated from SYNSPEC54.FOR subroutine IDMTAB (line 16380).
//!
//! Computes and formats molecular line parameters for the identification
//! table output, including equivalent widths and line strengths.
use crate::synspec::math::inibla::{CL, BOLK};
// ============================================================================
// Constants
// ============================================================================
/// Conversion factor: ln(10) for log-gf
const C1: f64 = 2.302_585_1;
/// Conversion factor: gf offset
const C2: f64 = 4.201_467_2;
/// Conversion factor: energy to temperature
const C3: f64 = 1.438_788_6;
/// Strength category labels
const APB: &str = " ";
const AP0: &str = " .";
const AP1: &str = " *";
const AP2: &str = " **";
const AP3: &str = " ***";
const AP4: &str = "****";
// ============================================================================
// IDMTAB parameters
// ============================================================================
/// Parameters for a single molecular line.
pub struct IdmtabLine {
/// Wavelength (Å)
pub alam: f64,
/// Molecule index
pub imol: usize,
/// Lower excitation potential (cm⁻¹)
pub excl: f64,
/// Log gf value
pub gfm: f64,
/// Van der Waals broadening
pub grm: f64,
/// Stark broadening
pub gsm: f64,
/// Van der Waals broadening (depth-dependent)
pub gvdw: f64,
/// Doppler width
pub dop1: f64,
/// Continuum opacity at line center
pub absta: f64,
/// Stimulated emission factor
pub stim: f64,
/// Molecular population ratio
pub rrmol: f64,
/// Temperature at standard depth (K)
pub temp: f64,
/// Electron density at standard depth
pub elec: f64,
}
/// Result of IDMTAB computation for a single line.
pub struct IdmtabResult {
/// Wavelength (Å)
pub alam: f64,
/// Molecule name
pub molecule: String,
/// Log gf
pub gf: f64,
/// Lower excitation energy (K)
pub excl_k: f64,
/// Line-to-continuum ratio (STR0)
pub str0: f64,
/// Equivalent width (mÅ)
pub eqw: f64,
/// Strength category label
pub apr: &'static str,
/// Depth index
pub id: usize,
/// Total broadening parameter
pub agam: f64,
}
// ============================================================================
// IDMTAB implementation
// ============================================================================
/// Compute molecular line parameters for the identification table.
///
/// For a given molecular line, computes the line strength, equivalent width,
/// and strength category.
///
/// # Fortran original
///
/// ```fortran
/// SUBROUTINE IDMTAB
/// DO IL0=1,NLINML
/// ...compute STR0, EQW, APR...
/// END DO
/// END
/// ```
pub fn idmtab_compute(line: &IdmtabLine) -> IdmtabResult {
let IdmtabLine {
alam, imol: _, excl, gfm, grm, gsm, gvdw,
dop1, absta, stim, rrmol, temp, elec,
} = *line;
// Total broadening parameter
// Fortran: AGAM=(GRM+GSM*ANE+GVDW)*DOP1
let agam = (grm + gsm * elec + gvdw) * dop1;
// Absorption at line center
// Fortran: ABCNT=EXP(GFM-EXCL/TEMP)*RRMOL*DOP1*STIM
let abcnt = (gfm - excl / temp).exp() * rrmol * dop1 * stim;
// Line-to-continuum ratio
// Fortran: STR0=ABCNT/ABSTA
let str0 = if absta > 0.0 { abcnt / absta } else { 0.0 };
// Log gf
let gf = (gfm + C2) / C1;
// Lower excitation energy in K
let excl_k = excl / C3;
// Equivalent width estimate
let ww1 = if str0 <= 1.2 {
0.886 * str0 * (1.0 - str0 * (0.707 - str0 * 0.577))
} else {
str0.ln().sqrt()
};
let ww1 = if str0 > 55.0 {
let ww2 = 0.5 * (std::f64::consts::PI * agam * str0).sqrt();
if ww2 > ww1 { ww2 } else { ww1 }
} else {
ww1
};
// Equivalent width in mÅ
// Fortran: EQW=ALAM/FREQ0*1.E3/DOP1*WW1
// Since ALAM is wavelength and we don't have FREQ0 directly,
// we use the relation: EQW ≈ ALAM * WW1 / (c/ALAM) / DOP1 * 1e3
// Simplified: EQW = ALAM^2 / CL * 1e3 / DOP1 * WW1
let eqw = alam * alam / CL * 1e3 / dop1 * ww1;
// Strength category
let str = eqw * 10.0;
let apr = if str >= 1e4 {
AP4
} else if str >= 1e3 {
AP3
} else if str >= 1e2 {
AP2
} else if str >= 1e1 {
AP1
} else if str >= 1e0 {
AP0
} else {
APB
};
IdmtabResult {
alam,
molecule: String::new(), // Filled by caller
gf,
excl_k,
str0,
eqw,
apr,
id: 0, // Filled by caller
agam,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_line() -> IdmtabLine {
IdmtabLine {
alam: 5000.0,
imol: 1,
excl: 10000.0,
gfm: -2.0,
grm: 0.1,
gsm: 0.01,
gvdw: 0.05,
dop1: 0.01,
absta: 1e-10,
stim: 1.0,
rrmol: 1e-5,
temp: 10000.0,
elec: 1e14,
}
}
#[test]
fn test_idmtab_basic() {
let line = create_test_line();
let result = idmtab_compute(&line);
assert!(result.alam > 0.0);
assert!(result.str0.is_finite());
assert!(result.eqw.is_finite());
assert!(result.eqw >= 0.0);
assert!(result.agam.is_finite());
}
#[test]
fn test_idmtab_weak_line() {
let mut line = create_test_line();
line.rrmol = 1e-20; // Very weak line
let result = idmtab_compute(&line);
// Weak line should have small STR0
assert!(result.str0 < 1.0);
assert_eq!(result.apr, APB);
}
#[test]
fn test_idmtab_strong_line() {
let mut line = create_test_line();
line.rrmol = 1e10; // Very strong line
line.absta = 1e-20;
let result = idmtab_compute(&line);
// Strong line should have large STR0
assert!(result.str0 > 1.0);
}
#[test]
fn test_idmtab_strength_categories() {
// Test that different strength values produce correct categories
let mut line = create_test_line();
// Weak line
line.rrmol = 1e-15;
let r = idmtab_compute(&line);
if r.str0 <= 1.2 {
// For weak lines, EQW is small
assert!(r.eqw < 1.0 || r.apr == APB);
}
}
}