SpectraRust/src/tlusty/io/iroset.rs
2026-04-01 16:35:36 +08:00

852 lines
25 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Iron-peak 元素不透明度采样初始化。
//!
//! 重构自 TLUSTY `iroset.f`
//!
//! # 功能
//!
//! - 设置深度点插值网格 (JIDR, JIDN, JIDI, XJID)
//! - 读取超级能级数据 (LEVCD)
//! - 读取谱线数据 (INKUL)
//! - 计算谱线截面并存储到 SIGFE
//!
//! # I/O 操作
//!
//! - fort.6: 进度输出
//! - fort.10: 调试输出
//! - fort.41: 谱线截面摘要
use super::Result;
use crate::tlusty::math::quit as quit_func;
use crate::tlusty::math::voigte as voigte_func;
use crate::tlusty::state::atomic::AtomicData;
use crate::tlusty::state::config::BasNum;
use crate::tlusty::state::constants::*;
use crate::tlusty::state::model::ModelState;
use crate::tlusty::state::odfpar::OdfData;
use std::io::Write;
// ============================================================================
// 常量参数
// ============================================================================
/// 碰撞展宽系数 (来自 Fortran PARAMETER)
const CSIG: f64 = 0.0149736;
// ============================================================================
// LINED COMMON 块 (谱线数据)
// ============================================================================
/// 谱线数据结构。
/// 对应 COMMON /LINED/
#[derive(Debug, Clone)]
pub struct Lined {
/// 波长 (Å)
pub wave: Vec<f64>,
/// 多普勒宽度 (深度依赖)
pub vdop: Vec<Vec<f32>>,
/// 阻尼参数 (深度依赖)
pub agam: Vec<Vec<f32>>,
/// 线强参数 (深度依赖)
pub sig0: Vec<Vec<f32>>,
/// 跃迁索引 [line][0=下能级, 1=上能级]
pub jtr: Vec<[i32; 2]>,
}
impl Lined {
pub fn new(nline: usize, nd: usize) -> Self {
Self {
wave: vec![0.0; nline],
vdop: vec![vec![0.0; nd]; nline],
agam: vec![vec![0.0; nd]; nline],
sig0: vec![vec![0.0; nd]; nline],
jtr: vec![[0; 2]; nline],
}
}
}
// ============================================================================
// COLKUR COMMON 块 (碰撞强度)
// ============================================================================
/// Kurucz 碰撞强度数据。
/// 对应 COMMON /COLKUR/
#[derive(Debug, Clone)]
pub struct ColKur {
/// 碰撞强度矩阵 Ω
pub omes: Vec<Vec<f64>>,
/// Kurucz 能级能量
pub eku: Vec<f64>,
/// Kurucz 能级 g 值
pub gku: Vec<f64>,
/// 碰撞强度总和
pub gst: f64,
/// Kurucz 能级索引
pub kku: Vec<i32>,
}
impl Default for ColKur {
fn default() -> Self {
Self {
omes: vec![vec![0.0; 100]; 100],
eku: vec![0.0; 15000],
gku: vec![0.0; 15000],
gst: 0.0,
kku: vec![0; 15000],
}
}
}
// ============================================================================
// 输入参数
// ============================================================================
/// IROSET 输入参数。
pub struct IrosetParams<'a> {
/// 深度点数
pub nd: i32,
/// 频率点数
pub nfreq: i32,
/// 离子数
pub nion: i32,
/// 有效温度 (K)
pub teff: f64,
/// β 引力因子
pub bergfc: f64,
/// 原子数据
pub atomic: &'a AtomicData,
/// 模型状态
pub model: &'a ModelState,
/// ODF 数据
pub odf: &'a mut OdfData,
/// 基本数值
pub basnum: &'a BasNum,
/// 占据概率写入选项
pub ifwop: &'a [i32],
/// Kurucz 文件路径列表
pub fiodf1_list: &'a [String],
}
/// IROSET 输出。
#[derive(Debug, Clone)]
pub struct IrosetOutput {
/// 最大频率点数/跃迁
pub nftmx: i32,
/// 总截面数
pub nftt: i32,
}
// ============================================================================
// Callback 接口
// ============================================================================
/// IROSET 子程序回调接口。
///
/// 用于在 IROSET 内部调用 LEVCD、INKUL、IJALI2 等子程序。
/// 这允许调用者提供具体实现,同时保持 IROSET 的流程与 Fortran 一致。
pub trait IrosetCallbacks {
/// 调用 LEVCD(ION, IOBS) - 设置超级能级
///
/// # 参数
/// * `ion` - 离子索引 (1-based)
/// * `iobs` - 观测标志 (0=标准, 1=使用观测能级, 2=使用所有能级)
fn call_levcd(&mut self, ion: usize, iobs: i32);
/// 调用 INKUL(ION, IOBS) - 读取谱线数据
///
/// # 参数
/// * `ion` - 离子索引 (1-based)
/// * `iobs` - 观测标志
fn call_inkul(&mut self, ion: usize, iobs: i32);
/// 调用 IJALI2() - 设置 ALI 频率索引
///
/// 在完全混合 CL/ALI 方案中,设置个别跃迁的 ALI 处理标志。
/// 对应 Fortran line 170: CALL IJALI2
fn call_ijali2(&mut self);
}
/// 空回调实现(默认不做任何操作)
#[derive(Debug, Clone, Default)]
pub struct NoOpCallbacks;
impl IrosetCallbacks for NoOpCallbacks {
fn call_levcd(&mut self, _ion: usize, _iobs: i32) {}
fn call_inkul(&mut self, _ion: usize, _iobs: i32) {}
fn call_ijali2(&mut self) {}
}
// ============================================================================
// 核心计算函数
// ============================================================================
/// 设置深度点插值索引。
///
/// 计算 JIDR, JIDN, JIDC, JIDI, XJID 数组。
fn setup_depth_interpolation(
nd: i32,
jids: i32,
dm: &[f64],
jidr: &mut [i32],
jidi: &mut [i32],
xjid: &mut [f64],
) -> (i32, i32) {
let mut jidn: i32;
let mut jidc: i32 = 2; // 默认值
jidr[0] = 1;
if jids == 0 {
// 默认3 个深度点
jidr[1] = (0.7 * nd as f64) as i32;
jidr[2] = nd;
jidn = 3;
jidc = 2;
} else {
// 用户指定间隔
let mut i: usize = 1;
while jidr[i - 1] < nd && i < MDODF {
jidr[i] = jidr[i - 1] + jids;
if jidr[i] <= (0.7 * nd as f64) as i32 {
jidc = jidr[i];
}
i += 1;
}
jidn = i as i32;
if jidr[i - 1] > nd {
if jidr[i - 2] >= nd - 5 {
jidn -= 1;
}
jidr[(jidn - 1) as usize] = nd;
}
if jidn > MDODF as i32 {
quit_func(
" Too many depths for Fe x-sections",
jidn,
MDODF as i32,
);
}
}
// 计算对数深度
let mut dml = vec![0.0f64; nd as usize];
for id in 0..nd as usize {
dml[id] = dm[id].ln();
}
// 设置插值权重
for i in 0..(jidn - 1) as usize {
let dxi = dml[jidr[i + 1] as usize - 1] - dml[jidr[i] as usize - 1];
for id in jidr[i] as usize..jidr[i + 1] as usize {
jidi[id] = (i + 1) as i32;
xjid[id] = (dml[jidr[i + 1] as usize - 1] - dml[id]) / dxi;
}
}
jidi[0] = 1;
xjid[0] = 1.0;
(jidn, jidc)
}
/// 执行 IROSET 核心计算(简化版本)。
///
/// # 参数
/// - `params`: 输入参数
/// - `lined`: 谱线数据 (可变,由 LEVCD/INKUL 填充)
/// - `colkur`: 碰撞强度数据 (可变)
/// - `wop`: 占据概率数组 (可变)
/// - `callbacks`: 子程序回调接口
///
/// # 返回
/// 计算结果
pub fn iroset_pure<C: IrosetCallbacks>(
params: &mut IrosetParams,
lined: &mut Lined,
_colkur: &mut ColKur,
wop: &mut [Vec<f64>],
callbacks: &mut C,
) -> IrosetOutput {
let nd = params.nd as usize;
let nion = params.nion as usize;
let splcom = &mut params.odf.splcom;
let odfion = &params.odf.odfion;
let atomic = params.atomic;
let model = params.model;
// 设置深度点插值
let (jidn, jidc) = setup_depth_interpolation(
params.nd,
splcom.jids,
&model.modpar.dm,
&mut splcom.jidr,
&mut splcom.jidi,
&mut splcom.xjid,
);
splcom.jidn = jidn;
// 预计算频率相关量
let xfrma = splcom.frs1.ln();
let dxnu = splcom.dxnu;
let ijd = ((9.0 / dxnu) as i32).max(2);
let mut nftt: i32 = 0;
let mut nftmx: i32 = 0;
// 处理每个离子
for ion in 0..nion {
// 边界检查
if ion >= odfion.inodf1.len() {
break;
}
let ind = odfion.inodf1[ion];
if ind <= 0 {
continue;
}
// 检查是否所有能级都在 LTE
if atomic.iondat.nllim[ion] >= atomic.iondat.nlevs[ion] {
// 设置所有能级的 WOP = UN
let nfirst = atomic.ionpar.nfirst[ion] as usize - 1;
let nlast = atomic.ionpar.nlast[ion] as usize - 1;
for id in 0..nd {
for i in nfirst..=nlast {
wop[i][id] = UN;
}
}
continue;
}
// 设置超级能级并读取谱线数据
// 对应 Fortran: CALL LEVCD(ION,IOBS)
let iobs = odfion.ikobs[ion];
// 调用 LEVCD 设置超级能级
// Fortran line 109: CALL LEVCD(ION,IOBS)
callbacks.call_levcd(ion + 1, iobs);
// 对应 Fortran: CALL INKUL(ION,IOBS)
// Fortran line 110: CALL INKUL(ION,IOBS)
callbacks.call_inkul(ion + 1, iobs);
// 输出进度信息 (对应 WRITE(6,610))
eprintln!(
"\n *** superlines for {:4}: {:4} selected internal lines: {:10}",
ion + 1,
atomic.printp.typion[ion],
params.odf.levcom.nlinku
);
if params.odf.levcom.nlinku > MLINE as i32 {
quit_func(
"too many internal lines",
params.odf.levcom.nlinku,
MLINE as i32,
);
}
// 处理每个跃迁(简化版本)
// 完整实现需要遍历所有跃迁并计算截面
// 这里只做基本的计数
// 计算 FCOL简化
let levcom = &params.odf.levcom;
for k in 0..levcom.nlinku as usize {
if k < lined.wave.len() && lined.wave[k] > 0.0 {
let _frl = CAS / lined.wave[k];
// 简化的频率点计数
let nft = (ijd * 2) as i32;
if nft > nftmx {
nftmx = nft;
}
nftt += nft;
}
}
}
splcom.nftt = nftt;
// 对应 Fortran WRITE(10,*):
// WRITE(10,*) ' Max. number of freq. per transition:',NFTMX
// WRITE(10,*) ' Number of iron line cross-sections: ',NFTT
eprintln!(" Max. number of freq. per transition:{}", nftmx);
eprintln!(" Number of iron line cross-sections: {}", nftt);
// 对应 Fortran line 170: CALL IJALI2
// 设置 ALI 频率索引
callbacks.call_ijali2();
IrosetOutput { nftmx, nftt }
}
// ============================================================================
// 带 I/O 的入口函数
// ============================================================================
/// 执行 IROSET包含 I/O 操作。
///
/// # 参数
/// - `params`: 输入参数
/// - `lined`: 谱线数据
/// - `colkur`: 碰撞强度数据
/// - `wop`: 占据概率数组
/// - `writer6`: 标准输出写入器 (fort.6)
/// - `writer10`: 调试输出写入器 (fort.10)
/// - `writer41`: 截面摘要写入器 (fort.41)
///
/// # 返回
/// 计算结果
pub fn iroset<W6: Write, W10: Write, W41: Write, C: IrosetCallbacks>(
params: &mut IrosetParams,
lined: &mut Lined,
colkur: &mut ColKur,
wop: &mut [Vec<f64>],
writer6: &mut W6,
writer10: &mut W10,
writer41: &mut W41,
callbacks: &mut C,
) -> Result<IrosetOutput> {
let nd = params.nd as usize;
let nfreq = params.nfreq as usize;
let nion = params.nion as usize;
let splcom = &mut params.odf.splcom;
let levcom = &mut params.odf.levcom;
let odfion = &params.odf.odfion;
let atomic = params.atomic;
let model = params.model;
// 设置深度点插值
let (jidn, jidc) = setup_depth_interpolation(
params.nd,
splcom.jids,
&model.modpar.dm,
&mut splcom.jidr,
&mut splcom.jidi,
&mut splcom.xjid,
);
splcom.jidn = jidn;
// 预计算频率相关量
let xfrma = splcom.frs1.ln();
let dxnu = splcom.dxnu;
let ijd = ((9.0 / dxnu) as i32).max(2);
let mut nftt: i32 = 0;
let mut nftmx: i32 = 0;
// 临时数组
let mut sigt = vec![vec![0.0f64; nfreq]; MDODF];
// 处理每个离子
for ion in 0..nion {
// 边界检查
if ion >= odfion.inodf1.len() {
break;
}
let ind = odfion.inodf1[ion];
if ind <= 0 {
continue;
}
// 检查是否所有能级都在 LTE
if atomic.iondat.nllim[ion] >= atomic.iondat.nlevs[ion] {
let nfirst = atomic.ionpar.nfirst[ion] as usize - 1;
let nlast = atomic.ionpar.nlast[ion] as usize - 1;
for id in 0..nd {
for i in nfirst..=nlast {
wop[i][id] = UN;
}
}
continue;
}
// 设置超级能级并读取谱线数据
// 对应 Fortran: CALL LEVCD(ION,IOBS)
let iobs = odfion.ikobs[ion];
// 调用 LEVCD 设置超级能级
// Fortran line 109: CALL LEVCD(ION,IOBS)
callbacks.call_levcd(ion + 1, iobs);
// 对应 Fortran: CALL INKUL(ION,IOBS)
// Fortran line 110: CALL INKUL(ION,IOBS)
callbacks.call_inkul(ion + 1, iobs);
// 进度输出
writeln!(
writer6,
"\n *** superlines for {:4}: {:4} selected internal lines: {:10}",
ion + 1,
atomic.printp.typion[ion],
levcom.nlinku
)?;
if levcom.nlinku > MLINE as i32 {
quit_func("too many internal lines", levcom.nlinku, MLINE as i32);
}
let n1 = atomic.ionpar.nfirst[ion] as usize;
let nlii = atomic.ionpar.nlast[ion] as usize - n1 + 1;
// 处理每个跃迁对
for il in 0..(nlii - 1) {
let mut kevl = 0;
let mut kodl = 0;
if levcom.jen[il] <= levcom.nevku[ion] {
kevl = levcom.jen[il];
} else {
kodl = levcom.jen[il] - levcom.nevku[ion];
}
let ilok = n1 + il - 1;
for iu in (il + 1)..nlii {
let iupk = n1 + iu - 1;
let itr = atomic.trapar.itra[ilok][iupk];
if itr <= 0 {
continue;
}
let itr_idx = (itr - 1) as usize;
let indxp = atomic.trapar.indexp[itr_idx].abs();
let mut w1 = 0.0;
let mut w2 = 0.0;
let mut ifrku: usize = 0;
let mut nft = 0;
let mut nlt = 0;
let mut kevu = 0;
let mut kodu = 0;
if levcom.jen[iu] <= levcom.nevku[ion] {
kevu = levcom.jen[iu];
} else {
kodu = levcom.jen[iu] - levcom.nevku[ion];
}
let (kev, kod, gsuper) = if kevl != 0 {
(
kevl,
kodu,
levcom.ymku[(levcom.jen[il] - 1) as usize][0],
)
} else {
(
kevu,
kodl,
levcom.ymku[(levcom.jen[il] - levcom.nevku[ion] - 1) as usize][1],
)
};
// 初始化 SIGT
for ij in 0..nfreq {
for i in 0..jidn as usize {
sigt[i][ij] = 0.0;
}
}
let mut fcol = 0.0;
for k in 0..levcom.nlinku as usize {
let ksev_val = levcom.ksev[k];
let ksod_val = levcom.ksod[k];
if ksev_val != kev && ksod_val != kod {
continue;
}
nlt += 1;
let frl = CAS / lined.wave[k];
let mut ijl = ((xfrma - frl.ln()) / dxnu) as i32 + splcom.nfrs1;
let mut ijl = ijl as usize;
if ijl >= nfreq {
ijl = nfreq - 1;
}
let d0 = ((model.frqall.freq[ijl] - frl)
/ (model.frqall.freq[ijl] - model.frqall.freq[ijl + 1]))
.abs();
if d0 > HALF {
while ijl > 0 && frl > model.frqall.freq[ijl] {
ijl -= 1;
}
while ijl < nfreq - 1 && frl < model.frqall.freq[ijl] {
ijl += 1;
}
if ijl > 0 {
let d1 = frl - model.frqall.freq[ijl];
let d2 = model.frqall.freq[ijl - 1] - frl;
if d2 < d1 {
ijl -= 1;
}
}
}
let ij0 = ijl.saturating_sub(ijd as usize).max(0);
let ij1 = (ijl + ijd as usize).min(nfreq - 1);
if ifrku == 0 {
ifrku = ij0;
}
nft = (ij1 - ifrku + 1) as i32;
for ij in ij0..=ij1 {
let dnu = model.frqall.freq[ij] - frl;
for i in 0..jidn as usize {
let vv = dnu * lined.vdop[k][i] as f64;
let prfk = voigte_func(vv, lined.agam[k][i] as f64) / gsuper;
sigt[i][ij] += lined.sig0[k][i] as f64 * prfk;
}
}
fcol += lined.sig0[k][jidc as usize - 1] as f64
/ lined.vdop[k][jidc as usize - 1] as f64;
}
// 设置振子强度(只读访问,实际写入需要可变引用)
let _osc0_val = if indxp == 3 || indxp == 4 {
atomic.trapar.strlx
} else if fcol > 0.0 {
fcol / gsuper / CSIG
} else {
0.0
};
if nlt > 0 {
w1 = CAS / model.frqall.freq[ifrku];
w2 = CAS / model.frqall.freq[ifrku + nft as usize - 1];
}
if nft > 0 {
nftt += nft;
// 存储截面对数
for ij in ifrku..(ifrku + nft as usize) {
let kj = ij - ifrku + nftt as usize - nft as usize + 1;
// Fortran line 189-191: 检查是否超过 MCFE 限制
if kj > MCFE {
quit_func(
" Too many Fe cross-sect. to store",
kj as i32,
MCFE as i32,
);
}
for i in 0..jidn as usize {
let sxx = (sigt[i][ij] + 1e-40f64).ln();
if kj < splcom.sigfe[0].len() && i < splcom.sigfe[0][kj].len() {
splcom.sigfe[0][kj][i] = sxx as f32;
}
}
}
}
// 输出谱线摘要
writeln!(
writer41,
"{:4}{:4}{:12.3}{:12.3}{:10}{:10}{:10}{:12.3e}",
il + 1,
iu + 1,
w1,
w2,
ifrku + 1,
nft,
nlt,
_osc0_val
)?;
if nft > nftmx {
nftmx = nft;
}
}
}
}
// 调试输出
writeln!(
writer10,
" Max. number of freq. per transition: {}",
nftmx
)?;
writeln!(
writer10,
" Number of iron line cross-sections: {}",
nftt
)?;
splcom.nftt = nftt;
// 对应 Fortran line 170: CALL IJALI2
// 设置 ALI 频率索引
callbacks.call_ijali2();
Ok(IrosetOutput { nftmx, nftt })
}
// ============================================================================
// 测试
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_csig_constant() {
assert!((CSIG - 0.0149736).abs() < 1e-10);
}
#[test]
fn test_lined_creation() {
let lined = Lined::new(100, 3);
assert_eq!(lined.wave.len(), 100);
assert_eq!(lined.vdop.len(), 100);
assert_eq!(lined.vdop[0].len(), 3);
assert_eq!(lined.jtr.len(), 100);
}
#[test]
fn test_colkur_default() {
let colkur = ColKur::default();
assert_eq!(colkur.omes.len(), 100);
assert_eq!(colkur.eku.len(), 15000);
assert_eq!(colkur.kku.len(), 15000);
}
#[test]
fn test_setup_depth_interpolation() {
let nd = 50;
let dm: Vec<f64> = (1..=nd).map(|i| i as f64 * 0.1).collect();
let mut jidr = vec![0; MDODF];
let mut jidi = vec![0; MDEPTH];
let mut xjid = vec![0.0; MDEPTH];
// 测试默认模式 (jids = 0)
let (jidn, jidc) = setup_depth_interpolation(nd, 0, &dm, &mut jidr, &mut jidi, &mut xjid);
assert_eq!(jidn, 3);
assert_eq!(jidc, 2);
assert_eq!(jidr[0], 1);
assert_eq!(jidr[2], nd);
// 测试用户指定间隔模式
let mut jidr2 = vec![0; MDODF];
let mut jidi2 = vec![0; MDEPTH];
let mut xjid2 = vec![0.0; MDEPTH];
let (jidn2, _jidc2) =
setup_depth_interpolation(nd, 10, &dm, &mut jidr2, &mut jidi2, &mut xjid2);
assert!(jidn2 >= 3);
assert_eq!(jidr2[0], 1);
}
#[test]
fn test_iroset_pure_empty() {
// 测试空输入
let mut atomic = AtomicData::default();
let mut model = ModelState::default();
let mut odf = OdfData::default();
// 设置基本参数
atomic.ionpar.nfirst[0] = 1;
atomic.ionpar.nlast[0] = 5;
atomic.iondat.nlevs[0] = 5;
atomic.iondat.nllim[0] = 5; // 所有能级在 LTE
// 初始化模型数据
model.modpar.dm = vec![0.1, 0.2, 0.3]; // 3 个深度点
let basnum = BasNum::default();
let ifwop = vec![0; 10];
let mut params = IrosetParams {
nd: 3,
nfreq: 100,
nion: 1,
teff: 10000.0,
bergfc: 0.0,
atomic: &atomic,
model: &model,
odf: &mut odf,
basnum: &basnum,
ifwop: &ifwop,
fiodf1_list: &[],
};
let mut lined = Lined::new(100, 3);
let mut colkur = ColKur::default();
let mut wop = vec![vec![0.0; 3]; 10];
let mut callbacks = NoOpCallbacks;
let result = iroset_pure(&mut params, &mut lined, &mut colkur, &mut wop, &mut callbacks);
// 由于所有能级在 LTE应该跳过处理
assert!(result.nftmx >= 0);
assert!(result.nftt >= 0);
}
#[test]
fn test_iroset_with_writer() {
use std::io::Cursor;
let mut atomic = AtomicData::default();
let mut model = ModelState::default();
let mut odf = OdfData::default();
atomic.iondat.nlevs[0] = 5;
atomic.iondat.nllim[0] = 5;
// 初始化模型数据
model.modpar.dm = vec![0.1, 0.2, 0.3]; // 3 个深度点
let basnum = BasNum::default();
let ifwop = vec![0; 10];
let mut params = IrosetParams {
nd: 3,
nfreq: 100,
nion: 1,
teff: 10000.0,
bergfc: 0.0,
atomic: &atomic,
model: &model,
odf: &mut odf,
basnum: &basnum,
ifwop: &ifwop,
fiodf1_list: &[],
};
let mut lined = Lined::new(100, 3);
let mut colkur = ColKur::default();
let mut wop = vec![vec![0.0; 3]; 10];
let mut callbacks = NoOpCallbacks;
let mut writer6 = Cursor::new(Vec::new());
let mut writer10 = Cursor::new(Vec::new());
let mut writer41 = Cursor::new(Vec::new());
let result = iroset(
&mut params,
&mut lined,
&mut colkur,
&mut wop,
&mut writer6,
&mut writer10,
&mut writer41,
&mut callbacks,
)
.unwrap();
assert!(result.nftmx >= 0);
// 验证调试输出
let debug_output = String::from_utf8(writer10.into_inner()).unwrap();
assert!(debug_output.contains("freq."));
}
}