重构3
This commit is contained in:
+1346
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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(¶ms);
|
||||
|
||||
// 当 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(¶ms);
|
||||
|
||||
// 检查 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(¶ms);
|
||||
|
||||
// 检查结果有限
|
||||
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(¶ms);
|
||||
|
||||
// 检查 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(¶ms);
|
||||
|
||||
// 所有 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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(¶ms);
|
||||
|
||||
// 禁用时所有输出应为 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(¶ms);
|
||||
|
||||
// 检查组占据数
|
||||
// 组 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(¶ms);
|
||||
|
||||
// 检查相对占据数
|
||||
// 能级 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(¶ms);
|
||||
|
||||
// 组 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(¶ms);
|
||||
|
||||
// 组占据数应被归零
|
||||
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(¶ms);
|
||||
|
||||
// 检查 rpop0 被设置
|
||||
for grp_idx in 0..3 {
|
||||
assert!(result.rpop0[grp_idx][0] >= 0.0);
|
||||
assert!(result.rpop0[grp_idx][0].is_finite());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(¶ms, &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(¶ms, &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(¶ms, &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(¶ms1, &mut prof1);
|
||||
linspl(¶ms2, &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(¶ms1, &mut prof1);
|
||||
linspl(¶ms2, &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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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 展宽辅助函数。
|
||||
///
|
||||
|
||||
@@ -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(¶ms);
|
||||
|
||||
// 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(¶ms);
|
||||
|
||||
// 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(¶ms);
|
||||
|
||||
// 超过 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(¶ms);
|
||||
|
||||
// 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(¶ms);
|
||||
|
||||
// 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(¶ms);
|
||||
|
||||
// 应该使用 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(¶ms);
|
||||
|
||||
// 应该使用默认值 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(¶ms);
|
||||
|
||||
// 目前 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(¶ms);
|
||||
|
||||
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(¶ms);
|
||||
|
||||
// Quasi 模式下 fac = 1
|
||||
assert!(result.is_finite());
|
||||
}
|
||||
}
|
||||
@@ -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) 为 0,gzr(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);
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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(¶ms, &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]);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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(¶ms);
|
||||
|
||||
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(¶ms);
|
||||
|
||||
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(¶ms);
|
||||
|
||||
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(¶ms);
|
||||
|
||||
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(¶ms);
|
||||
|
||||
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(¶ms);
|
||||
|
||||
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(¶ms);
|
||||
|
||||
// 检查辐射平衡相关输出
|
||||
assert!(result.vb[0].is_finite());
|
||||
assert!(result.wr[0].is_finite());
|
||||
assert!(result.va[5].is_finite());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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-indexed,j+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);
|
||||
}
|
||||
}
|
||||
@@ -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(¶ms);
|
||||
|
||||
// 检查结果合理性
|
||||
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(¶ms);
|
||||
|
||||
// 波长 = c / nu = 2.997925e18 / 1e15 = 2997.925 Å
|
||||
assert!((result.alm - 2997.925).abs() < 0.1);
|
||||
}
|
||||
}
|
||||
@@ -339,9 +339,263 @@ impl VoiPar {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ATOMAS - 原子质量额外存储
|
||||
// ============================================================================
|
||||
|
||||
/// 原子质量额外存储。
|
||||
/// 对应 COMMON /ATOMAS/
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AtoMas {
|
||||
pub amas: Vec<f64>,
|
||||
}
|
||||
|
||||
impl Default for AtoMas {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
amas: vec![0.0; 100],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TOPCS - 截面拟合参数
|
||||
// ============================================================================
|
||||
|
||||
/// 截面拟合参数。
|
||||
/// 对应 COMMON /TOPCS/
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TopCs {
|
||||
pub ctop: Vec<Vec<f64>>,
|
||||
pub xtop: Vec<Vec<f64>>,
|
||||
}
|
||||
|
||||
impl Default for TopCs {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
ctop: vec![vec![0.0; MCROSS]; MFIT],
|
||||
xtop: vec![vec![0.0; MCROSS]; MFIT],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TABCOL - 碰撞率表
|
||||
// ============================================================================
|
||||
|
||||
/// 碰撞率表。
|
||||
/// 对应 COMMON /TABCOL/
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TabCol {
|
||||
pub ctemp: Vec<Vec<Vec<f64>>>,
|
||||
pub crate_data: Vec<Vec<Vec<f64>>>,
|
||||
}
|
||||
|
||||
impl Default for TabCol {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
ctemp: vec![vec![vec![0.0; MCORAT]; MCFIT]; MXTCOL],
|
||||
crate_data: vec![vec![vec![0.0; MCORAT]; MCFIT]; MXTCOL],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TRACOR - 跃迁修正
|
||||
// ============================================================================
|
||||
|
||||
/// 跃迁修正标志。
|
||||
/// 对应 COMMON /TRACOR/
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TraCor {
|
||||
pub lexp: Vec<i32>,
|
||||
pub lali: Vec<i32>,
|
||||
}
|
||||
|
||||
impl Default for TraCor {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
lexp: vec![0; MTRANS],
|
||||
lali: vec![0; MTRANS],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TRAALI - ALI 跃迁标志
|
||||
// ============================================================================
|
||||
|
||||
/// ALI 跃迁标志。
|
||||
/// 对应 COMMON /TRAALI/
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct TraAli {
|
||||
pub nffix: i32,
|
||||
pub ifsub: i32,
|
||||
pub iflev: i32,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// AUXIND - 辅助索引
|
||||
// ============================================================================
|
||||
|
||||
/// 辅助索引。
|
||||
/// 对应 COMMON /AUXIND/
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct AuxInd {
|
||||
pub iath: i32,
|
||||
pub iathe: i32,
|
||||
pub ielh: i32,
|
||||
pub ielhm: i32,
|
||||
pub ielhe1: i32,
|
||||
pub ielhe2: i32,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// IONFIL - 离子文件映射
|
||||
// ============================================================================
|
||||
|
||||
/// 离子文件映射。
|
||||
/// 对应 COMMON /IONFIL/
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IonFil {
|
||||
pub fidata: Vec<String>,
|
||||
pub fiodf1: Vec<String>,
|
||||
pub fiodf2: Vec<String>,
|
||||
pub fibfcs: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for IonFil {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
fidata: vec![String::new(); MION],
|
||||
fiodf1: vec![String::new(); MION],
|
||||
fiodf2: vec![String::new(); MION],
|
||||
fibfcs: vec![String::new(); MION],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// IONDAT - 离子数据索引
|
||||
// ============================================================================
|
||||
|
||||
/// 离子数据索引。
|
||||
/// 对应 COMMON /IONDAT/
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IonDat {
|
||||
pub iati: Vec<i32>,
|
||||
pub izi: Vec<i32>,
|
||||
pub nlevs: Vec<i32>,
|
||||
pub nllim: Vec<i32>,
|
||||
}
|
||||
|
||||
impl Default for IonDat {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
iati: vec![0; MION],
|
||||
izi: vec![0; MION],
|
||||
nlevs: vec![0; MION],
|
||||
nllim: vec![0; MION],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// OSCHYD - 氢振子强度额外存储
|
||||
// ============================================================================
|
||||
|
||||
/// 氢振子强度额外存储。
|
||||
/// 对应 COMMON /OSCHYD/
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OscHyd {
|
||||
pub osh: [[f64; 20]; 20],
|
||||
}
|
||||
|
||||
impl Default for OscHyd {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
osh: [[0.0; 20]; 20],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PRINTP - 能级打印控制
|
||||
// ============================================================================
|
||||
|
||||
/// 能级打印控制。
|
||||
/// 对应 COMMON /PRINTP/
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PrintP {
|
||||
pub npgpop: i32,
|
||||
pub iipr: Vec<Vec<i32>>,
|
||||
pub typlev: Vec<String>,
|
||||
pub typion: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for PrintP {
|
||||
fn default() -> Self {
|
||||
let mpag = MLEVEL / 6 + 1;
|
||||
Self {
|
||||
npgpop: 0,
|
||||
iipr: vec![vec![0; mpag]; 6],
|
||||
typlev: vec![String::new(); MLEVEL],
|
||||
typion: vec![String::new(); MION],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TABMAX - 最大频率
|
||||
// ============================================================================
|
||||
|
||||
/// 最大频率。
|
||||
/// 对应 COMMON /TABMAX/
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct TabMax {
|
||||
pub frtabm: f64,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PRDPAR - PRD 参数
|
||||
// ============================================================================
|
||||
|
||||
/// PRD (部分频率重分布) 参数。
|
||||
/// 对应 COMMON /PRDPAR/
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PrdPar {
|
||||
pub doptr: Vec<Vec<f64>>,
|
||||
pub coher: Vec<Vec<f64>>,
|
||||
pub pjbar: Vec<Vec<f64>>,
|
||||
pub rjbar: Vec<Vec<f64>>,
|
||||
pub xpdiv: f64,
|
||||
pub iprd: Vec<i32>,
|
||||
pub itrtot: Vec<i32>,
|
||||
pub ntrprd: i32,
|
||||
pub ifprd: i32,
|
||||
}
|
||||
|
||||
impl Default for PrdPar {
|
||||
fn default() -> Self {
|
||||
const MTRPRD: usize = 5;
|
||||
Self {
|
||||
doptr: vec![vec![0.0; MDEPTH]; MTRPRD],
|
||||
coher: vec![vec![0.0; MDEPTH]; MTRPRD],
|
||||
pjbar: vec![vec![0.0; MDEPTH]; MTRPRD],
|
||||
rjbar: vec![vec![0.0; MDEPTH]; MTRPRD],
|
||||
xpdiv: 0.0,
|
||||
iprd: vec![0; MTRANS],
|
||||
itrtot: vec![0; MTRPRD],
|
||||
ntrprd: 0,
|
||||
ifprd: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HECRAT - 氦碰撞速率
|
||||
// ============================================================================
|
||||
// ============================================================================
|
||||
|
||||
/// 氦碰撞速率系数。
|
||||
/// 对应 COMMON /HECRAT/
|
||||
@@ -366,6 +620,18 @@ pub struct AtomicData {
|
||||
pub phoset: PhoSet,
|
||||
pub voipar: VoiPar,
|
||||
pub hecrat: HeCrat,
|
||||
pub atopas: AtoMas,
|
||||
pub topcs: TopCs,
|
||||
pub tabcol: TabCol,
|
||||
pub tracor: TraCor,
|
||||
pub traali: TraAli,
|
||||
pub auxind: AuxInd,
|
||||
pub ionfil: IonFil,
|
||||
pub iondat: IonDat,
|
||||
pub oschyd: OscHyd,
|
||||
pub printp: PrintP,
|
||||
pub tabmax: TabMax,
|
||||
pub prdpar: PrdPar,
|
||||
}
|
||||
|
||||
impl AtomicData {
|
||||
|
||||
@@ -94,6 +94,20 @@ impl Default for BasNum {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CENTRL - 中心点参数
|
||||
// ============================================================================
|
||||
|
||||
/// 中心点参数。
|
||||
/// 对应 COMMON /CENTRL/
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Centrl {
|
||||
/// 中心平面几何距离
|
||||
pub znd: f64,
|
||||
/// Z 修正迭代控制
|
||||
pub ifz0: i32,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// INPPAR - 输入参数
|
||||
// ============================================================================
|
||||
@@ -246,6 +260,128 @@ pub struct ConKey {
|
||||
pub idconz: i32,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// FIXDEN - 固定密度标志
|
||||
// ============================================================================
|
||||
|
||||
/// 固定密度标志。
|
||||
/// 对应 COMMON /FIXDEN/
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct FixDen {
|
||||
pub ifixde: i32,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// INVINT - 逆积分参数
|
||||
// ============================================================================
|
||||
|
||||
/// 逆积分参数。
|
||||
/// 对应 COMMON /INVINT/
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InvInt {
|
||||
pub xi2: Vec<f64>,
|
||||
pub xi3: Vec<f64>,
|
||||
}
|
||||
|
||||
impl Default for InvInt {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
xi2: vec![0.0; NLMX],
|
||||
xi3: vec![0.0; NLMX],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// OPCKEY - 不透明度控制键
|
||||
// ============================================================================
|
||||
|
||||
/// 不透明度控制键。
|
||||
/// 对应 COMMON /OPCKEY/
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct OpcKey {
|
||||
pub ncon: i32,
|
||||
pub iophl1: i32,
|
||||
pub iophl2: i32,
|
||||
pub iphe2c: i32,
|
||||
pub ifmoff: i32,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PSILIM - Ψ 限制参数
|
||||
// ============================================================================
|
||||
|
||||
/// Ψ 限制参数。
|
||||
/// 对应 COMMON /PSILIM/
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct PsiLim {
|
||||
pub dpsilg: f64,
|
||||
pub dpsilt: f64,
|
||||
pub dpsiln: f64,
|
||||
pub dpsild: f64,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// COMITE - 迭代控制键
|
||||
// ============================================================================
|
||||
|
||||
/// 迭代控制参数。
|
||||
/// 对应 COMMON /COMITE/
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Comite {
|
||||
pub ncfor1: i32,
|
||||
pub ncfor2: i32,
|
||||
pub nccoup: i32,
|
||||
pub ncitot: i32,
|
||||
pub ncfull: i32,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MLCONS - 混合长度常数
|
||||
// ============================================================================
|
||||
|
||||
/// 混合长度常数。
|
||||
/// 对应 COMMON /MLCONS/
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct MlCons {
|
||||
pub aconml: f64,
|
||||
pub bconml: f64,
|
||||
pub cconml: f64,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TAURSL - 光学深度表
|
||||
// ============================================================================
|
||||
|
||||
/// 光学深度表。
|
||||
/// 对应 COMMON /TAURSL/
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TaurSl {
|
||||
pub taurs: Vec<f64>,
|
||||
}
|
||||
|
||||
impl Default for TaurSl {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
taurs: vec![0.0; MDEPTH],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// IPRKEY - 打印键
|
||||
// ============================================================================
|
||||
|
||||
/// 打印键。
|
||||
/// 对应 COMMON /IPRKEY/
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct IprKey {
|
||||
pub iprybh: i32,
|
||||
pub ipelch: i32,
|
||||
pub ipeldo: i32,
|
||||
pub ipconf: i32,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// OPCPAR - 额外不透明度控制
|
||||
// ============================================================================
|
||||
@@ -254,6 +390,7 @@ pub struct ConKey {
|
||||
/// 对应 COMMON /OPCPAR/
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct OpcPar {
|
||||
pub iopadd: i32,
|
||||
pub iophmi: i32, // H⁻
|
||||
pub ioph2p: i32, // H₂⁺
|
||||
pub iophem: i32, // He⁻
|
||||
@@ -384,6 +521,31 @@ impl Default for Comptn {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// COMPTI - Compton 散射控制参数
|
||||
// ============================================================================
|
||||
|
||||
/// Compton 散射控制参数。
|
||||
/// 对应 COMMON /COMPTI/
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Compti {
|
||||
pub nedd: i32,
|
||||
pub nsti: i32,
|
||||
pub islab: i32,
|
||||
pub ilbc: i32,
|
||||
pub icompt: i32,
|
||||
pub icomst: i32,
|
||||
pub icomde: i32,
|
||||
pub icombc: i32,
|
||||
pub icmdra: i32,
|
||||
pub knish: i32,
|
||||
pub itcomp: i32,
|
||||
pub icomve: i32,
|
||||
pub icomrt: i32,
|
||||
pub ichcoo: i32,
|
||||
pub icomgr: i32,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 综合配置结构
|
||||
// ============================================================================
|
||||
@@ -401,6 +563,16 @@ pub struct TlustyConfig {
|
||||
pub prints: Prints,
|
||||
pub angles: Angles,
|
||||
pub comptn: Comptn,
|
||||
pub centrl: Centrl,
|
||||
pub compti: Compti,
|
||||
pub opckey: OpcKey,
|
||||
pub invint: InvInt,
|
||||
pub fixden: FixDen,
|
||||
pub psilim: PsiLim,
|
||||
pub comite: Comite,
|
||||
pub mlcons: MlCons,
|
||||
pub taursl: TaurSl,
|
||||
pub iprkey: IprKey,
|
||||
}
|
||||
|
||||
impl TlustyConfig {
|
||||
|
||||
@@ -20,6 +20,8 @@ pub const MTRANS: usize = 21000;
|
||||
pub const MDEPTH: usize = 100;
|
||||
/// 最大频率点数
|
||||
pub const MFREQ: usize = 135000;
|
||||
/// 最大频率点数 + 1
|
||||
pub const MFREQ1: usize = MFREQ; // 根据 BASICS.FOR: MFREQ1 = MFREQ
|
||||
/// 工作频率数组大小
|
||||
pub const MFREQP: usize = 220000;
|
||||
/// 连续谱频率点数
|
||||
@@ -66,6 +68,40 @@ pub const MTRAN3: usize = 1;
|
||||
pub const MCROSS: usize = MLEVEL + 5;
|
||||
/// 束缚-自由跃迁数
|
||||
pub const MBF: usize = MLEVEL;
|
||||
/// Gomez 氢不透明度表频率大小
|
||||
pub const MFHTAB: usize = 1000;
|
||||
/// Gomez 氢不透明度表温度大小
|
||||
pub const MTABTH: usize = 10;
|
||||
/// Gomez 氢不透明度表电子密度大小
|
||||
pub const MTABEH: usize = 10;
|
||||
/// 碰撞率表温度点数
|
||||
pub const MXTCOL: usize = 3;
|
||||
/// 碰撞率表拟合点数
|
||||
pub const MCFIT: usize = 10;
|
||||
/// 碰撞率表跃迁点数
|
||||
pub const MCORAT: usize = MTRANS;
|
||||
/// 不透明度表温度大小
|
||||
pub const MTABT: usize = 21;
|
||||
/// 不透明度表密度大小
|
||||
pub const MTABR: usize = 19;
|
||||
/// 不透明度表频率大小
|
||||
pub const MFRTAB: usize = 125000;
|
||||
|
||||
// ODF 相关
|
||||
/// ODF 频率点数
|
||||
pub const MFODF: usize = 180;
|
||||
/// ODF 热点数
|
||||
pub const MHOD: usize = 3;
|
||||
/// ODF 深度维度
|
||||
pub const MDODF: usize = 3;
|
||||
/// ODF 库能级数
|
||||
pub const MKULEV: usize = 7000;
|
||||
/// ODF 谱线数
|
||||
pub const MLINE: usize = 1140000;
|
||||
/// ODF 截面点数
|
||||
pub const MCFE: usize = 7824000;
|
||||
/// ODF 频率范围
|
||||
pub const MFRO: usize = MFREQL;
|
||||
|
||||
// ============================================================================
|
||||
// 物理常数 (CGS 单位)
|
||||
@@ -93,6 +129,10 @@ pub const PI4H: f64 = 1.8966e27;
|
||||
pub const PCK: f64 = 4.19168946e-10;
|
||||
/// 氢原子质量 (g)
|
||||
pub const HMASS: f64 = 1.67333e-24;
|
||||
/// 康普顿散射常数 XCON
|
||||
pub const XCON: f64 = 8.0935e-21;
|
||||
/// 康普顿散射常数 YCON
|
||||
pub const YCON: f64 = 1.68638e-10;
|
||||
|
||||
// ============================================================================
|
||||
// 数学常数
|
||||
|
||||
+1
-1
@@ -188,7 +188,7 @@ pub struct ChnAd {
|
||||
/// Lambda 计数
|
||||
pub nlamt: i32,
|
||||
/// 导数标志
|
||||
pubilder: i32,
|
||||
pub ilder: i32,
|
||||
/// BPOP 标志
|
||||
pub ibpope: i32,
|
||||
}
|
||||
|
||||
+1369
-27
File diff suppressed because it is too large
Load Diff
+2
-19
@@ -4,25 +4,6 @@
|
||||
|
||||
use super::constants::*;
|
||||
|
||||
// ============================================================================
|
||||
// ODF 维度参数
|
||||
// ============================================================================
|
||||
|
||||
/// ODF 频率点数
|
||||
pub const MFODF: usize = 180;
|
||||
/// ODF 热点数
|
||||
pub const MHOD: usize = 3;
|
||||
/// ODF 频率范围
|
||||
pub const MFRO: usize = MFREQL;
|
||||
/// ODF 深度维度
|
||||
pub const MDODF: usize = 3;
|
||||
/// Kurucz 能级数
|
||||
pub const MKULEV: usize = 7000;
|
||||
/// 谱线数
|
||||
pub const MLINE: usize = 1140000;
|
||||
/// Fe 系数数
|
||||
pub const MCFE: usize = 7824000;
|
||||
|
||||
// ============================================================================
|
||||
// ODFION - ODF 离子控制
|
||||
// ============================================================================
|
||||
@@ -411,6 +392,7 @@ pub struct OdfData {
|
||||
pub odfctr: OdfCtr,
|
||||
pub odffrq: OdfFrq,
|
||||
pub odfmod: OdfMod,
|
||||
pub odfstk: OdfStk,
|
||||
pub splcom: SplCom,
|
||||
pub opalim: OpaLim,
|
||||
pub oplimt: OpLimT,
|
||||
@@ -424,6 +406,7 @@ impl OdfData {
|
||||
odfctr: OdfCtr::new(),
|
||||
odffrq: OdfFrq::new(),
|
||||
odfmod: OdfMod::new(),
|
||||
odfstk: OdfStk::new(NLMX),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user