//! 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, /// 多普勒宽度 (深度依赖) pub vdop: Vec>, /// 阻尼参数 (深度依赖) pub agam: Vec>, /// 线强参数 (深度依赖) pub sig0: Vec>, /// 跃迁索引 [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>, /// Kurucz 能级能量 pub eku: Vec, /// Kurucz 能级 g 值 pub gku: Vec, /// 碰撞强度总和 pub gst: f64, /// Kurucz 能级索引 pub kku: Vec, } 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( params: &mut IrosetParams, lined: &mut Lined, _colkur: &mut ColKur, wop: &mut [Vec], callbacks: &mut C, ) -> IrosetOutput { let nd = params.nd as usize; let nion = params.nion as usize; let splcom = &mut params.odf.splcom; let odfion = ¶ms.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 = ¶ms.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( params: &mut IrosetParams, lined: &mut Lined, colkur: &mut ColKur, wop: &mut [Vec], writer6: &mut W6, writer10: &mut W10, writer41: &mut W41, callbacks: &mut C, ) -> Result { 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 = ¶ms.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 = (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.")); } }