SpectraRust/src/tlusty/math/continuum/opadd.rs
2026-03-25 18:34:41 +08:00

620 lines
18 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.

//! 额外不透明度计算。
//!
//! 重构自 TLUSTY `OPADD.f`
//!
//! 计算各种额外的非标准不透明度来源:
//! - Rayleigh 散射 (HI, HeI, H2)
//! - H⁻ 束缚-自由和自由-自由不透明度
//! - H₂⁺ 束缚-自由和自由-自由不透明度
//! - He⁻ 自由-自由不透明度
//! - H₂⁻ 自由-自由不透明度
//! - CH 和 OH 连续不透明度
//! - CIA (碰撞诱导吸收) 不透明度
use crate::tlusty::math::{cia_h2h, cia_h2h2, cia_h2he, cia_hhe, h2minus, sbfch, sbfoh, CiaH2h2Data, CiaH2heData, CiaH2hData, CiaHheData};
use crate::tlusty::math::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 CiaH2h2Data,
/// CIA H2-He 数据
pub cia_h2he_data: &'a CiaH2heData,
/// CIA H2-H 数据
pub cia_h2h_data: &'a CiaH2hData,
/// CIA H-He 数据
pub cia_hhe_data: &'a 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);
}
}