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>
203 lines
6.0 KiB
Rust
203 lines
6.0 KiB
Rust
//! Output of synthetic spectrum.
|
|
//!
|
|
//! Translated from SYNSPEC `OUTPRI` subroutine (synspec54.f:3343).
|
|
//!
|
|
//! Outputs synthetic spectrum to file units, computes equivalent widths.
|
|
|
|
/// Physical constants
|
|
const CAS: f64 = 1.0 / 2.997925e18;
|
|
const EQWC: f64 = 1.19917e22;
|
|
|
|
/// Parameters for OUTPRI calculation
|
|
pub struct OutpriParams {
|
|
/// Number of frequencies
|
|
pub nfreq: usize,
|
|
/// Frequency array (Hz)
|
|
pub freq: Vec<f64>,
|
|
/// Wavelength array (Angstroms)
|
|
pub wlam: Vec<f64>,
|
|
/// Emergent flux at each frequency [nfreq]
|
|
pub flux: Vec<f64>,
|
|
/// Frequency weights for equivalent width [nfreq]
|
|
pub w: Vec<f64>,
|
|
/// Print level
|
|
pub iprin: i32,
|
|
/// Blank flag
|
|
pub iblank: i32,
|
|
/// Number of blanks
|
|
pub nblank: i32,
|
|
/// Window mode flag
|
|
pub ifwin: i32,
|
|
/// Number of observed frequencies (window mode)
|
|
pub nfrobs: usize,
|
|
/// Observed frequencies (window mode)
|
|
pub frqobs: Vec<f64>,
|
|
/// Observed wavelengths (window mode)
|
|
pub wlobs: Vec<f64>,
|
|
}
|
|
|
|
/// Result of OUTPRI calculation
|
|
pub struct OutpriResult {
|
|
/// Equivalent width for this set (mA)
|
|
pub eqw: f64,
|
|
/// Positive equivalent width for this set (mA)
|
|
pub eqwp: f64,
|
|
/// Total equivalent width (mA)
|
|
pub eqwt: f64,
|
|
/// Total positive equivalent width (mA)
|
|
pub eqwtp: f64,
|
|
/// Spectrum data: (wavelength, flux_lambda) pairs
|
|
pub spectrum: Vec<(f64, f64)>,
|
|
/// Continuum data: (wavelength, flux_lambda) pairs
|
|
pub continuum: Vec<(f64, f64)>,
|
|
}
|
|
|
|
/// Output of synthetic spectrum.
|
|
///
|
|
/// Computes emergent flux in lambda units, writes spectrum and continuum,
|
|
/// and calculates equivalent widths.
|
|
pub fn outpri(params: &OutpriParams, eqwt_in: f64, eqwtp_in: f64) -> OutpriResult {
|
|
let nfreq = params.nfreq;
|
|
let freq = ¶ms.freq;
|
|
let wlam = ¶ms.wlam;
|
|
let flux = ¶ms.flux;
|
|
let w = ¶ms.w;
|
|
|
|
let mut spectrum = Vec::new();
|
|
let mut continuum = Vec::new();
|
|
let mut eqw = 0.0;
|
|
let mut eqwp = 0.0;
|
|
let mut eqwt = eqwt_in;
|
|
let mut eqwtp = eqwtp_in;
|
|
|
|
if params.ifwin <= 0 {
|
|
// Output synthetic spectrum
|
|
for ij in 2..nfreq - 1 {
|
|
let flam = flux[ij] * freq[ij] * freq[ij] * CAS;
|
|
spectrum.push((wlam[ij], flam));
|
|
}
|
|
|
|
// Output continuum flux
|
|
let flam = flux[0] * freq[0] * freq[0] * CAS;
|
|
continuum.push((wlam[0], flam));
|
|
if params.iblank == params.nblank {
|
|
let flam = flux[nfreq - 1] * freq[nfreq - 1] * freq[nfreq - 1] * CAS;
|
|
spectrum.push((wlam[nfreq - 1], flam));
|
|
let flam = flux[1] * freq[1] * freq[1] * CAS;
|
|
continuum.push((wlam[1], flam));
|
|
}
|
|
} else {
|
|
// Window mode
|
|
for ij in 0..params.nfrobs {
|
|
let flam = flux[ij] * params.frqobs[ij] * params.frqobs[ij] * CAS * 0.5;
|
|
let flam = flam.max(1.0e-40);
|
|
spectrum.push((params.wlobs[ij], flam));
|
|
}
|
|
}
|
|
|
|
// Compute equivalent widths
|
|
if params.iprin >= 3 {
|
|
let xx = 1.0 / (freq[1] - freq[0]);
|
|
let xxx = 1.0 / (freq[0] + freq[1]) / (freq[0] + freq[1]);
|
|
|
|
if params.ifwin <= 0 {
|
|
for ij in 0..nfreq {
|
|
let _flam = flux[ij] * freq[ij] * freq[ij] * CAS;
|
|
let cont = ((freq[ij] - freq[0]) * flux[1]
|
|
+ (freq[1] - freq[ij]) * flux[0])
|
|
* xx;
|
|
let re0 = flux[ij] / cont;
|
|
eqw += (1.0 - re0) * w[ij];
|
|
let rep = re0.min(1.0);
|
|
eqwp += (1.0 - rep) * w[ij];
|
|
}
|
|
} else {
|
|
for ij in 0..params.nfrobs {
|
|
let _flam = flux[ij] * freq[ij] * freq[ij] * CAS;
|
|
let cont = ((params.frqobs[ij] - freq[0]) * flux[1]
|
|
+ (freq[1] - params.frqobs[ij]) * flux[0])
|
|
* xx;
|
|
let re0 = flux[ij] / cont;
|
|
eqw += (1.0 - re0) * w[ij];
|
|
let rep = re0.min(1.0);
|
|
eqwp += (1.0 - rep) * w[ij];
|
|
}
|
|
}
|
|
|
|
eqw = eqw * EQWC * xxx;
|
|
eqwt += eqw;
|
|
eqwp = eqwp * EQWC * xxx;
|
|
eqwtp += eqwp;
|
|
}
|
|
|
|
OutpriResult {
|
|
eqw,
|
|
eqwp,
|
|
eqwt,
|
|
eqwtp,
|
|
spectrum,
|
|
continuum,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_outpri_basic() {
|
|
let nfreq = 5;
|
|
let params = OutpriParams {
|
|
nfreq,
|
|
freq: vec![1.0e15, 2.0e15, 3.0e15, 4.0e15, 5.0e15],
|
|
wlam: vec![3000.0, 2000.0, 1500.0, 1200.0, 1000.0],
|
|
flux: vec![1.0e-10, 2.0e-10, 3.0e-10, 4.0e-10, 5.0e-10],
|
|
w: vec![0.1, 0.2, 0.3, 0.4, 0.5],
|
|
iprin: 0,
|
|
iblank: 1,
|
|
nblank: 1,
|
|
ifwin: 0,
|
|
nfrobs: 0,
|
|
frqobs: vec![],
|
|
wlobs: vec![],
|
|
};
|
|
|
|
let result = outpri(¶ms, 0.0, 0.0);
|
|
// When iblank == nblank, extra points are added:
|
|
// spectrum: ij=2,3 from loop + nfreq-1=4 from iblank==nblank
|
|
assert_eq!(result.spectrum.len(), 3);
|
|
// continuum: ij=0 from main path + ij=1 from iblank==nblank
|
|
assert_eq!(result.continuum.len(), 2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_outpri_flux_conversion() {
|
|
// Verify FLAM = FLUX * FREQ^2 * CAS
|
|
let flux_val = 1.0e-10;
|
|
let freq_val = 3.0e15;
|
|
let expected_flam = flux_val * freq_val * freq_val * CAS;
|
|
|
|
let nfreq = 3;
|
|
let params = OutpriParams {
|
|
nfreq,
|
|
freq: vec![1.0e15, 2.0e15, 3.0e15],
|
|
wlam: vec![3000.0, 2000.0, 1500.0],
|
|
flux: vec![flux_val, flux_val, flux_val],
|
|
w: vec![0.1, 0.2, 0.3],
|
|
iprin: 0,
|
|
iblank: 1,
|
|
nblank: 1,
|
|
ifwin: 0,
|
|
nfrobs: 0,
|
|
frqobs: vec![],
|
|
wlobs: vec![],
|
|
};
|
|
|
|
let result = outpri(¶ms, 0.0, 0.0);
|
|
// Check that flux conversion is correct
|
|
if let Some((_, flam)) = result.spectrum.first() {
|
|
assert!((flam - expected_flam).abs() / expected_flam < 1.0e-10);
|
|
}
|
|
}
|
|
}
|