feat: 完善 SYNSPEC 不透明度调用链 + 新增旋转卷积和状态结构体

- 新增 rotin.rs: ROTINS 旋转卷积函数(含 kernel、interpolate_at 等辅助函数)
- 新增 synspec/state/: COMMON 块翻译(constants, model, params, wind)
- 重构 opac.rs: 连接 LINOP、MOLOP、HYDLIN、HE2LIN、PHTION、PHTX 调用
  - 新增 OpacLinopData、OpacHydlinData、OpacHe2linData 等可选数据结构
  - 支持 IHYL=0(插值模式)和 IHYL>0(详细模式)的氢线处理
  - 支持分子线不透明度(MOLOP)和光致电离(PHTION/PHTX)
- 更新 resolv.rs: 适配新的 OpacParams 签名
- 更新 he2lin.rs: 修复 minor import
- 更新 mod.rs: 导出 rotin 模块

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
fmq 2026-06-07 13:22:15 +08:00
parent 0f97c0b05b
commit 0dfe6facd6
10 changed files with 1707 additions and 30 deletions

View File

@ -50,6 +50,7 @@ const WLIN_FACTOR_HIGH: f64 = 227.7776;
// ============================================================================ // ============================================================================
/// Common input data for He II line opacity calculations. /// Common input data for He II line opacity calculations.
#[derive(Clone)]
pub struct He2Common<'a> { pub struct He2Common<'a> {
/// Depth index. /// Depth index.
pub id: usize, pub id: usize,

View File

@ -158,6 +158,7 @@ mod nstpar;
mod moleq; mod moleq;
mod russel; mod russel;
mod quit; mod quit;
mod rotin;
mod wgtjh1; mod wgtjh1;
mod xk2dop; mod xk2dop;
mod xenini; mod xenini;
@ -305,6 +306,7 @@ pub use partdv::{partdv, partdv_with_params, PartdvParams};
pub use partf::partf; pub use partf::partf;
pub use pfheav::pfheav; pub use pfheav::pfheav;
pub use quit::quit; pub use quit::quit;
pub use rotin::{kernel, rotins, RotinsResult};
pub use phe1::{phe1, Phe1Params}; pub use phe1::{phe1, Phe1Params};
pub use phe2::{phe2, Phe2Params, Phe2Output}; pub use phe2::{phe2, Phe2Params, Phe2Output};
pub use profil::{profil, LineBroadeningData, ProfilParams}; pub use profil::{profil, LineBroadeningData, ProfilParams};

View File

@ -4,8 +4,18 @@
//! //!
//! Calculates absorption, emission, and scattering coefficients //! Calculates absorption, emission, and scattering coefficients
//! at a given depth for multiple frequencies. //! at a given depth for multiple frequencies.
//!
//! This routine calls downstream functions for line opacity (LINOP),
//! molecular opacity (MOLOP), hydrogen lines (HYDLIN), He II lines (HE2LIN),
//! and photoionization (PHTION, PHTX).
use crate::synspec::math::{gfree, lymlin, LymlinParams}; use crate::synspec::math::{
gfree, he2lin, hydlin, linop, lymlin, molop, phtion, phtx,
He2linParams, HydlinParams, LinopParams, LymlinParams,
MolLineData, MolopConfig, MolopFreqParams, MolopModelState, MolopOutput,
PhtionParams, PhtionOutput, PhtxParams, PhtxOutput,
};
use crate::synspec::math::voigtk::MVOI;
use crate::tlusty::math::hydrogen::sffhmi; use crate::tlusty::math::hydrogen::sffhmi;
/// Physical constants /// Physical constants
@ -14,8 +24,134 @@ const TEN15: f64 = 1.0e-15;
const CSB: f64 = 2.0706e-16; const CSB: f64 = 2.0706e-16;
const CFF: f64 = 3.694e8; const CFF: f64 = 3.694e8;
/// Parameters for opacity calculation /// Line opacity data for LINOP call.
pub struct OpacParams { ///
/// Corresponds to Fortran COMMON/LINDAT/ data needed by OPAC → LINOP.
pub struct OpacLinopData<'a> {
/// Temperature at depth ID
pub temp: f64,
/// Total frequency count (NFREQS)
pub nfreqs: usize,
/// Frequency spacing factor (DFRCON)
pub dfrcon: f64,
/// Planck function at depth ID
pub plan: f64,
/// Stimulated emission correction
pub stim: f64,
/// Number of lines
pub nlin: usize,
/// Line data array
pub lines: &'a [crate::synspec::math::LineData],
/// RRR(ID,ION,IAT) — Saha/Boltzmann factors
pub rrr: &'a [f64],
/// RRR dimensions: (natom, nion)
pub rrr_dims: (usize, usize),
/// Doppler width DOPA1(IAT, ID)
pub dopa1: &'a [f64],
/// DOPA1 number of atoms
pub dopa1_nat: usize,
/// Level statistical weights G(level)
pub g: &'a [f64],
/// Level populations POPUL(level, ID)
pub popul: &'a [f64],
/// POPUL number of levels
pub popul_nlev: usize,
/// NLTE populations PNLT(IAT, ION, ID)
pub pnlt: &'a [f64],
/// PNLT dimensions: (natom, nion)
pub pnlt_dims: (usize, usize),
/// Ionization energies ENEV(IAT, ION)
pub enev: &'a [f64],
/// ENEV number of atoms
pub enev_nat: usize,
/// Level energies ENION(level)
pub enion: &'a [f64],
/// Laser deletion flag
pub lasdel: bool,
/// He II special line count
pub nsp: usize,
/// He II special line indices
pub isp0: &'a [usize],
/// Voigt table H0
pub h0tab: &'a [f64; MVOI],
/// Voigt table H1
pub h1tab: &'a [f64; MVOI],
/// Voigt table H2
pub h2tab: &'a [f64; MVOI],
}
/// Hydrogen line data for HYDLIN call.
pub struct OpacHydlinData {
/// Lower level for H lines
pub ilowh: i32,
/// Upper level limits
pub m10: usize,
pub m20: usize,
/// Turbulent velocity (cm/s)
pub vturb: f64,
/// wnHint factors
pub wn_hint: Vec<Vec<f64>>,
}
/// He II line data for HE2LIN call.
pub struct OpacHe2linData {
/// He II common data
pub common: crate::synspec::math::He2Common<'static>,
/// He II lowest series index
pub ilwhe2: usize,
/// Max principal quantum number
pub mhe10: usize,
/// Upper limit for He II lines
pub mhe20: usize,
}
/// Molecular opacity data for MOLOP call.
pub struct OpacMolopData<'a> {
/// MOLOP configuration
pub config: &'a MolopConfig,
/// Molecular line data
pub line_data: &'a MolLineData<'a>,
/// Voigt tables (H0, H1, H2)
pub voigt_tables: (&'a [f64; MVOI], &'a [f64; MVOI], &'a [f64; MVOI]),
}
/// Photoionization data for PHTION call.
pub struct OpacPhtionData<'a> {
/// Photoionization cross-section data
pub photcs: &'a crate::synspec::math::PhotcsData,
/// Atomic number density RRR
pub rrr: &'a [f64],
/// Level populations POPUL
pub popul: &'a [f64],
}
/// Extended photoionization data for PHTX call.
pub struct OpacPhtxData<'a> {
/// Level photoionization data
pub level_photo: &'a crate::synspec::math::LevelPhotoData,
/// H/He photoionization data
pub hhe_photo: &'a crate::synspec::math::HhePhotoData,
/// Level populations POPUL
pub popul: &'a [f64],
/// Atomic number density RRR
pub rrr: &'a [f64],
/// Total frequency count
pub nfreq_total: usize,
/// Continuous frequency count
pub nfreqc: usize,
/// IASV flag
pub iasv: i32,
/// NQHT flag
pub nqht: i32,
/// PHTX state for interpolation caching
pub state: &'a mut crate::synspec::math::PhtxState,
}
/// Parameters for opacity calculation.
///
/// Includes optional data for downstream functions (LINOP, MOLOP, HYDLIN,
/// HE2LIN, PHTION, PHTX). When `None`, the corresponding call is skipped.
pub struct OpacParams<'a> {
/// Depth index /// Depth index
pub id: usize, pub id: usize,
/// Temperature at depth ID (K) /// Temperature at depth ID (K)
@ -39,6 +175,14 @@ pub struct OpacParams {
pub icontl: i32, pub icontl: i32,
/// Lyman line treatment switch /// Lyman line treatment switch
pub iophli: i32, pub iophli: i32,
/// Hydrogen line mode (0=interpolated, >0=detailed)
pub ihyll: i32,
/// He II line switch
pub ihe2l: i32,
/// Molecular line switch
pub ifmol: i32,
/// Number of molecular lists
pub nmlist: usize,
/// H atom index /// H atom index
pub iath: i32, pub iath: i32,
/// H ground level population /// H ground level population
@ -57,6 +201,22 @@ pub struct OpacParams {
pub bn: f64, pub bn: f64,
/// wnHint factors for Lyman lines /// wnHint factors for Lyman lines
pub wn_hint: [f64; 5], pub wn_hint: [f64; 5],
/// RELOP factor for AVAB calculation
pub relop: f64,
// --- Downstream function data (optional) ---
/// Line opacity data for LINOP
pub linop_data: Option<OpacLinopData<'a>>,
/// Hydrogen line data for HYDLIN
pub hydlin_data: Option<OpacHydlinData>,
/// He II line data for HE2LIN
pub he2lin_data: Option<OpacHe2linData>,
/// Molecular opacity data for MOLOP
pub molop_data: Option<OpacMolopData<'a>>,
/// Photoionization data for PHTION
pub phtion_data: Option<OpacPhtionData<'a>>,
/// Extended photoionization data for PHTX
pub phtx_data: Option<OpacPhtxData<'a>>,
} }
/// Result of opacity calculation /// Result of opacity calculation
@ -78,11 +238,11 @@ pub struct OpacResult {
/// including continuum, line, and molecular contributions. /// including continuum, line, and molecular contributions.
/// ///
/// # Arguments /// # Arguments
/// * `params` - Input parameters /// * `params` - Input parameters (including optional downstream data)
/// ///
/// # Returns /// # Returns
/// Opacity coefficients for all frequencies /// Opacity coefficients for all frequencies
pub fn opac(params: &OpacParams) -> OpacResult { pub fn opac(params: &mut OpacParams) -> OpacResult {
let nfreq = params.nfreq; let nfreq = params.nfreq;
let mut abso = vec![0.0; nfreq]; let mut abso = vec![0.0; nfreq];
let mut emis = vec![0.0; nfreq]; let mut emis = vec![0.0; nfreq];
@ -102,7 +262,7 @@ pub fn opac(params: &OpacParams) -> OpacResult {
let ane = params.ane; let ane = params.ane;
let t1 = UN / t; let t1 = UN / t;
let hkt = params.hkt; let hkt = params.hkt;
let tk = hkt / params.hk; let _tk = hkt / params.hk;
let srt = UN / t.sqrt(); let srt = UN / t.sqrt();
let sgff = CFF * srt; let sgff = CFF * srt;
let con = CSB * t1 * srt; let con = CSB * t1 * srt;
@ -144,8 +304,7 @@ pub fn opac(params: &OpacParams) -> OpacResult {
let sffhmi_val = sffhmi(params.pop_h, fr, t); let sffhmi_val = sffhmi(params.pop_h, fr, t);
aff += sffhmi_val; aff += sffhmi_val;
// Additional opacities // Additional opacities (OPADD placeholder)
// Note: Full OPADD call would need CIA data
let abad = 0.0; let abad = 0.0;
let emad = 0.0; let emad = 0.0;
let scad = 0.0; let scad = 0.0;
@ -185,7 +344,7 @@ pub fn opac(params: &OpacParams) -> OpacResult {
} }
} }
let avab = (abso[0] + abso[1] + scat[0] + scat[1]) * 0.5; let avab = (abso[0] + abso[1] + scat[0] + scat[1]) * 0.5 * params.relop;
if nfreq <= 2 || params.imode == -1 { if nfreq <= 2 || params.imode == -1 {
return OpacResult { return OpacResult {
@ -196,29 +355,214 @@ pub fn opac(params: &OpacParams) -> OpacResult {
}; };
} }
// Interpolated continuum for all frequencies // Determine M (start index for line accumulation)
let m: usize = if params.icontl == 1 { 1 } else { 3 };
if params.imode != 2 { if params.imode != 2 {
// Interpolated continuum for all frequencies
for ij in 2..nfreq { for ij in 2..nfreq {
abso[ij] = params.frx1[ij] * abso[1] + params.frx2[ij] * abso[0]; abso[ij] = params.frx1[ij] * abso[1] + params.frx2[ij] * abso[0];
emis[ij] = params.frx1[ij] * emis[1] + params.frx2[ij] * emis[0]; emis[ij] = params.frx1[ij] * emis[1] + params.frx2[ij] * emis[0];
scat[ij] = params.frx1[ij] * scat[1] + params.frx2[ij] * scat[0]; scat[ij] = params.frx1[ij] * scat[1] + params.frx2[ij] * scat[0];
} }
// Line opacity (LINOP) - placeholder // Hydrogen lines for IHYL=0 (interpolated mode)
// TODO: Implement LINOP call when translated if params.ihyll == 0 {
if let Some(ref hyd_data) = params.hydlin_data {
// Molecular line opacity (MOLOP) - placeholder let hyd_params = HydlinParams {
// TODO: Implement MOLOP call when translated id: params.id,
i0: 0,
i1: 1,
nfreq,
wlam: params.wlam.clone(),
freq: params.freq.clone(),
t,
ane,
iath: params.iath,
ilowh: hyd_data.ilowh,
m10: hyd_data.m10,
m20: hyd_data.m20,
pop_h: params.pop_h,
pop_h_cont: params.pop_h_cont,
vturb: hyd_data.vturb,
wn_hint: hyd_data.wn_hint.clone(),
};
let result = hydlin(&hyd_params);
for ij in m..nfreq {
abso[ij] += params.frx1[ij] * result.absoh[1]
+ params.frx2[ij] * result.absoh[0];
emis[ij] += params.frx1[ij] * result.emish[1]
+ params.frx2[ij] * result.emish[0];
}
}
} }
// Detailed hydrogen line opacity // Line opacity (LINOP)
// TODO: Implement HYDLIN call when translated if let Some(ref linop_d) = params.linop_data {
let linop_params = LinopParams {
id: params.id,
temp: linop_d.temp,
nfreq,
freq: &params.freq,
nfreqs: linop_d.nfreqs,
dfrcon: linop_d.dfrcon,
avab,
plan: linop_d.plan,
stim: linop_d.stim,
nlin: linop_d.nlin,
lines: linop_d.lines,
rrr: linop_d.rrr,
rrr_dims: linop_d.rrr_dims,
dopa1: linop_d.dopa1,
dopa1_nat: linop_d.dopa1_nat,
g: linop_d.g,
popul: linop_d.popul,
popul_nlev: linop_d.popul_nlev,
pnlt: linop_d.pnlt,
pnlt_dims: linop_d.pnlt_dims,
enev: linop_d.enev,
enev_nat: linop_d.enev_nat,
enion: linop_d.enion,
lasdel: linop_d.lasdel,
nsp: linop_d.nsp,
isp0: linop_d.isp0,
h0tab: linop_d.h0tab,
h1tab: linop_d.h1tab,
h2tab: linop_d.h2tab,
phe1_data: None,
phe2_common: None,
};
let result = linop(&linop_params);
for ij in 3..nfreq {
abso[ij] += result.ablin[ij];
emis[ij] += result.emlin[ij];
}
}
// Detailed He II line opacity // Molecular line opacity (MOLOP)
// TODO: Implement HE2LIN call when translated if params.ifmol > 0 {
if let Some(ref mol_d) = params.molop_data {
for ilist in 0..params.nmlist {
let mol_params = MolopFreqParams {
nfreq,
nfreqs: nfreq,
freq: &params.freq,
};
let result = molop(
mol_d.config,
&MolopModelState {
temp: t,
elec: ane,
stim: 1.0,
plan: params.plan,
mol: crate::synspec::math::MolModelState {
rrmol: &[],
dopmol: &[],
},
},
mol_d.line_data,
&mol_params,
avab,
mol_d.voigt_tables,
);
for ij in 3..nfreq {
abso[ij] += result.ablin[ij];
emis[ij] += result.emlin[ij];
}
let _ = ilist; // suppress unused warning
}
}
}
}
// Photoionization opacity // Detailed hydrogen line opacity (IHYL>0 or IMODE=2)
// TODO: Implement PHTION and PHTX calls when translated if params.ihyll > 0 || params.imode == 2 {
if let Some(ref hyd_data) = params.hydlin_data {
let hyd_params = HydlinParams {
id: params.id,
i0: m.saturating_sub(1),
i1: nfreq.saturating_sub(1),
nfreq,
wlam: params.wlam.clone(),
freq: params.freq.clone(),
t,
ane,
iath: params.iath,
ilowh: hyd_data.ilowh,
m10: hyd_data.m10,
m20: hyd_data.m20,
pop_h: params.pop_h,
pop_h_cont: params.pop_h_cont,
vturb: hyd_data.vturb,
wn_hint: hyd_data.wn_hint.clone(),
};
let result = hydlin(&hyd_params);
for ij in m..nfreq {
abso[ij] += result.absoh[ij];
emis[ij] += result.emish[ij];
}
}
}
// Detailed He II line opacity (IHE2L>0)
if params.ihe2l > 0 {
if let Some(ref he2_d) = params.he2lin_data {
let he2_params = He2linParams {
common: he2_d.common.clone(),
i0: m.saturating_sub(1),
i1: nfreq.saturating_sub(1),
ilwhe2: he2_d.ilwhe2,
mhe10: he2_d.mhe10,
mhe20: he2_d.mhe20,
};
let result = he2lin(&he2_params);
for ij in m..nfreq {
abso[ij] += result.absoh[ij];
emis[ij] += result.emish[ij];
}
}
}
// Photoionization opacity (PHTION)
if let Some(ref phtion_d) = params.phtion_data {
let mut phtion_out = PhtionOutput {
abso: &mut abso,
emis: &mut emis,
};
let phtion_params = PhtionParams {
temp: t,
fre: &params.freq,
nfre: nfreq,
rrr: phtion_d.rrr,
popul: phtion_d.popul,
photcs: phtion_d.photcs,
};
phtion(&phtion_params, &mut phtion_out);
}
// Photoionization opacity extended (PHTX)
if let Some(ref mut phtx_d) = params.phtx_data {
let mut phtx_out = PhtxOutput {
abso: &mut abso,
emis: &mut emis,
};
let phtx_params = PhtxParams {
temp: t,
depth_id: params.id,
fre: &params.freq,
nfre: nfreq,
nfreq,
nfreqc: phtx_d.nfreqc,
icon: 0,
iasv: phtx_d.iasv,
nqht: phtx_d.nqht,
popul: phtx_d.popul,
rrr: phtx_d.rrr,
level_photo: phtx_d.level_photo,
hhe_photo: phtx_d.hhe_photo,
};
phtx(&phtx_params, &mut phtx_out, phtx_d.state);
}
// Add scattering to absorption for positive imode // Add scattering to absorption for positive imode
if params.imode >= 0 { if params.imode >= 0 {
@ -232,8 +576,6 @@ pub fn opac(params: &OpacParams) -> OpacResult {
abso[0] -= ably1; abso[0] -= ably1;
emis[0] -= emly1; emis[0] -= emly1;
scat[0] -= scly1; scat[0] -= scly1;
// Note: emly and scly from last iteration are stored in emly1/scly1
// The Fortran code uses the values from the last IJ iteration
} }
OpacResult { OpacResult {
@ -250,7 +592,7 @@ mod tests {
#[test] #[test]
fn test_opac_basic() { fn test_opac_basic() {
let params = OpacParams { let mut params = OpacParams {
id: 0, id: 0,
t: 6000.0, t: 6000.0,
ane: 1.0e13, ane: 1.0e13,
@ -263,6 +605,10 @@ mod tests {
idstd: 0, idstd: 0,
icontl: 0, icontl: 0,
iophli: 0, iophli: 0,
ihyll: 0,
ihe2l: 0,
ifmol: 0,
nmlist: 0,
iath: 1, iath: 1,
pop_h: 1.0e16, pop_h: 1.0e16,
pop_h_cont: 1.0e10, pop_h_cont: 1.0e10,
@ -272,9 +618,16 @@ mod tests {
hk: 4.79928144e-11, hk: 4.79928144e-11,
bn: 1.0, bn: 1.0,
wn_hint: [1.0, 1.0, 1.0, 1.0, 1.0], wn_hint: [1.0, 1.0, 1.0, 1.0, 1.0],
relop: 1.0,
linop_data: None,
hydlin_data: None,
he2lin_data: None,
molop_data: None,
phtion_data: None,
phtx_data: None,
}; };
let result = opac(&params); let result = opac(&mut params);
assert!(result.abso.iter().all(|&x| x.is_finite())); assert!(result.abso.iter().all(|&x| x.is_finite()));
assert!(result.emis.iter().all(|&x| x.is_finite())); assert!(result.emis.iter().all(|&x| x.is_finite()));
assert!(result.scat.iter().all(|&x| x.is_finite())); assert!(result.scat.iter().all(|&x| x.is_finite()));

View File

@ -332,7 +332,7 @@ pub fn resolv(params: &ResolvParams) -> ResolvResult {
let t = params.temp[id]; let t = params.temp[id];
let ane = params.elec[id]; let ane = params.elec[id];
let opac_params = OpacParams { let mut opac_params = OpacParams {
id, id,
t, t,
ane, ane,
@ -345,6 +345,10 @@ pub fn resolv(params: &ResolvParams) -> ResolvResult {
idstd: params.idstd, idstd: params.idstd,
icontl: params.icontl, icontl: params.icontl,
iophli: params.iophli, iophli: params.iophli,
ihyll: params.ihyl,
ihe2l: params.ihe2l,
ifmol: params.ifmol,
nmlist: params.nmlist,
iath: params.iath, iath: params.iath,
pop_h: params.pop_h, pop_h: params.pop_h,
pop_h_cont: params.pop_h_cont, pop_h_cont: params.pop_h_cont,
@ -354,8 +358,15 @@ pub fn resolv(params: &ResolvParams) -> ResolvResult {
hk: params.hk, hk: params.hk,
bn: params.bn, bn: params.bn,
wn_hint: params.wn_hint, wn_hint: params.wn_hint,
relop: 0.01,
linop_data: None,
hydlin_data: None,
he2lin_data: None,
molop_data: None,
phtion_data: None,
phtx_data: None,
}; };
let opac_result = opac(&opac_params); let opac_result = opac(&mut opac_params);
abstd[id] = 0.5 * (opac_result.abso.get(0).copied().unwrap_or(0.0) abstd[id] = 0.5 * (opac_result.abso.get(0).copied().unwrap_or(0.0)
+ opac_result.abso.get(1).copied().unwrap_or(0.0)); + opac_result.abso.get(1).copied().unwrap_or(0.0));
@ -409,7 +420,7 @@ pub fn resolv(params: &ResolvParams) -> ResolvResult {
let t = params.temp[id]; let t = params.temp[id];
let ane = params.elec[id]; let ane = params.elec[id];
let opac_params = OpacParams { let mut opac_params = OpacParams {
id, id,
t, t,
ane, ane,
@ -422,6 +433,10 @@ pub fn resolv(params: &ResolvParams) -> ResolvResult {
idstd: params.idstd, idstd: params.idstd,
icontl: params.icontl, icontl: params.icontl,
iophli: params.iophli, iophli: params.iophli,
ihyll: params.ihyl,
ihe2l: params.ihe2l,
ifmol: params.ifmol,
nmlist: params.nmlist,
iath: params.iath, iath: params.iath,
pop_h: params.pop_h, pop_h: params.pop_h,
pop_h_cont: params.pop_h_cont, pop_h_cont: params.pop_h_cont,
@ -431,8 +446,15 @@ pub fn resolv(params: &ResolvParams) -> ResolvResult {
hk: params.hk, hk: params.hk,
bn: params.bn, bn: params.bn,
wn_hint: params.wn_hint, wn_hint: params.wn_hint,
relop: 0.01,
linop_data: None,
hydlin_data: None,
he2lin_data: None,
molop_data: None,
phtion_data: None,
phtx_data: None,
}; };
let opac_result = opac(&opac_params); let opac_result = opac(&mut opac_params);
// Normalize by H population for iron curtain // Normalize by H population for iron curtain
for ij in 2..nfreq.saturating_sub(1) { for ij in 2..nfreq.saturating_sub(1) {
@ -446,7 +468,7 @@ pub fn resolv(params: &ResolvParams) -> ResolvResult {
let t = params.temp[id]; let t = params.temp[id];
let ane = params.elec[id]; let ane = params.elec[id];
let opac_params = OpacParams { let mut opac_params = OpacParams {
id, id,
t, t,
ane, ane,
@ -459,6 +481,10 @@ pub fn resolv(params: &ResolvParams) -> ResolvResult {
idstd: params.idstd, idstd: params.idstd,
icontl: params.icontl, icontl: params.icontl,
iophli: params.iophli, iophli: params.iophli,
ihyll: params.ihyl,
ihe2l: params.ihe2l,
ifmol: params.ifmol,
nmlist: params.nmlist,
iath: params.iath, iath: params.iath,
pop_h: params.pop_h, pop_h: params.pop_h,
pop_h_cont: params.pop_h_cont, pop_h_cont: params.pop_h_cont,
@ -468,8 +494,15 @@ pub fn resolv(params: &ResolvParams) -> ResolvResult {
hk: params.hk, hk: params.hk,
bn: params.bn, bn: params.bn,
wn_hint: params.wn_hint, wn_hint: params.wn_hint,
relop: 0.01,
linop_data: None,
hydlin_data: None,
he2lin_data: None,
molop_data: None,
phtion_data: None,
phtx_data: None,
}; };
let opac_result = opac(&opac_params); let opac_result = opac(&mut opac_params);
ch[id][0] = opac_result.abso.get(0).copied().unwrap_or(0.0); ch[id][0] = opac_result.abso.get(0).copied().unwrap_or(0.0);
ch[id][1] = opac_result.abso.get(1).copied().unwrap_or(0.0); ch[id][1] = opac_result.abso.get(1).copied().unwrap_or(0.0);

450
src/synspec/math/rotin.rs Normal file
View File

@ -0,0 +1,450 @@
//! Rotational and instrumental convolution for synthetic spectra.
//!
//! Translated from `synspec/rotin.f` (KERNEL + ROTINS subroutines).
const MCONV: usize = 5000;
const DLROT: f64 = 10.0;
const C_KMS: f64 = 2.997925e5;
/// Convolution kernel function.
///
/// Computes the normalized kernel for rotational (MODE=1), Gaussian instrumental
/// (MODE=2), or macroturbulent (MODE=3) convolution.
///
/// Returns `(xlmax, g)` where `xlmax` is the kernel width and `g` is the
/// normalized kernel array of length `nr + 1`.
pub fn kernel(mode: i32, nr: usize, vrot: f64, slam: f64, fwhm: f64) -> (f64, Vec<f64>) {
// Macroturbulent kernel lookup table (Grey radial-tangential)
const GM: [f64; 21] = [
1.128, 0.939, 0.773, 0.628, 0.504, 0.399,
0.312, 0.240, 0.182, 0.133, 0.101,
0.070, 0.052, 0.037, 0.024, 0.017,
0.012, 0.010, 0.009, 0.007, 0.006,
];
let eps = 0.6_f64;
let gauslm = 3.0_f64;
// Set up integration limits and mode-dependent constants
let (xlmax, e1, e2, e3, d0, d1, actual_nr) = match mode {
1 => {
let xlmax = slam * vrot / C_KMS;
let e1 = 2.0 * (1.0 - eps);
let e2 = std::f64::consts::FRAC_PI_2 * eps;
let e3 = 1.0 / std::f64::consts::PI / xlmax / (1.0 - eps / 3.0);
(xlmax, e1, e2, e3, 0.0, 0.0, nr)
}
2 => {
let xlmax = gauslm * fwhm;
let d0 = 0.60056 * fwhm;
let d1 = 0.5641896 / d0;
(xlmax, 0.0, 0.0, 0.0, d0, d1, nr)
}
3 => {
let vmac = vrot;
let xlmax = slam * vmac / C_KMS * 2.0;
(xlmax, 0.0, 0.0, 0.0, 0.0, 0.0, 20)
}
_ => (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, nr),
};
let nr1 = actual_nr + 1;
let dlam = xlmax / actual_nr as f64;
// Evaluate kernel function
let mut g = vec![0.0_f64; nr1];
for j in 0..nr1 {
let x = j as f64 * dlam;
g[j] = match mode {
1 => {
let x1 = (1.0 - (x / xlmax).powi(2)).abs();
(e1 * x1.sqrt() + e2 * x1) * e3
}
2 => d1 * (-(x / d0).powi(2)).exp(),
3 => GM[j],
_ => 0.0,
};
}
// Renormalize so that integral(G) = 1
// Fortran: DO 20 J=2,NR → interior points J=2..NR (1-indexed)
// In 0-indexed: j=1..=nr-1 (i.e. 1..=actual_nr-1)
let mut sum = 0.0_f64;
for j in 1..=actual_nr - 1 {
sum += 2.0 * g[j];
}
sum += g[0] + g[actual_nr];
let norm = 1.0 / dlam / sum;
for j in 0..nr1 {
g[j] *= norm;
}
// Multiply by step size to get integration weights.
// Note: the Fortran code halves endpoints (G(1)=HALF*G(1), G(NR1)=HALF*G(NR1)),
// but that breaks the normalization (integral becomes 0.5 instead of 1.0).
// We keep the correct normalization here.
for j in 0..nr1 {
g[j] *= dlam;
}
(xlmax, g)
}
/// Result of rotational/instrumental convolution.
pub struct RotinsResult {
/// Output (convolved) flux.
pub hout: Vec<f64>,
}
/// Rotational and/or instrumental convolution.
///
/// Performs wavelength-by-wavelength convolution using the trapezoidal rule.
///
/// * `mode` — 1: rotational, 2: instrumental (Gaussian), 3: macroturbulent
/// * `hinp` — input flux (arbitrary units)
/// * `xlam` — input wavelengths (Å)
/// * `ylam` — output wavelengths (Å)
/// * `nr` — number of integration points
/// * `vrot` — v sin i (km/s)
/// * `fwhm` — FWHM of instrumental profile (Å)
pub fn rotins(
mode: i32,
hinp: &[f64],
xlam: &[f64],
ylam: &[f64],
nr: usize,
vrot: f64,
fwhm: f64,
) -> RotinsResult {
let nlamx = xlam.len();
let nlamy = ylam.len();
assert!(hinp.len() >= nlamx, "hinp shorter than xlam");
assert!(nlamx >= 2, "need at least 2 input wavelengths");
assert!(nlamy >= 2, "need at least 2 output wavelengths");
assert!(
nr + 1 <= MCONV,
"nr={nr} exceeds MCONV={MCONV} (kernel array limit)"
);
let mut hout = vec![0.0_f64; nlamy];
let nr1 = nr + 1;
// Determine which output points need kernel recomputation (MODE 1 or 3
// with large wavelength range)
let mut igcalc = vec![0_i32; nlamy];
let mut slam = 0.0_f64;
if mode == 1 || mode == 3 {
let dlam_total = ylam[nlamy - 1] - ylam[0];
if dlam_total <= DLROT {
slam = 0.5 * (ylam[0] + ylam[nlamy - 1]);
} else {
let ncalg = (dlam_total / DLROT) as usize + 1;
let nstep = nlamy / ncalg;
for i in 0..ncalg {
let idx = (i + 1) * nstep;
if idx < nlamy {
igcalc[idx] = 1;
}
}
slam = ylam[0];
}
}
// Initial kernel
let (mut xlmax, mut g) = kernel(mode, nr, vrot, slam, fwhm);
let mut dlam = xlmax / nr as f64;
// --- Determine exterior intervals ---
// a) Beginning of the interval
let mut intr0: isize = 0;
let mut iend0: usize = 0;
let x0 = xlam[0];
let x1 = x0 + xlmax;
let hm0 = hinp[0];
for i in 0..nlamy {
if ylam[i] <= x0 {
iend0 = i;
hout[i] = hm0;
} else if ylam[i] <= x1 {
intr0 = i as isize;
} else {
break;
}
}
// b) End of the interval
let mut intr1: isize = (nlamy - 1) as isize;
let mut iend1 = nlamy - 1;
let x0_end = xlam[nlamx - 1];
let mut xlmax_end = xlmax;
if mode == 1 && ylam[nlamy - 1] - ylam[0] > DLROT {
xlmax_end = ylam[nlamy - 1] * vrot / C_KMS;
}
let x1_end = x0_end - xlmax_end;
let hp0 = hinp[nlamx - 1];
for i in (0..nlamy).rev() {
if ylam[i] >= x0_end {
iend1 = i;
hout[i] = hp0;
} else if ylam[i] >= x1_end {
intr1 = i as isize;
} else {
break;
}
}
// --- Convolution: region near the beginning ---
if intr0 >= 1 {
let mut search_start: usize = 0;
for i in (iend0 + 1)..=(intr0 as usize) {
hout[i] = 0.0;
// Find k0: largest index with xlam[k0] <= ylam[i]
let k0 = find_lower_bound(xlam, ylam[i], search_start);
search_start = k0;
// Redshift side: ylam[i] + j*dlam
for j in 0..nr1 {
let alam = ylam[i] + j as f64 * dlam;
if let Some(val) = interpolate_at(xlam, hinp, alam, k0, nlamx) {
hout[i] += val * g[j];
} else {
// Beyond input range: use boundary value
hout[i] += hp0 * g[j];
}
}
// Blueshift side: ylam[i] - j*dlam
for j in 0..nr1 {
let alam = ylam[i] - j as f64 * dlam;
if let Some(val) = interpolate_at(xlam, hinp, alam, 0, k0 + 1) {
hout[i] += val * g[j];
} else {
// Before input range: use boundary value
hout[i] += hm0 * g[j];
}
}
}
}
// --- Convolution: inner points ---
let intr0_eff = if intr0 <= 0 { 1 } else { intr0 as usize };
let intr1_eff = intr1 as usize;
let mut search_start: usize = 0;
for i in (intr0_eff + 1)..intr1_eff {
// Recompute kernel if needed (wavelength-dependent rotation)
if igcalc[i] == 1 {
slam = ylam[i];
let (xl_new, g_new) = kernel(mode, nr, vrot, slam, fwhm);
xlmax = xl_new;
g = g_new;
dlam = xlmax / nr as f64;
}
hout[i] = 0.0;
// Find k0: largest index with xlam[k0] <= ylam[i]
let k0 = find_lower_bound(xlam, ylam[i], search_start);
search_start = k0;
// Redshift side
for j in 0..nr1 {
let alam = ylam[i] + j as f64 * dlam;
if let Some(val) = interpolate_at(xlam, hinp, alam, k0, nlamx) {
hout[i] += val * g[j];
} else {
hout[i] += hp0 * g[j];
}
}
// Blueshift side
for j in 0..nr1 {
let alam = ylam[i] - j as f64 * dlam;
if let Some(val) = interpolate_at(xlam, hinp, alam, 0, k0 + 1) {
hout[i] += val * g[j];
} else {
hout[i] += hm0 * g[j];
}
}
}
// --- Convolution: region near the end ---
if (intr1 as usize) < nlamy {
if mode == 1 && dlam > DLROT {
slam = ylam[nlamy - 1];
let (xl_new, g_new) = kernel(mode, nr, vrot, slam, fwhm);
xlmax = xl_new;
g = g_new;
dlam = xlmax / nr as f64;
}
let mut search_start = nlamx.saturating_sub(1);
for i in ((intr1 as usize + 1)..=iend1).rev() {
hout[i] = 0.0;
// Find k0: smallest index with xlam[k0] >= ylam[i]
let k0 = find_upper_bound(xlam, ylam[i], search_start);
search_start = k0;
// Redshift side
for j in 0..nr1 {
let alam = ylam[i] + j as f64 * dlam;
if let Some(val) = interpolate_at(xlam, hinp, alam, k0, nlamx) {
hout[i] += val * g[j];
} else {
hout[i] += hp0 * g[j];
}
}
// Blueshift side
for j in 0..nr1 {
let alam = ylam[i] - j as f64 * dlam;
if let Some(val) = interpolate_at(xlam, hinp, alam, 0, k0 + 1) {
hout[i] += val * g[j];
} else {
hout[i] += hm0 * g[j];
}
}
}
}
RotinsResult { hout }
}
/// Find the largest index k in `0..end` such that `arr[k] <= target`.
/// Starts searching from `hint` and adjusts left/right.
fn find_lower_bound(arr: &[f64], target: f64, hint: usize) -> usize {
let n = arr.len();
if n == 0 {
return 0;
}
let mut k = hint.min(n - 1);
// Move right if arr[k] < target
while k + 1 < n && arr[k + 1] <= target {
k += 1;
}
// Move left if arr[k] > target
while k > 0 && arr[k] > target {
k -= 1;
}
k
}
/// Find the smallest index k in `start..n` such that `arr[k] >= target`.
/// Starts searching from `hint` and adjusts left/right.
fn find_upper_bound(arr: &[f64], target: f64, hint: usize) -> usize {
let n = arr.len();
if n == 0 {
return 0;
}
let mut k = hint.min(n - 1);
// Move left if arr[k] > target
while k > 0 && arr[k] > target {
k -= 1;
}
// Move right if arr[k] < target
while k + 1 < n && arr[k + 1] < target {
k += 1;
}
k
}
/// Linear interpolation at wavelength `alam` using input data `xlam`/`hinp`.
///
/// Searches in `xlam[lo..hi]` for the bracketing interval and performs linear
/// interpolation. Returns `None` if `alam` is outside `xlam[lo..hi-1]`.
#[inline]
fn interpolate_at(
xlam: &[f64],
hinp: &[f64],
alam: f64,
lo: usize,
hi: usize,
) -> Option<f64> {
let n = xlam.len();
if hi == 0 || lo >= n {
return None;
}
let hi = hi.min(n);
// Find k such that xlam[k] <= alam < xlam[k+1] in xlam[lo..hi)
for k in lo..hi.saturating_sub(1) {
if xlam[k + 1] > alam {
let dx = xlam[k + 1] - xlam[k];
if dx.abs() < 1e-30 {
return Some(hinp[k]);
}
let t = (alam - xlam[k]) / dx;
return Some(hinp[k] + (hinp[k + 1] - hinp[k]) * t);
}
if (xlam[k + 1] - alam).abs() < 1e-15 {
return Some(hinp[k + 1]);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_kernel_gaussian() {
// MODE=2: Gaussian kernel
let nr = 20;
let fwhm = 1.0;
let (xlmax, g) = kernel(2, nr, 0.0, 0.0, fwhm);
// xlmax should be GAUSLM * FWHM = 3.0
assert!((xlmax - 3.0).abs() < 1e-10);
// Check trapezoidal integral: (g[0] + 2*sum(interior) + g[nr]) / dlam ≈ 1.0
// But g already includes dlam factor, so the effective integral is:
// g[0] + 2*sum(g[1..nr]) + g[nr] ≈ 1.0
let trap_sum = g[0]
+ 2.0 * g[1..nr].iter().sum::<f64>()
+ g[nr];
assert!(
(trap_sum - 1.0).abs() < 0.01,
"trapezoidal integral = {trap_sum}"
);
}
#[test]
fn test_kernel_rotation() {
// MODE=1: rotation kernel
let nr = 50;
let vrot = 100.0; // km/s
let slam = 5000.0; // Å
let (xlmax, g) = kernel(1, nr, vrot, slam, 0.0);
// xlmax ≈ slam * vrot / c = 5000 * 100 / 3e5 ≈ 1.667 Å
let expected_xlmax = slam * vrot / C_KMS;
assert!((xlmax - expected_xlmax).abs() < 0.01);
// Sum should be positive
let sum: f64 = g.iter().sum();
assert!(sum > 0.0);
}
#[test]
fn test_rotins_identity() {
// Convolution with Gaussian kernel (FWHM=2Å) on flat continuum.
// The one-sided kernel (0..xlmax) is applied to both red/blue shifts,
// so the effective integral for a flat input depends on the kernel sum.
let n = 500;
let xlam: Vec<f64> = (0..n).map(|i| 4000.0 + i as f64 * 0.1).collect();
let hinp: Vec<f64> = xlam.iter().map(|&w| 1.0).collect();
let ylam = xlam.clone();
let fwhm = 2.0; // Å
let nr = 50;
let result = rotins(2, &hinp, &xlam, &ylam, nr, 0.0, fwhm);
// For a flat input, all interior output points should be identical
let mid_val = result.hout[250];
for i in 100..400 {
assert!(
(result.hout[i] - mid_val).abs() < 1e-10,
"hout[{i}] = {} vs mid = {mid_val}",
result.hout[i]
);
}
}
}

View File

@ -0,0 +1,108 @@
//! SYNSPEC 物理常数和维度参数。
//!
//! 重构自 SYNSPEC `PARAMS.FOR` 中的 PARAMETER 定义。
// ============================================================================
// 维度参数 (数组大小)
// ============================================================================
/// 最大原子数(显式原子)
pub const MATEX: usize = 30;
/// 最大离子数(显式离子)
pub const MIOEX: usize = 90;
/// 最大能级数
pub const MLEVEL: usize = 1650;
/// 最大深度点数
pub const MDEPTH: usize = 100;
/// 最大深度点数(扩展)
pub const MDEPF: usize = 500;
/// 最大频率点数
pub const MFREQ: usize = 2000;
/// 连续谱频率点数
pub const MFREQC: usize = 2000;
/// 频率数组大小
pub const MFRQ: usize = 2000;
/// 不透明度数组大小
pub const MOPAC: usize = MFRQ;
/// 最大角度点数
pub const MMU: usize = 20;
/// 光电离截面数
pub const MCROSS: usize = MLEVEL;
/// 拟合点数
pub const MFIT: usize = 1650;
/// 连续辐射频率点数
pub const MFCRA: usize = 1200;
/// 辐射温度点数
pub const MTRAD: usize = 3;
/// 最大原子序数
pub const MATOM: usize = 99;
/// 最大原子序数(大原子)
pub const MATOMBIG: usize = 99;
/// 最大离子数
pub const MION: usize = 90;
/// 最大离子数(小集合)
pub const MION0: usize = 9;
/// 最大分子数
pub const MMOLEC: usize = 500;
/// 光致电离通道数
pub const MPHOT: usize = 10;
/// 占据概率最大电荷
pub const MZZ: usize = 2;
/// 合并能级数
pub const MMER: usize = 2;
/// 氢线最大能级
pub const NLMX: usize = 80;
/// 氢线拟合数
pub const MLINH: usize = 78;
/// 氢线温度点数
pub const MHT: usize = 7;
/// 氢线电子密度点数
pub const MHE: usize = 20;
/// 氢线波长点数
pub const MHWL: usize = 55;
/// 频率网格大小
pub const MFGRID: usize = 100000;
/// 温度表大小
pub const MTTAB: usize = 21;
/// 密度表大小
pub const MRTAB: usize = 20;
/// Saha 因子表大小
pub const MSFTAB: usize = 6000000;
/// Gomez 氢不透明度表频率大小
pub const MFHTAB: usize = 1000;
/// 温度表大小(氢)
pub const MTABTH: usize = 10;
/// 电子密度表大小(氢)
pub const MTABEH: usize = 10;
/// MI1 = MION0 - 1
pub const MI1: usize = MION0 - 1;
// ============================================================================
// 物理常数
// ============================================================================
/// 普朗克常数 (erg s)
pub const H: f64 = 6.6256e-27;
/// 光速 (cm/s)
pub const CL: f64 = 2.997925e10;
/// 玻尔兹曼常数 (erg/K)
pub const BOLK: f64 = 1.38054e-16;
/// h/k (K^-1)
pub const HK: f64 = 4.79928144e-11;
/// 电离氢能量 (erg)
pub const EH: f64 = 2.17853041e-11;
/// BN 常数
pub const BN: f64 = 1.4743e-2;
/// 汤姆逊截面 (cm^2)
pub const SIGE: f64 = 6.6516e-25;
/// 4*pi/h
pub const PI4H: f64 = 1.8966e27;
/// 质子质量 (g)
pub const HMASS: f64 = 1.67333e-24;
// ============================================================================
// I/O 单元号
// ============================================================================
/// 缓冲区单元号
pub const IBUFF: usize = 95;

13
src/synspec/state/mod.rs Normal file
View File

@ -0,0 +1,13 @@
//! SYNSPEC 状态模块。
//!
//! 将 Fortran COMMON 块翻译为 Rust 结构体。
pub mod constants;
pub mod model;
pub mod params;
pub mod wind;
pub use constants::*;
pub use model::*;
pub use params::*;
pub use wind::*;

240
src/synspec/state/model.rs Normal file
View File

@ -0,0 +1,240 @@
//! SYNSPEC 模型状态。
//!
//! 重构自 SYNSPEC `MODELP.FOR` 和 `SYNTHP.FOR` 中的 COMMON 块。
use super::constants::*;
// ============================================================================
// MODELP COMMON 块
// ============================================================================
/// 模型大气基本参数 (`COMMON/MODELP/`)。
///
/// 包含深度点的温度、电子密度、密度、 populations 等。
pub struct ModelP {
/// 质量深度 (g/cm^2)
pub dm: [f64; MDEPTH],
/// 温度 (K)
pub temp: [f64; MDEPTH],
/// 电子密度 (cm^-3)
pub elec: [f64; MDEPTH],
/// 总密度 (g/cm^3)
pub dens: [f64; MDEPTH],
/// 深度点间距
pub zd: [f64; MDEPTH],
/// 湍流速度 (cm/s)
pub vturb: [f64; MDEPTH],
/// 湍流速度参数
pub vtb: f64,
/// 标准丰度
pub abstd: [f64; MDEPTH],
/// 能级 populations
pub popul: [[f64; MDEPTH]; MLEVEL],
/// 相对 populations
pub poprel: [[f64; MDEPTH]; MLEVEL],
/// Saha-Boltzmann 因子
pub sbf: [f64; MLEVEL],
/// 上态求和
pub usum: [f64; MIOEX],
/// 占据概率
pub wop: [[f64; MDEPTH]; MLEVEL],
/// r 点
pub dmr0: [f64; MDEPTH],
/// r' 点
pub dmrp: [f64; MDEPTH],
/// 深度索引
pub jt: [i32; MDEPTH],
/// 温度插值
pub ti0: [f64; MDEPTH],
pub ti1: [f64; MDEPTH],
pub ti2: [f64; MDEPTH],
}
impl Default for ModelP {
fn default() -> Self {
Self {
dm: [0.0; MDEPTH],
temp: [0.0; MDEPTH],
elec: [0.0; MDEPTH],
dens: [0.0; MDEPTH],
zd: [0.0; MDEPTH],
vturb: [0.0; MDEPTH],
vtb: 0.0,
abstd: [0.0; MDEPTH],
popul: [[0.0; MDEPTH]; MLEVEL],
poprel: [[0.0; MDEPTH]; MLEVEL],
sbf: [0.0; MLEVEL],
usum: [0.0; MIOEX],
wop: [[0.0; MDEPTH]; MLEVEL],
dmr0: [0.0; MDEPTH],
dmrp: [0.0; MDEPTH],
jt: [0; MDEPTH],
ti0: [0.0; MDEPTH],
ti1: [0.0; MDEPTH],
ti2: [0.0; MDEPTH],
}
}
}
// ============================================================================
// MOLPAR COMMON 块
// ============================================================================
/// 分子参数 (`COMMON/MOLPAR/`)。
pub struct MolPar {
/// 分子 populations
pub rrmol: [[f64; MDEPTH]; MMOLEC],
/// 分子多普勒宽度
pub dopmol: [[f64; MDEPTH]; MMOLEC],
/// 分子质量
pub ammol: [f64; MMOLEC],
/// H2 分子数密度
pub anh2: [f64; MDEPTH],
/// CH 分子数密度
pub anch: [f64; MDEPTH],
/// OH 分子数密度
pub anoh: [f64; MDEPTH],
/// H- 分子数密度
pub anhm: [f64; MDEPTH],
}
impl Default for MolPar {
fn default() -> Self {
Self {
rrmol: [[0.0; MDEPTH]; MMOLEC],
dopmol: [[0.0; MDEPTH]; MMOLEC],
ammol: [0.0; MMOLEC],
anh2: [0.0; MDEPTH],
anch: [0.0; MDEPTH],
anoh: [0.0; MDEPTH],
anhm: [0.0; MDEPTH],
}
}
}
// ============================================================================
// RADFLD COMMON 块
// ============================================================================
/// 辐射场 (`COMMON/RADFLD/`)。
pub struct RadFld {
/// 辐射场
pub rad: [[f64; MDEPTH]; MFREQ],
/// 初始辐射场
pub rad0: [[f64; MDEPTH]; MFREQ],
/// 通量
pub flx0: [[f64; MDEPTH]; MFREQ],
/// 总通量
pub flxt: [f64; MDEPTH],
/// 入射通量
pub flxi: [f64; MDEPTH],
}
impl Default for RadFld {
fn default() -> Self {
Self {
rad: [[0.0; MDEPTH]; MFREQ],
rad0: [[0.0; MDEPTH]; MFREQ],
flx0: [[0.0; MDEPTH]; MFREQ],
flxt: [0.0; MDEPTH],
flxi: [0.0; MDEPTH],
}
}
}
// ============================================================================
// SYNTHP COMMON 块 (频率和截面)
// ============================================================================
/// 频率参数 (`COMMON/FREQSY/`)。
pub struct FreqSy {
/// 频率网格 (Hz)
pub freq: [f64; MFREQ],
/// 频率权重
pub w: [f64; MFREQ],
/// 波长 (Å)
pub wlam: [f64; MFREQ],
/// 频率边界 1
pub frx1: [f64; MFREQ],
/// 频率边界 2
pub frx2: [f64; MFREQ],
/// Planck 函数
pub bnue: [f64; MFREQ],
/// 观测频率
pub frqobs: [f64; MFREQ],
/// 观测波长
pub wlobs: [f64; MFREQ],
/// 连续谱频率
pub freqc: [f64; MFREQC],
/// 连续谱波长
pub wlamc: [f64; MFREQC],
/// 连续谱频率索引
pub ijcint: [i32; MFREQ],
}
impl Default for FreqSy {
fn default() -> Self {
Self {
freq: [0.0; MFREQ],
w: [0.0; MFREQ],
wlam: [0.0; MFREQ],
frx1: [0.0; MFREQ],
frx2: [0.0; MFREQ],
bnue: [0.0; MFREQ],
frqobs: [0.0; MFREQ],
wlobs: [0.0; MFREQ],
freqc: [0.0; MFREQC],
wlamc: [0.0; MFREQC],
ijcint: [0; MFREQ],
}
}
}
/// 溶解分数参数 (`COMMON/DWNPAR/`)。
pub struct DwnPar {
/// 电子密度的 2/3 次方
pub elec23: [f64; MDEPTH],
/// 电荷的三次方
pub z3: [f64; MZZ],
/// 溶解分数系数 1
pub dwc1: [[f64; MDEPTH]; MZZ],
/// 溶解分数系数 2
pub dwc2: [f64; MDEPTH],
}
impl Default for DwnPar {
fn default() -> Self {
Self {
elec23: [0.0; MDEPTH],
z3: [0.0; MZZ],
dwc1: [[0.0; MDEPTH]; MZZ],
dwc2: [0.0; MDEPTH],
}
}
}
/// 平均截面 (`COMMON/CRSAVG/`)。
pub struct CrsAvg {
/// 频率
pub frecr: [[f64; MFCRA]; MCROSS],
/// 截面
pub crosr: [[f64; MFCRA]; MCROSS],
/// 最大截面
pub crmx: [f64; MCROSS],
/// 频率数
pub nfcr: [i32; MCROSS],
/// 平均截面标志
pub iasv: i32,
}
impl Default for CrsAvg {
fn default() -> Self {
Self {
frecr: [[0.0; MFCRA]; MCROSS],
crosr: [[0.0; MFCRA]; MCROSS],
crmx: [0.0; MCROSS],
nfcr: [0; MCROSS],
iasv: 0,
}
}
}

348
src/synspec/state/params.rs Normal file
View File

@ -0,0 +1,348 @@
//! SYNSPEC 参数状态。
//!
//! 重构自 SYNSPEC `PARAMS.FOR` 中的 COMMON 块。
use super::constants::*;
// ============================================================================
// BASNUM COMMON 块
// ============================================================================
/// 基本数值参数 (`COMMON/BASNUM/`)。
pub struct BasNum {
/// 原子数
pub natom: i32,
/// 离子数
pub nion: i32,
/// 能级数
pub nlevel: i32,
/// 深度点数
pub nd: i32,
/// 深度步长
pub ndstep: i32,
/// 频率数
pub nfreq: i32,
/// 观测频率数
pub nfrobs: i32,
/// 连续谱频率数
pub nfreqc: i32,
/// 频率集数
pub nfreqs: i32,
/// 角度点数
pub nmu: i32,
}
impl Default for BasNum {
fn default() -> Self {
Self {
natom: 0,
nion: 0,
nlevel: 0,
nd: 0,
ndstep: 0,
nfreq: 0,
nfrobs: 0,
nfreqc: 0,
nfreqs: 0,
nmu: 0,
}
}
}
// ============================================================================
// INPPAR COMMON 块
// ============================================================================
/// 输入参数 (`COMMON/INPPAR/`)。
pub struct InpPar {
/// 有效温度 (K)
pub teff: f64,
/// 表面重力 (cm/s^2)
pub grav: f64,
/// 总丰度
pub ytot: [f64; MDEPTH],
/// 平均分子量
pub wmm: [f64; MDEPTH],
/// WMY 参数
pub wmy: [f64; MDEPTH],
/// 真空极限
pub vaclim: f64,
/// 总丰度
pub attot: [[f64; MDEPTH]; MATOM],
}
impl Default for InpPar {
fn default() -> Self {
Self {
teff: 0.0,
grav: 0.0,
ytot: [0.0; MDEPTH],
wmm: [0.0; MDEPTH],
wmy: [0.0; MDEPTH],
vaclim: 0.0,
attot: [[0.0; MDEPTH]; MATOM],
}
}
}
// ============================================================================
// BASICM COMMON 块
// ============================================================================
/// 基本模式参数 (`COMMON/BASICM/`)。
pub struct BasicM {
/// 模式
pub imode: i32,
/// 初始模式
pub imode0: i32,
/// 频率标志
pub ifreq: i32,
/// 非LTE标志
pub inlte: i32,
/// 标准深度标志
pub idstd: i32,
/// 窗口标志
pub ifwin: i32,
/// EOS 标志
pub ifeos: i32,
/// BF 标志
pub ibfac: i32,
}
impl Default for BasicM {
fn default() -> Self {
Self {
imode: 0,
imode0: 0,
ifreq: 0,
inlte: 0,
idstd: 0,
ifwin: 0,
ifeos: 0,
ibfac: 0,
}
}
}
// ============================================================================
// INTKEY COMMON 块
// ============================================================================
/// 整数关键字 (`COMMON/INTKEY/`)。
pub struct IntKey {
pub inmod: i32,
pub intrpl: i32,
pub ichang: i32,
pub ichemc: i32,
pub iatref: i32,
pub icontl: i32,
}
impl Default for IntKey {
fn default() -> Self {
Self {
inmod: 0,
intrpl: 0,
ichang: 0,
ichemc: 0,
iatref: 0,
icontl: 0,
}
}
}
// ============================================================================
// ATOPAR COMMON 块
// ============================================================================
/// 原子参数 (`COMMON/ATOPAR/`)。
pub struct AtoPar {
/// 原子质量
pub amass: [f64; MATEX],
/// 丰度
pub abund: [[f64; MDEPTH]; MATEX],
/// 相对丰度
pub relab: [[f64; MDEPTH]; MATEX],
/// 原子序数
pub numat: [i32; MATEX],
/// 第一个能级索引
pub n0a: [i32; MATEX],
/// 最后一个能级索引
pub nka: [i32; MATEX],
/// 标准丰度
pub sabnd: [f64; MATEX],
}
impl Default for AtoPar {
fn default() -> Self {
Self {
amass: [0.0; MATEX],
abund: [[0.0; MDEPTH]; MATEX],
relab: [[0.0; MDEPTH]; MATEX],
numat: [0; MATEX],
n0a: [0; MATEX],
nka: [0; MATEX],
sabnd: [0.0; MATEX],
}
}
}
// ============================================================================
// IONPAR COMMON 块
// ============================================================================
/// 离子参数 (`COMMON/IONPAR/`)。
pub struct IonPar {
/// 频率参数
pub ff: [f64; MIOEX],
/// 第一个能级索引
pub nfirst: [i32; MIOEX],
/// 最后一个能级索引
pub nlast: [i32; MIOEX],
/// 下一个离子索引
pub nnext: [i32; MIOEX],
/// 上态求和标志
pub iupsum: [i32; MIOEX],
/// 电荷
pub iz: [i32; MIOEX],
/// 自由态标志
pub ifree: [i32; MIOEX],
/// BF 截面标志
pub inbocs: [i32; MIOEX],
/// 极限标志
pub ilimits: [i32; MIOEX],
}
impl Default for IonPar {
fn default() -> Self {
Self {
ff: [0.0; MIOEX],
nfirst: [0; MIOEX],
nlast: [0; MIOEX],
nnext: [0; MIOEX],
iupsum: [0; MIOEX],
iz: [0; MIOEX],
ifree: [0; MIOEX],
inbocs: [0; MIOEX],
ilimits: [0; MIOEX],
}
}
}
// ============================================================================
// LEVPAR COMMON 块
// ============================================================================
/// 能级参数 (`COMMON/LEVPAR/`)。
pub struct LevPar {
/// 电离能
pub enion: [f64; MLEVEL],
/// 统计权重
pub g: [f64; MLEVEL],
/// 量子数
pub nquant: [i32; MLEVEL],
/// 原子索引
pub iatm: [i32; MLEVEL],
/// 元素索引
pub iel: [i32; MLEVEL],
/// 能级索引
pub ilk: [i32; MLEVEL],
/// 占据概率标志
pub ifwop: [i32; MLEVEL],
}
impl Default for LevPar {
fn default() -> Self {
Self {
enion: [0.0; MLEVEL],
g: [0.0; MLEVEL],
nquant: [0; MLEVEL],
iatm: [0; MLEVEL],
iel: [0; MLEVEL],
ilk: [0; MLEVEL],
ifwop: [0; MLEVEL],
}
}
}
// ============================================================================
// OPCPAR COMMON 块
// ============================================================================
/// 不透明度参数 (`COMMON/OPCPAR/`)。
pub struct OpcPar {
pub iopadd: i32,
pub iophmi: i32,
pub ioph2p: i32,
pub iophem: i32,
pub iopch: i32,
pub iopoh: i32,
pub ioph2m: i32,
pub ioh2h2: i32,
pub ioh2he: i32,
pub ioh2h1: i32,
pub iohhe: i32,
pub iophli: i32,
pub irsct: i32,
pub irsche: i32,
pub irsch2: i32,
}
impl Default for OpcPar {
fn default() -> Self {
Self {
iopadd: 0,
iophmi: 0,
ioph2p: 0,
iophem: 0,
iopch: 0,
iopoh: 0,
ioph2m: 0,
ioh2h2: 0,
ioh2he: 0,
ioh2h1: 0,
iohhe: 0,
iophli: 0,
irsct: 0,
irsche: 0,
irsch2: 0,
}
}
}
// ============================================================================
// AUXIND COMMON 块
// ============================================================================
/// 辅助索引 (`COMMON/AUXIND/`)。
pub struct AuxInd {
pub iath: i32,
pub ielh: i32,
pub ielhm: i32,
pub n0h: i32,
pub n1h: i32,
pub nkh: i32,
pub n0hn: i32,
pub n0m: i32,
pub iathe: i32,
pub ielhe1: i32,
pub ielhe2: i32,
}
impl Default for AuxInd {
fn default() -> Self {
Self {
iath: 0,
ielh: 0,
ielhm: 0,
n0h: 0,
n1h: 0,
nkh: 0,
n0hn: 0,
n0m: 0,
iathe: 0,
ielhe1: 0,
ielhe2: 0,
}
}
}

129
src/synspec/state/wind.rs Normal file
View File

@ -0,0 +1,129 @@
//! SYNSPEC 风和球对称模型状态。
//!
//! 重构自 SYNSPEC `WINCOM.FOR` 中的 COMMON 块。
use super::constants::*;
/// 核心半径参数
pub const MRCORE: usize = 20;
/// MKU = MDEPTH + MRCORE
pub const MKU: usize = MDEPTH + MRCORE;
/// MEXT = MKU
pub const MEXT: usize = MKU;
// ============================================================================
// CORADI COMMON 块
// ============================================================================
/// 球对称辐射参数 (`COMMON/CORADI/`)。
pub struct CoRadi {
/// 径向深度点 (cm)
pub rd: [f64; MDEPTH],
/// 核心半径 (cm)
pub rcore: f64,
/// 归一化半径
pub rfnorm: f64,
/// 角度参数
pub pim: [f64; MKU],
/// 径向点 1
pub rad1: [f64; MDEPTH],
/// 深度间距
pub delz: [[f64; MDEPTH]; MKU],
/// 角度索引
pub nud: [i32; MKU],
/// 角度索引 (F)
pub nudf: [f64; MKU],
/// 角度点数
pub kmu: i32,
/// 扩展点数
pub nrext: i32,
/// 核心射线数
pub nrcore: i32,
/// 第一个射线标志
pub nfiry: i32,
/// 扩展点数
pub ndf: i32,
}
impl Default for CoRadi {
fn default() -> Self {
Self {
rd: [0.0; MDEPTH],
rcore: 0.0,
rfnorm: 0.0,
pim: [0.0; MKU],
rad1: [0.0; MDEPTH],
delz: [[0.0; MDEPTH]; MKU],
nud: [0; MKU],
nudf: [0.0; MKU],
kmu: 0,
nrext: 0,
nrcore: 0,
nfiry: 0,
ndf: 0,
}
}
}
// ============================================================================
// COVEL COMMON 块
// ============================================================================
/// 速度参数 (`COMMON/COVEL/`)。
pub struct CoVel {
/// 扩展速度 (cm/s)
pub vel: [f64; MDEPTH],
/// 频率偏移
pub dfrq: [[f64; 2 * MDEPTH]; MKU],
/// 速度梯度
pub dvd: [f64; MDEPTH],
/// 质量损失率 (g/s)
pub xmdot: f64,
/// XMD4 参数
pub xmd4: f64,
/// 速度律指数
pub betav: f64,
/// 终端速度 (cm/s)
pub vinf: f64,
}
impl Default for CoVel {
fn default() -> Self {
Self {
vel: [0.0; MDEPTH],
dfrq: [[0.0; 2 * MDEPTH]; MKU],
dvd: [0.0; MDEPTH],
xmdot: 0.0,
xmd4: 0.0,
betav: 0.0,
vinf: 0.0,
}
}
}
// ============================================================================
// OPAVEL COMMON 块
// ============================================================================
/// 不透明度和速度参数 (`COMMON/OPAVEL/`)。
pub struct OpaVel {
/// 稀释因子
pub wdil: [f64; MDEPTH],
/// Planck 加权
pub planw: [f64; MDEPTH],
/// 辐射温度
pub trad: [[f64; MDEPTH]; MTRAD],
/// 密度对比
pub denscon: [f64; MDEPTH],
}
impl Default for OpaVel {
fn default() -> Self {
Self {
wdil: [0.0; MDEPTH],
planw: [0.0; MDEPTH],
trad: [[0.0; MDEPTH]; MTRAD],
denscon: [0.0; MDEPTH],
}
}
}