重构4
This commit is contained in:
+1483
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,199 @@
|
||||
//! 碰撞强度计算。
|
||||
//!
|
||||
//! 重构自 TLUSTY `cspec.f`
|
||||
//!
|
||||
//! 使用 Van Regemorter 公式计算非标准的碰撞率,
|
||||
//! 遵循 Mihalas (1978, Stellar Atmospheres, 2nd edition) 的建议。
|
||||
//!
|
||||
//! ## 碰撞类型 (IC)
|
||||
//!
|
||||
//! - IC = -1: 中性粒子(Neutrals)
|
||||
//! - IC = -2: 离子(Ions)
|
||||
//! - IC = -11: 特殊处理
|
||||
//! - IC = -12: He I 禁戒跃迁
|
||||
|
||||
use crate::state::constants::UN;
|
||||
|
||||
/// He I 禁戒跃迁的系数 (CHE1FB)
|
||||
/// 原始 Fortran DATA 语句:
|
||||
/// CHE1FB(1,1-4) = 9.63675, -2.22941, -17.30103
|
||||
/// CHE1FB(2,1-4) = 10.85578, -2.40931, -27.00903
|
||||
/// CHE1FB(3,1-4) = 8.38043, -2.04791, -7.36621
|
||||
const CHE1FB: [[f64; 4]; 3] = [
|
||||
[9.63675, -2.22941, -17.30103, 0.0], // IFORB = 1
|
||||
[10.85578, -2.40931, -27.00903, 0.0], // IFORB = 2
|
||||
[8.38043, -2.04791, -7.36621, 0.0], // IFORB = 3
|
||||
];
|
||||
|
||||
/// 指数积分近似系数 (Abramowitz & Stegun)
|
||||
const EXPIA1: f64 = -0.57721566;
|
||||
const EXPIA2: f64 = 0.99999193;
|
||||
const EXPIA3: f64 = -0.24991055;
|
||||
const EXPIA4: f64 = 0.05519968;
|
||||
const EXPIA5: f64 = -0.00976004;
|
||||
const EXPIA6: f64 = 0.00107857;
|
||||
|
||||
const EXPIB1: f64 = 0.2677737343;
|
||||
const EXPIB2: f64 = 8.6347608925;
|
||||
const EXPIB3: f64 = 18.059016973;
|
||||
const EXPIB4: f64 = 8.5733287401;
|
||||
|
||||
const EXPIC1: f64 = 3.9584969228;
|
||||
const EXPIC2: f64 = 21.0996530827;
|
||||
const EXPIC3: f64 = 25.6329561486;
|
||||
const EXPIC4: f64 = 9.5733223454;
|
||||
|
||||
/// 计算碰撞强度。
|
||||
///
|
||||
/// # 参数
|
||||
///
|
||||
/// * `i` - 下能级索引 (1-indexed)
|
||||
/// * `j` - 上能级索引 (1-indexed)
|
||||
/// * `ic` - 碰撞类型:
|
||||
/// - -1: 中性粒子
|
||||
/// - -2: 离子
|
||||
/// - -11: 特殊处理
|
||||
/// - -12: He I 禁戒跃迁
|
||||
/// * `os` - 振子强度
|
||||
/// * `cp` - 碰撞参数(用于离子)
|
||||
/// * `u0` - 约化能量 (E/kT)
|
||||
/// * `t` - 温度 (K)
|
||||
///
|
||||
/// # 返回值
|
||||
///
|
||||
/// 碰撞强度 CS
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn cspec(i: i32, j: i32, ic: i32, os: f64, cp: f64, u0: f64, t: f64) -> f64 {
|
||||
let mut cs = 0.0;
|
||||
|
||||
if ic > -10 {
|
||||
// 计算指数积分 E1(U0)
|
||||
let expiu0 = if u0 <= UN {
|
||||
// U0 <= 1: 使用级数展开
|
||||
(-u0.ln()) + EXPIA1
|
||||
+ u0 * (EXPIA2 + u0 * (EXPIA3 + u0 * (EXPIA4 + u0 * (EXPIA5 + u0 * EXPIA6))))
|
||||
} else {
|
||||
// U0 > 1: 使用连分式近似
|
||||
let num = EXPIB1 + u0 * (EXPIB2 + u0 * (EXPIB3 + u0 * EXPIB4));
|
||||
let den = EXPIC1 + u0 * (EXPIC2 + u0 * (EXPIC3 + u0 * EXPIC4));
|
||||
(-u0).exp() * num / den / u0
|
||||
};
|
||||
|
||||
let gg = if ic == -1 {
|
||||
// 中性粒子 (Auer & Mihalas 1973)
|
||||
if u0 <= 14.0 {
|
||||
0.276 * u0.exp() * expiu0
|
||||
} else {
|
||||
0.066 * (1.0 + 1.5 / u0) / u0.sqrt()
|
||||
}
|
||||
} else if ic == -2 {
|
||||
// 离子 (Mihalas 1972)
|
||||
let gg0 = 0.276 * u0.exp() * expiu0;
|
||||
if gg0 > cp { gg0 } else { cp }
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
let t32 = t.powf(-1.5);
|
||||
cs += 19.7363 * t32 * (-u0).exp() / u0 * gg * os;
|
||||
|
||||
return cs;
|
||||
}
|
||||
|
||||
if ic == -11 {
|
||||
// 特殊处理
|
||||
let xr = -1.68_f64;
|
||||
cs += 2.16 * u0.powf(xr) / t / t.sqrt() * (-u0).exp() * os;
|
||||
return cs;
|
||||
}
|
||||
|
||||
if ic == -12 {
|
||||
// He I 禁戒跃迁 (from Klaus Werner)
|
||||
// 确定跃迁类型
|
||||
let iforb = match (i, j) {
|
||||
(2, 3) => 1,
|
||||
(2, 5) => 2,
|
||||
(3, 4) => 3,
|
||||
(4, 5) => 4,
|
||||
_ => {
|
||||
panic!("Inconsistent ICOL - CSPEC: i={}, j={}, iforb=0", i, j);
|
||||
}
|
||||
};
|
||||
|
||||
let xt = t.log10();
|
||||
let gam = if iforb <= 3 {
|
||||
CHE1FB[0][iforb as usize - 1]
|
||||
+ CHE1FB[1][iforb as usize - 1] * xt
|
||||
+ CHE1FB[2][iforb as usize - 1] / xt / xt
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let gam = (2.30258509299405_f64 * gam).exp();
|
||||
|
||||
cs += 5.465e-11 * t.sqrt() * (-u0).exp() * gam;
|
||||
}
|
||||
|
||||
cs
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use approx::assert_relative_eq;
|
||||
|
||||
#[test]
|
||||
fn test_cspec_neutral_low_u0() {
|
||||
// 中性粒子,U0 <= 1
|
||||
let cs = cspec(1, 2, -1, 0.5, 0.0, 0.5, 10000.0);
|
||||
assert!(cs > 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cspec_neutral_high_u0() {
|
||||
// 中性粒子,U0 > 14
|
||||
let cs = cspec(1, 2, -1, 0.5, 0.0, 15.0, 10000.0);
|
||||
assert!(cs > 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cspec_ion() {
|
||||
// 离子
|
||||
let cs = cspec(1, 2, -2, 0.5, 0.1, 2.0, 10000.0);
|
||||
assert!(cs > 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cspec_special() {
|
||||
// IC = -11
|
||||
let cs = cspec(1, 2, -11, 0.5, 0.0, 2.0, 10000.0);
|
||||
assert!(cs > 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cspec_hei_forbidden() {
|
||||
// He I 禁戒跃迁 (2->3)
|
||||
let cs = cspec(2, 3, -12, 0.0, 0.0, 2.0, 10000.0);
|
||||
assert!(cs > 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expiu0_low() {
|
||||
// 测试指数积分 U0 <= 1
|
||||
let u0: f64 = 0.5;
|
||||
let expiu0 = (-u0.ln()) + EXPIA1
|
||||
+ u0 * (EXPIA2 + u0 * (EXPIA3 + u0 * (EXPIA4 + u0 * (EXPIA5 + u0 * EXPIA6))));
|
||||
// E1(0.5) ≈ 0.5598
|
||||
assert_relative_eq!(expiu0, 0.5598, epsilon = 1e-3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expiu0_high() {
|
||||
// 测试指数积分 U0 > 1
|
||||
let u0: f64 = 2.0;
|
||||
let num = EXPIB1 + u0 * (EXPIB2 + u0 * (EXPIB3 + u0 * EXPIB4));
|
||||
let den = EXPIC1 + u0 * (EXPIC2 + u0 * (EXPIC3 + u0 * EXPIC4));
|
||||
let expiu0 = (-u0).exp() * num / den / u0;
|
||||
// E1(2.0) ≈ 0.0477 (Abramowitz & Stegun 近似)
|
||||
assert_relative_eq!(expiu0, 0.0477, epsilon = 1e-3);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,427 @@
|
||||
//! 能级参数设置。
|
||||
//!
|
||||
//! 重构自 TLUSTY `levset.f`
|
||||
//!
|
||||
//! 设置能级参数 IIEXP 和 IIFOR,控制能级的处理方式。
|
||||
//!
|
||||
//! ## 能级模式 (IMODL)
|
||||
//!
|
||||
//! - 0: 显式处理,完全 NLTE
|
||||
//! - 1, 3: 跳过
|
||||
//! - 4, 5: LTE
|
||||
//! - -1, -3: 跳过,禁戒跃迁
|
||||
//! - 6: 跳过
|
||||
//! - -5, -6: 跳过,禁戒跃迁
|
||||
//! - < -100: 分组处理
|
||||
//! - < -200: 分组处理(不同方式)
|
||||
|
||||
use crate::state::constants::{MLEVEL, MDEPTH, MLVEXP};
|
||||
|
||||
/// LEVSET 的输入参数
|
||||
pub struct LevsetParams {
|
||||
/// 是否启用 LTE 模式
|
||||
pub lte: bool,
|
||||
/// 能级处理模式 (0: 自动, 其他: 由 IMODL 决定)
|
||||
pub iflev: i32,
|
||||
/// 选项表标志
|
||||
pub ioptab: i32,
|
||||
}
|
||||
|
||||
/// LEVSET 的模型状态
|
||||
pub struct LevsetModelState<'a> {
|
||||
/// 深度点数
|
||||
pub nd: usize,
|
||||
/// 原子数
|
||||
pub natom: usize,
|
||||
/// 能级数
|
||||
pub nlevel: usize,
|
||||
/// 能级模式 (NLEVEL)
|
||||
pub imodl: &'a mut [i32],
|
||||
/// 能级所属原子 (NLEVEL)
|
||||
pub iatm: &'a [i32],
|
||||
/// 能级所属元素/离子 (NLEVEL)
|
||||
pub iel: &'a [i32],
|
||||
/// 原子固定标志 (NATOM)
|
||||
pub iifix: &'a [i32],
|
||||
/// 原子起始能级 (NATOM)
|
||||
pub n0a: &'a [i32],
|
||||
/// 原子结束能级 (NATOM)
|
||||
pub nka: &'a [i32],
|
||||
/// 离子第一个能级 (NLEVEL)
|
||||
pub nfirst: &'a [i32],
|
||||
/// 离子最后一个能级 (NLEVEL)
|
||||
pub nnext: &'a [i32],
|
||||
/// LTE 能级标志 (NLEVEL)
|
||||
pub iltlev: &'a [i32],
|
||||
}
|
||||
|
||||
/// LEVSET 的输出状态
|
||||
pub struct LevsetOutputState<'a> {
|
||||
/// 显式能级索引 (NLEVEL) - 正值表示显式处理
|
||||
pub iiexp: &'a mut [i32],
|
||||
/// 禁戒能级索引 (NLEVEL)
|
||||
pub iifor: &'a mut [i32],
|
||||
/// 显式能级对应的实际能级 (MLVEXP)
|
||||
pub indlev: &'a mut [i32],
|
||||
/// LTE 参考能级 (NLEVEL × MDEPTH)
|
||||
pub iltref: &'a mut [i32],
|
||||
/// B 因子 (NLEVEL × MDEPTH)
|
||||
pub bfac: &'a mut [f64],
|
||||
/// 积分零标志 (MLVEXP × MDEPTH)
|
||||
pub igzero: &'a mut [i32],
|
||||
/// 种群零标志 (NLEVEL × MDEPTH)
|
||||
pub ipzero: &'a mut [i32],
|
||||
/// 显式能级数
|
||||
pub nlvexp: &'a mut i32,
|
||||
/// 禁戒能级数
|
||||
pub nlvfor: &'a mut i32,
|
||||
}
|
||||
|
||||
/// 设置能级参数。
|
||||
///
|
||||
/// # 参数
|
||||
///
|
||||
/// * `params` - 输入参数
|
||||
/// * `model` - 模型状态
|
||||
/// * `output` - 输出状态
|
||||
pub fn levset(
|
||||
params: &LevsetParams,
|
||||
model: &mut LevsetModelState,
|
||||
output: &mut LevsetOutputState,
|
||||
) {
|
||||
// 如果 IOPTAB < 0,直接返回
|
||||
if params.ioptab < 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let nd = model.nd;
|
||||
let natom = model.natom;
|
||||
let nlevel = model.nlevel;
|
||||
|
||||
// 初始化 B 因子
|
||||
for i in 0..nlevel {
|
||||
for id in 0..nd {
|
||||
output.bfac[i * nd + id] = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
if params.iflev == 0 {
|
||||
// 情况 1: 由 IMODL 决定处理方式
|
||||
process_by_imodl(params, model, output, natom, nlevel, nd);
|
||||
} else {
|
||||
// 情况 2: 自动处理 - 所有 ILK=0 的能级在更新的 LTE 模式下
|
||||
process_automatic(params, model, output, nlevel, nd);
|
||||
}
|
||||
|
||||
// 初始化 IGZERO 和 IPZERO
|
||||
let nlvexp = *output.nlvexp as usize;
|
||||
for ii in 0..nlvexp.min(MLVEXP) {
|
||||
output.indlev[ii] = 0;
|
||||
for id in 0..nd {
|
||||
output.igzero[ii * nd + id] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..nlevel {
|
||||
for id in 0..nd {
|
||||
output.ipzero[i * nd + id] = 0;
|
||||
}
|
||||
if model.imodl[i].abs() <= 6 {
|
||||
if output.iiexp[i] > 0 {
|
||||
let ii_idx = (output.iiexp[i] - 1) as usize;
|
||||
if ii_idx < MLVEXP {
|
||||
output.indlev[ii_idx] = (i + 1) as i32;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 按能级模式处理
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn process_by_imodl(
|
||||
params: &LevsetParams,
|
||||
model: &mut LevsetModelState,
|
||||
output: &mut LevsetOutputState,
|
||||
natom: usize,
|
||||
nlevel: usize,
|
||||
nd: usize,
|
||||
) {
|
||||
// 初始化
|
||||
for i in 0..nlevel {
|
||||
output.iiexp[i] = 0;
|
||||
output.iifor[i] = 0;
|
||||
}
|
||||
|
||||
let mut iie: i32 = 0;
|
||||
let mut iif: i32 = 0;
|
||||
let mut igrp: i32 = 0;
|
||||
|
||||
for iat in 0..natom {
|
||||
igrp = 0;
|
||||
if model.iifix[iat] == 1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let n0a = model.n0a[iat] as usize;
|
||||
let nka = model.nka[iat] as usize;
|
||||
|
||||
for i in n0a..=nka {
|
||||
let imodl = model.imodl[i];
|
||||
let mut inew = 1;
|
||||
|
||||
if imodl == 0 {
|
||||
// 显式处理
|
||||
iie += 1;
|
||||
iif += 1;
|
||||
output.iiexp[i] = iie;
|
||||
output.iifor[i] = iif;
|
||||
output.indlev[(iie - 1) as usize] = (i + 1) as i32;
|
||||
} else if imodl > 0 {
|
||||
// 正模式
|
||||
iif += 1;
|
||||
output.iifor[i] = iif;
|
||||
if model.iltlev[i] >= 1 {
|
||||
iie += 1;
|
||||
output.iiexp[i] = iie;
|
||||
}
|
||||
let nfirst = model.nfirst[model.iel[i] as usize] as usize;
|
||||
let nnext = model.nnext[model.iel[i] as usize] as usize;
|
||||
if i + 1 == nfirst || i + 1 == nnext {
|
||||
iie += 1;
|
||||
output.iiexp[i] = iie;
|
||||
}
|
||||
} else if imodl < -100 {
|
||||
// 分组处理 (IMODL < -100)
|
||||
if i > n0a {
|
||||
if model.imodl[i] == model.imodl[i - 1] {
|
||||
inew = 0;
|
||||
}
|
||||
}
|
||||
output.iiexp[i] = -iie;
|
||||
if inew == 1 {
|
||||
iie += 1;
|
||||
output.iiexp[i] = -iie;
|
||||
let nfirst = model.nfirst[model.iel[i] as usize] as usize;
|
||||
let mut im = nfirst;
|
||||
let mut lml = true;
|
||||
while im < i && lml {
|
||||
if model.imodl[i] == model.imodl[im] {
|
||||
output.iiexp[i] = output.iiexp[im];
|
||||
iie -= 1;
|
||||
lml = false;
|
||||
}
|
||||
im += 1;
|
||||
}
|
||||
}
|
||||
igrp = 1;
|
||||
iif += 1;
|
||||
output.iifor[i] = iif;
|
||||
} else if imodl < -200 {
|
||||
// 分组处理 (IMODL < -200)
|
||||
if i > n0a {
|
||||
if model.imodl[i] == model.imodl[i - 1] {
|
||||
inew = 0;
|
||||
}
|
||||
}
|
||||
if inew == 1 {
|
||||
iie += 1;
|
||||
}
|
||||
if inew == 1 {
|
||||
iif += 1;
|
||||
}
|
||||
output.iiexp[i] = -iie;
|
||||
output.iifor[i] = -iif;
|
||||
igrp = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理分组
|
||||
if igrp == 1 {
|
||||
for i in n0a..=nka {
|
||||
if output.iiexp[i] > 0 {
|
||||
output.iiexp[i] = -output.iiexp[i];
|
||||
}
|
||||
if model.imodl[i] == 0 {
|
||||
model.imodl[i] = 7;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*output.nlvexp = iie.abs();
|
||||
if *output.nlvexp > MLVEXP as i32 {
|
||||
panic!("nlvexp.gt.mlvexp: {} > {}", *output.nlvexp, MLVEXP);
|
||||
}
|
||||
|
||||
*output.nlvfor = iif.abs();
|
||||
|
||||
// 清理特定模式
|
||||
for i in 0..nlevel {
|
||||
let imodl = model.imodl[i];
|
||||
if imodl == 1 || imodl == 3 {
|
||||
output.iiexp[i] = 0;
|
||||
} else if imodl == 4 || imodl == 5 {
|
||||
output.iiexp[i] = 0;
|
||||
} else if imodl == -1 || imodl == -3 {
|
||||
output.iiexp[i] = 0;
|
||||
output.iifor[i] = 0;
|
||||
} else if imodl == 6 {
|
||||
output.iiexp[i] = 0;
|
||||
} else if imodl == -5 || imodl == -6 {
|
||||
output.iiexp[i] = 0;
|
||||
output.iifor[i] = 0;
|
||||
} else if imodl < -100 {
|
||||
model.imodl[i] = 7;
|
||||
} else if imodl < -200 {
|
||||
model.imodl[i] = -7;
|
||||
}
|
||||
|
||||
// 设置 ILTREF
|
||||
let nnext = model.nnext[model.iel[i] as usize];
|
||||
for id in 0..nd {
|
||||
output.iltref[i * nd + id] = nnext;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有分组,将所有能级设为模式 7
|
||||
if igrp == 1 {
|
||||
for iat in 0..natom {
|
||||
if model.iifix[iat] == 1 {
|
||||
continue;
|
||||
}
|
||||
let n0a = model.n0a[iat] as usize;
|
||||
let nka = model.nka[iat] as usize;
|
||||
for i in n0a..=nka {
|
||||
model.imodl[i] = 7;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 自动处理模式
|
||||
fn process_automatic(
|
||||
params: &LevsetParams,
|
||||
model: &mut LevsetModelState,
|
||||
output: &mut LevsetOutputState,
|
||||
nlevel: usize,
|
||||
nd: usize,
|
||||
) {
|
||||
let mut iif: i32 = 0;
|
||||
|
||||
for i in 0..nlevel {
|
||||
if model.iifix[model.iatm[i] as usize] == 1 {
|
||||
continue;
|
||||
}
|
||||
model.imodl[i] = 5;
|
||||
let nfirst = model.nfirst[model.iel[i] as usize] as usize;
|
||||
let nnext = model.nnext[model.iel[i] as usize] as usize;
|
||||
if i + 1 == nfirst || i + 1 == nnext {
|
||||
iif += 1;
|
||||
output.iifor[i] = iif;
|
||||
}
|
||||
}
|
||||
|
||||
*output.nlvexp = iif;
|
||||
if *output.nlvexp > MLVEXP as i32 {
|
||||
panic!("nlvexp.gt.mlvexp: {} > {}", *output.nlvexp, MLVEXP);
|
||||
}
|
||||
|
||||
*output.nlvfor = iif;
|
||||
|
||||
// 清理非第一/最后能级的 IIFOR
|
||||
for i in 0..nlevel {
|
||||
let nfirst = model.nfirst[model.iel[i] as usize] as usize;
|
||||
let nnext = model.nnext[model.iel[i] as usize] as usize;
|
||||
if i + 1 != nfirst && i + 1 != nnext {
|
||||
output.iifor[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 设置 IIEXP 和 INDLEV
|
||||
for i in 0..nlevel {
|
||||
output.iiexp[i] = output.iifor[i];
|
||||
if output.iiexp[i] > 0 {
|
||||
output.indlev[(output.iiexp[i] - 1) as usize] = (i + 1) as i32;
|
||||
}
|
||||
let nnext = model.nnext[model.iel[i] as usize];
|
||||
for id in 0..nd {
|
||||
output.iltref[i * nd + id] = nnext;
|
||||
}
|
||||
}
|
||||
|
||||
// 非 LTE 模式下,所有能级都是显式的
|
||||
if !params.lte {
|
||||
for i in 0..nlevel {
|
||||
output.iifor[i] = (i + 1) as i32;
|
||||
}
|
||||
*output.nlvfor = nlevel as i32;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_levset_ioptab_negative() {
|
||||
// IOPTAB < 0 时应直接返回
|
||||
let params = LevsetParams {
|
||||
lte: false,
|
||||
iflev: 0,
|
||||
ioptab: -1,
|
||||
};
|
||||
// 创建简单的模型状态
|
||||
let mut imodl = vec![0i32; 10];
|
||||
let iatm = vec![1i32; 10];
|
||||
let iel = vec![1i32; 10];
|
||||
let iifix = vec![0i32; 2];
|
||||
let n0a = vec![0i32; 2];
|
||||
let nka = vec![9i32; 2];
|
||||
let nfirst = vec![0i32; 10];
|
||||
let nnext = vec![9i32; 10];
|
||||
let iltlev = vec![0i32; 10];
|
||||
|
||||
let mut model = LevsetModelState {
|
||||
nd: 5,
|
||||
natom: 2,
|
||||
nlevel: 10,
|
||||
imodl: &mut imodl,
|
||||
iatm: &iatm,
|
||||
iel: &iel,
|
||||
iifix: &iifix,
|
||||
n0a: &n0a,
|
||||
nka: &nka,
|
||||
nfirst: &nfirst,
|
||||
nnext: &nnext,
|
||||
iltlev: &iltlev,
|
||||
};
|
||||
|
||||
let mut iiexp = vec![0i32; 10];
|
||||
let mut iifor = vec![0i32; 10];
|
||||
let mut indlev = vec![0i32; MLVEXP];
|
||||
let mut iltref = vec![0i32; 10 * 5];
|
||||
let mut bfac = vec![1.0; 10 * 5];
|
||||
let mut igzero = vec![0i32; MLVEXP * 5];
|
||||
let mut ipzero = vec![0i32; 10 * 5];
|
||||
let mut nlvexp = 0i32;
|
||||
let mut nlvfor = 0i32;
|
||||
|
||||
let mut output = LevsetOutputState {
|
||||
iiexp: &mut iiexp,
|
||||
iifor: &mut iifor,
|
||||
indlev: &mut indlev,
|
||||
iltref: &mut iltref,
|
||||
bfac: &mut bfac,
|
||||
igzero: &mut igzero,
|
||||
ipzero: &mut ipzero,
|
||||
nlvexp: &mut nlvexp,
|
||||
nlvfor: &mut nlvfor,
|
||||
};
|
||||
|
||||
levset(¶ms, &mut model, &mut output);
|
||||
|
||||
// IOPTAB < 0 时,nlvexp 应该保持为 0
|
||||
assert_eq!(*output.nlvexp, 0);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
//! 数学工具函数,重构自 TLUSTY Fortran。
|
||||
|
||||
mod alifr1;
|
||||
mod alifr3;
|
||||
mod alifr6;
|
||||
mod alifrk;
|
||||
@@ -18,6 +19,7 @@ mod collhe;
|
||||
mod compt0;
|
||||
mod comset;
|
||||
mod cross;
|
||||
mod cspec;
|
||||
mod ctdata;
|
||||
mod cubic;
|
||||
mod dielrc;
|
||||
@@ -54,6 +56,7 @@ mod irc;
|
||||
mod interpolate;
|
||||
mod laguer;
|
||||
mod levsol;
|
||||
mod levset;
|
||||
mod levgrp;
|
||||
mod lineqs;
|
||||
mod linspl;
|
||||
@@ -62,6 +65,9 @@ mod matinv;
|
||||
mod meanop;
|
||||
mod minv3;
|
||||
mod odfhst;
|
||||
mod odffr;
|
||||
mod opadd0;
|
||||
mod opctab;
|
||||
mod pfcno;
|
||||
mod pffe;
|
||||
mod prdini;
|
||||
@@ -76,6 +82,7 @@ mod quit;
|
||||
mod reflev;
|
||||
mod raph;
|
||||
mod ratmal;
|
||||
mod ratmat;
|
||||
mod rayleigh;
|
||||
mod rybmat;
|
||||
mod rayset;
|
||||
@@ -120,6 +127,7 @@ mod xk2dop;
|
||||
mod ylintp;
|
||||
mod zmrho;
|
||||
|
||||
pub use alifr1::{alifr1, Alifr1Params, Alifr1ModelState, Alifr1RadState};
|
||||
pub use alifr3::{alifr3, Alifr3Params};
|
||||
pub use alifr6::{alifr6, Alifr6Params, Alifr6State};
|
||||
pub use alifrk::{alifrk, AlifrkParams, AlifrkState};
|
||||
@@ -137,6 +145,7 @@ pub use ckoest::ckoest;
|
||||
pub use collhe::collhe;
|
||||
pub use comset::{comset, ComsetParams, ComsetResult};
|
||||
pub use cross::{cross, crossd};
|
||||
pub use cspec::cspec;
|
||||
pub use ctdata::{hction, hctrecom, CTION, CTRECOMB};
|
||||
pub use cubic::{cubic, CubicCon};
|
||||
pub use dielrc::dielrc;
|
||||
@@ -173,6 +182,7 @@ pub use irc::irc;
|
||||
pub use interpolate::{lagran, yint};
|
||||
pub use laguer::laguer;
|
||||
pub use levsol::levsol;
|
||||
pub use levset::{levset, LevsetParams, LevsetModelState, LevsetOutputState};
|
||||
pub use levgrp::{levgrp, LevgrpParams, LevgrpResult};
|
||||
pub use lineqs::{lineqs, lineqs_nr};
|
||||
pub use linspl::{linspl, LinsplParams};
|
||||
@@ -180,7 +190,10 @@ pub use locate::locate;
|
||||
pub use matinv::matinv;
|
||||
pub use meanop::meanop;
|
||||
pub use minv3::minv3;
|
||||
pub use opadd0::{opadd0, Opadd0Params, Opadd0FreqData, Opadd0OutputState};
|
||||
pub use opctab::{opctab, OpctabParams, OpctabTableData, OpctabModelState, OpctabOutput};
|
||||
pub use odfhst::odfhst;
|
||||
pub use odffr::{odffr, OdffrParams, OdffrAtomicData, OdffrModelData, OdffrOutputState};
|
||||
pub use pfcno::pfcno;
|
||||
pub use pffe::pffe;
|
||||
pub use prdini::prdini;
|
||||
@@ -195,6 +208,7 @@ pub use reflev::reflev;
|
||||
pub use quit::{quit, quit_error};
|
||||
pub use raph::raph;
|
||||
pub use ratmal::ratmal;
|
||||
pub use ratmat::{ratmat, RatmatParams, RatmatOutput};
|
||||
pub use rayleigh::{
|
||||
rayleigh, rayleigh_h2_cross_section, rayleigh_h_cross_section, rayleigh_he_cross_section,
|
||||
RayleighParams, RayleighResult,
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
//! ODF 频率设置。
|
||||
//!
|
||||
//! 重构自 TLUSTY `odffr.f`
|
||||
//!
|
||||
//! 为 ODF(Opacity Distribution Function)设置内部频率,
|
||||
//! 用于处理系限附近的重叠谱线。
|
||||
//!
|
||||
//! ## 算法说明
|
||||
//!
|
||||
//! 谱线向连续跃迁 (IL - IU) 的边缘收敛。
|
||||
//! - IL: 下能级索引
|
||||
//! - IU: 上能级索引(通常是下一离子的基态或 ODF 形式中的平均能级)
|
||||
|
||||
use crate::state::constants::{HALF, MFRO};
|
||||
|
||||
/// 常量 (从 Fortran PARAMETER 语句)
|
||||
/// FRH = 3.28805D15
|
||||
const FRH: f64 = 3.28805e15;
|
||||
/// CDOP = 2.84511D-7
|
||||
const CDOP: f64 = 2.84511e-7;
|
||||
/// CDOM = 14.
|
||||
const CDOM: f64 = 14.0;
|
||||
/// SIX = 6.
|
||||
const SIX: f64 = 6.0;
|
||||
/// SEPT = 7.
|
||||
const SEPT: f64 = 7.0;
|
||||
|
||||
/// ODFFR 输入参数
|
||||
pub struct OdffrParams {
|
||||
/// 下能级索引 (1-indexed)
|
||||
pub il: usize,
|
||||
/// 上能级索引 (1-indexed)
|
||||
pub iu: usize,
|
||||
/// 有效温度 (K)
|
||||
pub teff: f64,
|
||||
/// 最大主量子数
|
||||
pub nlmx: usize,
|
||||
}
|
||||
|
||||
/// ODFFR 原子数据
|
||||
pub struct OdffrAtomicData<'a> {
|
||||
/// 元素索引 (NLEVEL)
|
||||
pub iel: &'a [i32],
|
||||
/// 电荷数 (NATOM)
|
||||
pub iz: &'a [f64],
|
||||
/// 电离能 (NLEVEL)
|
||||
pub enion: &'a [f64],
|
||||
/// 主量子数 (NLEVEL)
|
||||
pub nquant: &'a [i32],
|
||||
}
|
||||
|
||||
/// ODFFR 模型数据
|
||||
pub struct OdffrModelData<'a> {
|
||||
/// 跃迁索引 (NLEVEL × NLEVEL)
|
||||
pub itra: &'a [i32],
|
||||
/// ODF 索引
|
||||
pub jndodf: &'a [i32],
|
||||
}
|
||||
|
||||
/// ODFFR 输出状态
|
||||
pub struct OdffrOutputState<'a> {
|
||||
/// ODF 频率点数 (MODF)
|
||||
pub nfrodf: &'a mut [i32],
|
||||
/// ODF 频率 (MFRO × MODF)
|
||||
pub fros: &'a mut [f64],
|
||||
/// ODF 权重 (MFRO × MODF)
|
||||
pub wnus: &'a mut [f64],
|
||||
}
|
||||
|
||||
/// 普朗克常数 (从 TLUSTY 常量)
|
||||
const H: f64 = 6.626176e-27;
|
||||
/// UN = 1.0 (里德伯常数的倍数)
|
||||
const UN: f64 = 1.0;
|
||||
|
||||
/// 为 ODF 设置内部频率。
|
||||
///
|
||||
/// # 参数
|
||||
///
|
||||
/// * `params` - 输入参数
|
||||
/// * `atomic` - 原子数据
|
||||
/// * `model` - 模型数据
|
||||
/// * `output` - 输出状态
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// 当频率点数超过 MFRO 时 panic。
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn odffr(
|
||||
params: &OdffrParams,
|
||||
atomic: &OdffrAtomicData,
|
||||
model: &OdffrModelData,
|
||||
output: &mut OdffrOutputState,
|
||||
) {
|
||||
let il = params.il;
|
||||
let iu = params.iu;
|
||||
let il_idx = il - 1;
|
||||
let iu_idx = iu - 1;
|
||||
|
||||
// 计算电荷平方
|
||||
let iel_idx = atomic.iel[il_idx] as usize;
|
||||
let ch = atomic.iz[iel_idx - 1] * atomic.iz[iel_idx - 1];
|
||||
let frion = ch * FRH;
|
||||
|
||||
// 电离频率
|
||||
let fre = atomic.enion[il_idx] / H;
|
||||
|
||||
// 临时频率数组
|
||||
let mut ffro = vec![0.0_f64; MFRO];
|
||||
|
||||
let mut nf: usize = 1;
|
||||
|
||||
let nq1 = atomic.nquant[iu_idx] as usize;
|
||||
let xl2 = UN / (atomic.nquant[il_idx] as f64 * atomic.nquant[il_idx] as f64);
|
||||
let xu1 = UN / ((atomic.nquant[iu_idx] - 1) as f64 * (atomic.nquant[iu_idx] - 1) as f64);
|
||||
let xu2 = UN / (atomic.nquant[iu_idx] as f64 * atomic.nquant[iu_idx] as f64);
|
||||
|
||||
let frc = frion * (xl2 - xu2);
|
||||
ffro[nf - 1] = HALF * (frc + frion * (xl2 - xu1));
|
||||
|
||||
let kt = model.itra[il_idx * atomic.iel.len() + iu_idx] as usize;
|
||||
let kl = model.jndodf[kt - 1] as usize;
|
||||
|
||||
let dopo = CDOP * params.teff.sqrt() * frc;
|
||||
let dopm = CDOM * dopo;
|
||||
|
||||
let mut fr1 = ffro[0];
|
||||
|
||||
// 遍历主量子数
|
||||
for i in nq1..=params.nlmx {
|
||||
let ii = (i * i) as f64;
|
||||
let fr2 = fre - frion / ii;
|
||||
let df = fr2 - fr1;
|
||||
|
||||
if df > dopm {
|
||||
// 大间隔:添加密集点
|
||||
for j in 1..=7 {
|
||||
nf += 1;
|
||||
if nf > MFRO {
|
||||
nf -= 1;
|
||||
goto_end(&ffro, nf, kl, output);
|
||||
return;
|
||||
}
|
||||
ffro[nf - 1] = fr1 + j as f64 * dopo;
|
||||
}
|
||||
|
||||
let df_inner = fr2 - SEPT * dopo - ffro[nf - 1];
|
||||
let ni = (df_inner / (SIX * dopo)) as usize;
|
||||
let ddf = df_inner / ((ni + 1) as f64);
|
||||
|
||||
for j in 1..=ni {
|
||||
nf += 1;
|
||||
if nf > MFRO - 3 {
|
||||
nf -= 1;
|
||||
goto_end(&ffro, nf, kl, output);
|
||||
return;
|
||||
}
|
||||
ffro[nf - 1] = fr1 + SEPT * dopo + j as f64 * ddf;
|
||||
}
|
||||
|
||||
for j in (0..=7).rev() {
|
||||
nf += 1;
|
||||
if nf > MFRO {
|
||||
nf -= 1;
|
||||
goto_end(&ffro, nf, kl, output);
|
||||
return;
|
||||
}
|
||||
ffro[nf - 1] = fr2 - j as f64 * dopo;
|
||||
}
|
||||
|
||||
fr1 = fr2;
|
||||
} else {
|
||||
// 小间隔:均匀分布
|
||||
let ni = (df / dopo) as usize;
|
||||
let ddf = df / ((ni + 1) as f64);
|
||||
|
||||
for j in 1..=ni {
|
||||
nf += 1;
|
||||
if nf > MFRO - 3 {
|
||||
nf -= 1;
|
||||
goto_end(&ffro, nf, kl, output);
|
||||
return;
|
||||
}
|
||||
ffro[nf - 1] = fr1 + j as f64 * ddf;
|
||||
}
|
||||
|
||||
nf += 1;
|
||||
if nf > MFRO {
|
||||
nf -= 1;
|
||||
goto_end(&ffro, nf, kl, output);
|
||||
return;
|
||||
}
|
||||
ffro[nf - 1] = fr2;
|
||||
fr1 = fr2;
|
||||
}
|
||||
}
|
||||
|
||||
goto_end(&ffro, nf, kl, output);
|
||||
}
|
||||
|
||||
/// 完成频率设置并计算权重
|
||||
fn goto_end(ffro: &[f64], nf: usize, kl: usize, output: &mut OdffrOutputState) {
|
||||
let mut nf = nf;
|
||||
nf += 1;
|
||||
if nf > MFRO {
|
||||
panic!(
|
||||
"too many points for hydrogen ODF - nf.gt.mfro: {} > {}",
|
||||
nf, MFRO
|
||||
);
|
||||
}
|
||||
|
||||
// 添加最后一个点(略低于电离限)
|
||||
let kl_idx = kl - 1;
|
||||
let mut fros_local = vec![0.0; nf];
|
||||
|
||||
// 逆序复制
|
||||
let fre = ffro[0] / 0.999999999_f64; // 从第一个点估算 fre
|
||||
fros_local[nf - 1] = fre * 0.999999999;
|
||||
for i in 0..nf - 1 {
|
||||
fros_local[i] = ffro[nf - 2 - i];
|
||||
}
|
||||
|
||||
output.nfrodf[kl_idx] = nf as i32;
|
||||
|
||||
if nf > MFRO {
|
||||
panic!(
|
||||
"too many points for hydrogen ODF - nf.gt.mfro: {} > {}",
|
||||
nf, MFRO
|
||||
);
|
||||
}
|
||||
|
||||
// 复制到输出
|
||||
for i in 0..nf {
|
||||
output.fros[i * output.nfrodf.len() + kl_idx] = fros_local[i];
|
||||
}
|
||||
|
||||
// 计算权重
|
||||
// 第一个点
|
||||
output.wnus[kl_idx] = HALF * (fros_local[0] - fros_local[1]);
|
||||
// 最后一个点
|
||||
output.wnus[(nf - 1) * output.nfrodf.len() + kl_idx] =
|
||||
HALF * (fros_local[nf - 2] - fros_local[nf - 1]);
|
||||
|
||||
// 中间点
|
||||
for i in 2..nf {
|
||||
output.wnus[(i - 1) * output.nfrodf.len() + kl_idx] =
|
||||
HALF * (fros_local[i - 2] - fros_local[i]);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use approx::assert_relative_eq;
|
||||
|
||||
#[test]
|
||||
fn test_odffr_constants() {
|
||||
// 验证常量
|
||||
assert_relative_eq!(FRH, 3.28805e15, epsilon = 1e10);
|
||||
assert_relative_eq!(CDOP, 2.84511e-7, epsilon = 1e-12);
|
||||
assert_relative_eq!(CDOM, 14.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_odffr_basic() {
|
||||
// 基本测试
|
||||
let params = OdffrParams {
|
||||
il: 2,
|
||||
iu: 10,
|
||||
teff: 10000.0,
|
||||
nlmx: 10,
|
||||
};
|
||||
|
||||
let iel = vec![1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
|
||||
let iz = vec![1.0];
|
||||
let enion = vec![13.6, 13.5, 13.4, 13.3, 13.2, 13.1, 13.0, 12.9, 12.8, 12.7];
|
||||
let nquant = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
|
||||
let atomic = OdffrAtomicData {
|
||||
iel: &iel,
|
||||
iz: &iz,
|
||||
enion: &enion,
|
||||
nquant: &nquant,
|
||||
};
|
||||
|
||||
let itra = vec![1i32; 100]; // 所有跃迁索引设为 1
|
||||
let jndodf = vec![1i32]; // ODF 索引
|
||||
let model = OdffrModelData {
|
||||
itra: &itra,
|
||||
jndodf: &jndodf,
|
||||
};
|
||||
|
||||
let mut nfrodf = vec![0i32; 10];
|
||||
let mut fros = vec![0.0; MFRO * 10];
|
||||
let mut wnus = vec![0.0; MFRO * 10];
|
||||
|
||||
let mut output = OdffrOutputState {
|
||||
nfrodf: &mut nfrodf,
|
||||
fros: &mut fros,
|
||||
wnus: &mut wnus,
|
||||
};
|
||||
|
||||
odffr(¶ms, &atomic, &model, &mut output);
|
||||
|
||||
// 验证输出
|
||||
assert!(output.nfrodf[0] > 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
//! 附加不透明度源截面设置。
|
||||
//!
|
||||
//! 重构自 TLUSTY `opadd0.f`
|
||||
//!
|
||||
//! 设置各种附加不透明度源的截面,包括:
|
||||
//! - H I Rayleigh 散射
|
||||
//! - He I Rayleigh 散射
|
||||
//! - H2 Rayleigh 散射
|
||||
//! - H- 束缚-自由和自由-自由
|
||||
//! - H2+ 束缚-自由和自由-自由
|
||||
//! - He- 自由-自由
|
||||
|
||||
use crate::math::sbfhmi::sbfhmi;
|
||||
|
||||
/// 常量 (从 Fortran PARAMETER 语句)
|
||||
/// H I Rayleigh 散射阈值频率
|
||||
const FRRAY: f64 = 2.463e15;
|
||||
/// He I Rayleigh 散射阈值频率
|
||||
const FRAYHE: f64 = 5.150e15;
|
||||
/// H2 Rayleigh 散射阈值频率
|
||||
const FRAYH2: f64 = 2.922e15;
|
||||
/// 光速 (Angstrom/s)
|
||||
const CLS: f64 = 2.997925e18;
|
||||
/// H I Rayleigh 散射系数
|
||||
const CR0: f64 = 5.799e-13;
|
||||
const CR1: f64 = 1.422e-6;
|
||||
const CR2: f64 = 2.784;
|
||||
|
||||
/// OPADD0 输入参数
|
||||
pub struct Opadd0Params {
|
||||
/// 频率索引 (1-indexed)
|
||||
pub ij: usize,
|
||||
/// 连续谱数
|
||||
pub ncon: usize,
|
||||
/// 最大截面数
|
||||
pub mcross: usize,
|
||||
/// H I Rayleigh 散射标志
|
||||
pub irsct: i32,
|
||||
/// He I Rayleigh 散射标志
|
||||
pub irsche: i32,
|
||||
/// H2 Rayleigh 散射标志
|
||||
pub irsch2: i32,
|
||||
/// 分子标志
|
||||
pub ifmol: i32,
|
||||
/// H- 不透明度标志
|
||||
pub iophmi: i32,
|
||||
/// H2+ 不透明度标志
|
||||
pub ioph2p: i32,
|
||||
/// He- 不透明度标志
|
||||
pub iophem: i32,
|
||||
/// ODF 标志
|
||||
pub ispodf: i32,
|
||||
}
|
||||
|
||||
/// OPADD0 频率数据
|
||||
pub struct Opadd0FreqData<'a> {
|
||||
/// 频率数组
|
||||
pub freq: &'a [f64],
|
||||
/// ODF 频率映射 (可选)
|
||||
pub ifreqb: Option<&'a [i32]>,
|
||||
}
|
||||
|
||||
/// OPADD0 输出状态
|
||||
pub struct Opadd0OutputState<'a> {
|
||||
/// 截面数组 (MCROSS × MFREQ)
|
||||
pub bfcs: &'a mut [f32],
|
||||
/// 频率维度
|
||||
pub mfreq: usize,
|
||||
}
|
||||
|
||||
/// 设置附加不透明度源的截面。
|
||||
///
|
||||
/// # 参数
|
||||
///
|
||||
/// * `params` - 输入参数
|
||||
/// * `freq_data` - 频率数据
|
||||
/// * `output` - 输出状态
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// 当 IT > MCROSS 时 panic。
|
||||
pub fn opadd0(
|
||||
params: &Opadd0Params,
|
||||
freq_data: &Opadd0FreqData,
|
||||
output: &mut Opadd0OutputState,
|
||||
) {
|
||||
let ij = params.ij;
|
||||
let ij_idx = ij - 1;
|
||||
|
||||
// 获取频率
|
||||
let fr = if params.ispodf >= 1 {
|
||||
let ifreqb = freq_data.ifreqb.expect("ifreqb required for ODF mode");
|
||||
freq_data.freq[ifreqb[ij_idx] as usize - 1]
|
||||
} else {
|
||||
freq_data.freq[ij_idx]
|
||||
};
|
||||
|
||||
let mut it = params.ncon;
|
||||
|
||||
// H I Rayleigh 散射
|
||||
if params.irsct != 0 {
|
||||
it += 1;
|
||||
if it > params.mcross {
|
||||
panic!("it.gt.mcross in opadd: {} > {}", it, params.mcross);
|
||||
}
|
||||
let frm = fr.min(FRRAY);
|
||||
let x = (CLS / frm).powi(2);
|
||||
let cs = (CR0 + (CR1 + CR2 / x) / x) / x / x / x;
|
||||
output.bfcs[(it - 1) * output.mfreq + ij_idx] = cs as f32;
|
||||
}
|
||||
|
||||
// He I Rayleigh 散射
|
||||
if params.irsche != 0 {
|
||||
it += 1;
|
||||
if it > params.mcross {
|
||||
panic!("it.gt.mcross in opadd: {} > {}", it, params.mcross);
|
||||
}
|
||||
let x = (CLS / fr.min(FRAYHE)).powi(2);
|
||||
let cs = 5.484e-14 / x / x * (1.0 + (2.44e5 + 5.94e10 / (x - 2.90e5)) / x).powi(2);
|
||||
output.bfcs[(it - 1) * output.mfreq + ij_idx] = cs as f32;
|
||||
}
|
||||
|
||||
// H2 Rayleigh 散射
|
||||
if params.irsch2 != 0 && params.ifmol > 0 {
|
||||
it += 1;
|
||||
if it > params.mcross {
|
||||
panic!("it.gt.mcross in opadd: {} > {}", it, params.mcross);
|
||||
}
|
||||
let x = (CLS / fr.min(FRAYH2)).powi(2);
|
||||
let x2 = 1.0 / x / x;
|
||||
let cs = (8.14e-13 + 1.28e-6 / x + 1.61 * x2) * x2;
|
||||
output.bfcs[(it - 1) * output.mfreq + ij_idx] = cs as f32;
|
||||
}
|
||||
|
||||
// H- 束缚-自由和自由-自由
|
||||
if params.iophmi > 0 {
|
||||
it += 1;
|
||||
if it > params.mcross {
|
||||
panic!("it.gt.mcross in opadd: {} > {}", it, params.mcross);
|
||||
}
|
||||
let cs = sbfhmi(fr);
|
||||
output.bfcs[(it - 1) * output.mfreq + ij_idx] = cs as f32;
|
||||
}
|
||||
|
||||
// H2+ 束缚-自由和自由-自由
|
||||
if params.ioph2p > 0 {
|
||||
it += 1;
|
||||
if it + 1 > params.mcross {
|
||||
panic!("it.gt.mcross in opadd: {} > {}", it, params.mcross);
|
||||
}
|
||||
let x = fr * 1e-15;
|
||||
// H2+ bound-free
|
||||
let cs_bf = (-7.342e-3 + (-2.409 + (1.028 + (-4.23e-1 + (1.224e-1 - 1.351e-2 * x) * x) * x) * x) * x)
|
||||
* 1.602e-12
|
||||
/ 1.3806e-16; // BOLK
|
||||
output.bfcs[(it - 1) * output.mfreq + ij_idx] = cs_bf as f32;
|
||||
|
||||
it += 1;
|
||||
let x = fr.ln();
|
||||
// H2+ free-free
|
||||
let cs_ff = -3.0233e3 + (3.7797e2 + (-1.82496e1 + (3.9207e-1 - 3.1672e-3 * x) * x) * x) * x;
|
||||
output.bfcs[(it - 1) * output.mfreq + ij_idx] = cs_ff as f32;
|
||||
}
|
||||
|
||||
// He- 自由-自由
|
||||
if params.iophem > 0 {
|
||||
it += 1;
|
||||
if it + 2 > params.mcross {
|
||||
panic!("it.gt.mcross in opadd: {} > {}", it, params.mcross);
|
||||
}
|
||||
let a = 3.397e-46 + (-5.216e-31 + 7.039e-15 / fr) / fr;
|
||||
let b = -4.116e-42 + (1.067e-26 + 8.135e-11 / fr) / fr;
|
||||
let c = 5.081e-37 + (-8.724e-23 - 5.659e-8 / fr) / fr;
|
||||
|
||||
output.bfcs[(it - 1) * output.mfreq + ij_idx] = a as f32;
|
||||
output.bfcs[it * output.mfreq + ij_idx] = b as f32;
|
||||
output.bfcs[(it + 1) * output.mfreq + ij_idx] = c as f32;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use approx::assert_relative_eq;
|
||||
|
||||
#[test]
|
||||
fn test_opadd0_constants() {
|
||||
assert_relative_eq!(FRRAY, 2.463e15, epsilon = 1e10);
|
||||
assert_relative_eq!(FRAYHE, 5.150e15, epsilon = 1e10);
|
||||
assert_relative_eq!(FRAYH2, 2.922e15, epsilon = 1e10);
|
||||
assert_relative_eq!(CLS, 2.997925e18, epsilon = 1e13);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_opadd0_hi_rayleigh() {
|
||||
let params = Opadd0Params {
|
||||
ij: 1,
|
||||
ncon: 0,
|
||||
mcross: 10,
|
||||
irsct: 1,
|
||||
irsche: 0,
|
||||
irsch2: 0,
|
||||
ifmol: 0,
|
||||
iophmi: 0,
|
||||
ioph2p: 0,
|
||||
iophem: 0,
|
||||
ispodf: 0,
|
||||
};
|
||||
|
||||
let freq = vec![1e15];
|
||||
let freq_data = Opadd0FreqData {
|
||||
freq: &freq,
|
||||
ifreqb: None,
|
||||
};
|
||||
|
||||
let mut bfcs = vec![0.0f32; 10 * 1];
|
||||
let mut output = Opadd0OutputState {
|
||||
bfcs: &mut bfcs,
|
||||
mfreq: 1,
|
||||
};
|
||||
|
||||
opadd0(¶ms, &freq_data, &mut output);
|
||||
|
||||
// 验证 H I Rayleigh 截面被设置
|
||||
assert!(output.bfcs[0] > 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_opadd0_all_sources() {
|
||||
let params = Opadd0Params {
|
||||
ij: 1,
|
||||
ncon: 0,
|
||||
mcross: 20,
|
||||
irsct: 1,
|
||||
irsche: 1,
|
||||
irsch2: 1,
|
||||
ifmol: 1,
|
||||
iophmi: 1,
|
||||
ioph2p: 1,
|
||||
iophem: 1,
|
||||
ispodf: 0,
|
||||
};
|
||||
|
||||
let freq = vec![1e15];
|
||||
let freq_data = Opadd0FreqData {
|
||||
freq: &freq,
|
||||
ifreqb: None,
|
||||
};
|
||||
|
||||
let mut bfcs = vec![0.0f32; 20 * 1];
|
||||
let mut output = Opadd0OutputState {
|
||||
bfcs: &mut bfcs,
|
||||
mfreq: 1,
|
||||
};
|
||||
|
||||
opadd0(¶ms, &freq_data, &mut output);
|
||||
|
||||
// 验证所有截面都被设置
|
||||
// H I Rayleigh (1)
|
||||
assert!(output.bfcs[0] > 0.0);
|
||||
// He I Rayleigh (2)
|
||||
assert!(output.bfcs[1] > 0.0);
|
||||
// H2 Rayleigh (3)
|
||||
assert!(output.bfcs[2] > 0.0);
|
||||
// H- (4)
|
||||
assert!(output.bfcs[3] > 0.0);
|
||||
// H2+ bf (5)
|
||||
// H2+ ff (6)
|
||||
// He- (7,8,9)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
//! 不透明度表插值与散射计算。
|
||||
//!
|
||||
//! 重构自 TLUSTY `opctab.f`
|
||||
//!
|
||||
//! 通过对预计算的不透明度表进行二维线性插值(温度-密度)来计算给定温度和密度下的吸收不透明度。
|
||||
//! 同时计算散射不透明度,包括:
|
||||
//! - Rayleigh 散射(简单公式或完整 rayleigh 函数)
|
||||
//! - 电子散射
|
||||
//!
|
||||
//! 这是一个简化版本,所有插值都是线性的,fortran中也是线性插值。
|
||||
|
||||
use crate::math::rayleigh::{rayleigh, RayleighParams};
|
||||
use crate::state::model::{EosPar, RaySct};
|
||||
|
||||
/// 参考频率 (FRRAY0)
|
||||
const FRRAY0: f64 = 5.0872638e14;
|
||||
|
||||
/// OPCTAB 输入参数
|
||||
pub struct OpctabParams<'a> {
|
||||
/// 频率 (Hz)
|
||||
pub fr: f64,
|
||||
/// 频率索引 (1-indexed)
|
||||
pub ij: usize,
|
||||
/// 深度索引 (1-indexed)
|
||||
pub id: usize,
|
||||
/// 温度 (K)
|
||||
pub t: f64,
|
||||
/// 密度 (g cm^-3)
|
||||
pub rho: f64,
|
||||
/// 克拉马标志 (0: 返回每克, 1: 返回每体积)
|
||||
pub igram: i32,
|
||||
/// 迭代次数
|
||||
pub iter: i32,
|
||||
/// Rayleigh 散射标志 (<0: 使用简单公式, >0: 调用 rayleigh, =0: 关闭)
|
||||
pub ifrayl: i32,
|
||||
/// 选项表标志 (<0: 添加电子散射)
|
||||
pub ioptab: i32,
|
||||
/// Rayleigh 参数 (当 ifrayl > 0 时需要)
|
||||
pub rayleigh_params: Option<&'a RayleighParams<'a>>,
|
||||
}
|
||||
|
||||
/// OPCTAB 表数据
|
||||
pub struct OpctabTableData<'a> {
|
||||
/// 温度数
|
||||
pub numtemp: usize,
|
||||
/// 深度点数
|
||||
pub nd: usize,
|
||||
/// 频率数
|
||||
pub nfreq: usize,
|
||||
/// 最大密度点数 (用于数组维度)
|
||||
pub max_numrh: usize,
|
||||
/// 温度向量 (numtemp)
|
||||
pub tempvec: &'a [f64],
|
||||
/// 温度下限 (ln T)
|
||||
pub ttab1: f64,
|
||||
/// 温度上限 (ln T)
|
||||
pub ttab2: f64,
|
||||
/// 每个温度的密度数 (numtemp)
|
||||
pub numrh: &'a [i32],
|
||||
/// 密度矩阵 (numtemp × max_numrh) - 存储 ln(rho)
|
||||
pub rhomat: &'a [f64],
|
||||
/// 吸收不透明度表 (numtemp × max_numrh × nfreq) - 存储 ln(opacity)
|
||||
pub absopac: &'a [f64],
|
||||
/// Rayleigh 散射系数 (nd)
|
||||
pub raysc: &'a [f64],
|
||||
/// 频率数组 (nfreq)
|
||||
pub freq: &'a [f64],
|
||||
/// 电子散射系数
|
||||
pub sige: f64,
|
||||
}
|
||||
|
||||
/// OPCTAB 模型状态
|
||||
pub struct OpctabModelState<'a> {
|
||||
/// 电子密度 (nd)
|
||||
pub elec: &'a [f64],
|
||||
/// 密度 (nd)
|
||||
pub dens: &'a [f64],
|
||||
/// Rayleigh 散射截面 (当 ifrayl > 0 时需要)
|
||||
pub raysct: Option<&'a mut RaySct>,
|
||||
/// EOS 粒子数密度 (当 ifrayl > 0 时需要)
|
||||
pub eospar: Option<&'a EosPar>,
|
||||
}
|
||||
|
||||
/// OPCTAB 输出
|
||||
pub struct OpctabOutput {
|
||||
/// 吸收不透明度
|
||||
pub ab: f64,
|
||||
/// 散射不透明度
|
||||
pub sc: f64,
|
||||
/// 总散射
|
||||
pub sct: f64,
|
||||
}
|
||||
|
||||
/// 通过插值计算不透明度。
|
||||
///
|
||||
/// # 参数
|
||||
///
|
||||
/// * `params` - 输入参数
|
||||
/// * `table` - 表数据
|
||||
/// * `model` - 模型状态
|
||||
///
|
||||
/// # 返回值
|
||||
///
|
||||
/// 不透明度输出结构
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// 当 `ifrayl > 0` 但 `rayleigh_params`、`raysct` 或 `eospar` 为 `None` 时 panic。
|
||||
pub fn opctab(
|
||||
params: &OpctabParams,
|
||||
table: &OpctabTableData,
|
||||
model: &mut OpctabModelState,
|
||||
) -> OpctabOutput {
|
||||
let jf = params.ij;
|
||||
let id = params.id;
|
||||
let id_idx = id - 1;
|
||||
|
||||
// 检查是否直接使用预计算的值 (numtemp == nd)
|
||||
// Fortran: if(numtemp.eq.nd) then
|
||||
let opac = if table.numtemp == table.nd {
|
||||
// 直接使用当前深度点的值
|
||||
// Fortran: opac=absopac(id,1,jf)
|
||||
table.absopac[id_idx * table.max_numrh * table.nfreq + jf - 1]
|
||||
} else {
|
||||
compute_interpolated_opacity(params, table, jf)
|
||||
};
|
||||
|
||||
let ab = opac.exp();
|
||||
|
||||
// 散射计算
|
||||
let mut sct = 0.0;
|
||||
|
||||
// 1. Rayleigh 散射
|
||||
if params.ifrayl < 0 {
|
||||
// 简单公式: sct = raysc(id) * (freq(jf) / frray0)^4
|
||||
sct = table.raysc[id_idx] * (table.freq[jf - 1] / FRRAY0).powi(4);
|
||||
} else if params.ifrayl > 0 {
|
||||
// 调用完整的 rayleigh 函数
|
||||
let rayleigh_params = params
|
||||
.rayleigh_params
|
||||
.expect("rayleigh_params required when ifrayl > 0");
|
||||
let raysct = model
|
||||
.raysct
|
||||
.as_mut()
|
||||
.expect("raysct required when ifrayl > 0");
|
||||
let eospar = model
|
||||
.eospar
|
||||
.as_ref()
|
||||
.expect("eospar required when ifrayl > 0");
|
||||
|
||||
let scr = rayleigh(1, params.ij, params.id, rayleigh_params, raysct, eospar);
|
||||
sct = scr / model.dens[id_idx];
|
||||
}
|
||||
|
||||
// 电子散射
|
||||
if params.ioptab < 0 {
|
||||
sct = sct + table.sige * model.elec[id_idx] / params.rho;
|
||||
}
|
||||
|
||||
// 如果迭代次数 <= 0,直接返回
|
||||
if params.iter <= 0 {
|
||||
return OpctabOutput { ab, sc: sct, sct };
|
||||
}
|
||||
|
||||
// 根据 IGRAM 标志调整
|
||||
let (ab_out, sc_out, sct_out) = if params.igram == 0 {
|
||||
(ab * params.rho, sct * params.rho, sct * params.rho)
|
||||
} else {
|
||||
(ab, sct, sct)
|
||||
};
|
||||
|
||||
OpctabOutput {
|
||||
ab: ab_out,
|
||||
sc: sc_out,
|
||||
sct: sct_out,
|
||||
}
|
||||
}
|
||||
|
||||
/// 计算插值不透明度(内部函数)
|
||||
fn compute_interpolated_opacity(params: &OpctabParams, table: &OpctabTableData, jf: usize) -> f64 {
|
||||
let tl = params.t.ln();
|
||||
let deltat = (tl - table.ttab1) / (table.ttab2 - table.ttab1) * (table.numtemp - 1) as f64;
|
||||
|
||||
let mut jt = (1.0 + deltat.floor()) as isize;
|
||||
if jt < 1 {
|
||||
jt = 1;
|
||||
}
|
||||
if jt > table.numtemp as isize - 1 {
|
||||
jt = table.numtemp as isize - 1;
|
||||
}
|
||||
let jt = jt as usize;
|
||||
let jt_idx = jt - 1;
|
||||
|
||||
let t1i = table.tempvec[jt_idx];
|
||||
let t2i = table.tempvec[jt_idx + 1];
|
||||
let mut dti = (tl - t1i) / (t2i - t1i);
|
||||
if deltat < 0.0 {
|
||||
dti = 0.0;
|
||||
}
|
||||
|
||||
// 检查是否需要密度插值
|
||||
// Fortran: if(numrh(1).ne.1) then
|
||||
if table.numrh[0] != 1 {
|
||||
compute_2d_interpolation(params, table, jf, jt_idx, dti)
|
||||
} else {
|
||||
// 单密度情况: jr = 1 (Fortran), 所以 jr_idx = 0
|
||||
// Fortran: opac=absopac(jt,jr,jf)+(absopac(ju,jr,jf)-absopac(jt,jr,jf))*dti
|
||||
let jr_idx = 0; // jr = 1 in Fortran
|
||||
let idx_jt = jt_idx * table.max_numrh * table.nfreq + jr_idx * table.nfreq + (jf - 1);
|
||||
let idx_ju = (jt_idx + 1) * table.max_numrh * table.nfreq + jr_idx * table.nfreq + (jf - 1);
|
||||
table.absopac[idx_jt] + (table.absopac[idx_ju] - table.absopac[idx_jt]) * dti
|
||||
}
|
||||
}
|
||||
|
||||
/// 二维插值(温度和密度)
|
||||
fn compute_2d_interpolation(
|
||||
params: &OpctabParams,
|
||||
table: &OpctabTableData,
|
||||
jf: usize,
|
||||
jt_idx: usize,
|
||||
dti: f64,
|
||||
) -> f64 {
|
||||
let rl = params.rho.ln();
|
||||
|
||||
// 低温下的密度插值
|
||||
// Fortran: numrho=numrh(jt), rtab1=rhomat(jt,1), rtab2=rhomat(jt,numrho)
|
||||
let numrho = table.numrh[jt_idx] as usize;
|
||||
let rtab1 = table.rhomat[jt_idx * table.max_numrh];
|
||||
let rtab2 = table.rhomat[jt_idx * table.max_numrh + numrho - 1];
|
||||
|
||||
let deltar = (rl - rtab1) / (rtab2 - rtab1) * (numrho - 1) as f64;
|
||||
|
||||
let mut jr = (1.0 + deltar.floor()) as isize;
|
||||
if jr < 1 {
|
||||
jr = 1;
|
||||
}
|
||||
if jr > numrho as isize - 1 {
|
||||
jr = numrho as isize - 1;
|
||||
}
|
||||
let jr = jr as usize;
|
||||
let jr_idx = jr - 1;
|
||||
|
||||
// Fortran: r1i=rhomat(jt,jr), r2i=rhomat(jt,jr+1)
|
||||
let r1i = table.rhomat[jt_idx * table.max_numrh + jr_idx];
|
||||
let r2i = table.rhomat[jt_idx * table.max_numrh + jr_idx + 1];
|
||||
let mut dri = (rl - r1i) / (r2i - r1i);
|
||||
if deltar < 0.0 {
|
||||
dri = 0.0;
|
||||
}
|
||||
|
||||
// Fortran: opr1=absopac(jt,jr,jf)+dri*(absopac(jt,jr+1,jf)-absopac(jt,jr,jf))
|
||||
let idx_jt_jr = jt_idx * table.max_numrh * table.nfreq + jr_idx * table.nfreq + (jf - 1);
|
||||
let idx_jt_jrp1 = jt_idx * table.max_numrh * table.nfreq + (jr_idx + 1) * table.nfreq + (jf - 1);
|
||||
let opr1 =
|
||||
table.absopac[idx_jt_jr] + dri * (table.absopac[idx_jt_jrp1] - table.absopac[idx_jt_jr]);
|
||||
|
||||
// 高温下的密度插值
|
||||
// Fortran: ju=jt+1, numrho=numrh(ju)
|
||||
let ju_idx = jt_idx + 1;
|
||||
let numrho_h = table.numrh[ju_idx] as usize;
|
||||
let rtab1_h = table.rhomat[ju_idx * table.max_numrh];
|
||||
let rtab2_h = table.rhomat[ju_idx * table.max_numrh + numrho_h - 1];
|
||||
|
||||
let deltar_h = (rl - rtab1_h) / (rtab2_h - rtab1_h) * (numrho_h - 1) as f64;
|
||||
|
||||
let mut jr_h = (1.0 + deltar_h.floor()) as isize;
|
||||
if jr_h < 1 {
|
||||
jr_h = 1;
|
||||
}
|
||||
if jr_h > numrho_h as isize - 1 {
|
||||
jr_h = numrho_h as isize - 1;
|
||||
}
|
||||
let jr_h = jr_h as usize;
|
||||
let jr_h_idx = jr_h - 1;
|
||||
|
||||
let r1i_h = table.rhomat[ju_idx * table.max_numrh + jr_h_idx];
|
||||
let r2i_h = table.rhomat[ju_idx * table.max_numrh + jr_h_idx + 1];
|
||||
let mut dri_h = (rl - r1i_h) / (r2i_h - r1i_h);
|
||||
if deltar_h < 0.0 {
|
||||
dri_h = 0.0;
|
||||
}
|
||||
|
||||
// Fortran: opr2=absopac(ju,jr,jf)+dri*(absopac(ju,jr+1,jf)-absopac(ju,jr,jf))
|
||||
let idx_ju_jr = ju_idx * table.max_numrh * table.nfreq + jr_h_idx * table.nfreq + (jf - 1);
|
||||
let idx_ju_jrp1 = ju_idx * table.max_numrh * table.nfreq + (jr_h_idx + 1) * table.nfreq + (jf - 1);
|
||||
let opr2 = table.absopac[idx_ju_jr]
|
||||
+ dri_h * (table.absopac[idx_ju_jrp1] - table.absopac[idx_ju_jr]);
|
||||
|
||||
// Fortran: opac=opr1+(opr2-opr1)*dti
|
||||
opr1 + (opr2 - opr1) * dti
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use approx::assert_relative_eq;
|
||||
|
||||
#[test]
|
||||
fn test_opctab_constants() {
|
||||
assert_relative_eq!(FRRAY0, 5.0872638e14, epsilon = 1e9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_opctab_direct_value() {
|
||||
let params = OpctabParams {
|
||||
fr: 1e15,
|
||||
ij: 1,
|
||||
id: 1,
|
||||
t: 10000.0,
|
||||
rho: 1e-7,
|
||||
igram: 0,
|
||||
iter: 1,
|
||||
ifrayl: -1,
|
||||
ioptab: 0,
|
||||
rayleigh_params: None,
|
||||
};
|
||||
|
||||
let numtemp = 1;
|
||||
let nd = 1;
|
||||
let nfreq = 1;
|
||||
let max_numrh = 1;
|
||||
let tempvec = vec![9.2103];
|
||||
let numrh = vec![1];
|
||||
let rhomat = vec![-16.1181];
|
||||
let absopac = vec![0.0];
|
||||
let raysc = vec![1e-20];
|
||||
let freq = vec![5.0872638e14];
|
||||
|
||||
let table = OpctabTableData {
|
||||
numtemp,
|
||||
nd,
|
||||
nfreq,
|
||||
max_numrh,
|
||||
tempvec: &tempvec,
|
||||
ttab1: 8.0,
|
||||
ttab2: 11.0,
|
||||
numrh: &numrh,
|
||||
rhomat: &rhomat,
|
||||
absopac: &absopac,
|
||||
raysc: &raysc,
|
||||
freq: &freq,
|
||||
sige: 6.65e-25,
|
||||
};
|
||||
|
||||
let elec = vec![1e-10];
|
||||
let dens = vec![1e-7];
|
||||
let mut model = OpctabModelState {
|
||||
elec: &elec,
|
||||
dens: &dens,
|
||||
raysct: None,
|
||||
eospar: None,
|
||||
};
|
||||
|
||||
let result = opctab(¶ms, &table, &mut model);
|
||||
|
||||
// 当 numtemp == nd 时,直接使用 absopac(id, 1, jf) = 0.0
|
||||
// ab = exp(0.0) * rho = 1.0 * 1e-7 = 1e-7
|
||||
assert_relative_eq!(result.ab, 1e-7, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_opctab_rayleigh_scattering() {
|
||||
let params = OpctabParams {
|
||||
fr: 1e15,
|
||||
ij: 1,
|
||||
id: 1,
|
||||
t: 10000.0,
|
||||
rho: 1e-7,
|
||||
igram: 0,
|
||||
iter: 1,
|
||||
ifrayl: -1,
|
||||
ioptab: 0,
|
||||
rayleigh_params: None,
|
||||
};
|
||||
|
||||
let numtemp = 1;
|
||||
let nd = 1;
|
||||
let nfreq = 1;
|
||||
let max_numrh = 1;
|
||||
let tempvec = vec![9.2103];
|
||||
let numrh = vec![1];
|
||||
let rhomat = vec![-16.1181];
|
||||
let absopac = vec![0.0];
|
||||
let raysc = vec![1e-20];
|
||||
let freq = vec![5.0872638e14];
|
||||
|
||||
let table = OpctabTableData {
|
||||
numtemp,
|
||||
nd,
|
||||
nfreq,
|
||||
max_numrh,
|
||||
tempvec: &tempvec,
|
||||
ttab1: 8.0,
|
||||
ttab2: 11.0,
|
||||
numrh: &numrh,
|
||||
rhomat: &rhomat,
|
||||
absopac: &absopac,
|
||||
raysc: &raysc,
|
||||
freq: &freq,
|
||||
sige: 6.65e-25,
|
||||
};
|
||||
|
||||
let elec = vec![1e-10];
|
||||
let dens = vec![1e-7];
|
||||
let mut model = OpctabModelState {
|
||||
elec: &elec,
|
||||
dens: &dens,
|
||||
raysct: None,
|
||||
eospar: None,
|
||||
};
|
||||
|
||||
let result = opctab(¶ms, &table, &mut model);
|
||||
|
||||
// Rayleigh 散射 = raysc * (freq/frray0)^4 * rho
|
||||
// = 1e-20 * 1.0^4 * 1e-7 = 1e-27
|
||||
assert_relative_eq!(result.sct, 1e-27, epsilon = 1e-30);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
//! 速率矩阵计算 - RATMAT。
|
||||
//!
|
||||
//! 重构自 TLUSTY `ratmat.f`
|
||||
//!
|
||||
//! 计算统计平衡方程的速率矩阵 A 和右端向量 B。
|
||||
//!
|
||||
//! 方程形式为:A * n = B,其中 n 是能级占据数向量。
|
||||
//!
|
||||
//! 参考:Mihalas, 1978, pp.138-139
|
||||
|
||||
use crate::math::reflev::reflev;
|
||||
use crate::state::atomic::AtomicData;
|
||||
use crate::state::config::TlustyConfig;
|
||||
use crate::state::constants::{HK, MLEVEL, UN};
|
||||
use crate::state::iterat::IterControl;
|
||||
use crate::state::model::ModelState;
|
||||
|
||||
/// RATMAT 输入参数
|
||||
pub struct RatmatParams<'a> {
|
||||
/// 深度索引 (1-indexed)
|
||||
pub id: usize,
|
||||
/// 能级索引数组 (可能为负)
|
||||
pub iical: &'a mut [i32],
|
||||
/// 模式标志 (<0: 特殊模式, 0: 正常, >0: 调用 REFLEV)
|
||||
pub imode: i32,
|
||||
}
|
||||
|
||||
/// RATMAT 输出
|
||||
pub struct RatmatOutput {
|
||||
/// 速率矩阵 A (MLEVEL × MLEVEL)
|
||||
pub a: Vec<Vec<f64>>,
|
||||
/// 右端向量 B (MLEVEL)
|
||||
pub b: Vec<f64>,
|
||||
}
|
||||
|
||||
/// 计算速率矩阵。
|
||||
///
|
||||
/// # 参数
|
||||
///
|
||||
/// * `params` - 输入参数
|
||||
/// * `config` - 配置
|
||||
/// * `atomic` - 原子数据
|
||||
/// * `model` - 模型状态
|
||||
/// * `iterat` - 迭代控制
|
||||
///
|
||||
/// # 返回值
|
||||
///
|
||||
/// 速率矩阵 A 和右端向量 B
|
||||
pub fn ratmat(
|
||||
params: &mut RatmatParams,
|
||||
config: &mut TlustyConfig,
|
||||
atomic: &mut AtomicData,
|
||||
model: &mut ModelState,
|
||||
iterat: &IterControl,
|
||||
) -> RatmatOutput {
|
||||
let id = params.id;
|
||||
let id_idx = id - 1;
|
||||
|
||||
// 检查选项表标志
|
||||
if config.basnum.ioptab < 0 {
|
||||
return RatmatOutput {
|
||||
a: vec![vec![0.0; MLEVEL]; MLEVEL],
|
||||
b: vec![0.0; MLEVEL],
|
||||
};
|
||||
}
|
||||
|
||||
let nlevel = config.basnum.nlevel as usize;
|
||||
let ntrans = config.basnum.ntrans as usize;
|
||||
let natom = config.basnum.natom as usize;
|
||||
let lte = config.inppar.lte;
|
||||
let ipslte = config.inppar.ipslte;
|
||||
|
||||
// 如果使用 Slater 迭代,清零辐射速率
|
||||
if ipslte != 0 {
|
||||
for itr in 0..ntrans {
|
||||
model.rrrates.rru[itr][id_idx] = 0.0;
|
||||
model.rrrates.rrd[itr][id_idx] = 0.0;
|
||||
model.rrrates.drdt[itr][id_idx] = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化输出
|
||||
let mut a = vec![vec![0.0; MLEVEL]; MLEVEL];
|
||||
let mut b = vec![0.0; MLEVEL];
|
||||
|
||||
// 工作数组
|
||||
let mut aij = vec![0.0; ntrans.max(1)];
|
||||
let mut aji = vec![0.0; ntrans.max(1)];
|
||||
let mut sbw = vec![0.0; MLEVEL];
|
||||
let mut llte = vec![false; MLEVEL];
|
||||
|
||||
// 获取物理量
|
||||
let t = model.modpar.temp[id_idx];
|
||||
let ane = model.modpar.elec[id_idx];
|
||||
let hkt = HK / t;
|
||||
let tk = hkt / HK;
|
||||
|
||||
// 初始化
|
||||
for i in 0..nlevel {
|
||||
b[i] = 0.0;
|
||||
sbw[i] = ane * model.levpop.sbf[i] * model.wmcomp.wop[i][id_idx];
|
||||
llte[i] = false;
|
||||
|
||||
// 设置 LTE 标志
|
||||
let iel_i = atomic.levpar.iel[i] as usize;
|
||||
let ilt = atomic.ionpar.iltion[iel_i];
|
||||
let llt = ilt == 1 && params.imode == 0;
|
||||
llte[i] = llt || lte || atomic.ionpar.iltlev[i] >= 1 || ilt >= 2;
|
||||
llte[i] = llte[i] || id >= config.inppar.idlte as usize;
|
||||
|
||||
for j in 0..nlevel {
|
||||
a[j][i] = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
// 确定参考能级
|
||||
if params.imode != 0 {
|
||||
reflev(id, params.imode.abs(), config, atomic, model, iterat);
|
||||
}
|
||||
|
||||
// 第一部分:LTE 情况下的简化表达式
|
||||
for iat in 0..natom {
|
||||
if atomic.atopar.iifix[iat] != 1 || params.imode < 0 {
|
||||
let nrefi = atomic.atopar.nrefs[iat][id_idx] as usize;
|
||||
let n0 = atomic.atopar.n0a[iat] as usize;
|
||||
let nk = atomic.atopar.nka[iat] as usize;
|
||||
|
||||
for i in n0..=nk {
|
||||
let ii = params.iical[i].abs() as usize;
|
||||
if i != nrefi && ii != 0 && llte[i] {
|
||||
a[ii][ii] = UN;
|
||||
let n = params.iical[model.levref.ilterf[i][id_idx] as usize].abs() as usize;
|
||||
a[ii][n] = a[ii][n] - model.levref.sblpsi[i][id_idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 第二部分:NLTE 速率方程
|
||||
if !lte {
|
||||
// 计算跃迁速率
|
||||
for itr in 0..ntrans {
|
||||
let i = atomic.trapar.ilow[itr] as usize;
|
||||
if atomic.atopar.iifix[atomic.trapar.iatm[i] as usize] == 1 {
|
||||
continue;
|
||||
}
|
||||
let j = atomic.trapar.iup[itr] as usize;
|
||||
let nke = atomic.ionpar.nnext[atomic.levpar.iel[i] as usize] as usize;
|
||||
|
||||
// 向上总速率
|
||||
aij[itr] = (model.rrrates.colrat[itr][id_idx] + model.rrrates.rru[itr][id_idx])
|
||||
* model.wmcomp.wop[j][id_idx];
|
||||
|
||||
// 向下总速率
|
||||
if atomic.trapar.line[itr] {
|
||||
// 束缚-束缚跃迁
|
||||
aji[itr] = (model.rrrates.coltar[itr][id_idx]
|
||||
+ model.rrrates.rrd[itr][id_idx]
|
||||
* atomic.levpar.g[i] / atomic.levpar.g[j]
|
||||
* (hkt * atomic.trapar.fr0[itr]).exp())
|
||||
* model.wmcomp.wop[i][id_idx];
|
||||
} else {
|
||||
// 束缚-自由跃迁
|
||||
let corr = if nke != j {
|
||||
atomic.levpar.g[nke] / atomic.levpar.g[j]
|
||||
* ((atomic.levpar.enion[nke] - atomic.levpar.enion[j]) * tk).exp()
|
||||
} else {
|
||||
UN
|
||||
};
|
||||
aji[itr] = model.rrrates.coltar[itr][id_idx] * model.wmcomp.wop[i][id_idx]
|
||||
+ model.rrrates.rrd[itr][id_idx] * sbw[i] * corr;
|
||||
}
|
||||
|
||||
// 处理负索引
|
||||
if params.iical[i] < 0 && params.iical[j] < 0 {
|
||||
aij[itr] *= model.levref.sbpsi[i][id_idx];
|
||||
aji[itr] *= model.levref.sbpsi[j][id_idx];
|
||||
}
|
||||
}
|
||||
|
||||
// 填充速率矩阵
|
||||
for itr in 0..ntrans {
|
||||
let i = atomic.trapar.ilow[itr] as usize;
|
||||
if atomic.atopar.iifix[atomic.trapar.iatm[i] as usize] == 1 {
|
||||
continue;
|
||||
}
|
||||
let nrefi = atomic.atopar.nrefs[atomic.trapar.iatm[i] as usize][id_idx] as usize;
|
||||
let j = atomic.trapar.iup[itr] as usize;
|
||||
let ii = params.iical[i].abs() as usize;
|
||||
let jj = params.iical[j].abs() as usize;
|
||||
|
||||
if model.popzr0.ipzero[i][id_idx] != 0 || model.popzr0.ipzero[j][id_idx] != 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 下能级贡献
|
||||
if i != nrefi && ii > 0 && !llte[i] {
|
||||
a[ii][ii] += aij[itr];
|
||||
if jj > 0 {
|
||||
a[ii][jj] -= aji[itr];
|
||||
} else {
|
||||
let jjj = params.iical[model.levref.iltref[j][id_idx] as usize] as usize;
|
||||
a[ii][jjj] -= aji[itr] * model.levref.sbpsi[j][id_idx];
|
||||
}
|
||||
}
|
||||
|
||||
// 上能级贡献
|
||||
if j != nrefi && jj > 0 && !llte[j] {
|
||||
a[jj][jj] += aji[itr];
|
||||
if ii > 0 {
|
||||
a[jj][ii] -= aij[itr];
|
||||
} else {
|
||||
let iii = params.iical[model.levref.iltref[i][id_idx] as usize] as usize;
|
||||
a[jj][iii] -= aij[itr] * model.levref.sbpsi[i][id_idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 重置"小"占据数的速率矩阵元素
|
||||
for i in 0..nlevel {
|
||||
let ii = params.iical[i];
|
||||
if ii > 0 {
|
||||
let ii_idx = ii as usize;
|
||||
if model.popzr0.ipzero[i][id_idx] > 0 {
|
||||
for j in 0..nlevel {
|
||||
a[ii_idx][j] = 0.0;
|
||||
}
|
||||
a[ii_idx][ii_idx] = 1.0;
|
||||
}
|
||||
} else if ii < 0 {
|
||||
let ii_idx = (-ii) as usize;
|
||||
if model.popzr0.igzero[ii_idx - 1][id_idx] > 0 {
|
||||
for j in 0..nlevel {
|
||||
a[ii_idx - 1][j] = 0.0;
|
||||
}
|
||||
a[ii_idx - 1][ii_idx - 1] = 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 第三部分:丰度定义方程
|
||||
for iat in 0..natom {
|
||||
if atomic.atopar.iifix[iat] == 1 && params.imode >= 0 {
|
||||
continue;
|
||||
}
|
||||
let nrefii = params.iical[atomic.atopar.nrefs[iat][id_idx] as usize].abs() as usize;
|
||||
let n0 = atomic.atopar.n0a[iat] as usize;
|
||||
let nk = atomic.atopar.nka[iat] as usize;
|
||||
|
||||
for i in n0..=nk {
|
||||
let il = atomic.levpar.ilk[i];
|
||||
let ii = params.iical[i];
|
||||
if ii > 0 {
|
||||
let ii_idx = ii as usize;
|
||||
a[nrefii][ii_idx] += UN;
|
||||
if il != 0 {
|
||||
a[nrefii][ii_idx] += ane * model.levpop.usum[il as usize];
|
||||
}
|
||||
} else if ii < 0 {
|
||||
let ii_idx = (-ii) as usize;
|
||||
a[nrefii][ii_idx - 1] += model.levref.sbpsi[i][id_idx];
|
||||
if il != 0 {
|
||||
a[nrefii][ii_idx - 1] +=
|
||||
ane * model.levpop.usum[il as usize] * model.levref.sbpsi[i][id_idx];
|
||||
}
|
||||
} else {
|
||||
let iii = params.iical[model.levref.iltref[i][id_idx] as usize] as usize;
|
||||
if iii > 0 && iii <= nlevel {
|
||||
a[nrefii][iii - 1] += model.levref.sbpsi[i][id_idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b[nrefii] += model.modpar.dens[id_idx]
|
||||
/ model.modpar.wmm[id_idx]
|
||||
/ model.modpar.ytot[id_idx]
|
||||
* atomic.atopar.abund[iat][id_idx];
|
||||
}
|
||||
|
||||
RatmatOutput { a, b }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_ratmat_ioptab_negative() {
|
||||
let mut config = TlustyConfig::default();
|
||||
let mut atomic = AtomicData::default();
|
||||
let mut model = ModelState::default();
|
||||
let iterat = IterControl::default();
|
||||
|
||||
config.basnum.ioptab = -1;
|
||||
config.basnum.nlevel = 10;
|
||||
config.basnum.ntrans = 5;
|
||||
config.basnum.natom = 1;
|
||||
|
||||
let mut iical = vec![0; MLEVEL];
|
||||
let mut params = RatmatParams {
|
||||
id: 1,
|
||||
iical: &mut iical,
|
||||
imode: 0,
|
||||
};
|
||||
|
||||
let result = ratmat(&mut params, &mut config, &mut atomic, &mut model, &iterat);
|
||||
|
||||
// 当 ioptab < 0 时,应返回零矩阵
|
||||
assert_eq!(result.a.len(), MLEVEL);
|
||||
assert_eq!(result.b.len(), MLEVEL);
|
||||
for i in 0..MLEVEL {
|
||||
assert_relative_eq!(result.b[i], 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ratmat_basic_lte() {
|
||||
let mut config = TlustyConfig::default();
|
||||
let mut atomic = AtomicData::default();
|
||||
let mut model = ModelState::default();
|
||||
let iterat = IterControl::default();
|
||||
|
||||
config.basnum.ioptab = 0;
|
||||
config.basnum.nlevel = 5;
|
||||
config.basnum.ntrans = 3;
|
||||
config.basnum.natom = 1;
|
||||
config.inppar.lte = true;
|
||||
|
||||
atomic.atopar.n0a[0] = 0;
|
||||
atomic.atopar.nka[0] = 4;
|
||||
atomic.atopar.nrefs[0][0] = 2;
|
||||
atomic.atopar.iifix[0] = 0;
|
||||
|
||||
atomic.levpar.iel[0] = 0;
|
||||
atomic.levpar.iel[1] = 0;
|
||||
atomic.levpar.iel[2] = 1;
|
||||
atomic.levpar.iel[3] = 1;
|
||||
atomic.levpar.iel[4] = 1;
|
||||
atomic.levpar.ilk[0] = 0;
|
||||
atomic.levpar.ilk[1] = 0;
|
||||
atomic.levpar.ilk[2] = 1;
|
||||
atomic.levpar.ilk[3] = 0;
|
||||
atomic.levpar.ilk[4] = 0;
|
||||
|
||||
atomic.ionpar.iltion[0] = 1;
|
||||
atomic.ionpar.iltion[1] = 1;
|
||||
|
||||
model.modpar.temp[0] = 10000.0;
|
||||
model.modpar.elec[0] = 1e12;
|
||||
model.modpar.dens[0] = 1e14;
|
||||
model.modpar.wmm[0] = 1.0;
|
||||
model.modpar.ytot[0] = 1.0;
|
||||
atomic.atopar.abund[0][0] = 1.0;
|
||||
|
||||
let mut iical = vec![1, 2, 3, 4, 5];
|
||||
let mut params = RatmatParams {
|
||||
id: 1,
|
||||
iical: &mut iical,
|
||||
imode: 0,
|
||||
};
|
||||
|
||||
let result = ratmat(&mut params, &mut config, &mut atomic, &mut model, &iterat);
|
||||
|
||||
// 验证矩阵维度
|
||||
assert_eq!(result.a.len(), MLEVEL);
|
||||
assert_eq!(result.b.len(), MLEVEL);
|
||||
}
|
||||
}
|
||||
@@ -144,14 +144,20 @@ pub struct FixAlp {
|
||||
pub dsfdt: Vec<f64>,
|
||||
/// 源函数 N 导数
|
||||
pub dsfdn: Vec<f64>,
|
||||
/// 源函数 M 导数
|
||||
pub dsfdm: Vec<f64>,
|
||||
/// 前深度源函数 T 导数
|
||||
pub dsfdtm: Vec<f64>,
|
||||
/// 前深度源函数 N 导数
|
||||
pub dsfdnm: Vec<f64>,
|
||||
/// 前深度源函数 M 导数
|
||||
pub dsfdmm: Vec<f64>,
|
||||
/// 后深度源函数 T 导数
|
||||
pub dsfdtp: Vec<f64>,
|
||||
/// 后深度源函数 N 导数
|
||||
pub dsfdnp: Vec<f64>,
|
||||
/// 后深度源函数 M 导数
|
||||
pub dsfdmp: Vec<f64>,
|
||||
|
||||
// 源函数能级导数 (MLVEXP × MDEPTH)
|
||||
/// 源函数能级导数
|
||||
@@ -271,10 +277,13 @@ impl Default for FixAlp {
|
||||
// 源函数导数
|
||||
dsfdt: vec![0.0; MDEPTH],
|
||||
dsfdn: vec![0.0; MDEPTH],
|
||||
dsfdm: vec![0.0; MDEPTH],
|
||||
dsfdtm: vec![0.0; MDEPTH],
|
||||
dsfdnm: vec![0.0; MDEPTH],
|
||||
dsfdmm: vec![0.0; MDEPTH],
|
||||
dsfdtp: vec![0.0; MDEPTH],
|
||||
dsfdnp: vec![0.0; MDEPTH],
|
||||
dsfdmp: vec![0.0; MDEPTH],
|
||||
|
||||
// 源函数能级导数
|
||||
dsfdp: vec![vec![0.0; MDEPTH]; MLVEXP],
|
||||
|
||||
Reference in New Issue
Block a user