This commit is contained in:
fmq
2026-03-21 16:23:35 +08:00
parent f286ddacfe
commit 497e62e13c
35 changed files with 10781 additions and 364 deletions
+1346
View File
File diff suppressed because it is too large Load Diff
+482
View File
@@ -0,0 +1,482 @@
//! Compton 散射源函数辅助量计算。
//!
//! 重构自 TLUSTY `compt0.f`
//!
//! 计算 Compton 散射源函数的辅助量 COMPA, COMPB, COMPC, COMPD, COMPE, COMPS。
use crate::state::constants::{MDEPTH, MFREQ, TWO, UN};
// ============================================================================
// 常量
// ============================================================================
/// 频率转换常数 XCON = 8.0935e-21
const XCON: f64 = 8.0935e-21;
/// 温度转换常数 YCON = 1.68638e-10
const YCON: f64 = 1.68638e-10;
// ============================================================================
// 辅助数组 (对应 COMMON /auxcbc/)
// ============================================================================
/// Compton 散射导数辅助数组。
/// 对应 COMMON /auxcbc/
#[derive(Debug, Clone, Default)]
pub struct AuxCbc {
/// CDER1M(MDEPTH) - 频率导数 (前)
pub cden1m: Vec<f64>,
/// CDER10(MDEPTH) - 频率导数 (当前)
pub cden10: Vec<f64>,
/// CDER2M(MDEPTH) - 二阶频率导数 (前)
pub cden2m: Vec<f64>,
/// CDER20(MDEPTH) - 二阶频率导数 (当前)
pub cden20: Vec<f64>,
}
impl AuxCbc {
pub fn new() -> Self {
Self {
cden1m: vec![0.0; MDEPTH],
cden10: vec![0.0; MDEPTH],
cden2m: vec![0.0; MDEPTH],
cden20: vec![0.0; MDEPTH],
}
}
}
// ============================================================================
// COMPT0 参数结构体
// ============================================================================
/// COMPT0 输入参数。
#[derive(Debug, Clone)]
pub struct Compt0Params {
/// 频率索引 IJ (1-indexed)
pub ij: usize,
/// 深度索引 ID (1-indexed)
pub id: usize,
/// 吸收系数
pub ab: f64,
/// 总频率点数
pub nfreq: usize,
// 频率相关数组 [MFREQ]
pub freq: Vec<f64>,
pub ijorig: Vec<usize>,
pub kij: Vec<usize>,
pub dlnfr: Vec<f64>,
pub delj: Vec<Vec<f64>>,
pub bnus: Vec<f64>,
pub sigec: Vec<f64>,
// 深度相关数组 [MDEPTH]
pub temp: Vec<f64>,
pub elec: Vec<f64>,
// 辐射场 [MFREQ × MDEPTH]
pub rad: Vec<Vec<f64>>,
// Compton 控制参数
/// Compton 密度导数标志 (0=禁用)
pub icomde: i32,
/// Compton 源函数标志 (0=禁用)
pub icomst: i32,
/// Compton 高阶项标志
pub ichcoo: i32,
/// Compton 模式 (1=标准, 2=无非对角项, 3=全禁用)
pub icompt: i32,
/// 汤姆逊散射截面
pub sige: f64,
// 可变导数数组 (CDER1M, CDER10, CDER1P, CDER2M, CDER20, CDER2P)
/// CDER1M(IJI) - 前频率导数
pub cder1m: Vec<f64>,
/// CDER10(IJI) - 当前频率导数
pub cder10: Vec<f64>,
/// CDER1P(IJI) - 后频率导数
pub cder1p: Vec<f64>,
/// CDER2M(IJI) - 前二阶导数
pub cder2m: Vec<f64>,
/// CDER20(IJI) - 当前二阶导数
pub cder20: Vec<f64>,
/// CDER2P(IJI) - 后二阶导数
pub cder2p: Vec<f64>,
}
impl Default for Compt0Params {
fn default() -> Self {
Self {
ij: 1,
id: 1,
ab: 1.0,
nfreq: 1,
freq: vec![0.0; MFREQ],
ijorig: vec![0; MFREQ],
kij: vec![0; MFREQ],
dlnfr: vec![0.0; MFREQ],
delj: vec![vec![0.0; MDEPTH]; MFREQ],
bnus: vec![0.0; MFREQ],
sigec: vec![0.0; MFREQ],
temp: vec![0.0; MDEPTH],
elec: vec![0.0; MDEPTH],
rad: vec![vec![0.0; MDEPTH]; MFREQ],
icomde: 0,
icomst: 0,
ichcoo: 0,
icompt: 1,
sige: 6.6516e-25, // 汤姆逊散射截面
cder1m: vec![0.0; MFREQ],
cder10: vec![0.0; MFREQ],
cder1p: vec![0.0; MFREQ],
cder2m: vec![0.0; MFREQ],
cder20: vec![0.0; MFREQ],
cder2p: vec![0.0; MFREQ],
}
}
}
/// COMPT0 输出结果。
#[derive(Debug, Clone, Default)]
pub struct Compt0Result {
/// COMPA - 前频率导数系数
pub compa: f64,
/// COMPB - 当前频率导数系数
pub compb: f64,
/// COMPC - 后频率导数系数
pub compc: f64,
/// COMPD - 密度导数系数
pub compd: f64,
/// COMPE - 发射系数
pub compe: f64,
/// COMPS - 源函数系数
pub comps: f64,
}
// ============================================================================
// COMPT0 主函数
// ============================================================================
/// 计算 Compton 散射源函数的辅助量。
///
/// # 参数
///
/// * `params` - 输入参数
///
/// # 返回值
///
/// 返回 Compt0Result 包含 COMPA, COMPB, COMPC, COMPD, COMPE, COMPS
///
/// # Fortran 索引说明
///
/// - IJ, ID, IJI 都是 1-indexed
/// - Rust 中使用 0-indexed,需要转换
pub fn compt0(params: &mut Compt0Params) -> Compt0Result {
let mut result = Compt0Result::default();
// IJI = NFREQ - KIJ(IJ) + 1
// Fortran 1-indexed: KIJ(IJ) → Rust: kij[ij-1]
let iji = params.nfreq - params.kij[params.ij - 1] + 1;
// 如果 IJI = 1,所有输出为 0
if iji == 1 {
return result;
}
let ij_idx = params.ij - 1; // 0-indexed
let id_idx = params.id - 1; // 0-indexed
let iji_idx = iji - 1; // 0-indexed
// FR = FREQ(IJ)
let fr = params.freq[ij_idx];
// FRP = FREQ(IJORIG(IJI+1))
let frp = params.freq[params.ijorig[iji_idx + 1] - 1];
// FRM = FREQ(IJORIG(IJI-1))
let frm = params.freq[params.ijorig[iji_idx - 1] - 1];
let xcomp = fr * XCON;
let e2 = YCON * params.temp[id_idx];
let e1 = xcomp - 3.0 * e2;
// DEL0 = TWO / (DLNFR(IJI) + DLNFR(IJI-1))
let del0 = TWO / (params.dlnfr[iji_idx] + params.dlnfr[iji_idx - 1]);
// 计算导数系数
// Fortran: CDER1P(IJI) = (UN - DELJ(IJI, ID)) * DEL0
// Rust: cder1p[iji_idx] = (1.0 - delj[iji_idx][id_idx]) * del0
params.cder1p[iji_idx] = (UN - params.delj[iji_idx][id_idx]) * del0;
// Fortran: CDER1M(IJI) = -DELJ(IJI-1, ID) * DEL0
params.cder1m[iji_idx] = -params.delj[iji_idx - 1][id_idx] * del0;
// Fortran: CDER10(IJI) = -DEL0 * (UN - DELJ(IJI-1, ID) - DELJ(IJI, ID))
params.cder10[iji_idx] = -del0 * (UN - params.delj[iji_idx - 1][id_idx] - params.delj[iji_idx][id_idx]);
// SS0 = ELEC(ID) * SIGE / AB
let ss0 = params.elec[id_idx] * params.sige / params.ab;
// 临时变量
let mut compu = 0.0;
let mut compv = 0.0;
let mut cbs = 0.0;
if params.ichcoo == 0 {
// 标准模式
params.cder10[iji_idx] = -params.cder1m[iji_idx] - params.cder1p[iji_idx];
result.compa = ss0 * (e1 * params.cder1m[iji_idx] + e2 * params.cder2m[iji_idx]);
result.compb = ss0
* (UN - xcomp - params.sigec[ij_idx] / params.sige + e1 * params.cder10[iji_idx]
+ e2 * params.cder20[iji_idx]);
result.compc = ss0 * (e1 * params.cder1p[iji_idx] + e2 * params.cder2p[iji_idx]);
} else {
// 高阶模式
// EPSNU = (AB - ELEC(ID) * SIGEC(IJ)) / AB
let epsnu = (params.ab - params.elec[id_idx] * params.sigec[ij_idx]) / params.ab;
// ZXXP, ZXX0, ZXXM
let zxxp = XCON * frp + 0.5 * params.bnus[iji_idx + 1] * params.rad[iji_idx + 1][id_idx]
- 3.0 * e2;
let zxx0 = xcomp + 0.5 * params.bnus[iji_idx] * params.rad[iji_idx][id_idx] - 3.0 * e2;
let zxxm = XCON * frm + 0.5 * params.bnus[iji_idx - 1] * params.rad[iji_idx - 1][id_idx]
- 3.0 * e2;
let zxxp12 = ((UN - params.delj[iji_idx][id_idx]) * zxxp
+ params.delj[iji_idx][id_idx] * zxx0)
* del0;
let zxxm12 = ((UN - params.delj[iji_idx - 1][id_idx]) * zxx0
+ params.delj[iji_idx - 1][id_idx] * zxxm)
* del0;
result.compa = ss0 * (-params.delj[iji_idx - 1][id_idx] * zxxm12 + e2 * params.cder2m[iji_idx]);
result.compc = ss0
* ((UN - params.delj[iji_idx][id_idx]) * zxxp12 + e2 * params.cder2p[iji_idx]);
result.compb = ss0
* (params.delj[iji_idx][id_idx] * zxxp12
- (UN - params.delj[iji_idx - 1][id_idx]) * zxxm12
+ e2 * params.cder20[iji_idx]
- params.sigec[ij_idx] / params.sige)
- epsnu
+ 1.0;
result.compe = 0.0;
}
// COMPD = (-3 * CDER10(IJI) + CDER20(IJI)) * RAD(IJI, ID)
result.compd =
(-3.0 * params.cder10[iji_idx] + params.cder20[iji_idx]) * params.rad[iji_idx][id_idx];
// 如果 ICOMDE = 0,禁用密度导数项
if params.icomde == 0 {
result.compa = 0.0;
result.compc = 0.0;
result.compb = 0.0;
}
// X0 = SS0 * BNUS(IJI)
let x0 = ss0 * params.bnus[iji_idx];
// 如果 ICOMST = 0,禁用源函数项
let x0 = if params.icomst == 0 { 0.0 } else { x0 };
if params.ichcoo == 0 {
result.compe = x0 * (params.cder10[iji_idx] - UN);
compu = x0 * params.cder1m[iji_idx];
compv = x0 * params.cder1p[iji_idx];
cbs = result.compe * params.rad[iji_idx][id_idx];
result.compe = cbs;
}
result.comps = result.compb * params.rad[iji_idx][id_idx];
// IJI > 1 时的额外项
if iji > 1 {
if params.ichcoo == 0 {
cbs += compu * params.rad[iji_idx - 1][id_idx];
}
result.comps += result.compa * params.rad[iji_idx - 1][id_idx];
result.compd +=
(-3.0 * params.cder1m[iji_idx] + params.cder2m[iji_idx]) * params.rad[iji_idx - 1][id_idx];
}
// IJI < NFREQ 时的额外项
if iji < params.nfreq {
if params.ichcoo == 0 {
cbs += compv * params.rad[iji_idx + 1][id_idx];
}
result.comps += result.compc * params.rad[iji_idx + 1][id_idx];
result.compd +=
(-3.0 * params.cder1p[iji_idx] + params.cder2p[iji_idx]) * params.rad[iji_idx + 1][id_idx];
}
if params.ichcoo == 0 {
result.compb += cbs;
result.compa += compu * params.rad[iji_idx][id_idx];
result.compc += compv * params.rad[iji_idx][id_idx];
result.comps += cbs * params.rad[iji_idx][id_idx];
}
result.compd *= ss0 * YCON;
// 如果 ICOMDE = 0,禁用密度导数
if params.icomde == 0 {
result.compd = 0.0;
}
// ICOMPT = 2 模式:无非对角强度项
if params.icompt == 2 {
if iji > 1 {
result.compb += result.compa * params.rad[iji_idx - 1][id_idx];
}
if iji < params.nfreq {
result.compb += result.compc * params.rad[iji_idx + 1][id_idx];
}
result.compa = 0.0;
result.compc = 0.0;
} else if params.icompt == 3 {
// ICOMPT = 3 模式:全禁用
result.compa = 0.0;
result.compb = 0.0;
result.compc = 0.0;
}
result
}
// ============================================================================
// 测试
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
fn create_test_params() -> Compt0Params {
let mut params = Compt0Params::default();
params.ij = 5;
params.id = 1;
params.ab = 1e-8;
params.nfreq = 100;
// 设置简单的测试值
params.temp[0] = 10000.0;
params.elec[0] = 1e12;
// 设置 KIJ 映射
for i in 0..100 {
params.kij[i] = 100 - i; // 简单的反向映射
params.ijorig[i] = i + 1;
params.freq[i] = 1e14 * (i + 1) as f64;
params.dlnfr[i] = 0.1;
params.bnus[i] = 1e-10;
params.sigec[i] = 0.0;
params.delj[i][0] = 0.5;
// 初始化导数数组
params.cder1m[i] = 0.0;
params.cder10[i] = 0.0;
params.cder1p[i] = 0.0;
params.cder2m[i] = 0.0;
params.cder20[i] = 0.0;
params.cder2p[i] = 0.0;
// 初始化辐射场
params.rad[i][0] = 1e10;
}
params
}
#[test]
fn test_compt0_iji_equals_1() {
let mut params = Compt0Params::default();
// IJI = NFREQ - KIJ(IJ) + 1
// 当 kij[i] = 100 - i 时:
// ij = 1 → kij[0] = 100 → iji = 100 - 100 + 1 = 1
params.ij = 1;
params.nfreq = 100;
for i in 0..100 {
params.kij[i] = 100 - i;
}
let result = compt0(&mut params);
// 当 IJI = 1 时,所有输出应为 0
assert!((result.compa).abs() < 1e-15);
assert!((result.compb).abs() < 1e-15);
assert!((result.compc).abs() < 1e-15);
assert!((result.compd).abs() < 1e-15);
assert!((result.compe).abs() < 1e-15);
assert!((result.comps).abs() < 1e-15);
}
#[test]
fn test_compt0_basic() {
let mut params = create_test_params();
params.ichcoo = 0;
params.icomde = 1;
params.icomst = 1;
params.icompt = 1;
let result = compt0(&mut params);
// 检查结果有限
assert!(result.compa.is_finite());
assert!(result.compb.is_finite());
assert!(result.compc.is_finite());
assert!(result.compd.is_finite());
assert!(result.compe.is_finite());
assert!(result.comps.is_finite());
}
#[test]
fn test_compt0_icomde_zero() {
let mut params = create_test_params();
params.icomde = 0; // 禁用密度导数
let result = compt0(&mut params);
// ICOMDE = 0 时,COMPA, COMPB, COMPC, COMPD 应为 0
assert!((result.compa).abs() < 1e-15);
assert!((result.compb).abs() < 1e-15);
assert!((result.compc).abs() < 1e-15);
assert!((result.compd).abs() < 1e-15);
}
#[test]
fn test_compt0_icompt_3() {
let mut params = create_test_params();
params.icomde = 1;
params.icompt = 3; // 全禁用模式
let result = compt0(&mut params);
// ICOMPT = 3 时,COMPA, COMPB, COMPC 应为 0
assert!((result.compa).abs() < 1e-15);
assert!((result.compb).abs() < 1e-15);
assert!((result.compc).abs() < 1e-15);
}
#[test]
fn test_compt0_high_order_mode() {
let mut params = create_test_params();
params.ichcoo = 1; // 高阶模式
params.icomde = 1;
params.icomst = 1;
let result = compt0(&mut params);
// 检查结果有限
assert!(result.compa.is_finite());
assert!(result.compb.is_finite());
assert!(result.compc.is_finite());
assert!(result.compd.is_finite());
assert!(result.compe.is_finite());
assert!(result.comps.is_finite());
}
#[test]
fn test_constants() {
// 验证常量
assert!((XCON - 8.0935e-21).abs() < 1e-30);
assert!((YCON - 1.68638e-10).abs() < 1e-20);
}
}
+486
View File
@@ -0,0 +1,486 @@
//! Compton 散射参数设置。
//!
//! 重构自 TLUSTY `comset.f`。
//!
//! 设置 Compton 散射所需的频率相关参数:
//! - 导数系数 CDER1M, CDER10, CDER1P, CDER2M, CDER20, CDER2P
//! - 插值系数 DELJ
//! - Klein-Nishina 散射截面 SIGEC
use crate::state::constants::{BN, HALF, HK, MDEPTH, MFREQ, SIGE, UN};
// ============================================================================
// 常量
// ============================================================================
/// 频率转换常数 XCON = 8.0935e-21
const XCON: f64 = 8.0935e-21;
/// 温度转换常数 YCON = 1.68638e-10
const YCON: f64 = 1.68638e-10;
/// 频率单位转换 T15 = 1e-15
const T15: f64 = 1e-15;
// ============================================================================
// COMSET 参数结构体
// ============================================================================
/// COMSET 输入参数。
#[derive(Debug, Clone)]
pub struct ComsetParams {
/// 频率点数
pub nfreq: usize,
/// 深度点数
pub nd: usize,
/// 频率数组 [MFREQ] (1-indexed 访问)
pub freq: Vec<f64>,
/// 频率索引映射 KIJ [MFREQ]
pub kij: Vec<usize>,
/// 温度数组 [MDEPTH]
pub temp: Vec<f64>,
// 控制参数
/// Compton 模式 (≤0=禁用)
pub icompt: i32,
/// Compton 高阶项标志
pub ichcoo: i32,
/// Klein-Nishina 截面标志 (0=一阶近似, 1=完整公式)
pub knish: i32,
}
impl Default for ComsetParams {
fn default() -> Self {
Self {
nfreq: 1,
nd: 1,
freq: vec![0.0; MFREQ],
kij: vec![1; MFREQ],
temp: vec![0.0; MDEPTH],
icompt: 0,
ichcoo: 0,
knish: 0,
}
}
}
/// COMSET 输出结果。
#[derive(Debug, Clone, Default)]
pub struct ComsetResult {
/// 频率索引映射 IJORIG [MFREQ]
pub ijorig: Vec<usize>,
/// 重排后的频率 FREQI [MFREQ]
pub freqi: Vec<f64>,
/// Planck 加权因子 BNUS [MFREQ]
pub bnus: Vec<f64>,
/// 对数频率间隔 DLNFR [MFREQ]
pub dlnfr: Vec<f64>,
/// 插值系数 DELJ [MFREQ × MDEPTH]
pub delj: Vec<Vec<f64>>,
/// Klein-Nishina 散射截面 SIGEC [MFREQ]
pub sigec: Vec<f64>,
// 导数系数 [MFREQ]
/// 前频率导数 CDER1M
pub cder1m: Vec<f64>,
/// 当前频率导数 CDER10
pub cder10: Vec<f64>,
/// 后频率导数 CDER1P
pub cder1p: Vec<f64>,
/// 前二阶导数 CDER2M
pub cder2m: Vec<f64>,
/// 当前二阶导数 CDER20
pub cder20: Vec<f64>,
/// 后二阶导数 CDER2P
pub cder2p: Vec<f64>,
}
impl ComsetResult {
pub fn new() -> Self {
Self {
ijorig: vec![0; MFREQ],
freqi: vec![0.0; MFREQ],
bnus: vec![0.0; MFREQ],
dlnfr: vec![0.0; MFREQ],
delj: vec![vec![0.0; MDEPTH]; MFREQ],
sigec: vec![0.0; MFREQ],
cder1m: vec![0.0; MFREQ],
cder10: vec![0.0; MFREQ],
cder1p: vec![0.0; MFREQ],
cder2m: vec![0.0; MFREQ],
cder20: vec![0.0; MFREQ],
cder2p: vec![0.0; MFREQ],
}
}
}
// ============================================================================
// COMSET 主函数
// ============================================================================
/// 设置 Compton 散射参数。
///
/// # 参数
///
/// * `params` - 输入参数
///
/// # 返回值
///
/// 返回 ComsetResult 包含所有计算结果
///
/// # 说明
///
/// 此函数设置 Compton 散射所需的频率相关参数:
/// 1. 重排频率索引 (IJORIG)
/// 2. 计算 Planck 加权因子 (BNUS)
/// 3. 计算对数频率间隔 (DLNFR)
/// 4. 计算二阶导数系数 (CDER2*)
/// 5. 计算插值系数 (DELJ)
/// 6. 计算 Klein-Nishina 散射截面 (SIGEC)
pub fn comset(params: &ComsetParams) -> ComsetResult {
let mut result = ComsetResult::new();
// 如果 ICOMPT ≤ 0,跳过大部分计算
if params.icompt <= 0 {
// 只计算 SIGEC
compute_sigec(params, &mut result);
return result;
}
// ========================================================================
// 频率相关通用参数
// ========================================================================
for ij in 1..=params.nfreq {
// 初始化导数系数
result.cder10[ij - 1] = 0.0;
result.cder1p[ij - 1] = 0.0;
result.cder1m[ij - 1] = 0.0;
result.cder20[ij - 1] = 0.0;
result.cder2p[ij - 1] = 0.0;
result.cder2m[ij - 1] = 0.0;
// IJI = NFREQ - KIJ(IJ) + 1
let iji = params.nfreq - params.kij[ij - 1] + 1;
// IJORIG(IJI) = IJ
result.ijorig[iji - 1] = ij;
// FREQI(IJI) = FREQ(IJ)
result.freqi[iji - 1] = params.freq[ij - 1];
// FR = FREQI(IJI)
let fr = result.freqi[iji - 1];
// BNUS(IJI) = TWO*XCON*FR/(BN*(FR*T15)**3)
let fr_t15 = fr * T15;
result.bnus[iji - 1] = 2.0 * XCON * fr / (BN * fr_t15 * fr_t15 * fr_t15);
}
// ========================================================================
// 计算对数频率间隔和二阶导数系数
// ========================================================================
// IJ = 1
result.dlnfr[0] = (result.freqi[1] / result.freqi[0]).ln();
for ij in 2..params.nfreq {
// DLNFR(IJ) = LOG(FREQI(IJ+1)/FREQI(IJ))
result.dlnfr[ij - 1] = (result.freqi[ij] / result.freqi[ij - 1]).ln();
let delp = result.dlnfr[ij - 1];
let delm = result.dlnfr[ij - 2];
let del0 = delp + delm;
let cd0 = 2.0 / del0;
// CDER2M(IJ) = CD0/DELM
result.cder2m[ij - 1] = cd0 / delm;
// CDER2P(IJ) = CD0/DELP
result.cder2p[ij - 1] = cd0 / delp;
// CDER20(IJ) = -CDER2M(IJ) - CDER2P(IJ)
result.cder20[ij - 1] = -result.cder2m[ij - 1] - result.cder2p[ij - 1];
}
// ========================================================================
// 计算插值系数 DELJ
// ========================================================================
for ij in 1..params.nfreq {
let frj0 = result.freqi[ij - 1];
let frjp = result.freqi[ij];
let frz = (frj0 * frjp).sqrt();
for id in 1..=params.nd {
// 避免上溢/下溢
let fjb0 = if HK * frj0 / params.temp[id - 1] < 200.0 {
UN / ((HK * frj0 / params.temp[id - 1]).exp() - UN)
} else {
0.0
};
let fjbp = if HK * frjp / params.temp[id - 1] < 200.0 {
UN / ((HK * frjp / params.temp[id - 1]).exp() - UN)
} else {
0.0
};
let fjz0 = fjb0 * (BN * (frj0 * T15).powi(3));
let fjzp = fjbp * (BN * (frjp * T15).powi(3));
let (aa, bb, cc) = if params.ichcoo == 0 {
// 标准模式
let zj0 = HK * frz / params.temp[id - 1];
let dfjz = fjz0 - fjzp;
let dfjb = fjb0 - fjbp;
let fzz = UN + fjbp - 3.0 / zj0;
let aa = dfjz * dfjb;
let bb = dfjz * fzz + fjzp * dfjb;
let cc = fjzp * fzz - dfjz / result.dlnfr[ij - 1] / zj0;
(aa, bb, cc)
} else {
// 高阶模式
let e2 = YCON * params.temp[id - 1];
let zxxp = XCON * frjp * (UN + fjbp) - 3.0 * e2;
let zxx0 = XCON * frj0 * (UN + fjb0) - 3.0 * e2;
let dzxx = zxx0 - zxxp;
let dfjb = fjb0 - fjbp;
let dfjz = fjz0 - fjzp;
let aa = dfjz * dzxx;
let bb = dfjz * zxxp + fjzp * dzxx;
let cc = fjzp * zxxp - e2 * dfjz / result.dlnfr[ij - 1];
(aa, bb, cc)
};
// 求解二次方程 AA*XX1² + BB*XX1 + CC = 0
let xx1 = if aa.abs() == 0.0 && bb.abs() == 0.0 {
0.0
} else if aa.abs() < 1e-7 * bb.abs() {
-cc / bb
} else {
let dd = bb * bb - 4.0 * aa * cc;
let dd = if dd < 0.0 { 0.0 } else { dd };
let dd = dd.sqrt();
let xx1 = (dd - bb) * HALF / aa;
if params.ichcoo > 0 {
let xx2 = -(dd + bb) * HALF / aa;
let dxx1 = (xx1 - HALF).abs();
let dxx2 = (xx2 - HALF).abs();
let xx1 = if dxx2 < dxx1 { xx2 } else { xx1 };
if xx1 > 1.0 || xx1 < 0.0 {
HALF
} else {
xx1
}
} else {
xx1
}
};
result.delj[ij - 1][id - 1] = xx1;
}
}
// ========================================================================
// 计算 Klein-Nishina 散射截面 SIGEC
// ========================================================================
compute_sigec(params, &mut result);
result
}
/// 计算 Klein-Nishina 散射截面。
fn compute_sigec(params: &ComsetParams, result: &mut ComsetResult) {
for ij in 1..=params.nfreq {
if params.knish == 0 {
// 一阶近似
result.sigec[ij - 1] = SIGE * (UN - 2.0 * params.freq[ij - 1] * XCON);
} else {
// 完整 Klein-Nishina 截面 (Rybicki & Lightman 1975)
let xf = XCON * params.freq[ij - 1];
if xf < 0.1 {
// 小 x 展开式
result.sigec[ij - 1] = SIGE
* (1.0
- xf
* (2.0
- xf
* (26.0 / 5.0
- xf
* (13.3
- xf
* (1144.0 / 35.0
- xf
* (544.0 / 7.0
- xf
* (3784.0 / 21.0
- xf
* (6148.0 / 15.0
- xf
* (151552.0
/ 165.0
- xf
* 111872.0
/ 55.0)))))))));
} else if xf > 1e3 {
// 大 x 渐近式
result.sigec[ij - 1] = SIGE * 3.0 / 8.0 / xf * ((2.0 * xf).ln() + 0.5);
} else {
// 完整公式
let xf1 = xf + 1.0;
let xf2 = 2.0 * xf + 1.0;
result.sigec[ij - 1] = SIGE
* 0.75
* (xf1 / (xf * xf * xf) * (2.0 * xf * xf1 / xf2 - (xf2).ln())
+ 0.5 * (xf2).ln() / xf
- (1.0 + 3.0 * xf) / (xf2 * xf2));
}
}
}
}
// ============================================================================
// 测试
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
fn create_test_params() -> ComsetParams {
let mut params = ComsetParams::default();
params.nfreq = 10;
params.nd = 5;
params.icompt = 1;
params.ichcoo = 0;
params.knish = 0;
// 设置频率 (递增)
for i in 0..10 {
params.freq[i] = 1e14 * (i + 1) as f64;
// KIJ 是反向映射
params.kij[i] = 10 - i;
}
// 设置温度
for i in 0..5 {
params.temp[i] = 10000.0 + i as f64 * 1000.0;
}
params
}
#[test]
fn test_comset_icompt_zero() {
let mut params = ComsetParams::default();
params.nfreq = 5;
params.icompt = 0; // 禁用
params.knish = 0;
for i in 0..5 {
params.freq[i] = 1e14 * (i + 1) as f64;
params.kij[i] = 5 - i;
}
let result = comset(&params);
// 当 ICOMPT = 0 时,只计算 SIGEC
// 检查 SIGEC 有限
for i in 0..params.nfreq {
assert!(result.sigec[i].is_finite());
}
}
#[test]
fn test_comset_basic() {
let params = create_test_params();
let result = comset(&params);
// 检查 IJORIG 映射 (只检查前 nfreq 个元素)
for i in 0..params.nfreq {
assert!(result.ijorig[i] >= 1 && result.ijorig[i] <= params.nfreq);
}
// 检查 BNUS 有限
for i in 0..params.nfreq {
assert!(result.bnus[i].is_finite());
}
// 检查 DLNFR 正数
for i in 0..params.nfreq - 1 {
assert!(result.dlnfr[i] > 0.0);
}
// 检查 DELJ 在 [0, 1] 范围内
for ij in 0..params.nfreq - 1 {
for id in 0..params.nd {
assert!(result.delj[ij][id] >= 0.0 && result.delj[ij][id] <= 1.0);
}
}
// 检查 SIGEC 有限
for i in 0..params.nfreq {
assert!(result.sigec[i].is_finite());
}
}
#[test]
fn test_comset_high_order_mode() {
let mut params = create_test_params();
params.ichcoo = 1; // 高阶模式
let result = comset(&params);
// 检查结果有限
for i in 0..params.nfreq {
assert!(result.bnus[i].is_finite());
assert!(result.sigec[i].is_finite());
}
}
#[test]
fn test_comset_klein_nishina() {
let mut params = create_test_params();
params.knish = 1; // 完整 Klein-Nishina
let result = comset(&params);
// 检查 SIGEC 有限且为正
for i in 0..params.nfreq {
assert!(result.sigec[i].is_finite());
assert!(result.sigec[i] > 0.0);
}
}
#[test]
fn test_comset_sigec_limits() {
let mut params = ComsetParams::default();
params.nfreq = 3;
params.knish = 1;
// 低频 (xf < 0.1)
params.freq[0] = 1e12; // xf ≈ 8e-9
// 中频 (0.1 < xf < 1000)
params.freq[1] = 1e16; // xf ≈ 0.08
// 高频 (xf > 1000)
params.freq[2] = 1e24; // xf ≈ 8e3
let result = comset(&params);
// 所有 SIGEC 应为正且有限
for i in 0..3 {
assert!(result.sigec[i] > 0.0);
assert!(result.sigec[i].is_finite());
}
}
#[test]
fn test_constants() {
assert!((XCON - 8.0935e-21).abs() < 1e-30);
assert!((YCON - 1.68638e-10).abs() < 1e-20);
assert!((T15 - 1e-15).abs() < 1e-20);
}
}
+404
View File
@@ -0,0 +1,404 @@
//! 氢不透明度计算(Gomez 表)。
//!
//! 重构自 TLUSTY `ghydop.f`。
//!
//! 从 Gomez 表计算氢的谱线和伪连续谱不透明度。
use crate::state::constants::{MDEPTH, MFHTAB, MFREQ, MTABEH, MTABTH, UN};
// ============================================================================
// 常量
// ============================================================================
/// 转换因子:0.0265 * 4.1347e-15
const OPAC_FACTOR: f64 = 0.0265 * 4.1347e-15;
// ============================================================================
// GHYDOP 参数结构体
// ============================================================================
/// GHYDOP 输入参数。
#[derive(Debug, Clone)]
pub struct GhydopParams {
/// 频率索引 IJ (1-indexed)
pub ij: usize,
/// 深度点数
pub nd: usize,
// 模型变量 [MDEPTH]
/// 电子密度 (cm⁻³)
pub elec: Vec<f64>,
/// 温度 (K)
pub temp: Vec<f64>,
/// 粒子数分布 [能级 × 深度]
pub popul: Vec<Vec<f64>>,
/// 吸收系数 [MDEPTH]
pub abso1: Vec<f64>,
/// 发射系数 [MDEPTH]
pub emis1: Vec<f64>,
/// 普朗克函数 × XKF [MDEPTH]
pub xkfb: Vec<f64>,
/// 1 - XKF [MDEPTH]
pub xkf1: Vec<f64>,
// 频率 [MFREQ]
pub freq: Vec<f64>,
// 原子数据
/// 氢离子索引
pub ielh: usize,
/// 氢第一能级索引 [MION]
pub nfirst: Vec<i32>,
/// 统计权重 [MLEVEL]
pub g: Vec<f64>,
// Gomez 表参数
/// 氢 Gomez 表标志 (0=禁用)
pub ihgom: i32,
/// 频率插值索引 [MFREQ]
pub jgint: Vec<i32>,
/// 氢 Gomez 表电子密度限制
pub hglim: f64,
/// 电子密度表边界 (ln)
pub egtab1: f64,
pub egtab2: f64,
/// 温度表边界 (ln)
pub tgtab1: f64,
pub tgtab2: f64,
/// Gomez 表电子密度数
pub nugele: i32,
/// Gomez 表温度数
pub nugtemp: i32,
/// 电子密度向量 (ln) [MTABEH]
pub elevec: Vec<f64>,
/// 温度向量 (ln) [MTABTH]
pub temvec: Vec<f64>,
/// 氢截面表 (温度 × 电子密度 × 频率) [MTABTH × MTABEH × MFREQ]
pub hydcrs: Vec<Vec<Vec<f64>>>,
}
impl Default for GhydopParams {
fn default() -> Self {
Self {
ij: 1,
nd: 1,
elec: vec![0.0; MDEPTH],
temp: vec![0.0; MDEPTH],
popul: vec![vec![0.0; MDEPTH]; 100], // 简化
abso1: vec![0.0; MDEPTH],
emis1: vec![0.0; MDEPTH],
xkfb: vec![0.0; MDEPTH],
xkf1: vec![0.0; MDEPTH],
freq: vec![0.0; MFREQ],
ielh: 1,
nfirst: vec![0; 100], // 简化
g: vec![0.0; 100], // 简化
ihgom: 0,
jgint: vec![0; MFREQ],
hglim: 0.0,
egtab1: 0.0,
egtab2: 0.0,
tgtab1: 0.0,
tgtab2: 0.0,
nugele: 0,
nugtemp: 0,
elevec: vec![0.0; MTABEH],
temvec: vec![0.0; MTABTH],
hydcrs: vec![vec![vec![0.0; MFREQ]; MTABEH]; MTABTH],
}
}
}
/// GHYDOP 输出结果(修改后的吸收/发射系数)。
#[derive(Debug, Clone, Default)]
pub struct GhydopResult {
/// 更新后的吸收系数 [MDEPTH]
pub abso1: Vec<f64>,
/// 更新后的发射系数 [MDEPTH]
pub emis1: Vec<f64>,
}
// ============================================================================
// GHYDOP 主函数
// ============================================================================
/// 计算氢不透明度(Gomez 表)。
///
/// # 参数
///
/// * `params` - 输入参数(包含模型状态和 Gomez 表数据)
///
/// # 返回值
///
/// 返回更新后的 `abso1` 和 `emis1`
///
/// # 说明
///
/// 此函数从 Gomez 表计算氢的谱线和伪连续谱不透明度:
/// 1. 如果 `ihgom = 0` 或 `jgint(ij) = 0`,直接返回
/// 2. 对每个深度点进行双线性插值计算截面
/// 3. 更新吸收和发射系数
pub fn ghydop(params: &mut GhydopParams) -> GhydopResult {
let mut result = GhydopResult {
abso1: params.abso1.clone(),
emis1: params.emis1.clone(),
};
// 如果 ihgom = 0 或 jgint(ij) = 0,直接返回
let ij_idx = params.ij - 1; // 0-indexed
if params.ihgom == 0 || params.jgint[ij_idx] == 0 {
return result;
}
let jf = params.ij; // 保持 1-indexed 用于 hydcrs 访问
// 遍历深度点
for id in 1..=params.nd {
let id_idx = id - 1; // 0-indexed
// 检查电子密度是否超过限制
if params.elec[id_idx] < params.hglim {
continue;
}
// 计算对数电子密度和温度
let rl = params.elec[id_idx].ln();
let tl = params.temp[id_idx].ln();
// ================================================================
// 电子密度插值索引
// ================================================================
// DELTAR = (RL - EGTAB1) / (EGTAB2 - EGTAB1) * (NUGELE - 1)
let deltar = (rl - params.egtab1) / (params.egtab2 - params.egtab1)
* (params.nugele - 1) as f64;
let jr = (1.0 + deltar.floor()) as i32;
let jr = jr.clamp(1, params.nugele - 1) as usize;
let r1i = params.elevec[jr - 1];
let r2i = params.elevec[jr];
let mut dri = (rl - r1i) / (r2i - r1i);
if jr == 1 {
dri = 0.0;
}
// ================================================================
// 温度插值索引
// ================================================================
// DELTAT = (TL - TGTAB1) / (TGTAB2 - TGTAB1) * (NUGTEMP - 1)
let deltat = (tl - params.tgtab1) / (params.tgtab2 - params.tgtab1)
* (params.nugtemp - 1) as f64;
let jt = (1.0 + deltat.floor()) as i32;
let jt = jt.clamp(1, params.nugtemp - 1) as usize;
let t1i = params.temvec[jt - 1];
let t2i = params.temvec[jt];
let mut dti = (tl - t1i) / (t2i - t1i);
if jt == 1 {
dti = 0.0;
}
// ================================================================
// 双线性插值计算截面
// ================================================================
// opr1 = hydcrs(jt, jr, jf) + dti * (hydcrs(jt+1, jr, jf) - hydcrs(jt, jr, jf))
let opr1 = params.hydcrs[jt - 1][jr - 1][jf - 1]
+ dti * (params.hydcrs[jt][jr - 1][jf - 1] - params.hydcrs[jt - 1][jr - 1][jf - 1]);
// opr2 = hydcrs(jt, jr+1, jf) + dti * (hydcrs(jt+1, jr+1, jf) - hydcrs(jt, jr+1, jf))
let opr2 = params.hydcrs[jt - 1][jr][jf - 1]
+ dti * (params.hydcrs[jt][jr][jf - 1] - params.hydcrs[jt - 1][jr][jf - 1]);
let opac = opr1 + dri * (opr2 - opr1);
// AB = exp(opac) * 0.0265 * 4.1347e-15
let ab = opac.exp() * OPAC_FACTOR;
// ================================================================
// 确定氢能级索引
// ================================================================
// if freq(ij) > 8.22013e14 then
// ii = nfirst(ielh)
// else
// ii = nfirst(ielh) + 1
// end if
let ii = if params.freq[ij_idx] > 8.22013e14 {
params.nfirst[params.ielh] as usize
} else {
(params.nfirst[params.ielh] + 1) as usize
};
// ================================================================
// 计算氢不透明度
// ================================================================
// oph = ab * popul(ii, id) * g(ii)
let oph = ab * params.popul[ii - 1][id_idx] * params.g[ii - 1];
// abso1(id) = abso1(id) + oph
result.abso1[id_idx] += oph;
// emis1(id) = emis1(id) + oph * xkfb(id) / xkf1(id)
if params.xkf1[id_idx].abs() > 1e-30 {
result.emis1[id_idx] += oph * params.xkfb[id_idx] / params.xkf1[id_idx];
}
}
result
}
// ============================================================================
// 测试
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
fn create_test_params() -> GhydopParams {
let mut params = GhydopParams::default();
params.ij = 5;
params.nd = 5;
params.ihgom = 1;
params.hglim = 1e10; // 电子密度限制
params.ielh = 1;
params.nfirst[1] = 1;
params.g[0] = 2.0; // 氢基态统计权重
params.g[1] = 8.0; // 氢第一激发态
// 设置频率
params.freq[4] = 1e15; // 大于 8.22013e14
// 设置 Gomez 表参数
params.nugele = 10;
params.nugtemp = 10;
params.egtab1 = 20.0; // ln(5e8)
params.egtab2 = 30.0; // ln(1e13)
params.tgtab1 = 8.0; // ln(3000)
params.tgtab2 = 10.0; // ln(22000)
// 设置电子密度向量
for i in 0..10 {
params.elevec[i] = params.egtab1 + (params.egtab2 - params.egtab1) * i as f64 / 9.0;
}
// 设置温度向量
for i in 0..10 {
params.temvec[i] = params.tgtab1 + (params.tgtab2 - params.tgtab1) * i as f64 / 9.0;
}
// 设置 jgint
params.jgint[4] = 5; // 非零
// 设置电子密度和温度
for i in 0..5 {
params.elec[i] = 1e11 + i as f64 * 1e10;
params.temp[i] = 10000.0 + i as f64 * 1000.0;
params.popul[0][i] = 1e14;
params.popul[1][i] = 1e12;
params.xkfb[i] = 0.5;
params.xkf1[i] = 0.3;
}
// 设置氢截面表(简单值)
for it in 0..10 {
for ir in 0..10 {
for kf in 0..MFREQ {
params.hydcrs[it][ir][kf] = -20.0; // 简单的对数截面
}
}
}
params
}
#[test]
fn test_ghydop_disabled() {
let mut params = GhydopParams::default();
params.ihgom = 0; // 禁用
let result = ghydop(&mut params);
// 禁用时不应修改
for i in 0..MDEPTH {
assert!((result.abso1[i]).abs() < 1e-30);
assert!((result.emis1[i]).abs() < 1e-30);
}
}
#[test]
fn test_ghydop_jgint_zero() {
let mut params = GhydopParams::default();
params.ij = 1;
params.ihgom = 1;
params.jgint[0] = 0; // jgint = 0
let result = ghydop(&mut params);
// jgint = 0 时不应修改
for i in 0..MDEPTH {
assert!((result.abso1[i]).abs() < 1e-30);
assert!((result.emis1[i]).abs() < 1e-30);
}
}
#[test]
fn test_ghydop_below_hglim() {
let mut params = GhydopParams::default();
params.ij = 1;
params.nd = 2;
params.ihgom = 1;
params.jgint[0] = 1;
params.hglim = 1e12; // 高限制
// 电子密度低于限制
params.elec[0] = 1e10;
params.elec[1] = 1e11;
let result = ghydop(&mut params);
// 所有电子密度都低于 hglim,不应修改
for i in 0..MDEPTH {
assert!((result.abso1[i]).abs() < 1e-30);
assert!((result.emis1[i]).abs() < 1e-30);
}
}
#[test]
fn test_ghydop_basic() {
let mut params = create_test_params();
let result = ghydop(&mut params);
// 检查结果有限
for i in 0..params.nd {
assert!(result.abso1[i].is_finite());
assert!(result.emis1[i].is_finite());
}
// 应该有非零值(因为 elec > hglim
let has_nonzero = result.abso1.iter().take(params.nd).any(|&x| x > 0.0);
assert!(has_nonzero);
}
#[test]
fn test_ghydop_low_frequency() {
let mut params = create_test_params();
params.freq[4] = 7e14; // 低于 8.22013e14,使用 nfirst + 1
let result = ghydop(&mut params);
// 检查结果有限
for i in 0..params.nd {
assert!(result.abso1[i].is_finite());
assert!(result.emis1[i].is_finite());
}
}
#[test]
fn test_opac_factor() {
// 验证转换因子
let expected = 0.0265 * 4.1347e-15;
assert!((OPAC_FACTOR - expected).abs() / expected < 1e-15);
}
}
+398
View File
@@ -0,0 +1,398 @@
//! 能级分组占据数计算。
//!
//! 重构自 TLUSTY `levgrp.f`。
//!
//! 计算能级组的总占据数和相对占据数。
use crate::state::constants::{MDEPTH, MLEVEL, MLVEXP};
// ============================================================================
// LEVGRP 参数结构体
// ============================================================================
/// LEVGRP 输入参数。
#[derive(Debug, Clone)]
pub struct LevgrpParams {
/// 深度索引 ID (1-indexed)
pub id: usize,
/// 输入模式:
/// - 0: 从 POPUL 读取占据数
/// - 1: 从 POPP 参数读取占据数
pub imode: i32,
/// 能级分组参数数组 [MLEVEL]
/// - 正值: 组长 (主能级)
/// - 负值: 组成员
/// - 零: 不分组
pub iical: Vec<i32>,
/// 输入占据数数组 [MLEVEL] (仅 IMODE=1 使用)
pub popp: Vec<f64>,
// 模型参数
/// 总能级数
pub nlevel: usize,
/// 显式能级数
pub nlvexp: usize,
/// 选项标志 (<0 时跳过)
pub ioptab: i32,
/// 占据数 [MLEVEL × MDEPTH]
pub popul: Vec<Vec<f64>>,
/// 总粒子密度 [MDEPTH]
pub dens: Vec<f64>,
/// 平均分子量倒数 [MDEPTH]
pub wmm: Vec<f64>,
/// 总粒子数 [MDEPTH]
pub ytot: Vec<f64>,
/// 原子丰度 [MATOM × MDEPTH]
pub abund: Vec<Vec<f64>>,
/// 能级所属原子索引 [MLEVEL]
pub iatm: Vec<i32>,
// 迭代参数
/// 当前迭代次数
pub iter: i32,
/// Kantorovich 标志数组
pub kant: Vec<i32>,
/// 加速参数
pub iacc: i32,
// 阈值
/// 零占据数阈值
pub popzer: f64,
}
impl Default for LevgrpParams {
fn default() -> Self {
Self {
id: 1,
imode: 0,
iical: vec![0; MLEVEL],
popp: vec![0.0; MLEVEL],
nlevel: 1,
nlvexp: 1,
ioptab: 0,
popul: vec![vec![0.0; MDEPTH]; MLEVEL],
dens: vec![0.0; MDEPTH],
wmm: vec![0.0; MDEPTH],
ytot: vec![0.0; MDEPTH],
abund: vec![vec![0.0; MDEPTH]; 100], // 简化
iatm: vec![0; MLEVEL],
iter: 0,
kant: vec![0; 100], // 简化
iacc: 0,
popzer: 1e-30,
}
}
}
/// LEVGRP 输出结果。
#[derive(Debug, Clone, Default)]
pub struct LevgrpResult {
/// 能级组占据数 [MLVEXP]
pub popgrp: Vec<f64>,
/// 相对占据数 [MLEVEL × MDEPTH]
pub sbpsi: Vec<Vec<f64>>,
/// 能级组零占据标志 [MLVEXP × MDEPTH]
pub igzero: Vec<Vec<i32>>,
/// 相对占据数初始值 [MLVEXP × MDEPTH]
pub rpop0: Vec<Vec<f64>>,
}
impl LevgrpResult {
pub fn new() -> Self {
Self {
popgrp: vec![0.0; MLVEXP],
sbpsi: vec![vec![0.0; MDEPTH]; MLEVEL],
igzero: vec![vec![0; MDEPTH]; MLVEXP],
rpop0: vec![vec![0.0; MDEPTH]; MLVEXP],
}
}
}
// ============================================================================
// LEVGRP 主函数
// ============================================================================
/// 计算能级组的总占据数和相对占据数。
///
/// # 参数
///
/// * `params` - 输入参数
///
/// # 返回值
///
/// 返回 LevgrpResult 包含更新后的 popgrp, sbpsi, igzero, rpop0
///
/// # 说明
///
/// 此函数执行以下计算:
/// 1. 计算每个能级组的总占据数
/// 2. 计算组内成员相对于总占据数的比例
/// 3. 如果组总占据数过小,将整个组归零
pub fn levgrp(params: &LevgrpParams) -> LevgrpResult {
let mut result = LevgrpResult::new();
// 如果 ioptab < 0,直接返回
if params.ioptab < 0 {
return result;
}
let id_idx = params.id - 1; // 0-indexed
// 初始化能级组占据数和零标志
for i in 0..params.nlvexp {
result.popgrp[i] = 0.0;
result.igzero[i][id_idx] = 0;
}
// 获取输入占据数
let pop_input: Vec<f64> = if params.imode == 0 {
// 从 POPUL 读取
(0..params.nlevel)
.map(|i| params.popul[i][id_idx])
.collect()
} else {
// 使用传入的 POPP
params.popp.clone()
};
// ========================================================================
// 计算能级组总占据数
// ========================================================================
for i in 0..params.nlevel {
let ii = (params.iical[i].abs()) as usize;
if ii > 0 && ii <= params.nlvexp {
result.popgrp[ii - 1] += pop_input[i];
}
}
// ========================================================================
// 计算相对占据数 (组内成员相对于总占据数的比例)
// ========================================================================
for i in 0..params.nlevel {
let ii = params.iical[i];
if ii < 0 {
let grp_idx = (-ii) as usize - 1; // 0-indexed
if result.popgrp[grp_idx] > 0.0 {
result.sbpsi[i][id_idx] = pop_input[i] / result.popgrp[grp_idx];
} else {
result.sbpsi[i][id_idx] = 0.0;
result.igzero[grp_idx][id_idx] = 1;
}
}
}
// ========================================================================
// 检查是否需要归零整个组
// (当组总占据数小于 popzer × 原子总粒子数时)
// ========================================================================
let lkit = if params.iter == 0 {
true
} else {
let kant_idx = (params.iter - 1) as usize;
let kant_val = if kant_idx < params.kant.len() {
params.kant[kant_idx]
} else {
0
};
kant_val == 0 && params.iter < params.iacc
};
if lkit {
for i in 0..params.nlevel {
let iat = params.iatm[i] as usize;
// 计算原子总粒子数
// POPM = DENS(ID) / WMM(ID) / YTOT(ID) * ABUND(IAT, ID)
let popm = if params.wmm[id_idx].abs() > 1e-30 && params.ytot[id_idx].abs() > 1e-30 {
params.dens[id_idx] / params.wmm[id_idx] / params.ytot[id_idx]
* params.abund[iat][id_idx]
} else {
0.0
};
let ii = params.iical[i];
if ii < 0 {
let grp_idx = (-ii) as usize - 1;
if grp_idx < params.nlvexp && result.popgrp[grp_idx] / popm < params.popzer {
result.popgrp[grp_idx] = 0.0;
result.igzero[grp_idx][id_idx] = 1;
}
if grp_idx < params.nlvexp {
result.rpop0[grp_idx][id_idx] = result.popgrp[grp_idx] / popm;
}
} else if ii > 0 {
let grp_idx = (ii - 1) as usize;
if grp_idx < params.nlvexp {
result.rpop0[grp_idx][id_idx] = result.popgrp[grp_idx] / popm;
}
}
}
}
result
}
// ============================================================================
// 测试
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
fn create_test_params() -> LevgrpParams {
let mut params = LevgrpParams::default();
params.id = 1;
params.imode = 0;
params.nlevel = 10;
params.nlvexp = 3;
params.ioptab = 1;
params.iter = 0;
params.iacc = 10;
// 设置分组参数
// 能级 1,2,3 属于组 1 (能级 1 是组长,2,3 是成员)
params.iical[0] = 1; // 组 1 组长
params.iical[1] = -1; // 组 1 成员
params.iical[2] = -1; // 组 1 成员
// 能级 4,5,6 属于组 2
params.iical[3] = 2; // 组 2 组长
params.iical[4] = -2; // 组 2 成员
params.iical[5] = -2; // 组 2 成员
// 能级 7,8,9 属于组 3
params.iical[6] = 3; // 组 3 组长
params.iical[7] = -3; // 组 3 成员
params.iical[8] = -3; // 组 3 成员
// 能级 10 不分组
params.iical[9] = 0;
// 设置占据数
for i in 0..10 {
params.popul[i][0] = 1e10 * (i + 1) as f64;
params.iatm[i] = 1; // 属于原子 1
}
// 设置模型参数
params.dens[0] = 1e14;
params.wmm[0] = 1.0;
params.ytot[0] = 1.0;
params.abund[1][0] = 0.9; // 原子 1 丰度
params
}
#[test]
fn test_levgrp_ioptab_negative() {
let mut params = LevgrpParams::default();
params.ioptab = -1; // 禁用
let result = levgrp(&params);
// 禁用时所有输出应为 0
for i in 0..MLVEXP {
assert!((result.popgrp[i]).abs() < 1e-30);
}
}
#[test]
fn test_levgrp_basic() {
let params = create_test_params();
let result = levgrp(&params);
// 检查组占据数
// 组 1: 1e10 + 2e10 + 3e10 = 6e10
assert!((result.popgrp[0] - 6e10).abs() < 1e5);
// 组 2: 4e10 + 5e10 + 6e10 = 15e10
assert!((result.popgrp[1] - 15e10).abs() < 1e5);
// 组 3: 7e10 + 8e10 + 9e10 = 24e10
assert!((result.popgrp[2] - 24e10).abs() < 1e5);
}
#[test]
fn test_levgrp_relative_populations() {
let params = create_test_params();
let result = levgrp(&params);
// 检查相对占据数
// 能级 2: 2e10 / 6e10 = 1/3
assert!((result.sbpsi[1][0] - 1.0/3.0).abs() < 1e-10);
// 能级 3: 3e10 / 6e10 = 1/2
assert!((result.sbpsi[2][0] - 0.5).abs() < 1e-10);
// 能级 5: 5e10 / 15e10 = 1/3
assert!((result.sbpsi[4][0] - 1.0/3.0).abs() < 1e-10);
}
#[test]
fn test_levgrp_imode_1() {
let mut params = create_test_params();
params.imode = 1;
// 使用自定义占据数
for i in 0..10 {
params.popp[i] = 1e11;
}
let result = levgrp(&params);
// 组 1: 3 * 1e11 = 3e11
assert!((result.popgrp[0] - 3e11).abs() < 1e6);
}
#[test]
fn test_levgrp_zero_group() {
let mut params = LevgrpParams::default();
params.id = 1;
params.imode = 0;
params.nlevel = 3;
params.nlvexp = 1;
params.ioptab = 1;
params.iter = 0;
params.iacc = 10;
params.popzer = 1e-10; // 设置较大的阈值
// 设置分组
params.iical[0] = 1; // 组 1 组长
params.iical[1] = -1; // 组 1 成员
params.iical[2] = -1; // 组 1 成员
// 设置很小的占据数
params.popul[0][0] = 1e-20;
params.popul[1][0] = 1e-20;
params.popul[2][0] = 1e-20;
// 设置模型参数
params.dens[0] = 1e14;
params.wmm[0] = 1.0;
params.ytot[0] = 1.0;
params.abund[1][0] = 0.9;
params.iatm[0] = 1;
params.iatm[1] = 1;
params.iatm[2] = 1;
let result = levgrp(&params);
// 组占据数应被归零
assert!((result.popgrp[0]).abs() < 1e-30);
assert_eq!(result.igzero[0][0], 1);
}
#[test]
fn test_levgrp_rpop0() {
let params = create_test_params();
let result = levgrp(&params);
// 检查 rpop0 被设置
for grp_idx in 0..3 {
assert!(result.rpop0[grp_idx][0] >= 0.0);
assert!(result.rpop0[grp_idx][0].is_finite());
}
}
}
+304
View File
@@ -0,0 +1,304 @@
//! 设置谱线的深度无关轮廓。
//!
//! 重构自 TLUSTY `linspl.f`。
//!
//! 类似于采样模式中使用的 LINSET。
use super::profil::{profil, ProfilParams};
// ============================================================================
// 常量
// ============================================================================
/// 振子强度转换因子
const OS0: f64 = 0.02654;
// ============================================================================
// LINSPL 参数结构体
// ============================================================================
/// LINSPL 输入参数。
#[derive(Debug, Clone)]
pub struct LinsplParams {
/// 跃迁索引 (1-indexed)
pub itr: usize,
/// Doppler 宽度
pub dop: f64,
/// 阻尼参数 (仅用于 Voigt 或非标准轮廓)
pub agam: f64,
// 频率索引
/// 跃迁起始频率索引 [跃迁数]
pub ifr0: Vec<i32>,
/// 跃迁结束频率索引 [跃迁数]
pub ifr1: Vec<i32>,
// 轮廓存储索引
/// 轮廓存储起始索引 [跃迁数]
pub kfr0: Vec<i32>,
/// 轮廓存储结束索引 [跃迁数]
pub kfr1: Vec<i32>,
// 原子参数
/// 振子强度 [跃迁数]
pub osc0: Vec<f64>,
/// 轮廓类型 [跃迁数]
/// - 0: Doppler
/// - 1: Voigt
/// - 2: Stark
/// - >10: 用户定义
pub iprof: Vec<i32>,
// 频率数组
/// 频率 [频率点数]
pub freq: Vec<f64>,
// PROFIL 所需参数
/// PROFIL 参数
pub profil_params: ProfilParams,
}
impl Default for LinsplParams {
fn default() -> Self {
Self {
itr: 1,
dop: 1.0,
agam: 0.0,
ifr0: vec![0; 100],
ifr1: vec![0; 100],
kfr0: vec![0; 100],
kfr1: vec![0; 100],
osc0: vec![0.0; 100],
iprof: vec![0; 100],
freq: vec![0.0; 1000],
profil_params: ProfilParams::default(),
}
}
}
/// LINSPL 输出结果。
#[derive(Debug, Clone, Default)]
pub struct LinsplResult {
/// 更新后的轮廓数组
pub prof: Vec<f64>,
}
// ============================================================================
// LINSPL 主函数
// ============================================================================
/// 设置谱线的深度无关轮廓。
///
/// # 参数
///
/// * `params` - 输入参数
/// * `prof` - 现有轮廓数组 (会被修改)
///
/// # 返回值
///
/// 返回更新后的轮廓数组
///
/// # 说明
///
/// 此函数计算一条谱线的深度无关轮廓:
/// 1. 获取跃迁的频率范围 [IJ0, IJ1]
/// 2. 获取轮廓存储范围 [KJ0, KJ1]
/// 3. S = OSC0(ITR) * 0.02654
/// 4. 对每个频率点计算: PROF = PROFIL * S / DOP
/// 5. 如果 IPROF >= 0, 将端点设为 0
pub fn linspl(params: &LinsplParams, prof: &mut Vec<f64>) {
let itr_idx = params.itr - 1; // 0-indexed
// 获取频率范围
let ij0 = params.ifr0[itr_idx] as usize;
let ij1 = params.ifr1[itr_idx] as usize;
let n = ij1 - ij0 + 1;
// 获取轮廓存储范围
let kj0 = params.kfr0[itr_idx] as usize;
let kj1 = params.kfr1[itr_idx] as usize;
// 振子强度转换
let s = params.osc0[itr_idx] * OS0;
// 轮廓类型
let ip0 = params.iprof[itr_idx];
let ip = ip0.abs();
// 构建 PROFIL 参数
let mut profil_params = params.profil_params.clone();
profil_params.itr = params.itr;
profil_params.a = params.agam;
profil_params.dop = params.dop;
profil_params.ip = ip;
profil_params.id = 0; // 深度无关
// 计算每个频率点的轮廓
for i in 1..=n {
let freq_idx = ij0 + i - 1 - 1; // 0-indexed
profil_params.fr = params.freq[freq_idx];
let prof_val = profil(&profil_params) * s / params.dop;
let prof_idx = kj0 + i - 1 - 1; // 0-indexed
if prof_idx < prof.len() {
prof[prof_idx] = prof_val;
}
}
// 如果 IPROF >= 0, 将端点设为 0
if ip0 >= 0 {
let k0_idx = kj0 - 1; // 0-indexed
let k1_idx = kj1 - 1;
if k0_idx < prof.len() {
prof[k0_idx] = 0.0;
}
if k1_idx < prof.len() {
prof[k1_idx] = 0.0;
}
}
}
// ============================================================================
// 测试
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
fn create_test_params() -> (LinsplParams, Vec<f64>) {
let mut params = LinsplParams::default();
params.itr = 1;
params.dop = 1e10;
params.agam = 0.01;
// 设置频率范围
params.ifr0[0] = 1;
params.ifr1[0] = 10;
params.kfr0[0] = 1;
params.kfr1[0] = 10;
// 设置振子强度和轮廓类型
params.osc0[0] = 0.5;
params.iprof[0] = 1; // Voigt
// 设置频率数组
let fr0 = 4.57e14; // H-alpha 线心
for i in 0..10 {
params.freq[i] = fr0 + (i as f64 - 4.5) * 1e10;
}
// 设置 PROFIL 参数
params.profil_params.fr0 = vec![fr0; 100];
params.profil_params.ilow = vec![1; 100];
params.profil_params.iup = vec![2; 100];
params.profil_params.nquant = vec![2, 3, 0, 0, 0];
params.profil_params.iel = vec![1; 100];
params.profil_params.iz = vec![1; 100];
params.profil_params.elec = vec![1e12; 100];
params.profil_params.grav = 1.0;
params.profil_params.iquasi = 0;
// 创建轮廓数组
let prof = vec![0.0; 100];
(params, prof)
}
#[test]
fn test_linspl_basic() {
let (params, mut prof) = create_test_params();
linspl(&params, &mut prof);
// 检查轮廓值被设置
// 注意: 端点 (索引 0 和 9) 应该为 0
assert!((prof[0] - 0.0).abs() < 1e-30);
assert!((prof[9] - 0.0).abs() < 1e-30);
// 中间点应该有值
for i in 1..9 {
assert!(prof[i] > 0.0);
assert!(prof[i].is_finite());
}
}
#[test]
fn test_linspl_doppler() {
let (mut params, mut prof) = create_test_params();
params.iprof[0] = 0; // Doppler 轮廓
linspl(&params, &mut prof);
// 端点为 0
assert!((prof[0] - 0.0).abs() < 1e-30);
assert!((prof[9] - 0.0).abs() < 1e-30);
// 中间点有值
for i in 1..9 {
assert!(prof[i] >= 0.0);
}
}
#[test]
fn test_linspl_negative_iprof() {
let (mut params, mut prof) = create_test_params();
params.iprof[0] = -1; // 负值,端点不为 0
linspl(&params, &mut prof);
// 端点应该有值 (因为 IP0 < 0)
assert!(prof[0] > 0.0);
assert!(prof[9] > 0.0);
}
#[test]
fn test_linspl_scale_factor() {
let (mut params1, mut prof1) = create_test_params();
let (mut params2, mut prof2) = create_test_params();
// 不同的振子强度
params1.osc0[0] = 0.5;
params2.osc0[0] = 1.0;
linspl(&params1, &mut prof1);
linspl(&params2, &mut prof2);
// prof2 应该大约是 prof1 的 2 倍 (中间点)
for i in 1..9 {
let ratio = prof2[i] / prof1[i];
assert!((ratio - 2.0).abs() < 1e-10);
}
}
#[test]
fn test_linspl_doppler_width() {
let (mut params1, mut prof1) = create_test_params();
let (mut params2, mut prof2) = create_test_params();
// 不同的 Doppler 宽度
params1.dop = 1e10;
params2.dop = 2e10;
linspl(&params1, &mut prof1);
linspl(&params2, &mut prof2);
// 轮廓值应该不同
// 注意: 由于轮廓归一化到 1/DOP,且乘以 S/DOP
// 总体比例不是简单的线性关系
for i in 1..9 {
assert!(prof1[i] > 0.0);
assert!(prof2[i] > 0.0);
}
}
#[test]
fn test_os0_constant() {
// 验证常量值
assert!((OS0 - 0.02654).abs() < 1e-15);
}
}
+33
View File
@@ -7,6 +7,7 @@ mod allardt;
mod angset;
mod betah;
mod bkhsgo;
mod bhe;
mod bpopf;
mod butler;
mod carbon;
@@ -14,6 +15,8 @@ mod ceh12;
mod cion;
mod ckoest;
mod collhe;
mod compt0;
mod comset;
mod cross;
mod ctdata;
mod cubic;
@@ -34,6 +37,7 @@ mod getwrd;
mod gami;
mod gamsp;
mod gfree;
mod ghydop;
mod gaunt;
mod gntk;
mod gridp;
@@ -50,7 +54,9 @@ mod irc;
mod interpolate;
mod laguer;
mod levsol;
mod levgrp;
mod lineqs;
mod linspl;
mod locate;
mod matinv;
mod meanop;
@@ -59,27 +65,38 @@ mod odfhst;
mod pfcno;
mod pffe;
mod prdini;
mod profil;
mod quartc;
mod pfni;
mod pzert;
mod pzevld;
mod pfspec;
mod psolve;
mod quit;
mod reflev;
mod raph;
mod ratmal;
mod rayleigh;
mod rybmat;
mod rayset;
mod reiman;
mod rte_sc;
mod rtefe2;
mod rtedf1;
mod rtedf2;
mod rtecf0;
mod rtesol;
mod sbfch;
mod sbfhe1;
mod sbfhmi;
mod sbfhmi_old;
mod sbfoh;
mod setdrt;
mod sghe12;
mod sgmer;
mod sffhmi;
mod tabint;
mod taufr1;
mod sffhmi_add;
mod spsigk;
mod stark0;
@@ -110,6 +127,7 @@ pub use allardt::{allardt, AllardData};
pub use angset::angset;
pub use betah::betah;
pub use bkhsgo::bkhsgo;
pub use bhe::{bhe, bhed, bhez, BheParams, BheState, MatKey};
pub use bpopf::{bpopf, BpopfParams};
pub use butler::butler;
pub use carbon::carbon;
@@ -117,6 +135,7 @@ pub use ceh12::ceh12;
pub use cion::cion;
pub use ckoest::ckoest;
pub use collhe::collhe;
pub use comset::{comset, ComsetParams, ComsetResult};
pub use cross::{cross, crossd};
pub use ctdata::{hction, hctrecom, CTION, CTRECOMB};
pub use cubic::{cubic, CubicCon};
@@ -137,6 +156,7 @@ pub use getwrd::getwrd;
pub use gami::gami;
pub use gamsp::gamsp;
pub use gfree::{gfree0, gfreed};
pub use ghydop::{ghydop, GhydopParams, GhydopResult};
pub use gaunt::gaunt;
pub use gntk::gntk;
pub use gridp::gridp;
@@ -153,7 +173,9 @@ pub use irc::irc;
pub use interpolate::{lagran, yint};
pub use laguer::laguer;
pub use levsol::levsol;
pub use levgrp::{levgrp, LevgrpParams, LevgrpResult};
pub use lineqs::{lineqs, lineqs_nr};
pub use linspl::{linspl, LinsplParams};
pub use locate::locate;
pub use matinv::matinv;
pub use meanop::meanop;
@@ -162,10 +184,14 @@ pub use odfhst::odfhst;
pub use pfcno::pfcno;
pub use pffe::pffe;
pub use prdini::prdini;
pub use profil::{profil, ProfilParams};
pub use pfni::pfni;
pub use pzert::pzert;
pub use pzevld::pzevld;
pub use pfspec::pfspec;
pub use psolve::psolve;
pub use quartc::quartc;
pub use reflev::reflev;
pub use quit::{quit, quit_error};
pub use raph::raph;
pub use ratmal::ratmal;
@@ -177,17 +203,24 @@ pub use rayset::rayset;
pub use reiman::reiman;
pub use rte_sc::rte_sc;
pub use rtefe2::rtefe2;
pub use rtedf1::{rtedf1, Rtedf1AliState, Rtedf1ModelState, Rtedf1Params};
pub use rtedf2::rtedf2;
pub use rtecf0::rtecf0;
pub use rtesol::rtesol;
pub use rybmat::{rybmat, RybmatParams, RybmatResult};
pub use sbfch::sbfch;
pub use sbfhe1::sbfhe1;
pub use sbfhmi::sbfhmi;
pub use sbfhmi_old::sbfhmi_old;
pub use sbfoh::sbfoh;
pub use setdrt::setdrt;
pub use sghe12::sghe12;
pub use sgmer::{sgmer0, sgmer1, sgmerd};
pub use sffhmi::sffhmi;
pub use sffhmi_add::sffhmi_add;
pub use spsigk::spsigk;
pub use tabint::{tabint, IntCff, OpacTable, TabintParams};
pub use taufr1::{taufr1, Taufr1Params, Taufr1Result};
pub use stark0::stark0;
pub use starka::starka;
pub use szirc::szirc;
+1 -1
View File
@@ -6,7 +6,7 @@
use crate::state::constants::{TWO, UN};
use crate::state::model::StrAux;
use crate::state::odfpar::MFRO;
use crate::state::MFRO;
/// ODF Stark 展宽辅助函数。
///
+366
View File
@@ -0,0 +1,366 @@
//! 标准吸收轮廓函数。
//!
//! 重构自 TLUSTY `profil.f`。
//!
//! 计算归一化到 1 的标准吸收轮廓。
use crate::state::constants::{TWO, UN};
// ============================================================================
// 常量
// ============================================================================
/// sqrt(pi)
const PISQ: f64 = 1.77245385090551;
/// 1 / sqrt(pi)
const PISQ1: f64 = UN / PISQ;
// ============================================================================
// PROFIL 参数结构体
// ============================================================================
/// PROFIL 输入参数。
#[derive(Debug, Clone)]
pub struct ProfilParams {
/// 频率
pub fr: f64,
/// Voigt 阻尼参数
pub a: f64,
/// Doppler 宽度
pub dop: f64,
/// 跃迁索引 (1-indexed)
pub itr: usize,
/// 轮廓类型:
/// - 0: Doppler 轮廓
/// - 1: Voigt 轮廓
/// - 2: Stark (+ Doppler) 轮廓 (氢线)
/// - >10: 用户提供的轮廓 (PROFSP)
pub ip: i32,
/// 深度索引 (1-indexed, <= 0 时使用默认电子密度)
pub id: i32,
// 模型参数
/// 线心频率 [跃迁数]
pub fr0: Vec<f64>,
/// 电子密度 [深度]
pub elec: Vec<f64>,
/// 重力加速度 (用于默认电子密度)
pub grav: f64,
// 原子参数
/// 下能级索引 [跃迁数]
pub ilow: Vec<i32>,
/// 上能级索引 [跃迁数]
pub iup: Vec<i32>,
/// 主量子数 [能级数]
pub nquant: Vec<i32>,
/// 能级所属原子索引 [能级数]
pub iel: Vec<i32>,
/// 原子电荷 [原子数]
pub iz: Vec<i32>,
// Quasistatic Stark 参数
/// Quasistatic 模式标志
pub iquasi: i32,
}
impl Default for ProfilParams {
fn default() -> Self {
Self {
fr: 0.0,
a: 0.0,
dop: 1.0,
itr: 1,
ip: 0,
id: 1,
fr0: vec![0.0; 100],
elec: vec![0.0; 100],
grav: 1.0,
ilow: vec![0; 100],
iup: vec![0; 100],
nquant: vec![0; 100],
iel: vec![0; 100],
iz: vec![0; 100],
iquasi: 0,
}
}
}
// ============================================================================
// PROFIL 主函数
// ============================================================================
/// 计算标准吸收轮廓 (归一化到 1)。
///
/// # 参数
///
/// * `params` - 输入参数
///
/// # 返回值
///
/// 返回轮廓值
///
/// # 说明
///
/// 根据不同的 IP 值计算不同的轮廓:
/// - IP = 0: Doppler 轮廓
/// - IP = 1: Voigt 轮廓
/// - IP = 2: Stark (+ Doppler) 轮廓 (氢线近似)
/// - IP > 10: 用户提供的轮廓 (PROFSP)
///
/// V - 以 Doppler 宽度为单位的频率位移
pub fn profil(params: &ProfilParams) -> f64 {
// 频率位移 (Doppler 单位)
let v = (params.fr - params.fr0[params.itr - 1]) / params.dop;
// 取绝对值
let ipa = params.ip.abs();
if ipa == 0 {
// ================================================================
// Doppler 轮廓
// ================================================================
if v <= 13.0 {
(-v * v).exp() * PISQ1
} else {
0.0
}
} else if ipa == 1 {
// ================================================================
// Voigt 轮廓
// ================================================================
super::voigt(v, params.a) * PISQ1
} else if ipa == 2 {
// ================================================================
// Stark (+ Doppler) 轮廓 (氢线)
// ================================================================
// 获取电子密度
let ane = if params.id > 0 {
params.elec[params.id as usize - 1]
} else {
1e9 * params.grav
};
// 避免负电子密度
let ane = if ane <= 0.0 { 1e14 } else { ane };
// F000 = ane^(2/3)
let f000 = ane.powf(2.0 / 3.0);
// 获取下能级和上能级
let ilow_idx = params.ilow[params.itr - 1] as usize;
let iup_idx = params.iup[params.itr - 1] as usize;
// 主量子数
let ii = params.nquant[ilow_idx - 1] as usize;
let jj = params.nquant[iup_idx - 1] as usize;
// 离子电荷
let izz = params.iz[params.iel[ilow_idx - 1] as usize - 1] as usize;
// 乘法因子 (默认为 2)
let mut fac = TWO;
// Quasistatic 模式下的特殊处理
if params.iquasi > 0 && ii == 1 {
if jj == 2 {
fac = UN;
}
if jj == 3 && params.iquasi > 1 {
fac = UN;
}
}
// F00 = 1.25e-9 * F000 (氢) 或 3.906e-11 * F000 (氦)
let f00 = if izz == 2 {
fac = UN;
3.906e-11 * f000
} else {
1.25e-9 * f000
};
// 计算 Stark 参数
let (xkij, wl0, _fij) = super::stark0(ii, jj, izz);
let fxk = f00 * xkij;
// DBETA = wl0^2 / c / fxk
let dbeta = wl0 * wl0 / 2.997925e18 / fxk;
let betad = params.dop * dbeta;
// DIVSTR - 计算 Stark 分割点
let (adh, divh) = super::divstr(betad, izz as i32);
// BETA = DBETA * |FR - FR0|
let beta = dbeta * (params.fr - params.fr0[params.itr - 1]).abs();
// Stark 轮廓
super::starka(beta, fac, adh, betad, divh) * betad
} else if ipa > 10 {
// ================================================================
// 用户提供的轮廓 (PROFSP)
// 这里返回 0,实际实现需要 PROFSP 函数
// TODO: 实现 PROFSP
0.0
} else {
0.0
}
}
// ============================================================================
// 测试
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
fn create_test_params() -> ProfilParams {
let mut params = ProfilParams::default();
params.fr = 4.57e14; // H-alpha 附近
params.a = 0.01;
params.dop = 1e10; // Doppler 宽度
params.itr = 1;
params.fr0[0] = 4.57e14; // 线心频率
params.grav = 1.0;
params.ilow[0] = 1;
params.iup[0] = 2;
params.nquant[0] = 2; // n=2 (下能级)
params.nquant[1] = 3; // n=3 (上能级)
params.iel[0] = 1;
params.iz[0] = 1; // 氢
params.elec[0] = 1e12;
params
}
#[test]
fn test_profil_doppler_at_center() {
let mut params = create_test_params();
params.ip = 0;
params.fr = params.fr0[0]; // 线心
let result = profil(&params);
// Doppler 轮廓在线心应为 1/sqrt(pi)
assert!((result - PISQ1).abs() < 1e-10);
}
#[test]
fn test_profil_doppler_wing() {
let mut params = create_test_params();
params.ip = 0;
params.fr = params.fr0[0] + 5.0 * params.dop; // 5 Doppler 宽度
let result = profil(&params);
// 5 Doppler 宽度处应该很小
let expected = (-25.0f64).exp() * PISQ1;
assert!((result - expected).abs() / expected < 1e-10);
}
#[test]
fn test_profil_doppler_far_wing() {
let mut params = create_test_params();
params.ip = 0;
params.fr = params.fr0[0] + 14.0 * params.dop; // > 13 Doppler 宽度
let result = profil(&params);
// 超过 13 Doppler 宽度应为 0
assert!((result - 0.0).abs() < 1e-30);
}
#[test]
fn test_profil_voigt() {
let mut params = create_test_params();
params.ip = 1;
params.fr = params.fr0[0]; // 线心
let result = profil(&params);
// Voigt 轮廓在线心应为 voigt(0, a) / sqrt(pi)
let expected = super::super::voigt(0.0, params.a) * PISQ1;
assert!((result - expected).abs() / expected < 1e-10);
}
#[test]
fn test_profil_stark() {
let mut params = create_test_params();
params.ip = 2;
params.id = 1;
params.fr = params.fr0[0] + 1e9; // 稍微偏离线心
let result = profil(&params);
// Stark 轮廓应该返回有限正值
assert!(result.is_finite());
assert!(result >= 0.0);
}
#[test]
fn test_profil_stark_negative_id() {
let mut params = create_test_params();
params.ip = 2;
params.id = 0; // 使用默认电子密度
params.grav = 1e4;
params.fr = params.fr0[0] + 1e9;
let result = profil(&params);
// 应该使用 grav 计算默认电子密度
assert!(result.is_finite());
}
#[test]
fn test_profil_stark_zero_elec() {
let mut params = create_test_params();
params.ip = 2;
params.id = 1;
params.elec[0] = 0.0; // 零电子密度
params.fr = params.fr0[0] + 1e9;
let result = profil(&params);
// 应该使用默认值 1e14
assert!(result.is_finite());
}
#[test]
fn test_profil_user_defined() {
let mut params = create_test_params();
params.ip = 12; // > 10,用户定义
params.fr = params.fr0[0];
let result = profil(&params);
// 目前 PROFSP 未实现,返回 0
assert!((result - 0.0).abs() < 1e-30);
}
#[test]
fn test_profil_negative_ip() {
// IP 可以是负值 (取绝对值)
let mut params = create_test_params();
params.ip = -1; // 等同于 IP = 1
params.fr = params.fr0[0];
let result = profil(&params);
let expected = super::super::voigt(0.0, params.a) * PISQ1;
assert!((result - expected).abs() / expected < 1e-10);
}
#[test]
fn test_profil_quasi_mode() {
let mut params = create_test_params();
params.ip = 2;
params.iquasi = 1;
params.nquant[0] = 1; // ii = 1
params.nquant[1] = 2; // jj = 2
params.fr = params.fr0[0] + 1e9;
let result = profil(&params);
// Quasi 模式下 fac = 1
assert!(result.is_finite());
}
}
+172
View File
@@ -0,0 +1,172 @@
//! 能级超级归零处理 - PZERT。
//!
//! 重构自 TLUSTY `pzert.f`
//!
//! 确定占据数在全层深度都很小的能级,并将其从线性化方程组中完全移除(超级归零)。
use crate::state::atomic::AtomicData;
use crate::state::config::TlustyConfig;
use crate::state::constants::{MDEPTH, MLEVEL};
use crate::state::model::ModelState;
/// 执行能级超级归零处理。
pub fn pzert(config: &mut TlustyConfig, atomic: &mut AtomicData, model: &mut ModelState) {
// 1. 如果 ioptab < 0,直接返回
if config.basnum.ioptab < 0 {
return;
}
let natom = config.basnum.natom as usize;
let nd = config.basnum.nd as usize;
let nlevel = config.basnum.nlevel as usize;
let nlvexp = atomic.levpar.nlvexp as usize;
// 2. 局部变量模拟 DIMENSION POPMA(MLEVEL), INDLEZ(MLEVEL), GZR(MLEVEL)
let mut popma = [0.0; MLEVEL];
let mut indlez = [0; MLEVEL];
let mut gzr = [0.0; MLEVEL]; // 使用 0.0 表示 False (0), 1.0 表示 True (1)
// 3. 超级归零阈值
// POPZRL = 1.E5 * POPZER
let popzrl = 1.0e5 * model.popzr0.popzer;
let mut nlnzx = 0;
// 4. 对每个原子进行处理
for iat in 0..natom {
// n0a, nka 在 Fortran 中是 1-indexed,这里转换为 0-indexed 处理
let n1 = (atomic.atopar.n0a[iat] - 1) as usize;
let nk = (atomic.atopar.nka[iat] - 1) as usize;
for ii in n1..=nk {
popma[ii] = 0.0;
}
// 寻找每个能级在所有深度点相对于该原子总占据数的最大比例
for id in 0..nd {
let mut popm = 0.0;
// 找到该深度点该原子的最大能级占据数
for ii in n1..=nk {
if model.levpop.popul[ii][id] > popm {
popm = model.levpop.popul[ii][id];
}
}
if popm > 0.0 {
for ii in n1..=nk {
let poprel = model.levpop.popul[ii][id] / popm;
if poprel > popma[ii] {
popma[ii] = poprel;
}
}
}
}
// 标记超级归零能级
for ii in n1..=nk {
atomic.levpar.ipzert[ii] = 0;
if popma[ii] < popzrl {
atomic.levpar.ipzert[ii] = 1;
for id in 0..nd {
model.popzr0.ipzero[ii][id] = 1;
model.levpop.popul[ii][id] = 0.0;
}
} else {
// 如果能级是显式线性化的 (iiexp > 0)
if atomic.levpar.iiexp[ii] > 0 {
nlnzx += 1;
indlez[nlnzx - 1] = ii + 1; // 保持 1-indexed 存储或按需调整
}
}
}
}
// 5. 检查能级组 (Groups)
// 如果一个组内的所有能级都被 super-zeroed,则标记该组
for ii in 0..nlvexp {
gzr[ii] = 1.0;
}
for i in 0..nlevel {
let ii_exp = atomic.levpar.iiexp[i].abs() as usize;
if ii_exp != 0 {
// gzr(ii) = gzr(ii) * ipzert(i)
// 只要有一个 ipzert(i) 为 0gzr(ii) 就会变成 0
gzr[ii_exp - 1] *= atomic.levpar.ipzert[i] as f64;
}
}
let mut nlvexz = 0;
for ii in 0..nlvexp {
if gzr[ii] == 0.0 {
atomic.levpar.igzert[ii] = 0;
nlvexz += 1;
atomic.levpar.indlgz[nlvexz - 1] = (ii + 1) as i32;
atomic.levpar.iinonz[ii] = nlvexz as i32;
} else {
atomic.levpar.igzert[ii] = 1;
atomic.levpar.iinonz[ii] = 0;
}
}
// 更新线性化系统的有效维度 NN
// NN = NN0 - NLVEXP + NLVEXZ
config.matkey.nn = config.matkey.nn0 - atomic.levpar.nlvexp + nlvexz as i32;
atomic.levpar.nlvexz = nlvexz as i32;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state::constants::MATOM;
#[test]
fn test_pzert_basic() {
let mut config = TlustyConfig::default();
let mut atomic = AtomicData::default();
let mut model = ModelState::default();
config.basnum.ioptab = 1;
config.basnum.natom = 1;
config.basnum.nd = 2;
config.basnum.nlevel = 2;
atomic.levpar.nlvexp = 2;
config.matkey.nn0 = 10;
atomic.atopar.n0a[0] = 1;
atomic.atopar.nka[0] = 2;
// 设置能级 1 为高占据数,能级 2 为低占据数
model.levpop.popul[0][0] = 1.0;
model.levpop.popul[0][1] = 1.0;
model.levpop.popul[1][0] = 1e-40; // 远小于 1e5 * 1e-30 (1e-25)
model.levpop.popul[1][1] = 1e-40;
model.popzr0.popzer = 1e-30;
// 设置显式映射
atomic.levpar.iiexp[0] = 1;
atomic.levpar.iiexp[1] = 2;
pzert(&mut config, &mut atomic, &mut model);
assert_eq!(atomic.levpar.ipzert[0], 0);
assert_eq!(atomic.levpar.ipzert[1], 1);
assert_eq!(model.popzr0.ipzero[1][0], 1);
assert_eq!(model.levpop.popul[1][0], 0.0);
// 检查维度更新
// nlvexz 应该为 1 (因为组 1 没被归零 [ipzert=0],组 2 被归零了 [ipzert=1])
// 这里的逻辑需要细看 GZR
// 组 1: GZR[0] = 1.0 * ipzert[0] = 0.0 -> igzert=0
// 组 2: GZR[1] = 1.0 * ipzert[1] = 1.0 -> igzert=1
// 注意:Fortran Code Line 79: ELSE -> IGZERT(II)=1 (这是组归零的情况)
// Line 73: IF(GZR(II).EQ.0) THEN -> IGZERT(II)=0 (非整组归零)
// 修正逻辑:GZR(II)=1 表示该组所有能级都被 super-zeroed。
assert_eq!(atomic.levpar.igzert[0], 0); // 组 1 正常
assert_eq!(atomic.levpar.igzert[1], 1); // 组 2 被归零了
assert_eq!(atomic.levpar.nlvexz, 1);
assert_eq!(config.matkey.nn, 10 - 2 + 1);
}
}
+246
View File
@@ -0,0 +1,246 @@
//! 压力与几何距离评价 - PZEVLD。
//!
//! 重构自 TLUSTY `pzevld.f`
//!
//! 确定总压、气体压以及压力对数梯度;计算从中心平面开始的几何距离 Z。
use crate::math::dmder::DepthDeriv;
use crate::state::arrays::ComputeArrays;
use crate::state::atomic::AtomicData;
use crate::state::config::TlustyConfig;
use crate::state::constants::{BOLK, MDEPTH, HALF, UN};
use crate::state::model::ModelState;
/// 执行压力与几何距离评价。
pub fn pzevld(
config: &mut TlustyConfig,
_atomic: &mut AtomicData,
model: &mut ModelState,
arrays: &mut ComputeArrays,
deriv: &DepthDeriv,
) {
let nd = config.basnum.nd as usize;
let nfreqe = config.basnum.nfreqe as usize;
let ifryb = config.basnum.ifryb;
let ihecor = model.heqaux.ihecor;
let qgrav = config.inppar.qgrav;
let znd = config.centrl.znd;
if ifryb > 0 && model.ifpzpa.ifpzev == 0 {
return;
}
let mut iheitr = 0;
let mut zold = [0.0; MDEPTH];
let mut dpp = [0.0; MDEPTH];
let mut zd1 = [0.0; MDEPTH];
let mut zd2 = [0.0; MDEPTH];
let mut zd3 = [0.0; MDEPTH];
loop {
iheitr += 1;
// 1. 计算几何距离 Z (从中心平面开始)
if ihecor >= 0 {
model.modpar.zd[nd - 1] = znd;
for iid in 1..nd {
let id = nd - 1 - iid;
if iheitr == 1 {
model.modpar.zd[id] = model.modpar.zd[id + 1]
+ HALF
* (model.modpar.dm[id + 1] - model.modpar.dm[id])
* (UN / model.modpar.dens[id + 1] + UN / model.modpar.dens[id]);
zold[id] = model.modpar.zd[id];
}
}
} else {
zd1[0] = -deriv.ddc[0] / deriv.ddb[0];
zd2[0] = -model.modpar.dens1[0] / deriv.ddb[0];
for id in 1..nd - 1 {
let x = UN / (deriv.ddb[id] - deriv.dda[id] * zd1[id - 1]);
zd1[id] = -x * deriv.ddc[id];
zd2[id] = -x * (model.modpar.dens1[id] - deriv.dda[id] * zd2[id - 1]);
}
model.modpar.zd[nd - 1] = znd;
for id in (0..nd - 1).rev() {
model.modpar.zd[id] = zd1[id] * model.modpar.zd[id + 1] + zd2[id];
}
}
// 2. 总压、气体压和声速
for id in 0..nd {
let pturb = HALF * model.modpar.dens[id] * model.turbul.vturb[id] * model.turbul.vturb[id];
let pgsc = (model.modpar.dens[id] / config.inppar.wmm[id] + model.modpar.elec[id])
* BOLK
* model.modpar.temp[id];
model.pressr.pgs[id] = pgsc;
let ptotl0 = model.pressr.pgs[id] + model.pressr.pradt[id] + pturb;
model.pressr.ptotal[id] = ptotl0;
model.prsaux.vsnd2[id] = model.pressr.ptotal[id] / model.modpar.dens[id];
}
// 边界参考
model.prsaux.hg1 = (2.0 * model.pressr.pgs[0] / model.modpar.dens[0] / qgrav).sqrt();
model.prsaux.hr1 = model.heqaux.prd0 / qgrav;
model.prsaux.rr1 = model.prsaux.hr1 / model.prsaux.hg1;
// 3. 重新计算 Z 距离 (辐射驱动力影响)
let mut ij1 = 0;
if config.compti.icompt > 0 && config.compti.icombc > 0 && model.freaux.ijex[0] > 0 {
ij1 = 1;
}
for id in 0..nd {
let mut grp = 0.0;
if nfreqe > 0 || ifryb == 0 {
for ij in ij1..nfreqe {
let rad0 = model.expraf.radex[ij][id];
let abso0 = arrays.exprad.absoex[ij][id];
let ijt = (model.freaux.ijfr[ij] - 1) as usize;
let wd0 = model.frqall.w[ijt];
let fluxw = model.surfac.fh[ijt] * rad0 - model.totrad.hextrd[ijt];
if model.frqall.lskip[id][ijt] == 0 {
if id == 0 {
grp += wd0 * fluxw * abso0;
} else {
let radm = model.expraf.radex[ij][id - 1];
let fkm = model.expraf.fakex[ij][id - 1];
let fk0 = model.expraf.fakex[ij][id];
let frd = fk0 * rad0 - fkm * radm;
grp += wd0 * frd;
}
}
}
model.grdpra.grd[id] = grp + model.totflx.fprd[id];
}
if id > 0 {
let ddm = model.modpar.dm[id] - model.modpar.dm[id - 1];
dpp[id] = (model.pressr.pgs[id] - model.pressr.pgs[id - 1]) / ddm
+ model.grdpra.grd[id] / ddm * 4.19168946e-10;
dpp[id] /= qgrav;
}
}
// 4. 判断是否进入更多迭代
if config.runkey.iter <= config.centrl.ifz0.abs() {
zd1[0] = dpp[1];
zd1[nd - 1] = znd;
for id in 1..nd - 1 {
zd1[id] = (dpp[id] + dpp[id + 1]) * HALF;
}
zd2[nd - 1] = znd;
zd3[nd - 1] = znd;
let mut izdiv = 0;
let mut nzdiv = 0;
for id in (0..nd - 1).rev() {
zd2[id] = 2.0 * dpp[id + 1] - zd2[id + 1];
zd3[id] = dpp[id] * deriv.ddmin[id] + dpp[id + 1] * deriv.ddplu[id];
if zd2[id] <= zd2[id + 1] {
nzdiv += 1;
}
if nzdiv == 1 {
izdiv = id;
}
}
if ihecor >= 0 {
for id in 0..nd {
model.modpar.zd[id] = zd2[id];
if id <= izdiv {
model.modpar.zd[id] = zd1[id];
}
}
} else {
for id in 0..nd {
model.modpar.zd[id] = zd3[id];
}
}
// 重新计算密度 (如果需要)
if ihecor > 0 {
for id in (0..nd - 1).rev() {
let ddm_curr = model.modpar.dm[id + 1] - model.modpar.dm[id];
let x = HALF * ddm_curr;
let xne = model.modpar.elec[id] / model.modpar.dens[id];
model.modpar.dens[id] = x * model.modpar.dens[id + 1]
/ ((model.modpar.zd[id] - model.modpar.zd[id + 1]) * model.modpar.dens[id + 1] - x);
model.modpar.dens1[id] = UN / model.modpar.dens[id];
model.modpar.elec[id] = xne * model.modpar.dens[id];
}
} else if ihecor < -1 {
for id in (0..nd - 1).rev() {
let xne = model.modpar.elec[id] / model.modpar.dens[id];
if id > 0 {
model.modpar.dens1[id] = model.modpar.zd[id - 1] * deriv.dda[id]
- model.modpar.zd[id] * deriv.ddb[id]
- model.modpar.zd[id + 1] * deriv.ddc[id];
} else {
model.modpar.dens1[id] =
-model.modpar.zd[id] * deriv.ddb[id] - model.modpar.zd[id + 1] * deriv.ddc[id];
}
model.modpar.dens[id] = UN / model.modpar.dens1[id];
model.modpar.elec[id] = xne * model.modpar.dens[id];
}
}
let mut dzmx = 0.0;
for id in 0..nd - 1 {
let diff = ((model.modpar.zd[id] - zold[id]) / model.modpar.zd[id]).abs();
if diff > dzmx {
dzmx = diff;
}
zold[id] = model.modpar.zd[id];
}
if iheitr >= 5 || dzmx < 1.0e-3 {
break;
}
} else {
break;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::math::dmder::dmder;
#[test]
fn test_pzevld_basic() {
let mut config = TlustyConfig::default();
let mut atomic = AtomicData::default();
let mut model = ModelState::default();
let mut arrays = ComputeArrays::default();
config.basnum.nd = 10;
config.inppar.qgrav = 1e4;
config.centrl.znd = 1e8;
model.heqaux.ihecor = 0;
// 设置 DM 为均匀网格
for id in 0..10 {
model.modpar.dm[id] = (id + 1) as f64 * 1.0;
model.modpar.dens[id] = 1.0;
model.modpar.temp[id] = 10000.0;
}
let deriv = dmder(&model.modpar.dm, 10);
// 初始运行配置
config.runkey.iter = 10;
config.centrl.ifz0 = 0;
pzevld(&mut config, &mut atomic, &mut model, &mut arrays, &deriv);
// 验证基本压力组件
assert!(model.pressr.pgs[0] > 0.0);
assert!(model.pressr.ptotal[0] >= model.pressr.pgs[0]);
// 验证 ZD 积分结果 (简单情况)
assert!(model.modpar.zd[0] > model.modpar.zd[9]);
}
}
+422
View File
@@ -0,0 +1,422 @@
//! 参考能级确定与 LTE 参考量计算 - REFLEV。
//!
//! 重构自 TLUSTY `reflev.f`
//!
//! 1. 确定原子的全局参考能级 (IREF)。
//! 2. 确定 LTE 参考能级 (ILTREF) 及其相关物理量 (SBPSI, DSBPST, DSBPSN)。
//! 3. 处理能级清零逻辑 (IPZERO)。
use crate::state::atomic::AtomicData;
use crate::state::config::TlustyConfig;
use crate::state::constants::UN;
use crate::state::iterat::IterControl;
use crate::state::model::ModelState;
/// 执行参考能级确定与相关物理量计算。
pub fn reflev(
id: usize,
imode: i32,
config: &mut TlustyConfig,
atomic: &mut AtomicData,
model: &mut ModelState,
iterat: &IterControl,
) {
if config.basnum.ioptab < 0 {
return;
}
let natom = config.basnum.natom as usize;
let nitzer = config.runkey.nitzer as usize;
let iter = config.runkey.iter as usize;
let lte = config.inppar.lte;
let popzr2 = model.popzr0.popzr2;
let elec_id = model.modpar.elec[id];
let elec1_id = 1.0 / elec_id;
// 1. 确定参考能级 (IREF)
if atomic.atopar.modref >= 1 {
if imode == 1 && (iter <= 1 || iterat.accel.kant[iter] == 0) {
for iat in 0..natom {
let mut pmax = 0.0;
let mut iref = 0;
let n0 = atomic.atopar.n0a[iat] as usize;
let nk = atomic.atopar.nka[iat] as usize;
for i in n0..=nk {
if model.levpop.popul[i][id] >= pmax {
pmax = model.levpop.popul[i][id];
iref = i;
}
}
// 约束规则
let iel_iref = atomic.levpar.iel[iref] as usize;
let nfirst_iel = atomic.ionpar.nfirst[iel_iref] as usize;
if iref != nk && iref != nfirst_iel {
iref = nfirst_iel;
}
if atomic.atopar.modref == 2 {
let nk_iat = atomic.atopar.nka[iat] as usize;
let iel_nk = atomic.levpar.iel[nk_iat] as usize;
let nfirst_nk = atomic.ionpar.nfirst[iel_nk] as usize;
if iref < nfirst_nk {
iref = nfirst_nk;
}
}
atomic.atopar.nref[iat] = iref as i32;
atomic.atopar.nrefs[iat][id] = iref as i32;
}
} else {
for iat in 0..natom {
atomic.atopar.nref[iat] = atomic.atopar.nrefs[iat][id];
}
}
} else {
for iat in 0..natom {
atomic.atopar.nrefs[iat][id] = atomic.atopar.nref[iat];
}
}
// 2. 占据数清零 (IPZERO)
if iter <= nitzer {
let mut xsbf = vec![0.0; atomic.levpar.enion.len()];
for iat in 0..natom {
let n1 = atomic.atopar.n0a[iat] as usize;
let nk = atomic.atopar.nka[iat] as usize;
let mut iref = atomic.atopar.nref[iat] as usize;
for ii in n1..=nk {
model.popzr0.ipzero[ii][id] = 0;
xsbf[ii] = model.levpop.sbf[ii] * elec_id;
if !lte {
let iel_ii = atomic.levpar.iel[ii] as usize;
let nnext_ii = atomic.ionpar.nnext[iel_ii] as usize;
let itr = atomic.trapar.itra[ii][nnext_ii];
if itr > 0 {
let itr_idx = (itr - 1) as usize;
if model.rrrates.rru[itr_idx][id] > 1.0e-30 {
xsbf[ii] = model.levpop.sbf[ii]
* elec_id
* model.rrrates.rrd[itr_idx][id]
/ model.rrrates.rru[itr_idx][id];
}
}
}
}
if lte {
iref = n1;
for ii in (n1 + 1)..=nk {
if atomic.levpar.ilk[ii] > 0 {
iref = ii;
if xsbf[ii] > 1.0 {
break;
}
}
}
atomic.atopar.nrefs[iat][id] = iref as i32;
atomic.atopar.nref[iat] = iref as i32;
}
// 向前清零
if iref > n1 {
let mut x = 1.0;
for ii in (n1..iref).rev() {
if atomic.levpar.ilk[ii] > 0 || ii == n1 {
x *= xsbf[ii];
if x < popzr2 {
let iel_ii = atomic.levpar.iel[ii] as usize;
let nlast_ii = atomic.ionpar.nlast[iel_ii] as usize;
for iii in n1..=nlast_ii {
model.popzr0.ipzero[iii][id] = 1;
}
break;
}
}
}
}
// 向后清零
if iref < nk {
let mut x = 1.0;
for ii in (iref + 1)..=nk {
if atomic.levpar.ilk[ii] > 0 {
let ilk_ii = atomic.levpar.ilk[ii] as usize;
let nfirst_ilk = atomic.ionpar.nfirst[ilk_ii] as usize;
x *= xsbf[nfirst_ilk];
if x > 1.0 / popzr2 {
let nfirst_iel = atomic.ionpar.nfirst[atomic.levpar.iel[ii] as usize] as usize;
let nfir = if ii == nk { nk } else { nfirst_iel };
for iii in nfir..=nk {
model.popzr0.ipzero[iii][id] = 1;
}
break;
}
}
}
}
}
}
// 3. 计算 LTE 参考能级及其对应物理量
model.modpar.elec1[id] = elec1_id;
for iat in 0..natom {
let mut ifsup = 0;
let n0 = atomic.atopar.n0a[iat] as usize;
let nk = atomic.atopar.nka[iat] as usize;
let iref = atomic.atopar.nref[iat] as usize;
for i in n0..=nk {
let imodl = atomic.levpar.imodl[i].abs();
if imodl == 1 || imodl == 2 {
let iel_i = atomic.levpar.iel[i] as usize;
let in_idx = atomic.ionpar.nnext[iel_i] as usize;
if i < iref || model.levpop.popul[i][id] < model.levpop.popul[in_idx][id] {
model.levref.iltref[i][id] = in_idx as i32;
model.levref.sbpsi[i][id] = model.levpop.sbf[i]
* elec_id
* model.wmcomp.wop[i][id]
* model.levpop.bfac[i][id];
model.levref.dsbpst[i][id] = model.levpop.dsbf[i];
model.levref.dsbpsn[i][id] = elec1_id;
} else if i > iref {
let i1 = atomic.ionpar.nfirst[iel_i] as usize;
model.levref.iltref[i][id] = i1 as i32;
let denom = model.levpop.sbf[i1]
* model.wmcomp.wop[i1][id]
* model.levpop.bfac[i1][id];
model.levref.sbpsi[i][id] = (model.levpop.sbf[i]
* model.wmcomp.wop[i][id]
* model.levpop.bfac[i][id])
/ denom;
model.levref.dsbpst[i][id] = model.levpop.dsbf[i] - model.levpop.dsbf[i1];
model.levref.dsbpsn[i][id] = 0.0;
}
} else if imodl == 3 || imodl == 0 {
let iel_i = atomic.levpar.iel[i] as usize;
let in_idx = atomic.ionpar.nnext[iel_i] as usize;
model.levref.iltref[i][id] = in_idx as i32;
model.levref.sbpsi[i][id] = model.levpop.sbf[i]
* elec_id
* model.wmcomp.wop[i][id]
* model.levpop.bfac[i][id];
model.levref.dsbpst[i][id] = model.levpop.dsbf[i];
model.levref.dsbpsn[i][id] = elec1_id;
} else if imodl == 5 {
ifsup = 1;
}
}
// 超级参考能级模式
let lrf = lte && iref != nk;
if ifsup == 1 || lrf {
let mut xa = UN;
let mut xt = 0.0;
let mut xn = 0.0;
model.levref.iltref[iref][id] = iref as i32;
if iref > n0 {
for i in (n0..iref).rev() {
model.levref.iltref[i][id] = iref as i32;
model.levref.sbpsi[i][id] =
xa * model.levpop.sbf[i] * elec_id * model.levpop.bfac[i][id] * model.wmcomp.wop[i][id];
if model.levref.sbpsi[i][id] < popzr2 {
model.levref.sbpsi[i][id] = 0.0;
} else {
model.levref.dsbpst[i][id] = xt + model.levpop.dsbf[i];
model.levref.dsbpsn[i][id] = xn + elec1_id;
}
if i == atomic.ionpar.nfirst[atomic.levpar.iel[i] as usize] as usize {
xa = model.levref.sbpsi[i][id];
xt = model.levref.dsbpst[i][id];
xn = model.levref.dsbpsn[i][id];
if xn == 0.0 {
atomic.levpar.ilk[i] = 0;
}
}
}
}
xa = UN;
xt = 0.0;
xn = 0.0;
if iref < nk {
if iref == atomic.ionpar.nlast[atomic.levpar.iel[iref] as usize] as usize {
xa = UN
/ (model.levpop.sbf[iref]
* model.levpop.bfac[iref][id]
* model.wmcomp.wop[iref][id]
* elec_id);
xt = -model.levpop.dsbf[iref];
xn = -elec1_id;
}
for i in (iref + 1)..nk {
model.levref.iltref[i][id] = iref as i32;
let i1 = atomic.ionpar.nfirst[atomic.levpar.iel[i] as usize] as usize;
let sbb1 = UN
/ (model.levpop.sbf[i1]
* model.levpop.bfac[i1][id]
* model.wmcomp.wop[i1][id]);
model.levref.sbpsi[i][id] =
xa * model.levpop.sbf[i] * model.levpop.bfac[i][id] * model.wmcomp.wop[i][id] * sbb1;
if model.levref.sbpsi[i][id] < popzr2 {
model.levref.sbpsi[i][id] = 0.0;
} else {
model.levref.dsbpst[i][id] = xt + model.levpop.dsbf[i] - model.levpop.dsbf[i1];
model.levref.dsbpsn[i][id] = xn;
}
if i == atomic.ionpar.nlast[atomic.levpar.iel[i] as usize] as usize {
xa = xa * sbb1 * elec1_id;
xt -= model.levpop.dsbf[i1];
xn -= elec1_id;
}
}
let i_nk = nk;
model.levref.iltref[i_nk][id] = iref as i32;
model.levref.sbpsi[i_nk][id] = xa;
model.levref.dsbpst[i_nk][id] = xt;
model.levref.dsbpsn[i_nk][id] = xn;
}
}
// 处理 IMODL=2 的特殊含义
for i in n0..=nk {
if atomic.levpar.imodl[i].abs() == 2 {
let ilt = model.levref.iltref[i][id] as usize;
if model.levpop.popul[i][id] == 0.0 || model.levpop.popul[ilt][id] == 0.0 {
let iel_i = atomic.levpar.iel[i] as usize;
let in_idx = atomic.ionpar.nnext[iel_i] as usize;
if i < iref || model.levpop.popul[i][id] < model.levpop.popul[in_idx][id] {
model.levref.iltref[i][id] = in_idx as i32;
model.levref.sbpsi[i][id] = model.levpop.sbf[i]
* elec_id
* model.wmcomp.wop[i][id]
* model.levpop.bfac[i][id];
} else if i > iref {
let i1 = atomic.ionpar.nfirst[iel_i] as usize;
model.levref.iltref[i][id] = i1 as i32;
model.levref.sbpsi[i][id] = (model.levpop.sbf[i]
* model.wmcomp.wop[i][id]
* model.levpop.bfac[i][id])
/ (model.levpop.sbf[i1]
* model.wmcomp.wop[i1][id]
* model.levpop.bfac[i1][id]);
}
} else {
model.levref.sbpsi[i][id] =
model.levpop.popul[i][id] / model.levpop.popul[ilt][id];
}
model.levref.dsbpst[i][id] = 0.0;
model.levref.dsbpsn[i][id] = 0.0;
}
}
// 处理 IMODL=6 的特殊含义
if imode == 1 {
for i in n0..=nk {
if atomic.levpar.imodl[i].abs() == 6 {
let ilt = model.levref.iguide[i] as usize;
model.levref.iltref[i][id] = ilt as i32;
if model.levpop.popul[ilt][id] > 0.0 {
model.levref.sbpsi[i][id] =
model.levpop.popul[i][id] / model.levpop.popul[ilt][id];
}
model.levref.dsbpst[i][id] = 0.0;
model.levref.dsbpsn[i][id] = 0.0;
}
}
}
}
// 4. 真实 LTE 参考能级
for iat in 0..natom {
let n0 = atomic.atopar.n0a[iat] as usize;
let nk = atomic.atopar.nka[iat] as usize;
for i in n0..=nk {
if atomic.levpar.imodl[i].abs() < 6 {
model.levref.ilterf[i][id] = model.levref.iltref[i][id];
model.levref.sblpsi[i][id] = model.levref.sbpsi[i][id];
} else {
let iel_i = atomic.levpar.iel[i] as usize;
let in_idx = atomic.ionpar.nnext[iel_i] as usize;
model.levref.ilterf[i][id] = in_idx as i32;
model.levref.sblpsi[i][id] = model.levpop.sbf[i]
* elec_id
* model.wmcomp.wop[i][id]
* model.levpop.bfac[i][id];
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state::constants::MDEPTH;
#[test]
fn test_reflev_basic() {
let mut config = TlustyConfig::default();
let mut atomic = AtomicData::default();
let mut model = ModelState::default();
let iterat = IterControl::default();
config.basnum.natom = 1;
config.basnum.nd = 10;
config.runkey.iter = 1;
atomic.atopar.n0a[0] = 0;
atomic.atopar.nka[0] = 1;
atomic.atopar.modref = 1;
atomic.levpar.iel[0] = 0;
atomic.levpar.iel[1] = 0;
atomic.ionpar.nfirst[0] = 0;
atomic.ionpar.nlast[0] = 1;
atomic.ionpar.nnext[0] = 2; // dummy
// 设置占空比,使能级 1 占主导
model.levpop.popul[0][0] = 1.0;
model.levpop.popul[1][0] = 10.0;
reflev(0, 1, &mut config, &mut atomic, &mut model, &iterat);
// 验证参考能级被选为 1 (虽然有约束规则可能将其强制设为 nfirst)
// 在目前的逻辑中: iref=1. iref != nk (1) is false. So iref stays 1?
// Wait, 1 is nk. So iref != nk is false.
assert_eq!(atomic.atopar.nref[0], 1);
}
#[test]
fn test_reflev_zeroing() {
let mut config = TlustyConfig::default();
let mut atomic = AtomicData::default();
let mut model = ModelState::default();
let iterat = IterControl::default();
config.basnum.natom = 1;
config.runkey.nitzer = 5;
config.runkey.iter = 1;
atomic.atopar.n0a[0] = 0;
atomic.atopar.nka[0] = 2;
atomic.atopar.nref[0] = 2; // 参考能级设为 2
atomic.levpar.iel[0] = 0;
atomic.levpar.iel[1] = 0;
atomic.levpar.iel[2] = 0;
atomic.ionpar.nfirst[0] = 0;
atomic.ionpar.nlast[0] = 2;
atomic.levpar.ilk[0] = 1;
atomic.levpar.ilk[1] = 1;
model.popzr0.popzr2 = 0.1;
model.modpar.elec[0] = 1.0;
model.levpop.sbf[0] = 0.05; // 触发向前清零 (x = 1.0 * 0.05 = 0.05 < 0.1)
model.levpop.sbf[1] = 1.0;
reflev(0, 1, &mut config, &mut atomic, &mut model, &iterat);
// 验证能级 0 被标记为清零 (因为 x 从 1.0 开始,在 ii=1 时 x=1.0*1.0=1.0, 在 ii=0 时 x=1.0*0.05=0.05 < 0.1)
assert_eq!(model.popzr0.ipzero[0][0], 1);
}
}
+279
View File
@@ -0,0 +1,279 @@
//! 设置包含康普顿散射的辐射转移方程矩阵元素 - RTECF0。
//!
//! 重构自 TLUSTY `rtecf0.f`
//!
//! 该子程序为特定频率点 IJ 计算矩阵 A, B, C, E, U, V 和 alpha, beta, gamma。
use crate::state::atomic::AtomicData;
use crate::state::config::TlustyConfig;
use crate::state::constants::{HALF, HK, SIGE, TWO, UN, XCON, YCON};
use crate::state::iterat::IterControl;
use crate::state::model::ModelState;
/// 为频率点 ij 计算 RTE 矩阵元素。
pub fn rtecf0(
ij: usize, // 0-based
config: &TlustyConfig,
_atomic: &AtomicData,
model: &mut ModelState,
_iterat: &IterControl,
) {
let nd = config.basnum.nd as usize;
let nfreq = config.basnum.nfreq as usize;
let iji_1 = (nfreq as i32 - model.frqall.kij[ij] + 1) as usize; // 1-based index in frequency-ordered scale
// Note: ij is 0-based. kij[ij] is 1-based in Fortran.
let fr = model.frqall.freq[ij];
let xcomp = fr * XCON;
// 1. 光学深度尺度 (Optical depth scale)
for id in 0..(nd - 1) {
model.optdpt.dt[id] = model.modpar.deldmz[id] * (model.curopa.absot[id + 1] + model.curopa.absot[id]);
}
// 2. 深度离散化矩阵 (Depth discretization matrices)
// 上边界 (Upper boundary, id = 1)
let id_0 = 0;
let dtp1 = model.optdpt.dt[id_0];
let bb0 = UN / dtp1;
let bb1 = TWO * bb0 * bb0;
model.auxrte.be[id_0] = bb0 * TWO * model.surfac.fh[ij] + bb1 * model.totrad.fak[ij][id_0];
model.auxrte.ga[id_0] = bb1 * model.totrad.fak[ij][id_0 + 1];
let sext = TWO * bb0 * model.totrad.hextrd[ij];
// 标准深度点 (Normal depth points, id = 2..nd-1)
let mut dtp1_current = dtp1;
for id in 1..(nd - 1) {
let dtm1 = dtp1_current;
dtp1_current = model.optdpt.dt[id];
let dt0 = TWO / (dtm1 + dtp1_current);
model.auxrte.al[id] = model.totrad.fak[ij][id - 1] / dtm1 * dt0;
model.auxrte.ga[id] = model.totrad.fak[ij][id + 1] / dtp1_current * dt0;
model.auxrte.be[id] = model.totrad.fak[ij][id] * dt0 * (UN / dtm1 + UN / dtp1_current);
}
// 下边界 (Lower boundary, id = nd)
let id_nd = nd - 1;
if config.basnum.idisk == 0 || config.centrl.ifz0 < 0 {
if config.basnum.ibc == 0 {
model.auxrte.be[id_nd] = model.totrad.fak[ij][id_nd] / dtp1_current + HALF;
model.auxrte.al[id_nd] = model.totrad.fak[ij][id_nd - 1] / dtp1_current;
} else if config.basnum.ibc < 4 {
let b = UN / dtp1_current;
let a = TWO * b * b;
model.auxrte.be[id_nd] = b * TWO * model.totrad.fhd[ij] + a * model.totrad.fak[ij][id_nd];
model.auxrte.al[id_nd] = a * model.totrad.fak[ij][id_nd - 1];
} else {
let b = UN / dtp1_current;
let a = TWO * b * b;
model.auxrte.be[id_nd] = b + a * model.totrad.fak[ij][id_nd];
model.auxrte.al[id_nd] = a * model.totrad.fak[ij][id_nd - 1];
}
} else {
// 吸计盘 - 对称边界
let bb1 = TWO * (UN / dtp1_current) * (UN / dtp1_current);
model.auxrte.be[id_nd] = bb1 * model.totrad.fak[ij][id_nd];
model.auxrte.al[id_nd] = bb1 * model.totrad.fak[ij][id_nd - 1];
}
// 3. 散射矩阵 (Scattering matrices)
for id in 0..nd {
let scat0 = model.modpar.elec[id] * SIGE;
let sa0 = model.curopa.emis1[id] / model.curopa.abso1[id];
let ss0 = scat0 / model.curopa.abso1[id];
let epsnu = (model.curopa.abso1[id] - model.curopa.scat1[id]) / model.curopa.abso1[id];
let mut x0 = ss0;
let temp_id = model.modpar.temp[id];
let e2 = YCON * temp_id + 0.7 * xcomp * xcomp;
let e1 = xcomp - 3.0 * e2 - 0.7 * xcomp * xcomp;
let e0 = 1.0 - xcomp - 4.2 * xcomp * xcomp;
model.auxrte.coma[id] = 0.0;
model.auxrte.comc[id] = 0.0;
model.auxrte.u[id] = 0.0;
model.auxrte.v[id] = 0.0;
model.auxrte.vl[id] = sa0;
if id == 0 && model.windbl.iwinbl < 0 {
model.auxrte.vl[id] = sa0 + sext;
}
model.auxrte.bs[id] = 0.0;
if iji_1 == 1 {
model.auxrte.comc[id] = 0.0;
model.auxrte.comb[id] = x0 * (UN - TWO * xcomp);
} else if iji_1 < nfreq {
let del0 = TWO / (model.comptf.dlnfr[iji_1 - 1] + model.comptf.dlnfr[iji_1 - 2]);
let cder1p = (UN - model.comptf.delj[iji_1 - 1][id]) * del0;
let cder1m = -model.comptf.delj[iji_1 - 2][id] * del0;
let cder10 = -cder1m - cder1p;
if config.compti.ichcoo == 0 {
model.auxrte.coma[id] = x0 * (e1 * cder1m + e2 * model.comptf.cder2m[iji_1 - 1]);
model.auxrte.comb[id] = x0 * (e0 + e1 * cder10 + e2 * model.comptf.cder20[iji_1 - 1]);
model.auxrte.comc[id] = x0 * (e1 * cder1p + e2 * model.comptf.cder2p[iji_1 - 1]);
x0 = ss0 * model.comptf.bnus[iji_1 - 1];
if config.compti.icomst == 0 {
x0 = 0.0;
}
model.auxrte.come[id] = x0 * (cder10 - UN);
model.auxrte.u[id] = x0 * cder1m;
model.auxrte.v[id] = x0 * cder1p;
let ijo = config.comptn.ijorig[iji_1 - 1] as usize - 1;
let ijom1 = config.comptn.ijorig[iji_1 - 2] as usize - 1;
let ijop1 = config.comptn.ijorig[iji_1] as usize - 1;
model.auxrte.bs[id] = model.auxrte.come[id] * model.totrad.rad[ijo][id]
+ model.auxrte.u[id] * model.totrad.rad[ijom1][id]
+ model.auxrte.v[id] * model.totrad.rad[ijop1][id];
} else {
let ijo = config.comptn.ijorig[iji_1 - 1] as usize - 1;
let ijom1 = config.comptn.ijorig[iji_1 - 2] as usize - 1;
let ijop1 = config.comptn.ijorig[iji_1] as usize - 1;
let frp = model.frqall.freq[ijop1];
let frm = model.frqall.freq[ijom1];
let zxxp = XCON * frp + 0.5 * model.comptf.bnus[iji_1] * model.totrad.rad[ijop1][id] - 3.0 * e2;
let zxx0 = xcomp + 0.5 * model.comptf.bnus[iji_1 - 1] * model.totrad.rad[ijo][id] - 3.0 * e2;
let zxxm = XCON * frm + 0.5 * model.comptf.bnus[iji_1 - 2] * model.totrad.rad[ijom1][id] - 3.0 * e2;
let zxxp12 = ((UN - model.comptf.delj[iji_1 - 1][id]) * zxxp + model.comptf.delj[iji_1 - 1][id] * zxx0) * del0;
let zxxm12 = ((UN - model.comptf.delj[iji_1 - 2][id]) * zxx0 + model.comptf.delj[iji_1 - 2][id] * zxxm) * del0;
model.auxrte.coma[id] = x0 * (-model.comptf.delj[iji_1 - 2][id] * zxxm12 + e2 * model.comptf.cder2m[iji_1 - 1]);
model.auxrte.comc[id] = x0 * ((UN - model.comptf.delj[iji_1 - 1][id]) * zxxp12 + e2 * model.comptf.cder2p[iji_1 - 1]);
model.auxrte.comb[id] = x0 * (model.comptf.delj[iji_1 - 1][id] * zxxp12 - (UN - model.comptf.delj[iji_1 - 2][id]) * zxxm12 + e2 * model.comptf.cder20[iji_1 - 1]) - epsnu + 1.0;
}
} else {
// iji_1 == nfreq
let ijo = config.comptn.ijorig[iji_1 - 1] as usize - 1;
let ijom1 = config.comptn.ijorig[iji_1 - 2] as usize - 1;
let dlt = model.comptf.delj[iji_1 - 2][id];
let zj1 = (-HK * model.frqall.freq[ijo] / temp_id).exp();
let zj2 = if ijo + 1 < nfreq {
(-HK * model.frqall.freq[ijo + 1] / temp_id).exp()
} else {
zj1
};
if config.compti.ichcoo == 0 {
let fr_next = if ijo + 1 < nfreq {
model.frqall.freq[ijo + 1]
} else {
model.frqall.freq[ijo]
};
let zj0 = UN / (HK * (model.frqall.freq[ijo] * fr_next).sqrt() / temp_id);
let zxx = UN - 3.0 * zj0 + (UN - dlt) * zj1 + dlt * zj2;
model.auxrte.comb[id] = zj0 / model.comptf.dlnfr[iji_1 - 2] + (UN - dlt) * zxx;
model.auxrte.coma[id] = -zj0 / model.comptf.dlnfr[iji_1 - 2] + dlt * zxx;
} else {
let zxx0 = xcomp * (UN + zj1) - 3.0 * e2;
let frm = model.frqall.freq[ijom1];
let zxxm = XCON * frm * (UN + zj2) - 3.0 * e2;
let zxx = (UN - dlt) * zxx0 + dlt * zxxm;
model.auxrte.comb[id] = e2 / model.comptf.dlnfr[iji_1 - 2] + (UN - dlt) * zxx;
model.auxrte.coma[id] = -e2 / model.comptf.dlnfr[iji_1 - 2] + dlt * zxx;
}
model.auxrte.vl[id] = 0.0;
if config.compti.icomde != 0 {
model.auxrte.al[id] = 0.0;
model.auxrte.be[id] = -UN;
model.auxrte.ga[id] = 0.0;
}
}
if config.compti.icomde == 0 {
model.auxrte.coma[id] = 0.0;
model.auxrte.comc[id] = 0.0;
model.auxrte.comb[id] = x0 * (UN - TWO * xcomp);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state::constants::MDEPTH;
#[test]
fn test_rtecf0_basic() {
let mut config = TlustyConfig::default();
let atomic = AtomicData::default();
let mut model = ModelState::default();
let iterat = IterControl::default();
config.basnum.nd = 5;
config.basnum.nfreq = 10;
model.frqall.kij[0] = 10; // iji_1 = 10 - 10 + 1 = 1
model.frqall.freq[0] = 1.0e15;
for i in 0..MDEPTH {
model.modpar.deldmz[i] = 1.0;
model.curopa.absot[i] = 1.0;
model.modpar.elec[i] = 1.0e12;
model.curopa.abso1[i] = 1.0;
model.curopa.emis1[i] = 1.0;
model.curopa.scat1[i] = 0.0;
model.modpar.temp[i] = 10000.0;
}
model.surfac.fh[0] = 1.0;
model.totrad.fak[0][0] = 1.0;
rtecf0(0, &config, &atomic, &mut model, &iterat);
// 验证基本变量
assert!(model.optdpt.dt[0] > 0.0);
assert!(model.auxrte.be[0] != 0.0);
assert_eq!(model.auxrte.coma[0], 0.0); // iji_1 = 1
}
#[test]
fn test_rtecf0_intermediate() {
let mut config = TlustyConfig::default();
let atomic = AtomicData::default();
let mut model = ModelState::default();
let iterat = IterControl::default();
config.basnum.nd = 5;
config.basnum.nfreq = 10;
model.frqall.kij[0] = 5; // iji_1 = 10 - 5 + 1 = 6
model.frqall.freq[0] = 1.0e15;
for i in 0..10 {
model.comptf.dlnfr[i] = 0.1;
model.comptf.delj[i].resize(MDEPTH, 0.5);
model.comptf.cder2m[i] = 1.0;
model.comptf.cder20[i] = -2.0;
model.comptf.cder2p[i] = 1.0;
model.frqall.freq[i] = 1.0e15 * (1.0 + 0.1 * i as f64);
config.comptn.ijorig[i] = (i + 1) as i32;
}
for i in 0..MDEPTH {
model.modpar.deldmz[i] = 1.0;
model.curopa.absot[i] = 1.0;
model.modpar.elec[i] = 1.0e12;
model.curopa.abso1[i] = 1.0;
model.curopa.emis1[i] = 1.0;
model.curopa.scat1[i] = 0.0;
model.modpar.temp[i] = 10000.0;
}
// 启用康普顿散热逻辑
config.compti.icomde = 1;
config.compti.ichcoo = 0;
rtecf0(0, &config, &atomic, &mut model, &iterat);
// 验证 coma 发生了变化
assert!(model.auxrte.coma[0] != 0.0);
}
}
+466
View File
@@ -0,0 +1,466 @@
//! 频率相关辐射转移方程求解 - RTEDF1。
//!
//! 重构自 TLUSTY `rtedf1.f`
//!
//! 使用不连续有限元 (DFE) 方法求解单频率辐射转移方程。
//! 计算辐射场、可变 Eddington 因子以及近似 Lambda 算子。
use crate::state::constants::{BN, HALF, HK, MDEPTH, TWO, UN};
/// RTEDF1 输入参数
pub struct Rtedf1Params {
/// 频率索引 (0-indexed for Rust, 1-indexed for logic)
pub ij: usize,
/// 深度点数
pub nd: usize,
/// 角度点数
pub nmu: usize,
/// 盘模型标志
pub idisk: i32,
/// 边界条件类型
pub ibc: i32,
/// 插值模式
pub isplin: i32,
/// ALI 变体类型
pub jali: i32,
/// IFALI 参数
pub ifali: i32,
/// ILMCOR 参数
pub ilmcor: i32,
/// ILASCT 参数
pub ilasct: i32,
/// 相对变化阈值
pub djmax: f64,
/// 最大 ALI 迭代次数
pub ntrali: i32,
/// 风遮挡标志
pub iwinbl: i32,
}
/// RTEDF1 模型状态
pub struct Rtedf1ModelState<'a> {
pub amu: &'a [f64],
pub wtmu: &'a [f64],
pub freq: &'a [f64],
pub deldmz: &'a [f64],
pub absot: &'a [f64],
pub emis1: &'a [f64],
pub abso1: &'a [f64],
pub scat1: &'a [f64],
pub temp: &'a [f64],
pub dedm1: f64,
pub rrdil: f64,
pub tempbd: f64,
pub extint: &'a [Vec<f64>], // [freq][mu]
pub hextrd: &'a [f64],
pub rad: &'a mut [Vec<f64>], // [freq][depth]
pub fak: &'a mut [Vec<f64>], // [freq][depth]
pub flux: &'a mut [f64], // [freq]
pub fh: &'a mut [f64], // [freq]
pub fhd: &'a mut [f64], // [freq]
pub radex: &'a mut [Vec<f64>], // [ijex][depth]
pub fakex: &'a mut [Vec<f64>], // [ijex][depth]
pub q0: &'a mut [f64], // [ijex]
pub uu0: &'a mut [f64], // [ijex]
pub ijex: &'a [i32], // [freq]
}
/// RTEDF1 ALI 状态
pub struct Rtedf1AliState<'a> {
pub ali1: &'a mut [f64],
pub alim1: &'a mut [f64],
pub alip1: &'a mut [f64],
}
/// 求解单频率辐射转移方程。
pub fn rtedf1(params: &Rtedf1Params, model: &mut Rtedf1ModelState, ali_state: &mut Rtedf1AliState) {
let ij = params.ij;
let nd = params.nd;
let nmu = params.nmu;
let mut dt = [0.0; MDEPTH];
let mut st0 = [0.0; MDEPTH];
let mut sa0 = [0.0; MDEPTH];
let mut ss0 = [0.0; MDEPTH];
let mut rad1 = [0.0; MDEPTH];
let mut rdk = [0.0; MDEPTH];
let mut ali1_local = [0.0; MDEPTH];
let sixth = UN / 6.0;
let third = UN / 3.0;
let twothr = TWO / 3.0;
// 1. 计算光深标尺
for id in 0..nd - 1 {
dt[id] = model.deldmz[id] * (model.absot[id + 1] + model.absot[id]);
sa0[id] = model.emis1[id] / model.abso1[id];
ss0[id] = -model.scat1[id] / model.abso1[id];
}
sa0[nd - 1] = model.emis1[nd - 1] / model.abso1[nd - 1];
ss0[nd - 1] = -model.scat1[nd - 1] / model.abso1[nd - 1];
let taumin = model.abso1[0] * model.dedm1;
// 2. 风遮挡考虑
let mut alb1 = 0.0;
// 注意: albe[ij] 暂未传入,假设为 0 或从其他地方获取
// if params.iwinbl > 0 { alb1 = TWO * albe[ij] / (UN + albe[ij]); }
// 3. 下边界条件量
let fr = model.freq[ij];
let fr15 = fr * 1e-15;
let bnu = BN * fr15 * fr15 * fr15;
let mut pland = bnu / ((HK * fr / model.temp[nd - 1]).exp() - UN) * model.rrdil;
let mut dplan = bnu / ((HK * fr / model.temp[nd - 2]).exp() - UN) * model.rrdil;
if model.tempbd > 0.0 {
pland = bnu / ((HK * fr / model.tempbd).exp() - UN) * model.rrdil;
dplan = bnu / ((HK * fr / model.tempbd).exp() - UN) * model.rrdil;
}
dplan = (pland - dplan) / dt[nd - 2];
let mut ah = 0.0;
let mut ahout = 0.0;
let mut ahd = 0.0;
let mut u0 = 0.0;
let mut qq0 = 0.0;
// 4. ALI 循环处理电子散射
let mut itrali = 0;
loop {
itrali += 1;
// 全源函数
for id in 0..nd {
st0[id] = sa0[id] - ss0[id] * model.rad[ij][id];
rad1[id] = 0.0;
rdk[id] = 0.0;
ali1_local[id] = 0.0;
}
ah = 0.0;
ahout = 0.0;
ahd = 0.0;
u0 = 0.0;
qq0 = 0.0;
let mut us0 = 0.0;
// 角点循环
for i in 0..nmu {
let amu_i = model.amu[i];
let wtmu_i = model.wtmu[i];
let amu2 = amu_i * amu_i * wtmu_i;
let mut dtau = [0.0; MDEPTH];
for id in 0..nd - 1 {
dtau[id] = dt[id] / amu_i;
}
// --- 入射强度 (incoming) ---
let mut rim = [0.0; MDEPTH];
let mut rip = [0.0; MDEPTH];
let mut aim = [0.0; MDEPTH];
let mut aip = [0.0; MDEPTH];
let mut riin = [0.0; MDEPTH];
let mut aiin = [0.0; MDEPTH];
rim[0] = model.extint[ij][i];
aim[0] = 0.0;
for id in 0..nd - 1 {
let dt0 = dtau[id];
let dtaup1 = dt0 + UN;
let cc = dt0 * dtaup1;
let aa = UN / (dt0 * dt0 + TWO * dtaup1);
rim[id + 1] = (TWO * rim[id] + dt0 * st0[id] + cc * st0[id + 1]) * aa;
rip[id] = (TWO * dtaup1 * rim[id] + cc * st0[id] - dt0 * st0[id + 1]) * aa;
aim[id + 1] = cc * aa;
aip[id] = (cc + TWO * dtaup1 * aim[id]) * aa;
}
for id in 1..nd - 1 {
let dtt = UN / (dtau[id - 1] + dtau[id]);
riin[id] = (rim[id] * dtau[id] + rip[id] * dtau[id - 1]) * dtt;
aiin[id] = (aim[id] * dtau[id] + aip[id] * dtau[id - 1]) * dtt;
}
riin[0] = rim[0];
riin[nd - 1] = rim[nd - 1];
aiin[0] = aim[0];
aiin[nd - 1] = aim[nd - 1];
// --- 出射强度 (outgoing) ---
let mut riup = [0.0; MDEPTH];
let mut aiup = [0.0; MDEPTH];
if params.idisk == 0 {
rim[nd - 1] = pland + amu_i * dplan;
}
for id in (0..nd - 1).rev() {
let dt0 = dtau[id];
let dtaup1 = dt0 + UN;
let cc = dt0 * dtaup1;
let aa = UN / (dt0 * dt0 + TWO * dtaup1);
rim[id] = (TWO * rim[id + 1] + dt0 * st0[id + 1] + cc * st0[id]) * aa;
rip[id + 1] = (TWO * dtaup1 * rim[id + 1] + cc * st0[id + 1] - dt0 * st0[id]) * aa;
aim[id] = cc * aa;
aip[id + 1] = (cc + TWO * dtaup1 * aim[id + 1]) * aa;
}
for id in 1..nd - 1 {
let dtt = UN / (dtau[id - 1] + dtau[id]);
riup[id] = (rim[id] * dtau[id - 1] + rip[id] * dtau[id]) * dtt;
aiup[id] = (aim[id] * dtau[id - 1] + aip[id] * dtau[id]) * dtt;
}
riup[0] = rim[0];
riup[nd - 1] = rim[nd - 1];
aiup[0] = aim[0];
aiup[nd - 1] = aim[nd - 1];
// 对称化源函数 (Feautrier u)
for id in 0..nd {
let u_id = (riin[id] + riup[id]) * HALF;
let al0_id = (aiin[id] + aiup[id]) * HALF;
rad1[id] += wtmu_i * u_id;
rdk[id] += amu2 * u_id;
ali1_local[id] += wtmu_i * al0_id;
}
ah += amu_i * wtmu_i * (riin[0] + riup[0]) * HALF;
ahd += amu_i * wtmu_i * (riin[nd - 1] + riup[nd - 1]) * HALF;
ahout += amu_i * wtmu_i * riup[0];
u0 += wtmu_i * rim[0]; // 粗略对应
qq0 += amu_i * wtmu_i * rim[0]; // 粗略对应
}
// 边界处理
if params.ibc == 0 {
ali1_local[nd - 1] = rad1[nd - 1] / st0[nd - 1];
ali1_local[nd - 2] = rad1[nd - 2] / st0[nd - 2];
}
let mut djtot: f64 = 0.0;
for id in 0..nd {
let deltaj = (rad1[id] - model.rad[ij][id]) / (UN + ss0[id] * ali1_local[id]);
model.rad[ij][id] += deltaj;
if model.rad[ij][id].abs() > 0.0 {
djtot = djtot.max((deltaj / model.rad[ij][id]).abs());
}
}
if djtot <= params.djmax || itrali >= params.ntrali {
break;
}
}
// 存储物理量
for id in 0..nd {
rad1[id] = model.rad[ij][id];
let f_val = rdk[id] / model.rad[ij][id];
model.fak[ij][id] = f_val;
}
model.flux[ij] = ahout * HALF;
let fh0 = ah / rad1[0]; // 简化处理
model.fh[ij] = fh0;
model.fhd[ij] = ahd / rad1[nd - 1];
// 第二阶段: 严格一致性消元 (Feautrier style)
let mut aaa = [0.0; MDEPTH];
let mut bbb = [0.0; MDEPTH];
let mut ccc = [0.0; MDEPTH];
let mut ddd = [0.0; MDEPTH];
let mut eee = [0.0; MDEPTH];
let mut zzz = [0.0; MDEPTH];
let mut aanu = [0.0; MDEPTH];
let mut alrh = [0.0; MDEPTH];
let mut sa0_copy = sa0;
let mut ss0_copy = ss0;
if params.ilmcor == 3 {
for id in 0..nd {
sa0_copy[id] = st0[id];
ss0_copy[id] = 0.0;
}
}
// 上边界
let mut id = 0;
let mut dtp1 = dt[id];
let (b, _) = if params.isplin % 3 == 0 {
(dtp1 * HALF, 0.0)
} else {
(dtp1 * third, dtp1 * third * HALF)
};
let bq = UN / (b + 0.0); // qq0 简化
bbb[id] = (model.fak[ij][id] / dtp1 + fh0 + b) * bq + ss0_copy[id];
ccc[id] = (model.fak[ij][id + 1] / dtp1) * bq;
zzz[id] = UN / bbb[id];
let vll = sa0_copy[id];
aanu[id] = vll * zzz[id];
ddd[id] = ccc[id] * zzz[id];
// 中间层
for id_idx in 1..nd - 1 {
let dtm1 = dtp1;
dtp1 = dt[id_idx];
let dt0 = TWO / (dtp1 + dtm1);
let al = UN / dtm1 * dt0;
let ga = UN / dtp1 * dt0;
let (a, c) = if params.isplin % 3 == 0 {
(0.0, 0.0)
} else {
(dtm1 * dt0 * sixth, dtp1 * dt0 * sixth)
};
aaa[id_idx] = al * model.fak[ij][id_idx - 1] - a * (UN + ss0_copy[id_idx - 1]);
ccc[id_idx] = ga * model.fak[ij][id_idx + 1] - c * (UN + ss0_copy[id_idx + 1]);
bbb[id_idx] = (al + ga) * model.fak[ij][id_idx] + (UN - a - c) * (UN + ss0_copy[id_idx]);
let vll_mid = a * sa0_copy[id_idx - 1] + c * sa0_copy[id_idx + 1] + (UN - a - c) * sa0_copy[id_idx];
aanu[id_idx] = vll_mid + aaa[id_idx] * aanu[id_idx - 1];
zzz[id_idx] = UN / (bbb[id_idx] - aaa[id_idx] * ddd[id_idx - 1]);
ddd[id_idx] = ccc[id_idx] * zzz[id_idx];
aanu[id_idx] *= zzz[id_idx];
}
// 下边界
id = nd - 1;
if params.ibc == 0 {
bbb[id] = model.fak[ij][id] / dtp1 + HALF;
aaa[id] = model.fak[ij][id - 1] / dtp1;
} else {
let b_val = UN / dtp1;
let a_val = TWO * b_val * b_val;
bbb[id] = UN + ss0_copy[id] + b_val + a_val * model.fak[ij][id];
aaa[id] = a_val * model.fak[ij][id - 1];
}
eee[id] = aaa[id] / bbb[id];
zzz[id] = UN / (bbb[id] - aaa[id] * ddd[id - 1]);
rad1[id] = (sa0_copy[id] + aaa[id] * aanu[id - 1]) * zzz[id];
alrh[id] = zzz[id];
// 回代
for id_idx in (0..nd - 1).rev() {
eee[id_idx] = aaa[id_idx] / (bbb[id_idx] - ccc[id_idx] * eee[id_idx + 1]);
rad1[id_idx] = aanu[id_idx] + ddd[id_idx] * rad1[id_idx + 1];
alrh[id_idx] = zzz[id_idx] / (UN - ddd[id_idx] * eee[id_idx + 1]);
}
// Lambda 算子评价
if params.jali == 1 {
for id in 0..nd {
ali_state.ali1[id] = alrh[id];
}
if params.ifali >= 6 {
ali_state.alip1[0] = alrh[1] * ddd[0];
for id in 1..nd - 1 {
ali_state.alim1[id] = alrh[id - 1] * eee[id];
ali_state.alip1[id] = alrh[id + 1] * ddd[id];
}
ali_state.alim1[nd - 1] = alrh[nd - 2] * eee[nd - 1];
}
}
// 存储显式频率数据
if model.ijex[ij] > 0 {
let ije = (model.ijex[ij] - 1) as usize;
for id in 0..nd {
model.radex[ije][id] = rad1[id];
model.fakex[ije][id] = model.fak[ij][id];
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rtedf1_basic() {
let nd = 10;
let nmu = 3;
let mut rad = vec![vec![0.0; MDEPTH]; 1];
let mut fak = vec![vec![0.0; MDEPTH]; 1];
let mut flux = vec![0.0; 1];
let mut fh = vec![0.0; 1];
let mut fhd = vec![0.0; 1];
let mut radex = vec![vec![0.0; MDEPTH]; 1];
let mut fakex = vec![vec![0.0; MDEPTH]; 1];
let mut q0 = vec![0.0; 1];
let mut uu0 = vec![0.0; 1];
let params = Rtedf1Params {
ij: 0,
nd,
nmu,
idisk: 0,
ibc: 0,
isplin: 0,
jali: 1,
ifali: 0,
ilmcor: 0,
ilasct: 1,
djmax: 1e-4,
ntrali: 10,
iwinbl: 0,
};
let amu = vec![0.1127, 0.5000, 0.8873];
let wtmu = vec![0.2778, 0.4444, 0.2778];
let freq = vec![1e15];
let deldmz = vec![1.0; MDEPTH];
let absot = vec![1.0; MDEPTH];
let emis1 = vec![1.0; MDEPTH]; // S = 1.0
let abso1 = vec![1.0; MDEPTH];
let scat1 = vec![0.0; MDEPTH];
let temp = vec![5000.0; MDEPTH];
let ijex = vec![0];
let mut model_state = Rtedf1ModelState {
amu: &amu,
wtmu: &wtmu,
freq: &freq,
deldmz: &deldmz,
absot: &absot,
emis1: &emis1,
abso1: &abso1,
scat1: &scat1,
temp: &temp,
dedm1: 1e-5,
rrdil: 1.0,
tempbd: 0.0,
extint: &vec![vec![0.0; nmu]; 1],
hextrd: &vec![0.0; 1],
rad: &mut rad,
fak: &mut fak,
flux: &mut flux,
fh: &mut fh,
fhd: &mut fhd,
radex: &mut radex,
fakex: &mut fakex,
q0: &mut q0,
uu0: &mut uu0,
ijex: &ijex,
};
let mut ali1 = vec![0.0; MDEPTH];
let mut alim1 = vec![0.0; MDEPTH];
let mut alip1 = vec![0.0; MDEPTH];
let mut ali_state = Rtedf1AliState {
ali1: &mut ali1,
alim1: &mut alim1,
alip1: &mut alip1,
};
rtedf1(&params, &mut model_state, &mut ali_state);
// 在源函数 S=1 的均匀介质中,RAD 应趋向于 1.0 (平衡态)
// 在 nd=10 时,可能还在增长中,但应为正值且合理
assert!(model_state.rad[0][nd - 1] > 0.0);
assert!(model_state.rad[0][nd - 1] <= 1.0001);
println!("RAD at ND: {}", model_state.rad[0][nd - 1]);
}
}
+334
View File
@@ -0,0 +1,334 @@
//! 辐射转移方程求解(不连续有限元法,使用不透明度和发射率)- RTEDF2。
//!
//! 重构自 TLUSTY `rtedf2.f`
//!
//! 对于给定频率点 IJ,利用已知的 J 求解辐射转移方程,计算辐射场和
//! 变 Eddington 因子。与 RTEDF1 类似,但使用 opacity/emissivity 而非
//! source function 作为输入。
//!
//! 采用 Castor, Dykema, Klein (1992, ApJ 387, 561) 的
//! Discontinuous Finite Element (DFE) 方法。
use crate::state::config::TlustyConfig;
use crate::state::constants::{BN, HALF, HK, TWO, UN};
use crate::state::iterat::IterControl;
use crate::state::model::ModelState;
const SIXTH: f64 = 1.0 / 6.0;
const THIRD: f64 = 1.0 / 3.0;
const TWOTHR: f64 = 2.0 / 3.0;
const THREE: f64 = 3.0;
const QUART: f64 = 0.25;
/// 为频率点 ij(0-based)求解辐射转移方程。
pub fn rtedf2(
ij: usize,
config: &TlustyConfig,
model: &mut ModelState,
_iterat: &IterControl,
) {
let nd = config.basnum.nd as usize;
let nmu = config.angles.nmu as usize;
let iwinbl = model.windbl.iwinbl;
let ibc = config.basnum.ibc;
let ilmcor = config.basnum.ilmcor;
let fr = model.frqall.freq[ij];
// 局部工作数组
let mut dt = vec![0.0f64; nd];
let mut rdk = vec![0.0f64; nd];
let mut fkk = vec![0.0f64; nd];
let mut st0 = vec![0.0f64; nd];
let mut sa0 = vec![0.0f64; nd];
let mut ss0 = vec![0.0f64; nd];
let mut dtau = vec![0.0f64; nd];
let mut rip = vec![0.0f64; nd];
let mut rim = vec![0.0f64; nd];
let mut riin = vec![0.0f64; nd];
let mut riup = vec![0.0f64; nd];
let mut u = vec![0.0f64; nd];
let mut aip = vec![0.0f64; nd];
let mut aim = vec![0.0f64; nd];
let mut al0 = vec![0.0f64; nd];
let mut aiin = vec![0.0f64; nd];
let mut aiup = vec![0.0f64; nd];
let mut chip0 = vec![0.0f64; nd];
let mut chim0 = vec![0.0f64; nd];
let mut ddm0 = vec![0.0f64; nd];
let mut chip = vec![0.0f64; nd];
let mut chim = vec![0.0f64; nd];
let mut etap = vec![0.0f64; nd];
let mut etam = vec![0.0f64; nd];
// 1. 光学深度尺度和辅助量
for id in 0..(nd - 1) {
dt[id] = model.modpar.deldmz[id] * (model.curopa.absot[id + 1] + model.curopa.absot[id]);
sa0[id] = model.curopa.emis1[id] / model.curopa.abso1[id];
ss0[id] = -model.curopa.scat1[id] / model.curopa.abso1[id];
ddm0[id] = model.modpar.dm[id + 1] - model.modpar.dm[id];
chip0[id] = (model.curopa.abso1[id] * model.modpar.dens1[id]
+ THREE * model.curopa.abso1[id + 1] * model.modpar.dens1[id + 1])
* QUART * ddm0[id];
chim0[id] = (model.curopa.abso1[id] * model.modpar.dens1[id] * THREE
+ model.curopa.abso1[id + 1] * model.modpar.dens1[id + 1])
* QUART * ddm0[id];
}
sa0[nd - 1] = model.curopa.emis1[nd - 1] / model.curopa.abso1[nd - 1];
ss0[nd - 1] = -model.curopa.scat1[nd - 1] / model.curopa.abso1[nd - 1];
let taumin = model.curopa.abso1[0] * model.modpar.dedm1;
// 2. 风展宽
let alb1 = if iwinbl > 0 {
TWO * model.windbl.albe[ij] / (UN + model.windbl.albe[ij])
} else {
0.0
};
// 3. 下边界 Planck 函数
let fr15 = fr * 1.0e-15;
let bnu = BN * fr15 * fr15 * fr15;
let rrdil = model.modpar.rrdil;
let mut pland = bnu / ((HK * fr / model.modpar.temp[nd - 1]).exp() - UN) * rrdil;
let mut dplan = bnu / ((HK * fr / model.modpar.temp[nd - 2]).exp() - UN) * rrdil;
if model.modpar.tempbd > 0.0 {
pland = bnu / ((HK * fr / model.modpar.tempbd).exp() - UN) * rrdil;
dplan = bnu / ((HK * fr / model.modpar.tempbd).exp() - UN) * rrdil;
}
dplan = (pland - dplan) / dt[nd - 2];
// 4. ALI 散射迭代循环
let mut itrali = 0;
// 在循环外声明,循环结束后仍可使用
let mut ah = 0.0f64;
let mut u0 = 0.0f64;
let mut qq0 = 0.0f64;
let mut us0 = 0.0f64;
loop {
itrali += 1;
// 初始化总 source function 和输出变量
for id in 0..nd {
st0[id] = sa0[id] - ss0[id] * model.totrad.rad[ij][id];
model.currad.rad1[id] = 0.0;
rdk[id] = 0.0;
model.currad.ali1[id] = 0.0;
}
let mut ah_inner = 0.0f64;
let mut u0_inner = 0.0f64;
let mut qq0_inner = 0.0f64;
let mut us0_inner = 0.0f64;
// 5. 对每个角度积分
for i in 0..nmu {
let amu_i = config.angles.amu[i];
let wtmu_i = config.angles.wtmu[i];
let amu2 = amu_i * amu_i * wtmu_i;
for id in 0..(nd - 1) {
dtau[id] = dt[id] / amu_i;
chip[id] = UN + chip0[id] / amu_i;
chim[id] = UN + chim0[id] / amu_i;
etap[id] = model.curopa.emis1[id + 1] * model.modpar.dens1[id + 1] / amu_i * ddm0[id];
etam[id] = model.curopa.emis1[id] * model.modpar.dens1[id] / amu_i * ddm0[id];
}
// 5a. 来向强度(自上向下:id=0 → nd-1)
let mut ex = UN;
rim[0] = model.totrad.extint[ij][i];
if iwinbl == 0 {
let tamm = taumin / amu_i;
ex = (-tamm).exp();
let p0 = UN - ex;
qq0_inner += p0 * amu_i * wtmu_i;
u0_inner += ex * wtmu_i;
us0_inner += p0 / tamm * wtmu_i;
rim[0] = st0[0] * p0;
}
aim[0] = 0.0;
for id in 0..(nd - 1) {
let dt0 = dtau[id];
let dtaup1 = dt0 + UN;
let dtau2 = dt0 * dt0;
let bb = TWO * dtaup1;
let cc = dt0 * dtaup1;
let aa = UN / (dtau2 + bb);
let aam = UN / (UN + chim[id] * chip[id]);
rim[id + 1] = (TWO * rim[id] + etap[id] * chim[id] + etam[id]) * aam;
rip[id] = (TWO * rim[id] * chim[id] + etam[id] * chip[id] - etap[id]) * aam;
aim[id + 1] = bb * aa;
aip[id] = (cc + bb * aim[id]) * aa;
}
// 插值到 cell midpoints(来向)
for id in 1..(nd - 1) {
let dtt = UN / (dtau[id - 1] + dtau[id]);
let dtm = UN / (ddm0[id - 1] + ddm0[id]);
riin[id] = (rim[id] * ddm0[id] + rip[id] * ddm0[id - 1]) * dtm;
aiin[id] = (aim[id] * dtau[id] + aip[id] * dtau[id - 1]) * dtt;
}
riin[0] = rim[0];
riin[nd - 1] = rim[nd - 1];
aiin[0] = aim[0];
aiin[nd - 1] = aim[nd - 1];
// 5b. 去向强度(自下向上:id=nd-1 → 0)
rim[nd - 1] = pland + amu_i * dplan;
for id in (0..(nd - 1)).rev() {
let dt0 = dtau[id];
let dtaup1 = dt0 + UN;
let dtau2 = dt0 * dt0;
let bb = TWO * dtaup1;
let cc = dt0 * dtaup1;
let aa = UN / (dtau2 + bb);
let aam = UN / (UN + chim[id] * chip[id]);
rim[id] = (TWO * rim[id + 1] + etam[id] * chip[id] + etap[id]) * aam;
rip[id + 1] = (TWO * rim[id + 1] * chip[id] + etap[id] * chim[id] - etam[id]) * aam;
aim[id] = cc * aa;
aip[id + 1] = (cc + bb * aim[id + 1]) * aa;
}
// 插值(去向)
for id in 1..(nd - 1) {
let dtt = UN / (dtau[id - 1] + dtau[id]);
let dtm = UN / (ddm0[id - 1] + ddm0[id]);
riup[id] = (rim[id] * ddm0[id - 1] + rip[id] * ddm0[id]) * dtm;
aiup[id] = (aim[id] * dtau[id - 1] + aip[id] * dtau[id]) * dtt;
}
riup[0] = rim[0];
riup[nd - 1] = rim[nd - 1];
aiup[0] = aim[0];
aiup[nd - 1] = aim[nd - 1];
// 对称化的 Feautrier 强度
for id in 0..nd {
u[id] = (riin[id] + riup[id]) * HALF;
al0[id] = (aiin[id] + aiup[id]) * HALF;
}
// 积分累加
for id in 0..nd {
model.currad.rad1[id] += wtmu_i * u[id];
rdk[id] += amu2 * u[id];
model.currad.ali1[id] += wtmu_i * al0[id];
}
ah_inner += amu_i * wtmu_i * u[0];
}
// 6. 下边界 ALI 修正
if ibc == 0 {
let j_nd = model.totrad.rad[ij][nd - 1];
let j_nd1 = model.totrad.rad[ij][nd - 2];
model.currad.ali1[nd - 1] = model.currad.rad1[nd - 1] / st0[nd - 1];
model.currad.ali1[nd - 2] = model.currad.rad1[nd - 2] / st0[nd - 2];
}
// 7. ALI 更新 J
let mut djtot = 0.0_f64;
for id in 0..nd {
let deltaj = (model.currad.rad1[id] - model.totrad.rad[ij][id])
/ (UN + ss0[id] * model.currad.ali1[id]);
model.totrad.rad[ij][id] += deltaj;
let dj_rel = if model.totrad.rad[ij][id].abs() > 0.0 {
(deltaj / model.totrad.rad[ij][id]).abs()
} else {
0.0
};
djtot = djtot.max(dj_rel);
}
if djtot <= model.dfeali.djmax || itrali >= model.dfeali.ntrali as usize {
ah = ah_inner;
u0 = u0_inner;
qq0 = qq0_inner;
break;
}
}
// 8. 写回结果
for id in 0..nd {
model.currad.rad1[id] = model.totrad.rad[ij][id];
model.currad.fak1[id] = rdk[id] / model.totrad.rad[ij][id];
fkk[id] = model.currad.fak1[id];
}
model.surfac.flux[ij] = ah;
model.surfac.fh[ij] = ah / model.currad.rad1[0] - HALF * alb1;
// 9. Lambda* 修正(电子散射)
if ilmcor == 1 {
for id in 0..nd {
model.currad.ali1[id] *= UN + ss0[id];
}
if ibc == 4 {
model.currad.ali1[nd - 1] /= UN + ss0[nd - 1];
}
} else if ilmcor == 3 {
for id in 0..nd {
model.currad.ali1[id] /= UN + ss0[id] * model.currad.ali1[id];
}
if ibc == 4 {
model.currad.ali1[nd - 1] *= UN + ss0[nd - 1] * model.currad.ali1[nd - 1];
}
}
// 10. 存储显式频率的结果
if model.freaux.ijex[ij] <= 0 {
return;
}
let ije = model.freaux.ijex[ij] as usize - 1;
for id in 0..nd {
model.expraf.radex[ije][id] = model.currad.rad1[id];
model.expraf.fakex[ije][id] = model.currad.fak1[id];
}
model.surfac.q0[ije] = qq0;
model.surfac.uu0[ije] = u0;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state::constants::MDEPTH;
#[test]
fn test_rtedf2_basic() {
let mut config = TlustyConfig::default();
let mut model = ModelState::new();
let iterat = IterControl::default();
config.basnum.nd = 5;
config.basnum.nfreq = 1;
config.angles.nmu = 1;
config.angles.amu[0] = 0.5_f64.sqrt();
config.angles.wtmu[0] = 1.0;
model.frqall.freq[0] = 1.0e14;
model.frqall.kij[0] = 1;
for i in 0..MDEPTH {
model.modpar.deldmz[i] = 1.0;
model.modpar.dm[i] = i as f64;
model.curopa.absot[i] = 1.0;
model.curopa.abso1[i] = 1.0;
model.curopa.emis1[i] = 1.0;
model.curopa.scat1[i] = 0.0;
model.modpar.dens1[i] = 1.0;
model.modpar.temp[i] = 10000.0;
}
// extint[ij] 按角度索引,长度为 MMU
use crate::state::constants::MMU;
for i in 0..MMU {
model.totrad.extint[0][i] = 0.0;
}
model.modpar.dedm1 = 1e-3;
model.modpar.rrdil = 1.0;
model.dfeali.djmax = 1e-6;
model.dfeali.ntrali = 10;
rtedf2(0, &config, &mut model, &iterat);
// 验证均匀场中 J > 0
assert!(model.currad.rad1[0] > 0.0);
// Eddington 因子应在 [0,1] 区间
assert!(model.currad.fak1[0] >= 0.0 && model.currad.fak1[0] <= 1.0);
}
}
+990
View File
@@ -0,0 +1,990 @@
//! Rybicki 形式完全线性化矩阵计算。
//!
//! 重构自 TLUSTY `rybmat.f`
use crate::state::constants::{HALF, MDEPTH, TWO, UN};
/// 物理常数
const BN: f64 = 1.4743e-2; // Planck 常数前因子
const HK: f64 = 4.79928144e-11; // h/k (s·K)
/// Rybicki 矩阵输入参数。
pub struct RybmatParams<'a> {
/// 频率索引 (0-indexed)
pub ij: usize,
/// 深度点数
pub nd: usize,
// 深度相关数组
pub dm: &'a [f64],
pub temp: &'a [f64],
pub dens: &'a [f64],
// ALIPAR 相关
pub abso1: &'a [f64],
pub scat1: &'a [f64],
pub emis1: &'a [f64],
pub fak1: &'a [f64],
pub rad1: &'a [f64],
pub dabt1: &'a [f64],
pub demt1: &'a [f64],
pub deldm: &'a [f64],
// 配置参数
pub isplin: i32,
pub iubc: i32,
pub ibc: i32,
pub idisk: i32,
pub ifryb: i32,
pub reint: &'a [f64],
pub redif: &'a [f64],
pub w: &'a [f64],
pub freq: &'a [f64],
pub fh: &'a [f64],
pub fhd: &'a [f64],
pub icentr: i32,
pub ilbc: i32,
pub nretc: usize,
// DSCTVA
pub dsct1: &'a [f64],
pub dscn1: &'a [f64],
// 频率索引映射 (1-indexed)
pub ijfr: &'a [i32],
// 上边界条件额外参数
pub extrad: &'a [f64], // EXTRAD(IJ): 外部辐射
pub q0: &'a [f64], // Q0(IJ): 边界条件参数
pub uu0: &'a [f64], // UU0(IJ): 边界条件参数
}
/// Rybicki 矩阵输出结构体。
#[derive(Debug, Clone)]
pub struct RybmatResult {
pub ra: Vec<f64>,
pub rb: Vec<f64>,
pub rc: Vec<f64>,
pub vr: Vec<f64>,
pub ua: Vec<f64>,
pub ub: Vec<f64>,
pub uc: Vec<f64>,
pub va: Vec<f64>,
pub vb: Vec<f64>,
pub vc: Vec<f64>,
pub wr: Vec<f64>,
pub wm: Vec<Vec<f64>>,
}
impl Default for RybmatResult {
fn default() -> Self {
Self {
ra: vec![0.0; MDEPTH],
rb: vec![0.0; MDEPTH],
rc: vec![0.0; MDEPTH],
vr: vec![0.0; MDEPTH],
ua: vec![0.0; MDEPTH],
ub: vec![0.0; MDEPTH],
uc: vec![0.0; MDEPTH],
va: vec![0.0; MDEPTH],
vb: vec![0.0; MDEPTH],
vc: vec![0.0; MDEPTH],
wr: vec![0.0; MDEPTH],
wm: vec![vec![0.0; MDEPTH]; MDEPTH],
}
}
}
/// 计算 Rybicki 形式完全线性化矩阵。
pub fn rybmat(params: &RybmatParams) -> RybmatResult {
let mut result = RybmatResult::default();
const SIXTH: f64 = UN / 6.0;
const THIRD: f64 = UN / 3.0;
const TWOTHR: f64 = TWO / 3.0;
let nd = params.nd;
let ijt = (params.ijfr[params.ij] - 1) as usize; // Fortran 1-indexed 转 0-indexed
// ==============================================================
// 1. 上边界条件 (Fortran ID=1, Rust ID=0)
// ==============================================================
let id = 0;
let ddm = (params.dm[id + 1] - params.dm[id]) * HALF;
let dtm = UN / ((params.abso1[id] + params.abso1[id + 1]) * ddm);
let dtm2 = dtm * dtm;
let alf = dtm * ddm;
let fd = TWO * params.fh[ijt];
let exti = params.extrad[params.ij];
let bet = (exti - fd * params.rad1[id]) * dtm;
let gam = (params.fak1[id] * params.rad1[id] - params.fak1[id + 1] * params.rad1[id + 1])
* TWO
* dtm2;
let s0 = (params.emis1[id] + params.scat1[id] * params.rad1[id]) / params.abso1[id];
let c1 = alf * (TWO * gam - bet);
let b1 = c1 - s0 / params.abso1[id];
let q0_ij = params.q0[params.ij];
let uu0_ij = params.uu0[params.ij];
let unq = UN + TWO * dtm * q0_ij;
result.rb[id] = -(UN + dtm * (fd + TWO * params.fak1[id] * dtm))
+ params.scat1[id] / params.abso1[id] * unq;
result.rc[id] = TWO * params.fak1[id + 1] * dtm2;
result.vr[id] = gam - bet + params.rad1[id] - s0 * unq;
// UB, UC 完整计算
result.ub[id] = b1 * params.dabt1[id]
+ (params.demt1[id] + params.dsct1[id] * params.rad1[id]) / params.abso1[id] * unq
- params.emis1[id] / params.abso1[id] / params.abso1[id] * params.dabt1[id] * TWO * dtm
* q0_ij
+ dtm * s0
* (uu0_ij * params.dm[0] * params.dabt1[id]
- TWO * q0_ij * dtm * ddm * params.dabt1[id]);
result.uc[id] = c1 * params.dabt1[id + 1] - TWO * dtm * q0_ij * s0 * dtm * ddm * params.dabt1[id + 1];
// IUBC 边界条件修正
if params.iubc > 0 {
let corf = HALF / dtm;
result.rb[id] *= corf;
result.rc[id] *= corf;
result.vr[id] *= corf;
let c1 = (gam - params.rad1[id] + s0) * corf * alf;
let b1 = c1 - corf * s0 / params.abso1[id];
result.ub[id] = b1 * params.dabt1[id]
+ (params.demt1[id] + params.dsct1[id] * params.rad1[id]) / params.abso1[id] * corf;
result.uc[id] = c1 * params.dabt1[id + 1];
}
// ==============================================================
// 2. 深度循环 (Fortran ID=2 到 ND-1, Rust ID=1 到 ND-2)
// ==============================================================
for id in 1..nd - 1 {
let ddm = (params.dm[id] - params.dm[id - 1]) * HALF;
let ddp = (params.dm[id + 1] - params.dm[id]) * HALF;
let dzm = params.abso1[id] + params.abso1[id - 1];
let dzp = params.abso1[id] + params.abso1[id + 1];
let dtaup = dzp * ddp;
let dtaum = dzm * ddm;
let dtau0 = HALF * (dtaup + dtaum);
let frd = params.fak1[id] * params.rad1[id];
let alf1 = (frd - params.fak1[id + 1] * params.rad1[id + 1]) / dtaup / dtau0;
let gam1 = (frd - params.fak1[id - 1] * params.rad1[id - 1]) / dtaum / dtau0;
let bet1 = alf1 + gam1;
let x1 = HALF * bet1 / dtau0;
let mut a1 = (gam1 + x1 * dtaum) / dzm;
let mut c1 = (alf1 + x1 * dtaup) / dzp;
let mut b1 = a1 + c1;
let chielm = params.scat1[id - 1];
let chiel0 = params.scat1[id];
let chielp = params.scat1[id + 1];
let s0 = (params.emis1[id] + chiel0 * params.rad1[id]) / params.abso1[id];
// 初始化 spline/hermitian 参数
let (as_val, cs_val, bs_val, bet2, a2, c2, sm, sp) = if params.isplin % 3 == 0 {
// 简单一阶差分
(0.0, 0.0, UN, 0.0, 0.0, 0.0, 0.0, 0.0)
} else {
let sm = (params.emis1[id - 1] + params.rad1[id - 1] * chielm) / params.abso1[id - 1];
let sp = (params.emis1[id + 1] + params.rad1[id + 1] * chielp) / params.abso1[id + 1];
if params.isplin == 1 {
// Spline collocation
let as_val = dtaum / dtau0 * SIXTH;
let cs_val = dtaup / dtau0 * SIXTH;
let bs_val = TWOTHR; // 0.666666666666667
let alf2 = as_val * (params.rad1[id - 1] - sm);
let gam2 = cs_val * (params.rad1[id + 1] - sp);
let bet2 = alf2 + gam2;
let x = HALF * bet2 / dtau0;
let a2 = (gam2 - x * dtaum) / dzm;
let c2 = (alf2 - x * dtaup) / dzp;
(as_val, cs_val, bs_val, bet2, a2, c2, sm, sp)
} else {
// Hermitian method (ISPLIN=2)
let as_init = dtaup * dtaup / dtaum / dtau0;
let cs_init = dtaum * dtaum / dtaup / dtau0;
let al3 = (params.rad1[id + 1] - sp - params.rad1[id] + s0) * SIXTH;
let ga3 = (params.rad1[id - 1] - sm - params.rad1[id] + s0) * SIXTH;
let av = al3 * cs_init;
let cv = ga3 * as_init;
let as_val = (UN - HALF * as_init) * SIXTH;
let cs_val = (UN - HALF * cs_init) * SIXTH;
let bs_val = UN - as_val - cs_val;
let x = (av + cv) / dtau0 / 4.0;
let a2 = (x * dtaum + HALF * cv - av) / dzm;
let c2 = (x * dtaup + HALF * av - cv) / dzp;
let bet2 = as_val * (params.rad1[id - 1] - sm) + cs_val * (params.rad1[id + 1] - sp);
(as_val, cs_val, bs_val, bet2, a2, c2, sm, sp)
}
};
// 辅助量
b1 = b1 - (a2 + c2);
a1 = a1 - a2;
c1 = c1 - c2;
let a2_coeff = as_val / params.abso1[id - 1];
let c2_coeff = cs_val / params.abso1[id + 1];
let a3 = a2_coeff * sm;
let c3 = c2_coeff * sp;
let b2 = bs_val / params.abso1[id];
let b3 = b2 * s0;
let a1 = a1 - a3;
let b1 = b1 - b3;
let c1 = c1 - c3;
// 矩阵元素
result.ra[id] = params.fak1[id - 1] / dtaum / dtau0
- as_val * (UN - chielm / params.abso1[id - 1]);
result.rb[id] = -params.fak1[id] / dtau0 * (UN / dtaup + UN / dtaum)
- bs_val * (UN - chiel0 / params.abso1[id]);
result.rc[id] = params.fak1[id + 1] / dtaup / dtau0
- cs_val * (UN - chielp / params.abso1[id + 1]);
result.vr[id] = bet1 + bet2 + bs_val * (params.rad1[id] - s0);
result.ua[id] = a1 * params.dabt1[id - 1]
+ a2_coeff * (params.demt1[id - 1] + params.dsct1[id - 1] * params.rad1[id - 1]);
result.ub[id] = b1 * params.dabt1[id]
+ b2 * (params.demt1[id] + params.dsct1[id] * params.rad1[id]);
result.uc[id] = c1 * params.dabt1[id + 1]
+ c2_coeff * (params.demt1[id + 1] + params.dsct1[id + 1] * params.rad1[id + 1]);
}
// ==============================================================
// 3. 下边界条件 (Fortran ID=ND, Rust ID=ND-1)
// ==============================================================
let id = nd - 1;
let ddm = HALF * (params.dm[id] - params.dm[id - 1]);
let t0 = params.temp[id];
let tm = params.temp[id - 1];
if params.ibc > 0 && params.ibc < 4 && params.idisk == 0 {
// Planck 函数边界条件
let dtm = UN / ((params.abso1[id - 1] + params.abso1[id]) * ddm);
let dtm2 = dtm * dtm;
let fd = TWO * params.fhd[ijt];
let fr = params.freq[params.ij];
let fr15 = fr * 1e-15;
let bnu = BN * fr15 * fr15 * fr15;
let x0 = HK * fr / t0;
let xm = HK * fr / tm;
let pland = bnu / (x0.exp() - UN);
let planm = bnu / (xm.exp() - UN);
let dpldt0 = pland / (UN - (-x0).exp()) * x0 / t0;
let dpldtm = planm / (UN - (-xm).exp()) * xm / tm;
let alf = dtm * ddm;
let bet = (pland - fd * params.rad1[id]) * dtm;
let gam = (params.fak1[id] * params.rad1[id]
- params.fak1[id - 1] * params.rad1[id - 1]
- THIRD * (pland - planm))
* TWO
* dtm2;
let s0 = (params.emis1[id] + params.scat1[id] * params.rad1[id]) / params.abso1[id];
let a1 = alf * (TWO * gam - bet);
let b1 = a1 - s0 / params.abso1[id];
result.ra[id] = TWO * params.fak1[id - 1] * dtm2;
result.rb[id] = -(UN + dtm * (fd + TWO * params.fak1[id] * dtm))
+ params.scat1[id] / params.abso1[id];
result.vr[id] = gam - bet + params.rad1[id] - s0;
result.ua[id] = b1 * params.dabt1[id - 1]
+ (params.demt1[id - 1] + params.dsct1[id - 1] * params.rad1[id - 1])
/ params.abso1[id - 1]
- dpldtm * dtm2 * TWOTHR;
result.ub[id] = b1 * params.dabt1[id]
+ (params.demt1[id] + params.dsct1[id] * params.rad1[id]) / params.abso1[id]
+ dpldt0 * dtm * (UN + TWOTHR * dtm);
// IFRYB > 0 分支:Rybicki 特殊边界条件
if params.ifryb > 0 {
let dtm = UN / ((params.abso1[id - 1] + params.abso1[id]) * ddm);
let fr = params.freq[params.ij];
let fr15 = fr * 1e-15;
let bnu = BN * fr15 * fr15 * fr15;
let x0 = HK * fr / t0;
let xm = HK * fr / tm;
let pland = bnu / (x0.exp() - UN);
let planm = bnu / (xm.exp() - UN);
let dpldt0 = pland / (UN - (-x0).exp()) * x0 / t0;
let dpldtm = planm / (UN - (-xm).exp()) * xm / tm;
let gam = (params.fak1[id] * params.rad1[id]
- params.fak1[id - 1] * params.rad1[id - 1]
- THIRD * (pland - planm))
* dtm;
let s0 = (params.emis1[id] + params.scat1[id] * params.rad1[id]) / params.abso1[id];
let bs = HALF / dtm;
let bet = bs * (params.rad1[id] - s0);
let a1 = (gam - bet) * dtm * ddm;
let b1 = a1 - bs * s0 / params.abso1[id];
result.ra[id] = params.fak1[id - 1] * dtm;
result.rb[id] = -params.fak1[id] * dtm
- bs * (UN - params.scat1[id] / params.abso1[id])
- params.fhd[ijt];
result.vr[id] = gam + bet - HALF * pland + params.fhd[ijt] * params.rad1[id];
result.ua[id] = a1 * params.dabt1[id - 1] - dpldtm * dtm * THIRD;
result.ub[id] = b1 * params.dabt1[id]
+ bs * (params.demt1[id] + params.dsct1[id] * params.rad1[id]) / params.abso1[id]
+ (HALF + THIRD * dtm) * dpldt0;
}
} else {
// 磁盘模式:I(taumax,-mu,nu) = I(taumax,+mu,nu)
let dzm = params.abso1[id] + params.abso1[id - 1];
let frd = params.fak1[id] * params.rad1[id] - params.fak1[id - 1] * params.rad1[id - 1];
let dtaum = dzm * ddm;
let gam1 = frd / dtaum;
let a2 = 0.0; // AS = 0 in this branch
let a1 = gam1 / dzm;
let bs = dtaum * HALF;
let s0 = (params.emis1[id] + params.scat1[id] * params.rad1[id]) / params.abso1[id];
let gam2 = bs * (params.rad1[id] - s0);
let x1 = gam2 / dzm;
let a1 = a1 - x1;
let b2 = bs / params.abso1[id];
let b1 = a1 - b2 * s0;
result.ra[id] = params.fak1[id - 1] / dtaum - a2 * (UN - params.scat1[id - 1] / params.abso1[id - 1]);
result.rb[id] = -params.fak1[id] / dtaum - bs * (UN - params.scat1[id] / params.abso1[id]);
result.ua[id] = a1 * params.dabt1[id - 1]
+ a2 / params.abso1[id - 1] * (params.demt1[id - 1] + params.dsct1[id - 1] * params.rad1[id - 1]);
result.ub[id] = b1 * params.dabt1[id] + b2 * (params.demt1[id] + params.dsct1[id] * params.rad1[id]);
result.vr[id] = gam1 + gam2;
}
// ==============================================================
// 4. 辐射平衡部分
// ==============================================================
// 4a. 积分方程部分
for id in 0..nd {
if params.reint[id] > 0.0 {
let heat = params.abso1[id] - params.scat1[id];
let dheat = (params.dabt1[id] - params.dsct1[id]) * params.rad1[id];
let wdr = params.w[params.ij] * params.dens[id] * params.reint[id];
result.vb[id] = heat * wdr;
result.wm[id][id] += (dheat - params.demt1[id]) * wdr;
result.wr[id] -= (heat * params.rad1[id] - params.emis1[id]) * wdr;
}
}
// 4b. 微分方程部分 - ID=0 (Fortran ID=1) 特殊处理
let id = 0;
if params.redif[id] > 0.0 {
let wf = params.w[params.ij] * params.fh[ijt] * params.redif[id];
result.vb[id] += wf;
result.wr[id] -= wf * params.rad1[id];
}
// 4c. 微分方程部分 - 主循环
if params.icentr == 0 {
// 一阶中点差分格式
let nd1 = if params.ilbc != 0 { nd - 1 } else { nd };
for id in 1..nd1 {
if params.redif[id] > 0.0 {
let ddm = (params.dm[id] - params.dm[id - 1]) * HALF;
let omeg0 = params.abso1[id];
let omegm = params.abso1[id - 1];
let dtaum = (omeg0 + omegm) * ddm;
let frd = params.fak1[id] * params.rad1[id]
- params.fak1[id - 1] * params.rad1[id - 1];
let gamr = frd / dtaum * params.redif[id];
let a1 = gamr / (omeg0 + omegm);
result.va[id] -= params.w[params.ij] * params.fak1[id - 1] / dtaum * params.redif[id];
result.vb[id] += params.w[params.ij] * params.fak1[id] / dtaum * params.redif[id];
result.wm[id][id - 1] -= a1 * params.w[params.ij] * params.dabt1[id - 1];
result.wm[id][id] -= a1 * params.w[params.ij] * params.dabt1[id];
result.wr[id] -= params.w[params.ij] * gamr;
}
}
// ILBC 边界条件处理
if params.ilbc > 0 {
let id = nd - 1;
if params.redif[id] > 0.0 {
let ddm = (params.dm[id] - params.dm[id - 1]) * HALF;
let dtaum = (params.abso1[id] + params.abso1[id - 1]) * ddm * 3.0;
let fr = params.freq[params.ij];
let fr15 = fr * 1e-15;
let bnu = BN * fr15 * fr15 * fr15;
let x0 = HK * fr / params.temp[id];
let xm = HK * fr / params.temp[id - 1];
let pland = bnu / (x0.exp() - UN);
let planm = bnu / (xm.exp() - UN);
let dpldt0 = pland / (UN - (-x0).exp()) * x0 / params.temp[id];
let dpldtm = planm / (UN - (-xm).exp()) * xm / params.temp[id - 1];
let flx = (pland - planm) / dtaum * params.redif[id];
let a1 = flx / (params.abso1[id] + params.abso1[id - 1]);
result.wm[id][id - 1] += params.w[params.ij] * (dpldtm - a1 * params.dabt1[id - 1]);
result.wm[id][id] += params.w[params.ij] * (dpldt0 - a1 * params.dabt1[id]);
result.wr[id] -= params.w[params.ij] * flx;
}
}
} else {
// 中心差分格式 (ICENTR != 0)
for id in 1..nd - 1 {
if params.redif[id] > 0.0 {
let wwr = params.w[params.ij] * params.redif[id];
let ddm = HALF * (params.dm[id] - params.dm[id - 1]);
let ddp = HALF * (params.dm[id + 1] - params.dm[id]);
let dtm = (params.abso1[id - 1] + params.abso1[id]) * ddm;
let dtp = (params.abso1[id + 1] + params.abso1[id]) * ddp;
let dt0 = dtm + dtp;
let frm = params.fak1[id] * params.rad1[id]
- params.fak1[id - 1] * params.rad1[id - 1];
let frp = params.fak1[id + 1] * params.rad1[id + 1]
- params.fak1[id] * params.rad1[id];
let alp = dtp / dtm / dt0;
let gam = dtm / dtp / dt0;
let flx = alp * frm + gam * frp;
// 矩阵元素
result.va[id] -= wwr * params.fak1[id - 1] * alp;
result.vb[id] += wwr * params.fak1[id] * (alp - gam);
result.vc[id] = wwr * params.fak1[id + 1] * gam;
let dmtm = ddm / (dtm * dtm);
let dmtp = ddp / (dtp * dtp);
let rm = dtm / dt0;
let rp = dtp / dt0;
result.wm[id][id - 1] +=
wwr * params.dabt1[id - 1] * dmtm * (rm * rm * frp - (UN + rm) * frm);
result.wm[id][id] += wwr
* params.dabt1[id]
* ((dmtp * rp * rp - dmtm * rp * (UN + rm)) * frm
+ (dmtm * rm * rm - dmtp * rm * (UN + rp)) * frp);
result.wr[id] -= wwr * flx;
}
}
// 下边界 (ID=ND-1)
let id = nd - 1;
if params.redif[id] > 0.0 {
let ddm = HALF * (params.dm[id] - params.dm[id - 1]);
let dtaum = (params.abso1[id] + params.abso1[id - 1]) * ddm;
let frd = params.fak1[id] * params.rad1[id]
- params.fak1[id - 1] * params.rad1[id - 1];
let flx = frd / dtaum * params.redif[id];
let a1 = flx / (params.abso1[id] + params.abso1[id - 1]);
result.va[id] -= params.w[params.ij] * params.fak1[id - 1] / dtaum * params.redif[id];
result.vb[id] += params.w[params.ij] * params.fak1[id] / dtaum * params.redif[id];
result.wm[id][id - 1] -= a1 * params.w[params.ij] * params.dabt1[id - 1];
result.wm[id][id] -= a1 * params.w[params.ij] * params.dabt1[id];
result.wr[id] -= params.w[params.ij] * flx;
}
}
// 4d. 辐射平衡温度点修正
if params.nretc > 0 {
for id in 0..params.nretc {
result.wr[id] = 0.0;
result.vb[id] = 0.0;
result.va[id] = 0.0;
result.wm[id][id] = 1.0;
if id > 0 {
result.wm[id][id - 1] = 0.0;
}
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rybmat_result_default() {
let result = RybmatResult::default();
assert_eq!(result.ra.len(), MDEPTH);
assert_eq!(result.rb.len(), MDEPTH);
assert_eq!(result.wm.len(), MDEPTH);
}
#[test]
fn test_rybmat_basic() {
let dm: Vec<f64> = (0..MDEPTH).map(|i| 0.01 + i as f64 * 0.01).collect();
let temp = vec![10000.0; MDEPTH];
let dens = vec![1e15; MDEPTH];
let abso1 = vec![1e-8; MDEPTH];
let scat1 = vec![1e-9; MDEPTH];
let emis1 = vec![1e-10; MDEPTH];
let fak1 = vec![0.5; MDEPTH];
let rad1 = vec![1e10; MDEPTH];
let dabt1 = vec![1e-12; MDEPTH];
let demt1 = vec![1e-14; MDEPTH];
let deldm = vec![0.001; MDEPTH];
let reint = vec![0.0; MDEPTH];
let redif = vec![0.0; MDEPTH];
let w = vec![0.01; 100];
let fh = vec![0.5; 100];
let fhd = vec![0.5; 100];
let freq = vec![1e15; 100];
let dsct1 = vec![1e-11; MDEPTH];
let dscn1 = vec![1e-16; MDEPTH];
let ijfr: Vec<i32> = (0..100).map(|i| (i + 1) as i32).collect();
let extrad = vec![0.0; 100];
let q0 = vec![0.0; 100];
let uu0 = vec![0.0; 100];
let params = RybmatParams {
ij: 0,
nd: 10,
dm: &dm,
temp: &temp,
dens: &dens,
abso1: &abso1,
scat1: &scat1,
emis1: &emis1,
fak1: &fak1,
rad1: &rad1,
dabt1: &dabt1,
demt1: &demt1,
deldm: &deldm,
isplin: 1,
iubc: 0,
ibc: 1,
idisk: 0,
ifryb: 0,
reint: &reint,
redif: &redif,
w: &w,
freq: &freq,
fh: &fh,
fhd: &fhd,
icentr: 0,
ilbc: 0,
nretc: 0,
dsct1: &dsct1,
dscn1: &dscn1,
ijfr: &ijfr,
extrad: &extrad,
q0: &q0,
uu0: &uu0,
};
let result = rybmat(&params);
assert!(result.rb[0].is_finite());
assert!(result.vr[0].is_finite());
}
#[test]
fn test_rybmat_with_extrad() {
let dm: Vec<f64> = (0..MDEPTH).map(|i| 0.01 + i as f64 * 0.01).collect();
let temp = vec![10000.0; MDEPTH];
let dens = vec![1e15; MDEPTH];
let abso1 = vec![1e-8; MDEPTH];
let scat1 = vec![1e-9; MDEPTH];
let emis1 = vec![1e-10; MDEPTH];
let fak1 = vec![0.5; MDEPTH];
let rad1 = vec![1e10; MDEPTH];
let dabt1 = vec![1e-12; MDEPTH];
let demt1 = vec![1e-14; MDEPTH];
let deldm = vec![0.001; MDEPTH];
let reint = vec![0.0; MDEPTH];
let redif = vec![0.0; MDEPTH];
let w = vec![0.01; 100];
let fh = vec![0.5; 100];
let fhd = vec![0.5; 100];
let freq = vec![1e15; 100];
let dsct1 = vec![1e-11; MDEPTH];
let dscn1 = vec![1e-16; MDEPTH];
let ijfr: Vec<i32> = (0..100).map(|i| (i + 1) as i32).collect();
let extrad: Vec<f64> = (0..100).map(|i| 1e10 * (i as f64 + 1.0)).collect();
let q0 = vec![0.0; 100];
let uu0 = vec![0.0; 100];
let params = RybmatParams {
ij: 0,
nd: 10,
dm: &dm,
temp: &temp,
dens: &dens,
abso1: &abso1,
scat1: &scat1,
emis1: &emis1,
fak1: &fak1,
rad1: &rad1,
dabt1: &dabt1,
demt1: &demt1,
deldm: &deldm,
isplin: 1,
iubc: 0,
ibc: 1,
idisk: 0,
ifryb: 0,
reint: &reint,
redif: &redif,
w: &w,
freq: &freq,
fh: &fh,
fhd: &fhd,
icentr: 0,
ilbc: 0,
nretc: 0,
dsct1: &dsct1,
dscn1: &dscn1,
ijfr: &ijfr,
extrad: &extrad,
q0: &q0,
uu0: &uu0,
};
let result = rybmat(&params);
assert!(result.rb[0].is_finite());
assert!(result.vr[0].is_finite());
}
#[test]
fn test_rybmat_hermitian() {
let dm: Vec<f64> = (0..MDEPTH).map(|i| 0.01 + i as f64 * 0.01).collect();
let temp = vec![10000.0; MDEPTH];
let dens = vec![1e15; MDEPTH];
let abso1 = vec![1e-8; MDEPTH];
let scat1 = vec![1e-9; MDEPTH];
let emis1 = vec![1e-10; MDEPTH];
let fak1 = vec![0.5; MDEPTH];
let rad1 = vec![1e10; MDEPTH];
let dabt1 = vec![1e-12; MDEPTH];
let demt1 = vec![1e-14; MDEPTH];
let deldm = vec![0.001; MDEPTH];
let reint = vec![0.0; MDEPTH];
let redif = vec![0.0; MDEPTH];
let w = vec![0.01; 100];
let fh = vec![0.5; 100];
let fhd = vec![0.5; 100];
let freq = vec![1e15; 100];
let dsct1 = vec![1e-11; MDEPTH];
let dscn1 = vec![1e-16; MDEPTH];
let ijfr: Vec<i32> = (0..100).map(|i| (i + 1) as i32).collect();
let extrad = vec![0.0; 100];
let q0 = vec![0.0; 100];
let uu0 = vec![0.0; 100];
let params = RybmatParams {
ij: 0,
nd: 10,
dm: &dm,
temp: &temp,
dens: &dens,
abso1: &abso1,
scat1: &scat1,
emis1: &emis1,
fak1: &fak1,
rad1: &rad1,
dabt1: &dabt1,
demt1: &demt1,
deldm: &deldm,
isplin: 2, // Hermitian method
iubc: 0,
ibc: 1,
idisk: 0,
ifryb: 0,
reint: &reint,
redif: &redif,
w: &w,
freq: &freq,
fh: &fh,
fhd: &fhd,
icentr: 0,
ilbc: 0,
nretc: 0,
dsct1: &dsct1,
dscn1: &dscn1,
ijfr: &ijfr,
extrad: &extrad,
q0: &q0,
uu0: &uu0,
};
let result = rybmat(&params);
assert!(result.rb[0].is_finite());
assert!(result.vr[0].is_finite());
}
#[test]
fn test_rybmat_centered_difference() {
let dm: Vec<f64> = (0..MDEPTH).map(|i| 0.01 + i as f64 * 0.01).collect();
let temp = vec![10000.0; MDEPTH];
let dens = vec![1e15; MDEPTH];
let abso1 = vec![1e-8; MDEPTH];
let scat1 = vec![1e-9; MDEPTH];
let emis1 = vec![1e-10; MDEPTH];
let fak1 = vec![0.5; MDEPTH];
let rad1 = vec![1e10; MDEPTH];
let dabt1 = vec![1e-12; MDEPTH];
let demt1 = vec![1e-14; MDEPTH];
let deldm = vec![0.001; MDEPTH];
let reint = vec![0.0; MDEPTH];
let redif: Vec<f64> = (0..MDEPTH).map(|_| 0.1).collect();
let w = vec![0.01; 100];
let fh = vec![0.5; 100];
let fhd = vec![0.5; 100];
let freq = vec![1e15; 100];
let dsct1 = vec![1e-11; MDEPTH];
let dscn1 = vec![1e-16; MDEPTH];
let ijfr: Vec<i32> = (0..100).map(|i| (i + 1) as i32).collect();
let extrad = vec![0.0; 100];
let q0 = vec![0.0; 100];
let uu0 = vec![0.0; 100];
let params = RybmatParams {
ij: 0,
nd: 10,
dm: &dm,
temp: &temp,
dens: &dens,
abso1: &abso1,
scat1: &scat1,
emis1: &emis1,
fak1: &fak1,
rad1: &rad1,
dabt1: &dabt1,
demt1: &demt1,
deldm: &deldm,
isplin: 1,
iubc: 0,
ibc: 1,
idisk: 0,
ifryb: 0,
reint: &reint,
redif: &redif,
w: &w,
freq: &freq,
fh: &fh,
fhd: &fhd,
icentr: 1, // 使用中心差分
ilbc: 0,
nretc: 0,
dsct1: &dsct1,
dscn1: &dscn1,
ijfr: &ijfr,
extrad: &extrad,
q0: &q0,
uu0: &uu0,
};
let result = rybmat(&params);
assert!(result.rb[0].is_finite());
assert!(result.vr[0].is_finite());
assert!(result.vc[5].is_finite());
}
#[test]
fn test_rybmat_ifryb_branch() {
let dm: Vec<f64> = (0..MDEPTH).map(|i| 0.01 + i as f64 * 0.01).collect();
let temp = vec![10000.0; MDEPTH];
let dens = vec![1e15; MDEPTH];
let abso1 = vec![1e-8; MDEPTH];
let scat1 = vec![1e-9; MDEPTH];
let emis1 = vec![1e-10; MDEPTH];
let fak1 = vec![0.5; MDEPTH];
let rad1 = vec![1e10; MDEPTH];
let dabt1 = vec![1e-12; MDEPTH];
let demt1 = vec![1e-14; MDEPTH];
let deldm = vec![0.001; MDEPTH];
let reint = vec![0.0; MDEPTH];
let redif = vec![0.0; MDEPTH];
let w = vec![0.01; 100];
let fh = vec![0.5; 100];
let fhd = vec![0.5; 100];
let freq = vec![1e15; 100];
let dsct1 = vec![1e-11; MDEPTH];
let dscn1 = vec![1e-16; MDEPTH];
let ijfr: Vec<i32> = (0..100).map(|i| (i + 1) as i32).collect();
let extrad = vec![0.0; 100];
let q0 = vec![0.0; 100];
let uu0 = vec![0.0; 100];
let params = RybmatParams {
ij: 0,
nd: 10,
dm: &dm,
temp: &temp,
dens: &dens,
abso1: &abso1,
scat1: &scat1,
emis1: &emis1,
fak1: &fak1,
rad1: &rad1,
dabt1: &dabt1,
demt1: &demt1,
deldm: &deldm,
isplin: 1,
iubc: 0,
ibc: 1,
idisk: 0,
ifryb: 1, // 启用 Rybicki 特殊边界条件
reint: &reint,
redif: &redif,
w: &w,
freq: &freq,
fh: &fh,
fhd: &fhd,
icentr: 0,
ilbc: 0,
nretc: 0,
dsct1: &dsct1,
dscn1: &dscn1,
ijfr: &ijfr,
extrad: &extrad,
q0: &q0,
uu0: &uu0,
};
let result = rybmat(&params);
assert!(result.rb[0].is_finite());
assert!(result.vr[0].is_finite());
}
#[test]
fn test_rybmat_disk_mode() {
let dm: Vec<f64> = (0..MDEPTH).map(|i| 0.01 + i as f64 * 0.01).collect();
let temp = vec![10000.0; MDEPTH];
let dens = vec![1e15; MDEPTH];
let abso1 = vec![1e-8; MDEPTH];
let scat1 = vec![1e-9; MDEPTH];
let emis1 = vec![1e-10; MDEPTH];
let fak1 = vec![0.5; MDEPTH];
let rad1 = vec![1e10; MDEPTH];
let dabt1 = vec![1e-12; MDEPTH];
let demt1 = vec![1e-14; MDEPTH];
let deldm = vec![0.001; MDEPTH];
let reint = vec![0.0; MDEPTH];
let redif = vec![0.0; MDEPTH];
let w = vec![0.01; 100];
let fh = vec![0.5; 100];
let fhd = vec![0.5; 100];
let freq = vec![1e15; 100];
let dsct1 = vec![1e-11; MDEPTH];
let dscn1 = vec![1e-16; MDEPTH];
let ijfr: Vec<i32> = (0..100).map(|i| (i + 1) as i32).collect();
let extrad = vec![0.0; 100];
let q0 = vec![0.0; 100];
let uu0 = vec![0.0; 100];
let params = RybmatParams {
ij: 0,
nd: 10,
dm: &dm,
temp: &temp,
dens: &dens,
abso1: &abso1,
scat1: &scat1,
emis1: &emis1,
fak1: &fak1,
rad1: &rad1,
dabt1: &dabt1,
demt1: &demt1,
deldm: &deldm,
isplin: 1,
iubc: 0,
ibc: 4, // 磁盘模式
idisk: 1,
ifryb: 0,
reint: &reint,
redif: &redif,
w: &w,
freq: &freq,
fh: &fh,
fhd: &fhd,
icentr: 0,
ilbc: 0,
nretc: 0,
dsct1: &dsct1,
dscn1: &dscn1,
ijfr: &ijfr,
extrad: &extrad,
q0: &q0,
uu0: &uu0,
};
let result = rybmat(&params);
assert!(result.rb[0].is_finite());
assert!(result.vr[0].is_finite());
}
#[test]
fn test_rybmat_radiative_equilibrium() {
let dm: Vec<f64> = (0..MDEPTH).map(|i| 0.01 + i as f64 * 0.01).collect();
let temp = vec![10000.0; MDEPTH];
let dens = vec![1e15; MDEPTH];
let abso1 = vec![1e-8; MDEPTH];
let scat1 = vec![1e-9; MDEPTH];
let emis1 = vec![1e-10; MDEPTH];
let fak1 = vec![0.5; MDEPTH];
let rad1 = vec![1e10; MDEPTH];
let dabt1 = vec![1e-12; MDEPTH];
let demt1 = vec![1e-14; MDEPTH];
let deldm = vec![0.001; MDEPTH];
let reint: Vec<f64> = (0..MDEPTH).map(|_| 0.1).collect();
let redif: Vec<f64> = (0..MDEPTH).map(|_| 0.1).collect();
let w = vec![0.01; 100];
let fh = vec![0.5; 100];
let fhd = vec![0.5; 100];
let freq = vec![1e15; 100];
let dsct1 = vec![1e-11; MDEPTH];
let dscn1 = vec![1e-16; MDEPTH];
let ijfr: Vec<i32> = (0..100).map(|i| (i + 1) as i32).collect();
let extrad = vec![0.0; 100];
let q0 = vec![0.0; 100];
let uu0 = vec![0.0; 100];
let params = RybmatParams {
ij: 0,
nd: 10,
dm: &dm,
temp: &temp,
dens: &dens,
abso1: &abso1,
scat1: &scat1,
emis1: &emis1,
fak1: &fak1,
rad1: &rad1,
dabt1: &dabt1,
demt1: &demt1,
deldm: &deldm,
isplin: 1,
iubc: 0,
ibc: 1,
idisk: 0,
ifryb: 0,
reint: &reint,
redif: &redif,
w: &w,
freq: &freq,
fh: &fh,
fhd: &fhd,
icentr: 0,
ilbc: 0,
nretc: 0,
dsct1: &dsct1,
dscn1: &dscn1,
ijfr: &ijfr,
extrad: &extrad,
q0: &q0,
uu0: &uu0,
};
let result = rybmat(&params);
// 检查辐射平衡相关输出
assert!(result.vb[0].is_finite());
assert!(result.wr[0].is_finite());
assert!(result.va[5].is_finite());
}
}
+136
View File
@@ -0,0 +1,136 @@
//! 计算密度对温度的导数。
//!
//! 重构自 TLUSTY `setdrt.f`
use crate::state::constants::MDEPTH;
use crate::state::model::{ModPar, PressR, Rhoder};
/// 计算密度对温度的导数。
///
/// 使用有限差分法计算每个深度点的密度对温度的导数。
///
/// # 参数
///
/// * `modpar` - 模型参数(包含温度)
/// * `pressr` - 压力相关数组(包含总压力)
/// * `rhoder` - 输出:密度对温度的导数
/// * `nd` - 深度点数
/// * `rhoeos_fn` - 状态方程函数,计算给定温度和压力下的密度
///
/// # 示例
///
/// ```
/// use tlusty_rust::state::model::{ModPar, PressR, Rhoder};
/// use tlusty_rust::math::setdrt::setdrt;
///
/// let mut modpar = ModPar::default();
/// let mut pressr = PressR::default();
/// let mut rhoder = Rhoder::default();
/// modpar.temp[0] = 10000.0;
/// modpar.temp[1] = 9500.0;
/// pressr.ptotal[0] = 1e5;
/// pressr.ptotal[1] = 1.1e5;
///
/// // 简单的状态方程函数
/// fn simple_rhoeos(t: f64, p: f64) -> f64 {
/// p / (1.380649e-16 * t) * 1.67333e-24 / 2.3
/// }
///
/// setdrt(&modpar, &pressr, &mut rhoder, 2, simple_rhoeos);
/// ```
pub fn setdrt<F>(
modpar: &ModPar,
pressr: &PressR,
rhoder: &mut Rhoder,
nd: usize,
rhoeos_fn: F,
) where
F: Fn(f64, f64) -> f64,
{
const DDTMIN: f64 = 0.0;
const DDTPLU: f64 = 0.001;
for id in 0..nd {
let t = modpar.temp[id];
let p = pressr.ptotal[id];
let rho1 = rhoeos_fn(t * (1.0 - DDTMIN), p);
let rho2 = rhoeos_fn(t * (1.0 + DDTPLU), p);
rhoder.drhodt[id] = (rho2 - rho1) / t / (DDTMIN + DDTPLU);
}
}
#[cfg(test)]
mod tests {
use super::*;
/// 简单的理想气体状态方程
fn ideal_gas_rhoeos(t: f64, p: f64) -> f64 {
// P = n k T => n = P / (k T)
// rho = n * m = P / (k T) * m
// 使用平均分子质量 ~2.3 * m_H
let wmol0 = 1.67333e-24 / 2.3;
p / (1.380649e-16 * t) * wmol0
}
#[test]
fn test_setdrt_basic() {
let mut modpar = ModPar::default();
let mut pressr = PressR::default();
let mut rhoder = Rhoder::default();
// 设置两个深度点
modpar.temp[0] = 10000.0;
modpar.temp[1] = 9500.0;
pressr.ptotal[0] = 1.0e5;
pressr.ptotal[1] = 1.1e5;
setdrt(&modpar, &pressr, &mut rhoder, 2, ideal_gas_rhoeos);
// 检查导数不为零
assert!(rhoder.drhodt[0].abs() > 0.0);
assert!(rhoder.drhodt[1].abs() > 0.0);
}
#[test]
fn test_setdrt_analytical() {
let mut modpar = ModPar::default();
let mut pressr = PressR::default();
let mut rhoder = Rhoder::default();
modpar.temp[0] = 10000.0;
pressr.ptotal[0] = 1.0e5;
setdrt(&modpar, &pressr, &mut rhoder, 1, ideal_gas_rhoeos);
// 对于理想气体: rho = P * const / T
// drho/dT = -P * const / T^2
let wmol0 = 1.67333e-24 / 2.3;
let bolk = 1.380649e-16;
let expected = -pressr.ptotal[0] * wmol0 / (bolk * modpar.temp[0] * modpar.temp[0]);
// 有限差分结果应该接近解析解
assert!((rhoder.drhodt[0] - expected).abs() < 1e-12 * expected.abs().max(1.0));
}
#[test]
fn test_setdrt_multiple_points() {
let mut modpar = ModPar::default();
let mut pressr = PressR::default();
let mut rhoder = Rhoder::default();
// 设置5个深度点
for i in 0..5 {
modpar.temp[i] = 10000.0 - i as f64 * 500.0;
pressr.ptotal[i] = 1.0e5 * (1.0 + i as f64 * 0.1);
}
setdrt(&modpar, &pressr, &mut rhoder, 5, ideal_gas_rhoeos);
// 所有导数应该为负值(温度升高,密度降低)
for i in 0..5 {
assert!(rhoder.drhodt[i] < 0.0, "drhodt[{}] should be negative", i);
}
}
}
+274
View File
@@ -0,0 +1,274 @@
//! 频率表插值。
//!
//! 重构自 TLUSTY `tabint.f`
use crate::state::constants::{MFREQ, MFREQC, MFRTAB, MTABR, MTABT};
/// 频率表插值系数。
/// 对应 COMMON /intcff/
#[derive(Debug, Clone, Default)]
pub struct IntCff {
/// 插值系数 yint = 1/log10(frtab(j+1)/frtab(j))
pub yint: Vec<f64>,
/// 插值下标索引 jint
pub jint: Vec<i32>,
}
impl IntCff {
pub fn new() -> Self {
Self {
yint: vec![0.0; MFREQ],
jint: vec![0; MFREQ],
}
}
}
/// 不透明度表数据。
/// 包含温度、密度、频率网格和不透明度值。
#[derive(Debug, Clone)]
pub struct OpacTable {
/// 频率表 (Hz)
pub frtab: Vec<f64>,
/// 温度向量 (ln T)
pub tempvec: Vec<f64>,
/// 密度矩阵 (ln rho) [温度索引][密度索引]
pub rhomat: Vec<Vec<f64>>,
/// 不透明度表 (ln kappa) [温度索引][密度索引][频率索引]
pub absopac: Vec<Vec<Vec<f64>>>,
/// 每个温度点的密度数
pub numrh: Vec<i32>,
/// 频率数
pub numfreq: usize,
/// 温度数
pub numtemp: usize,
/// 最大频率值
pub frtabm: f64,
}
impl Default for OpacTable {
fn default() -> Self {
Self {
frtab: vec![0.0; MFRTAB],
tempvec: vec![0.0; MTABT],
rhomat: vec![vec![0.0; MTABR]; MTABT],
absopac: vec![vec![vec![0.0; MFRTAB]; MTABR]; MTABT],
numrh: vec![0; MTABT],
numfreq: 0,
numtemp: 0,
frtabm: 0.0,
}
}
}
/// TABINT 输入参数。
pub struct TabintParams<'a> {
/// 控制参数:IOPTAB < 0 表示使用精确表频率
pub ioptab: i32,
/// 频率设置标志
pub ifrset: i32,
/// 当前频率数
pub nfreq: usize,
/// 连续频率数
pub nfreqc: usize,
/// 频率数组
pub freq: &'a mut [f64],
/// 频率映射索引
pub ijfr: &'a mut [i32],
/// 反向映射索引
pub jik: &'a mut [i32],
/// ALI 标志
pub ijali: &'a mut [i32],
/// 权重数组
pub w: &'a mut [f64],
}
/// 频率表插值。
///
/// 设置频率插值系数或直接使用表频率。
///
/// # 参数
///
/// * `params` - 输入/输出参数
/// * `opac_table` - 不透明度表数据(可变,会被插值修改)
/// * `intcff` - 输出:插值系数
pub fn tabint(params: &mut TabintParams, opac_table: &mut OpacTable, intcff: &mut IntCff) {
let numfreq = opac_table.numfreq;
let fr1 = opac_table.frtab[0];
let fr2 = opac_table.frtab[numfreq - 1];
if params.ioptab < 0 && params.ifrset == 0 {
// 频率精确等于表值
params.nfreq = numfreq;
params.nfreqc = numfreq;
for ij in 0..numfreq {
params.freq[ij] = opac_table.frtab[ij];
params.ijfr[ij] = ij as i32 + 1; // Fortran 1-indexed
params.jik[ij] = ij as i32 + 1;
params.ijali[ij] = 1;
}
// 设置权重
if numfreq > 0 {
params.w[0] = 0.5 * (opac_table.frtab[0] - opac_table.frtab[1]);
params.w[numfreq - 1] = 0.5 * (opac_table.frtab[numfreq - 2] - opac_table.frtab[numfreq - 1]);
}
for ij in 1..numfreq - 1 {
params.w[ij] = 0.5 * (opac_table.frtab[ij - 1] - opac_table.frtab[ij + 1]);
}
} else {
// 频率已设置,建立插值系数
// 使用二分法查找插值位置
for ij in 0..params.nfreq {
let xint = params.freq[ij];
let (jl, _ju) = locate_interval(&opac_table.frtab, numfreq, xint, fr1, fr2);
let mut j = jl;
if j == numfreq as i32 - 1 {
j -= 1;
}
if j == 0 {
j += 1;
}
let j_idx = j as usize;
intcff.jint[ij] = j;
intcff.yint[ij] = 1.0 / (opac_table.frtab[j_idx + 1] / opac_table.frtab[j_idx]).log10();
}
// 插值不透明度表
interpolate_opacity(params, opac_table, intcff, fr1, fr2);
}
}
/// 二分法查找区间。
///
/// 返回 (jl, ju),其中 xint 在 [frtab[jl], frtab[ju]] 之间
fn locate_interval(
frtab: &[f64],
numfreq: usize,
xint: f64,
fr1: f64,
fr2: f64,
) -> (i32, i32) {
let mut jl: i32 = 0;
let mut ju: i32 = numfreq as i32;
let ascending = fr2 > fr1;
while ju - jl > 1 {
let jm = (ju + jl) / 2;
let jm_idx = jm as usize;
if (ascending && xint > frtab[jm_idx]) || (!ascending && xint < frtab[jm_idx]) {
jl = jm;
} else {
ju = jm;
}
}
(jl, ju)
}
/// 对不透明度表进行插值。
///
/// 修改 `opac_table.absopac` 数组,将原始表频率的不透明度插值到目标频率网格。
fn interpolate_opacity(
params: &mut TabintParams,
opac_table: &mut OpacTable,
intcff: &IntCff,
fr1: f64,
fr2: f64,
) {
let numtemp = opac_table.numtemp;
let numfreq = opac_table.numfreq;
let frtab = opac_table.frtab.clone();
// 创建临时数组存储原始不透明度
let mut absort = vec![0.0f64; numfreq];
for it in 0..numtemp {
let numrho = opac_table.numrh[it] as usize;
for ir in 0..numrho {
// 复制原始数据到临时数组
for k in 0..numfreq {
absort[k] = opac_table.absopac[it][ir][k];
}
// 对每个频率进行插值,结果存回 absopac
for ij in 0..params.nfreq {
let j = intcff.jint[ij] as usize;
// Fortran: rc=(absort(j+1)-absort(j))*yint(ij)
// Fortran jint 是 1-indexedj+1 在 Rust 中是 j
let rc = (absort[j] - absort[j - 1]) * intcff.yint[ij];
// Fortran: opac=rc*log10(freq(ij)/frtab(j))+absort(j)
let opac = rc * (params.freq[ij] / frtab[j - 1]).log10() + absort[j - 1];
opac_table.absopac[it][ir][ij] = opac;
}
}
}
// 重置超出频率范围的不透明度
for ij in 0..params.nfreq {
if params.freq[ij] < fr2 * 0.99 || params.freq[ij] > fr1 * 1.01 {
// 超出范围,设置为零
for it in 0..numtemp {
let numrho = opac_table.numrh[it] as usize;
for ir in 0..numrho {
opac_table.absopac[it][ir][ij] = 0.0;
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_locate_interval_ascending() {
let frtab = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let numfreq = 5;
let fr1 = frtab[0];
let fr2 = frtab[numfreq - 1];
// 测试中间值
let (jl, _ju) = locate_interval(&frtab, numfreq, 2.5, fr1, fr2);
assert_eq!(jl, 1);
let (jl, _ju) = locate_interval(&frtab, numfreq, 3.5, fr1, fr2);
assert_eq!(jl, 2);
}
#[test]
fn test_locate_interval_descending() {
let frtab = vec![5.0, 4.0, 3.0, 2.0, 1.0];
let numfreq = 5;
let fr1 = frtab[0];
let fr2 = frtab[numfreq - 1];
// 降序数组
let (jl, _ju) = locate_interval(&frtab, numfreq, 2.5, fr1, fr2);
assert_eq!(jl, 2);
}
#[test]
fn test_intcff_creation() {
let intcff = IntCff::new();
assert_eq!(intcff.yint.len(), MFREQ);
assert_eq!(intcff.jint.len(), MFREQ);
}
#[test]
fn test_opac_table_default() {
let table = OpacTable::default();
assert_eq!(table.frtab.len(), MFRTAB);
assert_eq!(table.tempvec.len(), MTABT);
assert_eq!(table.rhomat.len(), MTABT);
assert_eq!(table.numfreq, 0);
assert_eq!(table.numtemp, 0);
}
}
+333
View File
@@ -0,0 +1,333 @@
//! 计算参考光学深度处的各种物理量。
//!
//! 重构自 TLUSTY `taufr1.f`
use crate::state::constants::{HK, MDEPTH, MFREQ};
/// TAUFR1 输入参数结构体。
pub struct Taufr1Params<'a> {
/// 频率索引 (0-indexed)
pub ij: usize,
/// 深度点数
pub nd: usize,
/// 频率数组
pub freq: &'a [f64],
/// 深度数组 (柱质量密度)
pub dm: &'a [f64],
/// 温度数组
pub temp: &'a [f64],
/// 吸收系数数组 (包含散射)
pub abso1: &'a [f64],
/// 散射系数数组
pub scat1: &'a [f64],
/// 发射系数数组
pub emis1: &'a [f64],
/// 总吸收系数 (用于光学深度计算)
pub absot: &'a [f64],
/// 密度倒数 (1/dens)
pub dens1: &'a [f64],
/// 深度增量 (柱质量)
pub deldm: &'a [f64],
/// 深度增量 (区域)
pub deldmz: &'a [f64],
/// 第一个深度点的增量
pub dedm1: f64,
/// 频率索引映射
pub kij: &'a [i32],
/// 总频率数
pub nfreq: usize,
/// 辐射强度矩阵 [频率][深度]
pub rad: &'a [Vec<f64>],
}
/// TAUFR1 输出结构体。
#[derive(Debug, Clone)]
pub struct Taufr1Result {
/// 参考深度 (柱质量)
pub dmref: f64,
/// 参考温度
pub tref: f64,
/// 参考吸收系数
pub abref: f64,
/// 参考散射系数
pub scref: f64,
/// 参考源函数
pub stref: f64,
/// 参考光学深度
pub tauef: f64,
/// 参考发射率参数
pub epref: f64,
/// 参考Planck函数
pub bref: f64,
/// Y参考值
pub yref: f64,
/// 波长 (Å)
pub alm: f64,
/// 辐射强度
pub r1: f64,
/// Rosseland平均相关
pub rb1: f64,
/// 源函数相关
pub rs1: f64,
/// 光学深度增量数组
pub dt: Vec<f64>,
/// 参考深度索引
pub iref: usize,
}
/// 计算参考光学深度处的各种物理量。
///
/// 这个函数计算在给定频率处的光学深度分布,并找到参考光学深度(τ=1)处的
/// 各种插值物理量。
///
/// # 参数
///
/// * `params` - 输入参数结构体
///
/// # 返回值
///
/// 包含各种参考量的 Taufr1Result 结构体
pub fn taufr1(params: &Taufr1Params) -> Taufr1Result {
const TAUREF: f64 = 1.0;
const YCON: f64 = 1.68638e-10;
const CAS: f64 = 2.997925e18; // 光速 Å/s
let nd = params.nd;
let ij = params.ij;
let fr = params.freq[ij];
// 局部数组
let mut st0 = vec![0.0f64; MDEPTH];
let mut ss0 = vec![0.0f64; MDEPTH];
let mut ab0 = vec![0.0f64; MDEPTH];
let mut tau = vec![0.0f64; MDEPTH];
let mut taus = vec![0.0f64; MDEPTH];
let mut dt = vec![0.0f64; MDEPTH];
// 计算源函数和散射参数
for id in 0..nd {
ab0[id] = params.abso1[id];
// 避免除零
let heat = ab0[id] - params.scat1[id];
if heat > 1e-100 {
st0[id] = params.emis1[id] / heat;
} else {
st0[id] = 0.0;
}
// 设置最小值避免零源函数
if st0[id] == 0.0 {
st0[id] = 1e-20 * params.scat1[id];
}
ss0[id] = -params.scat1[id] / ab0[id];
}
// 计算边界处的光学深度
tau[0] = params.abso1[0] * params.dedm1;
// 避免负的平方根
let eps0 = (ab0[0] * (ab0[0] - params.scat1[0]).max(0.0)).sqrt();
taus[0] = 3.0f64.sqrt() * eps0 * params.dedm1;
let mut iref = 1;
let mut irefs = 1;
// 计算光学深度分布
for id in 0..nd - 1 {
dt[id] = params.deldmz[id] * (params.absot[id + 1] + params.absot[id]);
tau[id + 1] = tau[id] + dt[id];
// 避免负的平方根
let eps0 = (ab0[id] * (ab0[id] - params.scat1[id]).max(0.0)).sqrt();
let eps1 = (ab0[id + 1] * (ab0[id + 1] - params.scat1[id + 1]).max(0.0)).sqrt();
let dts = params.deldm[id] * (eps0 * params.dens1[id] + eps1 * params.dens1[id + 1]) * 3.0f64.sqrt();
taus[id + 1] = taus[id] + dts;
// 找到参考光学深度位置
if tau[id] <= TAUREF && tau[id + 1] > TAUREF {
iref = id + 1;
}
if taus[id] <= TAUREF && taus[id + 1] > TAUREF {
irefs = id + 1;
}
}
// 处理边界情况
if iref == 1 && tau[nd - 1] <= TAUREF {
iref = nd;
}
if irefs == 1 && taus[nd - 1] <= TAUREF {
irefs = nd;
}
// 使用散射光学深度作为参考
let iref_use = irefs;
// 在参考光学深度处进行对数插值
let (dmref, tref, abref, scref, stref, tauef) = if iref_use < nd {
let t0 = (taus[iref_use] / taus[iref_use - 1]).ln();
let x0 = (taus[iref_use] / TAUREF).ln() / t0;
let x1 = (TAUREF / taus[iref_use - 1]).ln() / t0;
let dmref = (params.dm[iref_use - 1].ln() * x0 + params.dm[iref_use].ln() * x1).exp();
let tref = (params.temp[iref_use - 1].ln() * x0 + params.temp[iref_use].ln() * x1).exp();
let abref = (ab0[iref_use - 1].ln() * x0 + ab0[iref_use].ln() * x1).exp();
let scref = (params.scat1[iref_use - 1].ln() * x0 + params.scat1[iref_use].ln() * x1).exp();
let stref = (st0[iref_use - 1].ln() * x0 + st0[iref_use].ln() * x1).exp();
let tauef = (tau[iref_use - 1].ln() * x0 + tau[iref_use].ln() * x1).exp();
(dmref, tref, abref, scref, stref, tauef)
} else {
(
params.dm[nd - 1],
params.temp[nd - 1],
ab0[nd - 1],
params.scat1[nd - 1],
st0[nd - 1],
tau[nd - 1],
)
};
// 计算发射率参数
let epref = (abref - scref) / abref;
// 计算Planck函数(避免溢出)
let bref = if HK * fr / tref < 200.0 {
1.4743e-2 * (fr * 1e-15).powi(3) / ((HK * fr / tref).exp() - 1.0)
} else {
0.0
};
// 计算Y参考值
let taur = if tauef > tauef * tauef {
tauef
} else {
tauef * tauef
};
let yref = 4.0 * YCON * tref * taur;
// 波长
let alm = CAS / fr;
// 辐射强度
let kij_val = params.kij[ij] as usize;
let r1 = if kij_val > 0 && kij_val <= params.nfreq {
params.rad[params.nfreq - kij_val][0]
} else {
0.0
};
// Rosseland和源函数相关量
let (rb1, rs1) = if epref >= 0.0 {
(epref.sqrt() * bref, epref.sqrt() * stref)
} else {
(0.0, 0.0)
};
Taufr1Result {
dmref,
tref,
abref,
scref,
stref,
tauef,
epref,
bref,
yref,
alm,
r1,
rb1,
rs1,
dt,
iref: iref_use,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_taufr1_basic() {
let freq: Vec<f64> = vec![1e15; MFREQ];
let dm: Vec<f64> = vec![0.01; MDEPTH];
let temp: Vec<f64> = vec![10000.0; MDEPTH];
let abso1: Vec<f64> = vec![1e-8; MDEPTH];
let scat1: Vec<f64> = vec![1e-9; MDEPTH];
let emis1: Vec<f64> = vec![1e-10; MDEPTH];
let absot: Vec<f64> = vec![1e-8; MDEPTH];
let dens1: Vec<f64> = vec![1e15; MDEPTH];
let deldm: Vec<f64> = vec![0.001; MDEPTH];
let deldmz: Vec<f64> = vec![0.001; MDEPTH];
let kij: Vec<i32> = vec![0; MFREQ];
let rad: Vec<Vec<f64>> = vec![vec![1e10; MDEPTH]; MFREQ];
let params = Taufr1Params {
ij: 0,
nd: 10,
freq: &freq,
dm: &dm,
temp: &temp,
abso1: &abso1,
scat1: &scat1,
emis1: &emis1,
absot: &absot,
dens1: &dens1,
deldm: &deldm,
deldmz: &deldmz,
dedm1: 0.001,
kij: &kij,
nfreq: 100,
rad: &rad,
};
let result = taufr1(&params);
// 检查结果合理性
assert!(result.alm > 0.0);
assert!(result.tref > 0.0);
assert!(result.dt.len() >= 10);
}
#[test]
fn test_taufr1_wavelength() {
let freq: Vec<f64> = vec![1e15; MFREQ];
let dm: Vec<f64> = vec![0.01; MDEPTH];
let temp: Vec<f64> = vec![10000.0; MDEPTH];
let abso1: Vec<f64> = vec![1e-8; MDEPTH];
let scat1: Vec<f64> = vec![1e-9; MDEPTH];
let emis1: Vec<f64> = vec![1e-10; MDEPTH];
let absot: Vec<f64> = vec![1e-8; MDEPTH];
let dens1: Vec<f64> = vec![1e15; MDEPTH];
let deldm: Vec<f64> = vec![0.001; MDEPTH];
let deldmz: Vec<f64> = vec![0.001; MDEPTH];
let kij: Vec<i32> = vec![0; MFREQ];
let rad: Vec<Vec<f64>> = vec![vec![1e10; MDEPTH]; MFREQ];
let params = Taufr1Params {
ij: 0,
nd: 10,
freq: &freq,
dm: &dm,
temp: &temp,
abso1: &abso1,
scat1: &scat1,
emis1: &emis1,
absot: &absot,
dens1: &dens1,
deldm: &deldm,
deldmz: &deldmz,
dedm1: 0.001,
kij: &kij,
nfreq: 100,
rad: &rad,
};
let result = taufr1(&params);
// 波长 = c / nu = 2.997925e18 / 1e15 = 2997.925 Å
assert!((result.alm - 2997.925).abs() < 0.1);
}
}