feat: 添加更多重构模块 (第7批)

包含 IO 和 math 模块的实现:
- IO: initia, levcd, linset, ltegr, ltegrd, odfset, outpri, resolv, srtfrq, start, tabini, xenini
- Math: accel2, alisk1, alisk2, alist1, alist2, concor, conout, conref, contmd, contmp, coolrt, greyd, inilam, linsel, lucy, lymlin, matcon, matgen, moleq, newdm, newdmt, odf1, opacf0, opacf1, opacfa, opacfd, opacfl, opactr, opadd, opahst, pgset, princ, prnt, pzeval, quasim, radpre, radtot, rates1, ratsp1, rdata, rdatax, rechck, rhoeos, rhonen, rhsgen, rossop, rtecf1, rtecmc, rtecmu, rtecom, rtefr1, rteint, russel, rybchn, rybene, rybheq, rybsol, sgmer1, sigave, sigk, solve, solves, state, steqeq, temcor, temper, topbas, trmder, trmdrt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
fmq
2026-03-25 01:46:29 +08:00
co-authored by Claude Opus 4.6
parent 21cb6af16c
commit a086e313cb
85 changed files with 48464 additions and 6 deletions
+619
View File
@@ -0,0 +1,619 @@
//! 额外不透明度计算。
//!
//! 重构自 TLUSTY `OPADD.f`
//!
//! 计算各种额外的非标准不透明度来源:
//! - Rayleigh 散射 (HI, HeI, H2)
//! - H⁻ 束缚-自由和自由-自由不透明度
//! - H₂⁺ 束缚-自由和自由-自由不透明度
//! - He⁻ 自由-自由不透明度
//! - H₂⁻ 自由-自由不透明度
//! - CH 和 OH 连续不透明度
//! - CIA (碰撞诱导吸收) 不透明度
use super::{cia_h2h, cia_h2h2, cia_h2he, cia_hhe, h2minus, sbfch, sbfoh};
use crate::math::sffhmi::sffhmi;
// ============================================================================
// 常量
// ============================================================================
/// HI Rayleigh 散射阈值频率 (Hz)
const FRAY: f64 = 2.463e15;
/// HeI Rayleigh 散射阈值频率 (Hz)
const FRAYHE: f64 = 5.150e15;
/// H2 Rayleigh 散射阈值频率 (Hz)
const FRAYH2: f64 = 2.922e15;
/// 光速 (Å/s)
const CLS: f64 = 2.997925e18;
/// 光速 (cm/s)
const C18: f64 = 2.997925e18;
/// H⁻ 束缚-自由常数
const CR0: f64 = 5.799e-13;
const CR1: f64 = 1.422e-6;
const CR2: f64 = 2.784;
const TENM4: f64 = 1.0e-4;
/// H⁻ 束缚-自由阈值
const THM0: f64 = 8.7629e3;
/// Saha 常数
const SBHM: f64 = 1.0353e-16;
/// H₂⁺ 阈值比率
const TRHA: f64 = 1.5;
/// H⁻ 自由-自由常数
const SFF0: f64 = 1.3727e-25;
const SFF1: f64 = 4.3748e-10;
const SFFM2: f64 = -2.5993e-7;
/// HeI 阈值频率
const F0HE1: f64 = 3.29e15;
/// HeII 阈值频率
const F0HE2: f64 = 1.316e16;
/// Saha 常数
const SBH0: f64 = 4.1412e-16;
const SG01: f64 = 2.815e-16;
const SG02: f64 = 4.504e-15;
/// 普朗克常数 / 玻尔兹曼常数 (erg/K)
const HK: f64 = 6.626176e-27 / 1.380662e-16;
// ============================================================================
// OPADD 输入参数
// ============================================================================
/// OPADD 输入参数。
#[derive(Debug, Clone)]
pub struct OpaddInput {
/// 计算模式
/// - -1: 初始化(计算深度相关量)
/// - 0: 计算不透明度
/// - 1: 计算不透明度和导数
pub mode: i32,
/// 调用标志 (>0 表示需要初始化温度相关量)
pub icall: i32,
/// 频率索引 (0-indexed)
pub ij: usize,
/// 深度索引 (0-indexed)
pub id: usize,
}
/// OPADD 开关参数(来自 COMMON/OPCKEY)。
#[derive(Debug, Clone, Default)]
pub struct OpaddSwitches {
/// HI Rayleigh 散射开关
pub irsct: i32,
/// HeI Rayleigh 散射开关
pub irsche: i32,
/// H2 Rayleigh 散射开关
pub irsch2: i32,
/// H⁻ 不透明度开关
pub iophmi: i32,
/// H₂⁺ 不透明度开关
pub ioph2p: i32,
/// He⁻ 不透明度开关
pub iophem: i32,
/// H₂⁻ 不透明度开关
pub ioph2m: i32,
/// CH 不透明度开关
pub iopch: i32,
/// OH 不透明度开关
pub iopoh: i32,
/// CIA H2-H2 不透明度开关
pub ioh2h2: i32,
/// CIA H2-He 不透明度开关
pub ioh2he: i32,
/// CIA H2-H 不透明度开关
pub ioh2h: i32,
/// CIA H-He 不透明度开关
pub iohhe: i32,
/// 分子开关
pub ifmol: i32,
/// 分子温度极限
pub tmolim: f64,
}
/// OPADD 模型状态(所需变量)。
#[derive(Debug, Clone)]
pub struct OpaddModel<'a> {
/// 温度数组
pub temp: &'a [f64],
/// 电子密度数组
pub elec: &'a [f64],
/// 频率数组
pub freq: &'a [f64],
/// 深度数
pub nd: usize,
/// 能级数
pub nlevel: usize,
/// H 离子索引
pub ielh: i32,
/// He 离子索引
pub iathe: i32,
/// H 第一能级索引 (1-indexed in Fortran)
pub n0hn: i32,
/// H⁺ 索引
pub nkh: i32,
/// He 第一能级索引
pub n0ahe: i32,
/// 原子数密度数组 [物种][深度]
pub anato: &'a [Vec<f64>],
/// 离子数密度数组 [物种][深度]
pub anion: &'a [Vec<f64>],
/// 分子数密度数组 [物种][深度]
pub anmol: &'a [Vec<f64>],
/// 占据数数组 [能级][深度]
pub popul: &'a [Vec<f64>],
/// 光电离截面表 [跃迁][频率]
pub cross: &'a [Vec<f64>],
/// 连续跃迁计数
pub ncon: usize,
/// CIA H2-H2 数据
pub cia_h2h2_data: &'a cia_h2h2::CiaH2h2Data,
/// CIA H2-He 数据
pub cia_h2he_data: &'a cia_h2he::CiaH2heData,
/// CIA H2-H 数据
pub cia_h2h_data: &'a cia_h2h::CiaH2hData,
/// CIA H-He 数据
pub cia_hhe_data: &'a cia_hhe::CiaHheData,
}
/// OPADD 输出结果。
#[derive(Debug, Clone, Default)]
pub struct OpaddOutput {
/// 吸收系数
pub abad: f64,
/// 发射系数
pub emad: f64,
/// 散射系数
pub scad: f64,
/// 对温度的吸收导数
pub dat: f64,
/// 对电子密度的吸收导数
pub dan: f64,
/// 对温度的发射导数
pub det: f64,
/// 对电子密度的发射导数
pub den: f64,
/// 对温度的散射导数
pub dst: f64,
/// 对电子密度的散射导数
pub dsn: f64,
/// 对能级占据数的导数
pub ddn: Vec<f64>,
}
// ============================================================================
// 缓存状态(对应 Fortran SAVE 变量)
// ============================================================================
/// 缓存的温度相关量。
#[derive(Debug, Clone, Default)]
pub struct OpaddCache {
/// 温度
pub t: f64,
/// 0.0001 * T
pub deltat: f64,
/// 电子密度
pub ane: f64,
/// h/kT
pub hkt: f64,
/// 1/(T*sqrt(T))
pub t32: f64,
/// THM0/T
pub xhm: f64,
/// 中性氢占据数
pub popi: f64,
/// H⁻ Saha 因子
pub sb00: f64,
}
// ============================================================================
// OPADD 主函数
// ============================================================================
/// 计算额外不透明度。
///
/// # 参数
///
/// * `input` - 输入参数
/// * `switches` - 开关配置
/// * `model` - 模型状态
/// * `cache` - 缓存的温度相关量(可变引用)
///
/// # 返回值
///
/// 计算结果包含吸收、发射、散射系数及其导数。
///
/// # Fortran 原始代码
///
/// ```fortran
/// SUBROUTINE OPADD(MODE,ICALL,IJ,ID)
/// ```
pub fn opadd(
input: &OpaddInput,
switches: &OpaddSwitches,
model: &OpaddModel,
cache: &mut OpaddCache,
) -> OpaddOutput {
let mut output = OpaddOutput {
ddn: vec![0.0; model.nlevel],
..Default::default()
};
let mut ab0 = 0.0_f64;
let mut ab1 = 0.0_f64;
let mut dab1 = 0.0_f64;
let fr = model.freq[input.ij];
let _al = CLS / fr; // 波长 (Å)
// 获取 H 和 He 相关变量
let (n0hn, nkh, n0ahe): (i32, i32, i32) = if model.ielh > 0 {
(
model.n0hn,
model.nkh,
if model.iathe > 0 { model.n0ahe } else { 0 },
)
} else {
(0, 0, 0)
};
// 初始化温度相关量
if input.icall > 0 {
let id = input.id;
cache.t = model.temp[id];
cache.deltat = TENM4 * cache.t;
cache.ane = model.elec[id];
cache.hkt = HK / cache.t;
cache.t32 = 1.0 / cache.t / cache.t.sqrt();
cache.xhm = THM0 / cache.t;
// 获取 H 和 H⁺ 数密度
let (ah, ahp): (f64, f64) = if model.ielh > 0 {
let ah = model.popul[(n0hn - 1) as usize][id];
let ahp = model.popul[(nkh - 1) as usize][id];
(ah, ahp)
} else {
(model.anato[0][id], model.anion[0][id])
};
cache.popi = ah;
// H⁻ Saha 因子
cache.sb00 = SBHM * cache.t32 * (cache.xhm.exp()) * cache.popi * cache.ane;
}
// 获取 He 数密度
let ahe: f64 = if model.iathe > 0 {
model.popul[(n0ahe - 1) as usize][input.id]
} else {
model.anato[1][input.id]
};
// 获取 H 数密度(用于散射)
let ah: f64 = if model.ielh > 0 {
model.popul[(n0hn - 1) as usize][input.id]
} else {
model.anato[0][input.id]
};
let t = cache.t;
let ane = cache.ane;
let mut it = model.ncon as i32;
// -----------------------------------------------------------------------
// HI Rayleigh 散射
// -----------------------------------------------------------------------
if switches.irsct != 0 {
it += 1;
output.scad = ah * model.cross[it as usize][input.ij];
}
// -----------------------------------------------------------------------
// HeI Rayleigh 散射
// -----------------------------------------------------------------------
if switches.irsche != 0 && input.mode >= 0 {
it += 1;
output.scad += ahe * model.cross[it as usize][input.ij];
}
// -----------------------------------------------------------------------
// H2 Rayleigh 散射
// -----------------------------------------------------------------------
if switches.irsch2 != 0 && input.mode >= 0 && switches.ifmol > 0 {
it += 1;
let sg = model.cross[it as usize][input.ij] * model.anmol[2][input.id];
if t < switches.tmolim {
output.scad += sg;
}
}
// -----------------------------------------------------------------------
// H⁻ 束缚-自由和自由-自由
// -----------------------------------------------------------------------
if switches.iophmi > 0 {
it += 1;
if t < 20000.0 {
let sb = cache.sb00 * model.cross[it as usize][input.ij];
let sf = sffhmi(ah, fr, t) * ane;
ab0 = sb + sf;
}
}
// -----------------------------------------------------------------------
// H₂⁺ 束缚-自由和自由-自由
// -----------------------------------------------------------------------
if switches.ioph2p > 0 {
it += 1;
let x2 = -model.cross[it as usize][input.ij] / t + model.cross[it as usize + 1][input.ij];
it += 1;
let mut sb = 0.0;
if x2 > -150.0 && fr < 3.28e15 && t <= 9000.0 {
let ahp = if model.ielh > 0 {
model.popul[(nkh - 1) as usize][input.id]
} else {
model.anion[0][input.id]
};
sb = ah * x2.exp() * ahp;
}
ab0 += sb;
dab1 = sb * model.cross[it as usize - 1][input.ij] / t / t;
if n0hn > 0 {
output.ddn[(n0hn - 1) as usize] += sb / cache.popi;
}
if nkh > 0 {
output.ddn[(nkh - 1) as usize] += sb / model.popul[(nkh - 1) as usize][input.id];
}
}
// -----------------------------------------------------------------------
// He⁻ 自由-自由
// -----------------------------------------------------------------------
if switches.iophem > 0 {
it += 1;
let sg = model.cross[it as usize][input.ij] * t
+ model.cross[it as usize + 1][input.ij]
+ model.cross[it as usize + 2][input.ij] / t;
ab0 += sg * ane * ahe;
}
// -----------------------------------------------------------------------
// H₂⁻ 自由-自由
// -----------------------------------------------------------------------
if switches.ioph2m != 0 && input.mode >= 0 && switches.ifmol > 0 && t < switches.tmolim {
let oph2 = h2minus(t, model.anmol[2][input.id], ane, fr);
ab1 += oph2;
}
// -----------------------------------------------------------------------
// CH 和 OH 连续不透明度
// -----------------------------------------------------------------------
if input.mode >= 0 && switches.ifmol > 0 && t < switches.tmolim {
if switches.iopch > 0 {
ab0 += sbfch(fr, t) * model.anmol[5][input.id];
}
if switches.iopoh > 0 {
ab0 += sbfoh(fr, t) * model.anmol[4][input.id];
}
// -------------------------------------------------------------------
// CIA H2-H2 不透明度
// -------------------------------------------------------------------
if switches.ioh2h2 > 0 {
let oph2 = cia_h2h2(t, model.anmol[2][input.id], fr, model.cia_h2h2_data);
ab1 += oph2;
}
// -------------------------------------------------------------------
// CIA H2-He 不透明度
// -------------------------------------------------------------------
if switches.ioh2he > 0 {
let oph2 = cia_h2he(t, model.anmol[2][input.id], ahe, fr, model.cia_h2he_data);
ab1 += oph2;
}
// -------------------------------------------------------------------
// CIA H2-H 不透明度
// -------------------------------------------------------------------
if switches.ioh2h > 0 {
let oph2 = cia_h2h(t, model.anmol[2][input.id], ah, fr, model.cia_h2h_data);
ab1 += oph2;
}
// -------------------------------------------------------------------
// CIA H-He 不透明度
// -------------------------------------------------------------------
if switches.iohhe > 0 {
let oph2 = cia_hhe(t, ah, ahe, fr, model.cia_hhe_data);
ab1 += oph2;
}
}
// -----------------------------------------------------------------------
// 最终计算吸收和发射系数及其导数
// -----------------------------------------------------------------------
if input.mode < 0 {
return output;
}
let x = (-cache.hkt * fr).exp();
let x1 = 1.0 - x;
let fr15 = fr * 1.0e-15;
let _bnx = 1.0e-22 * fr15 * fr15 * fr15 * x; // BN 常数(未使用)
ab1 /= x1;
output.abad = ab0 + ab1;
output.emad = ab0 + ab1;
if input.mode == 1 {
let hkft = cache.hkt * fr / t;
let _db = hkft * ab0; // 未使用
output.dat = dab1;
output.det = dab1;
output.dan = ab0 / ane;
output.den = ab0 / ane;
}
output
}
// ============================================================================
// 测试
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
use std::sync::LazyLock;
/// 测试数据
static TEST_DATA: LazyLock<TestData> = LazyLock::new(|| {
TestData {
temp: vec![10000.0, 8000.0, 6000.0],
elec: vec![1.0e13, 1.0e12, 1.0e11],
freq: vec![1.0e15, 2.0e15, 3.0e15],
anato: vec![
vec![1.0e14, 1.0e14, 1.0e14], // H
vec![1.0e13, 1.0e13, 1.0e13], // He
],
anion: vec![
vec![1.0e12, 1.0e12, 1.0e12], // H+
],
anmol: vec![
vec![0.0; 3], // H2
vec![0.0; 3], // 索引 1
vec![0.0; 3], // 索引 2 (H2)
vec![0.0; 3], // OH (索引 3)
vec![0.0; 3], // 索引 4 (OH)
vec![0.0; 3], // CH (索引 5)
vec![0.0; 3], // 额外
],
popul: vec![vec![0.0; 3]; 10],
cross: vec![vec![0.0; 3]; 20],
cia_h2h2_data: cia_h2h2::CiaH2h2Data::default(),
cia_h2he_data: cia_h2he::CiaH2heData::default(),
cia_h2h_data: cia_h2h::CiaH2hData::default(),
cia_hhe_data: cia_hhe::CiaHheData::default(),
}
});
struct TestData {
temp: Vec<f64>,
elec: Vec<f64>,
freq: Vec<f64>,
anato: Vec<Vec<f64>>,
anion: Vec<Vec<f64>>,
anmol: Vec<Vec<f64>>,
popul: Vec<Vec<f64>>,
cross: Vec<Vec<f64>>,
cia_h2h2_data: cia_h2h2::CiaH2h2Data,
cia_h2he_data: cia_h2he::CiaH2heData,
cia_h2h_data: cia_h2h::CiaH2hData,
cia_hhe_data: cia_hhe::CiaHheData,
}
/// 创建测试用的模型状态
fn create_test_model() -> (OpaddModel<'static>, OpaddSwitches) {
let data = &*TEST_DATA;
let model = OpaddModel {
temp: &data.temp,
elec: &data.elec,
freq: &data.freq,
nd: 3,
nlevel: 10,
ielh: 0,
iathe: 0,
n0hn: 0,
nkh: 0,
n0ahe: 0,
anato: &data.anato,
anion: &data.anion,
anmol: &data.anmol,
popul: &data.popul,
cross: &data.cross,
ncon: 5,
cia_h2h2_data: &data.cia_h2h2_data,
cia_h2he_data: &data.cia_h2he_data,
cia_h2h_data: &data.cia_h2h_data,
cia_hhe_data: &data.cia_hhe_data,
};
let switches = OpaddSwitches {
irsct: 0,
irsche: 0,
irsch2: 0,
iophmi: 0,
ioph2p: 0,
iophem: 0,
ioph2m: 0,
iopch: 0,
iopoh: 0,
ioh2h2: 0,
ioh2he: 0,
ioh2h: 0,
iohhe: 0,
ifmol: 0,
tmolim: 5000.0,
};
(model, switches)
}
#[test]
fn test_opadd_basic() {
let (model, switches) = create_test_model();
let mut cache = OpaddCache::default();
let input = OpaddInput {
mode: 0,
icall: 1,
ij: 0,
id: 0,
};
let result = opadd(&input, &switches, &model, &mut cache);
// 当所有开关关闭时,结果应为 0
assert_relative_eq!(result.abad, 0.0, epsilon = 1e-20);
assert_relative_eq!(result.emad, 0.0, epsilon = 1e-20);
assert_relative_eq!(result.scad, 0.0, epsilon = 1e-20);
}
#[test]
fn test_opadd_mode_negative() {
let (model, switches) = create_test_model();
let mut cache = OpaddCache::default();
let input = OpaddInput {
mode: -1,
icall: 1,
ij: 0,
id: 0,
};
let result = opadd(&input, &switches, &model, &mut cache);
// mode < 0 应立即返回
assert_relative_eq!(result.abad, 0.0, epsilon = 1e-20);
}
#[test]
fn test_opadd_cache_initialization() {
let (model, switches) = create_test_model();
let mut cache = OpaddCache::default();
let input = OpaddInput {
mode: 0,
icall: 1,
ij: 0,
id: 0,
};
let _ = opadd(&input, &switches, &model, &mut cache);
// 验证缓存已初始化
assert_relative_eq!(cache.t, 10000.0, epsilon = 1e-10);
assert_relative_eq!(cache.ane, 1.0e13, epsilon = 1e-10);
assert!(cache.hkt > 0.0);
}
}