//! 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, /// Wavelength array (Angstroms) pub wlam: Vec, /// Emergent flux at each frequency [nfreq] pub flux: Vec, /// Frequency weights for equivalent width [nfreq] pub w: Vec, /// 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, /// Observed wavelengths (window mode) pub wlobs: Vec, } /// 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); } } }