SpectraRust/src/math/switch.rs
2026-03-23 15:45:52 +08:00

357 lines
9.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 碰撞-辐射切换参数评估。
//!
//! 重构自 TLUSTY `SWITCH` 子程序。
//!
//! # 功能
//!
//! - 计算碰撞-辐射切换参数 lambda(R) = CRSW
//! - 基于 Hummer & Voels (1988) 方法
//! - 支持深度依赖的切换参数
// ============================================================================
// 常量
// ============================================================================
/// 1.0
const UN: f64 = 1.0;
/// h/k (Planck/Boltzmann)
const HK: f64 = 4.7994e0;
// ============================================================================
// 输入/输出结构体
// ============================================================================
/// SWITCH 输入参数(初始化模式)。
pub struct SwitchInitParams<'a> {
/// 深度点数
pub nd: usize,
/// 跃迁数
pub ntrans: usize,
/// 碰撞速率 COLRAT(ITR, ID)
pub colrat: &'a [Vec<f64>],
/// 向上辐射速率 RRU(ITR, ID)
pub rru: &'a [Vec<f64>],
/// 向下辐射速率 RRD(ITR, ID)
pub rrd: &'a [Vec<f64>],
/// 参考频率 FR0(ITR)
pub fr0: &'a [f64],
/// 温度 TEMP(ID)
pub temp: &'a [f64],
/// 是否是谱线 LINE(ITR)
pub line: &'a [bool],
/// ICRSW 模式 (0=禁用, 1=全局最小, 2=深度依赖)
pub icrsw: i32,
/// SWPFAC 因子
pub swpfac: f64,
/// SWPLIM 限制
pub swplim: f64,
}
/// SWITCH 输入参数(更新模式)。
pub struct SwitchUpdateParams<'a> {
/// 深度点数
pub nd: usize,
/// 当前 CRSW 值
pub crsw: &'a mut [f64],
/// SWPINC 增量因子
pub swpinc: f64,
/// SWPLIM 限制
pub swplim: f64,
}
/// SWITCH 输出结果。
#[derive(Debug, Clone)]
pub struct SwitchOutput {
/// CRSW 数组
pub crsw: Vec<f64>,
/// 全局最小值(仅在初始化模式)
pub swmin: Option<f64>,
}
// ============================================================================
// 核心计算函数
// ============================================================================
/// 执行 SWITCH 初始化计算INITM = 1
///
/// # 参数
/// - `params`: 输入参数
///
/// # 返回
/// CRSW 数组
pub fn switch_init(params: &SwitchInitParams) -> SwitchOutput {
if params.icrsw == 0 {
// 碰撞-辐射切换未启用
return SwitchOutput {
crsw: vec![UN; params.nd],
swmin: None,
};
}
let nd = params.nd;
let ntrans = params.ntrans;
let mut swtch = vec![UN; nd];
let mut swmin = UN;
// 遍历每个深度点
for id in 0..nd {
swtch[id] = UN;
// 遍历每个跃迁
for itr in 0..ntrans {
let c = params.colrat.get(itr).and_then(|v| v.get(id)).copied().unwrap_or(0.0);
let rru_val = params.rru.get(itr).and_then(|v| v.get(id)).copied().unwrap_or(0.0);
let rrd_val = params.rrd.get(itr).and_then(|v| v.get(id)).copied().unwrap_or(0.0);
if rru_val.abs() > 1e-30 {
// 向上速率
let swu = c / rru_val;
// 向下速率
let swd = if params.line.get(itr).copied().unwrap_or(false) {
// 谱线
let fr0 = params.fr0.get(itr).copied().unwrap_or(0.0);
let temp = params.temp.get(id).copied().unwrap_or(10000.0);
c * (HK * fr0 / temp).exp() / rrd_val
} else {
// 连续谱
c / rrd_val
};
// 取最小值
if swu < swtch[id] {
swtch[id] = swu;
}
if swd < swtch[id] {
swtch[id] = swd;
}
}
}
// 更新全局最小值
if swtch[id] < swmin {
swmin = swtch[id];
}
}
// 计算 CRSW
let mut crsw = vec![0.0; nd];
for id in 0..nd {
crsw[id] = if params.icrsw == 1 {
// 使用全局最小值
swmin * params.swpfac
} else {
// 使用深度依赖值
swtch[id] * params.swpfac
};
// 限制
if crsw[id] > params.swplim {
crsw[id] = UN;
}
}
SwitchOutput {
crsw,
swmin: Some(swmin),
}
}
/// 执行 SWITCH 更新计算INITM = 0
///
/// # 参数
/// - `params`: 输入参数crsw 会被修改)
///
/// # 返回
/// 更新后的 CRSW 数组
pub fn switch_update(params: &mut SwitchUpdateParams) -> SwitchOutput {
for id in 0..params.nd {
params.crsw[id] *= params.swpinc;
// 限制
if params.crsw[id] > params.swplim {
params.crsw[id] = UN;
}
}
SwitchOutput {
crsw: params.crsw.to_vec(),
swmin: None,
}
}
/// 生成 CRSW 输出消息。
pub fn format_crsw_message(crsw: &[f64]) -> String {
let mut msg = String::new();
for (i, &val) in crsw.iter().enumerate() {
msg.push_str(&format!("{:10.3e}", val));
if (i + 1) % 8 == 0 {
msg.push('\n');
}
}
if !crsw.is_empty() && crsw.len() % 8 != 0 {
msg.push('\n');
}
msg
}
// ============================================================================
// 测试
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_switch_disabled() {
let params = SwitchInitParams {
nd: 3,
ntrans: 2,
colrat: &vec![vec![0.1, 0.1, 0.1], vec![0.1, 0.1, 0.1]],
rru: &vec![vec![1.0, 1.0, 1.0], vec![1.0, 1.0, 1.0]],
rrd: &vec![vec![1.0, 1.0, 1.0], vec![1.0, 1.0, 1.0]],
fr0: &vec![1.0e15, 2.0e15],
temp: &vec![10000.0, 9000.0, 8000.0],
line: &vec![true, false],
icrsw: 0, // 禁用
swpfac: 1.0,
swplim: 1.0,
};
let result = switch_init(&params);
// 禁用时应该返回全 1
for &val in &result.crsw {
assert!((val - 1.0).abs() < 1e-10);
}
}
#[test]
fn test_switch_global_minimum() {
let params = SwitchInitParams {
nd: 3,
ntrans: 2,
colrat: &vec![vec![0.1, 0.2, 0.3], vec![0.05, 0.1, 0.15]],
rru: &vec![vec![1.0, 1.0, 1.0], vec![1.0, 1.0, 1.0]],
rrd: &vec![vec![1.0, 1.0, 1.0], vec![1.0, 1.0, 1.0]],
fr0: &vec![1.0e15, 2.0e15],
temp: &vec![10000.0, 9000.0, 8000.0],
line: &vec![false, false],
icrsw: 1, // 全局最小
swpfac: 0.5,
swplim: 1.0,
};
let result = switch_init(&params);
// 所有深度应该使用相同的全局最小值
assert!(result.swmin.is_some());
let swmin = result.swmin.unwrap();
assert!(swmin < 1.0);
// CRSW 应该是 swmin * swpfac
for &val in &result.crsw {
assert!((val - swmin * 0.5).abs() < 1e-10);
}
}
#[test]
fn test_switch_depth_dependent() {
let params = SwitchInitParams {
nd: 3,
ntrans: 1,
colrat: &vec![vec![0.1, 0.2, 0.3]],
rru: &vec![vec![1.0, 1.0, 1.0]],
rrd: &vec![vec![1.0, 1.0, 1.0]],
fr0: &vec![1.0e15],
temp: &vec![10000.0, 9000.0, 8000.0],
line: &vec![false],
icrsw: 2, // 深度依赖
swpfac: 1.0,
swplim: 1.0,
};
let result = switch_init(&params);
// 每个深度应该有不同的值
assert!((result.crsw[0] - 0.1).abs() < 1e-10);
assert!((result.crsw[1] - 0.2).abs() < 1e-10);
assert!((result.crsw[2] - 0.3).abs() < 1e-10);
}
#[test]
fn test_switch_limit() {
let params = SwitchInitParams {
nd: 2,
ntrans: 1,
colrat: &vec![vec![10.0, 0.5]], // 第一个深度比例很大
rru: &vec![vec![1.0, 1.0]],
rrd: &vec![vec![1.0, 1.0]],
fr0: &vec![1.0e15],
temp: &vec![10000.0, 9000.0],
line: &vec![false],
icrsw: 2,
swpfac: 1.0,
swplim: 0.5, // 限制为 0.5
};
let result = switch_init(&params);
// 第一个深度应该被限制为 1.0
assert!((result.crsw[0] - 1.0).abs() < 1e-10);
// 第二个深度应该正常
assert!((result.crsw[1] - 0.5).abs() < 1e-10);
}
#[test]
fn test_switch_update() {
let mut crsw = vec![0.1, 0.2, 0.3];
let mut params = SwitchUpdateParams {
nd: 3,
crsw: &mut crsw,
swpinc: 2.0,
swplim: 1.0,
};
let result = switch_update(&mut params);
// 应该乘以 swpinc
assert!((result.crsw[0] - 0.2).abs() < 1e-10);
assert!((result.crsw[1] - 0.4).abs() < 1e-10);
assert!((result.crsw[2] - 0.6).abs() < 1e-10);
}
#[test]
fn test_switch_update_limit() {
let mut crsw = vec![0.8, 0.9];
let mut params = SwitchUpdateParams {
nd: 2,
crsw: &mut crsw,
swpinc: 2.0,
swplim: 1.0,
};
let result = switch_update(&mut params);
// 超过限制应该设为 1.0
assert!((result.crsw[0] - 1.0).abs() < 1e-10);
assert!((result.crsw[1] - 1.0).abs() < 1e-10);
}
#[test]
fn test_format_message() {
let crsw = vec![0.1, 0.2, 0.3];
let msg = format_crsw_message(&crsw);
// Rust 的 {:10.3e} 格式产生类似 " 1.000e-1" 的输出
// 检查包含科学计数法
assert!(msg.contains("e-1") || msg.contains("E-1"));
// 检查第一个值
assert!(msg.contains("1.000e-1") || msg.contains("1.00e-1"));
}
}