新增 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>
249 lines
6.4 KiB
Rust
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);
|
|
}
|
|
}
|
|
}
|