SpectraRust/src/synspec/math/nstpar.rs
fmq 0f97c0b05b feat: 添加 TLUSTY 新模块 + 修复编译错误
新增 TLUSTY 模块:
- crossd: 光电离截面评估 (bound-free cross section)
- sgmer0: 合并能级光电离截面初始化
- sgmerd: 合并能级光电离截面计算
- dwnfr0: 频率网格下载 (continuum)
- convc1: 对流收敛控制 (radiative)
- chckse: 统计平衡检查 (rates)

扩展 RESOLV 编排器:
- 添加 Feautrier 形式解
- 添加 Lucy 温度修正
- 添加 ROSSTD/PZEVAL/CONOUT 调用
- 添加 IFPOPR=2 占据数更新
- 添加 HESOL6 流体静力平衡修正

修复:
- sgmer0.rs: 修复 config 未声明为 mut 的编译错误
- crossd.rs: 修复测试中使用错误字段路径的问题
  (frqall.ijbf/phoexp.aijbf/phoexp.bfcs 而非 obfpar)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 12:35:09 +08:00

417 lines
11 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.

//! nstpar — 非标准参数输入。
//!
//! Fortran 原始签名: SUBROUTINE NSTPAR(FINSTD)
//!
//! 从输入文件读取关键字-值对,设置 SYNSPEC 的各种默认参数。
//! 支持 44 个参数名,格式为 `KEYWORD VALUE` 或 `KEYWORD = VALUE`。
use std::collections::HashMap;
/// 默认参数值表(按 VARNAM 顺序)
const VARNAM: [&str; 44] = [
"IATREF",
"BERGFC", "IHYDPR", "NUNHHE", "STHE",
"ND", "NFREQS", "IBFAC",
"INTRPL", "ICHANG", "IFEOS",
"IOPHMI", "IOPH2P", "IOPHEM", "IOPCH", "IOPOH",
"IOPH2M", "IOH2H2", "IOH2HE", "IOH2H1", "IOHHE",
"IRSCT", "IRSCH2", "IRSCHE",
"TRAD", "WDIL",
"VTB", "IFMOL", "TMOLIM",
"MOLTAB", "IRWTAB", "IIRWIN", "IPFEXO",
"CUTLYM", "CUTBAL", "IHXENB",
"GSSTD", "GWSTD",
"IHGOM", "HGLIM",
"ERANGE",
"ISPICK", "ILPICK", "IPPICK",
];
const PVALUE_DEFAULT: [&str; 44] = [
"1",
"1.D0", "0", "0", "1.e19",
"70", "120", "0",
"0", "0", "0",
"1", "1", "1", "1", "1",
"1", "1", "1", "1", "1",
"1", "1", "1",
"0.", "0.",
"2.", "1", "9000.",
"1", "1", "1", "1",
"0.", "0.", "0",
"3.1e-5", "1.0e-7",
"0", "1.e18",
"0.10",
"1", "1", "1",
];
/// 解析后的非标准参数
#[derive(Debug, Clone)]
pub struct NstparParams {
pub iatref: i32,
pub bergfc: f64,
pub ihydpr: i32,
pub nunhhe: i32,
pub sthe: f64,
pub nd: i32,
pub nfreqs: i32,
pub ibfac: i32,
pub intrpl: i32,
pub ichang: i32,
pub ifeos: i32,
pub iophmi: i32,
pub ioph2p: i32,
pub iophem: i32,
pub iopch: i32,
pub iopoh: i32,
pub ioph2m: i32,
pub ioh2h2: i32,
pub ioh2he: i32,
pub ioh2h1: i32,
pub iohee: i32,
pub irsct: i32,
pub irsch2: i32,
pub irsche: i32,
pub trad: f64,
pub wdil: f64,
pub vtb: f64,
pub ifmol: i32,
pub tmolim: f64,
pub moltab: i32,
pub irwtab: i32,
pub iirwin: i32,
pub ipfexo: i32,
pub cutlym: f64,
pub cutbal: f64,
pub ihxenb: i32,
pub gsstd: f64,
pub gwstd: f64,
pub ihgom: i32,
pub hglim: f64,
pub erange: f64,
pub ispick: i32,
pub ilpick: i32,
pub ippick: i32,
}
impl Default for NstparParams {
fn default() -> Self {
Self {
iatref: 1,
bergfc: 1.0,
ihydpr: 0,
nunhhe: 0,
sthe: 1.0e19,
nd: 70,
nfreqs: 120,
ibfac: 0,
intrpl: 0,
ichang: 0,
ifeos: 0,
iophmi: 1,
ioph2p: 1,
iophem: 1,
iopch: 1,
iopoh: 1,
ioph2m: 1,
ioh2h2: 1,
ioh2he: 1,
ioh2h1: 1,
iohee: 1,
irsct: 1,
irsch2: 1,
irsche: 1,
trad: 0.0,
wdil: 0.0,
vtb: 2.0,
ifmol: 1,
tmolim: 9000.0,
moltab: 1,
irwtab: 1,
iirwin: 1,
ipfexo: 1,
cutlym: 0.0,
cutbal: 0.0,
ihxenb: 0,
gsstd: 3.1e-5,
gwstd: 1.0e-7,
ihgom: 0,
hglim: 1.0e18,
erange: 0.10,
ispick: 1,
ilpick: 1,
ippick: 1,
}
}
}
/// 从文本行中提取下一个单词。
///
/// Fortran 原始: SUBROUTINE GETWRD(TEXT,K0,K1,K2)
/// 返回 (k1, k2) - 单词的起始和结束位置1-indexed(0,0) 表示无单词。
fn getwrd(text: &str, k0: usize) -> (usize, usize) {
let chars: Vec<char> = text.chars().collect();
let n = chars.len();
let mut k1 = k0;
// 跳过前导空格
while k1 <= n && chars[k1 - 1] == ' ' {
k1 += 1;
}
if k1 > n {
return (0, 0);
}
let mut k2 = k1;
while k2 <= n && chars[k2 - 1] != ' ' {
k2 += 1;
}
k2 -= 1;
(k1, k2)
}
/// 解析非标准参数输入。
///
/// Fortran 原始逻辑:
/// 1. 逐行读取输入文本
/// 2. 提取关键字-值对(支持 `KEY VALUE` 和 `KEY = VALUE` 格式)
/// 3. 匹配 VARNAM 表中的关键字
/// 4. 将值写入对应的 PVALUE 槽位
/// 5. 最终按顺序解析所有值为具体类型
pub fn nstpar(input_text: &str) -> NstparParams {
let mut pvalue: Vec<String> = PVALUE_DEFAULT.iter().map(|s| s.to_string()).collect();
// 解析输入文本中的关键字-值对
let mut var_map: HashMap<String, usize> = HashMap::new();
for (i, name) in VARNAM.iter().enumerate() {
var_map.insert(name.to_string(), i);
}
for line in input_text.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('!') || line.starts_with('#') {
continue;
}
let mut k0: usize = 1;
let text_len = line.len();
let mut current_var: usize = 0;
let mut found_var = false;
// 解析关键字-值对循环(匹配 Fortran 的 indv 切换逻辑)
let mut indv = -1_i32;
loop {
if k0 > text_len {
break;
}
let (k1, k2) = getwrd(line, k0);
if k1 == 0 {
break;
}
k0 = k2 + 2;
let word = &line[k1 - 1..k2];
// 跳过等号
if word == "=" {
continue;
}
indv = -indv;
if indv == 1 {
// 关键字位置
if let Some(&idx) = var_map.get(word) {
current_var = idx;
found_var = true;
} else {
found_var = false;
}
} else if found_var {
// 值位置,且前面找到了有效关键字
pvalue[current_var] = word.to_string();
found_var = false;
}
}
}
// 解析所有值为具体类型
let parse_i32 = |s: &str| -> i32 { s.trim().parse::<f64>().unwrap_or(0.0) as i32 };
let parse_f64 = |s: &str| -> f64 { s.trim().parse::<f64>().unwrap_or(0.0) };
let mut params = NstparParams::default();
params.iatref = parse_i32(&pvalue[0]);
params.bergfc = parse_f64(&pvalue[1]);
params.ihydpr = parse_i32(&pvalue[2]);
params.nunhhe = parse_i32(&pvalue[3]);
params.sthe = parse_f64(&pvalue[4]);
params.nd = parse_i32(&pvalue[5]);
params.nfreqs = parse_i32(&pvalue[6]);
params.ibfac = parse_i32(&pvalue[7]);
params.intrpl = parse_i32(&pvalue[8]);
params.ichang = parse_i32(&pvalue[9]);
params.ifeos = parse_i32(&pvalue[10]);
params.iophmi = parse_i32(&pvalue[11]);
params.ioph2p = parse_i32(&pvalue[12]);
params.iophem = parse_i32(&pvalue[13]);
params.iopch = parse_i32(&pvalue[14]);
params.iopoh = parse_i32(&pvalue[15]);
params.ioph2m = parse_i32(&pvalue[16]);
params.ioh2h2 = parse_i32(&pvalue[17]);
params.ioh2he = parse_i32(&pvalue[18]);
params.ioh2h1 = parse_i32(&pvalue[19]);
params.iohee = parse_i32(&pvalue[20]);
params.irsct = parse_i32(&pvalue[21]);
params.irsch2 = parse_i32(&pvalue[22]);
params.irsche = parse_i32(&pvalue[23]);
params.trad = parse_f64(&pvalue[24]);
params.wdil = parse_f64(&pvalue[25]);
params.vtb = parse_f64(&pvalue[26]);
params.ifmol = parse_i32(&pvalue[27]);
params.tmolim = parse_f64(&pvalue[28]);
params.moltab = parse_i32(&pvalue[29]);
params.irwtab = parse_i32(&pvalue[30]);
params.iirwin = parse_i32(&pvalue[31]);
params.ipfexo = parse_i32(&pvalue[32]);
params.cutlym = parse_f64(&pvalue[33]);
params.cutbal = parse_f64(&pvalue[34]);
params.ihxenb = parse_i32(&pvalue[35]);
params.gsstd = parse_f64(&pvalue[36]);
params.gwstd = parse_f64(&pvalue[37]);
params.ihgom = parse_i32(&pvalue[38]);
params.hglim = parse_f64(&pvalue[39]);
params.erange = parse_f64(&pvalue[40]);
params.ispick = parse_i32(&pvalue[41]);
params.ilpick = parse_i32(&pvalue[42]);
params.ippick = parse_i32(&pvalue[43]);
// Fortran 逻辑: if(imode.le.-3) 则关闭 Rayleigh 散射
// 这里不处理 imode由调用方负责
params
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_params() {
let params = NstparParams::default();
assert_eq!(params.iatref, 1);
assert_eq!(params.nd, 70);
assert_eq!(params.nfreqs, 120);
assert_eq!(params.vtb, 2.0);
assert_eq!(params.tmolim, 9000.0);
}
#[test]
fn test_empty_input() {
let params = nstpar("");
assert_eq!(params.iatref, 1);
assert_eq!(params.nd, 70);
}
#[test]
fn test_parse_keyword_value() {
let input = "ND 50\nNFREQS 200\nVTB 3.0\n";
let params = nstpar(input);
assert_eq!(params.nd, 50);
assert_eq!(params.nfreqs, 200);
assert!((params.vtb - 3.0).abs() < 1e-10);
}
#[test]
fn test_parse_with_equals() {
let input = "ND = 50\nNFREQS = 200\n";
let params = nstpar(input);
assert_eq!(params.nd, 50);
assert_eq!(params.nfreqs, 200);
}
#[test]
fn test_parse_float_values() {
let input = "BERGFC 2.5\nSTHE 5.e18\nCUTLYM 0.5\n";
let params = nstpar(input);
assert!((params.bergfc - 2.5).abs() < 1e-10);
assert!((params.sthe - 5.0e18).abs() < 1e10);
assert!((params.cutlym - 0.5).abs() < 1e-10);
}
#[test]
fn test_parse_opacity_switches() {
let input = "IOPHMI 0\nIOPH2P 0\nIOPOH 0\n";
let params = nstpar(input);
assert_eq!(params.iophmi, 0);
assert_eq!(params.ioph2p, 0);
assert_eq!(params.iopoh, 0);
}
#[test]
fn test_unknown_keyword_ignored() {
let input = "UNKNOWN 42\nND 50\n";
let params = nstpar(input);
assert_eq!(params.nd, 50);
}
#[test]
fn test_comments_skipped() {
let input = "! This is a comment\n# Another comment\nND 50\n";
let params = nstpar(input);
assert_eq!(params.nd, 50);
}
#[test]
fn test_getwrd_basic() {
let (k1, k2) = getwrd("HELLO WORLD", 1);
assert_eq!(k1, 1);
assert_eq!(k2, 5);
let (k1, k2) = getwrd("HELLO WORLD", 6);
assert_eq!(k1, 7);
assert_eq!(k2, 11);
}
#[test]
fn test_getwrd_empty() {
let (k1, k2) = getwrd(" ", 1);
assert_eq!(k1, 0);
assert_eq!(k2, 0);
}
#[test]
fn test_full_parameter_set() {
let input = "\
IATREF 2
BERGFC 1.5
ND 30
NFREQS 100
IBFAC 1
IFEOS 1
IOPHMI 0
VTB 3.5
IFMOL 0
TMOLIM 8000.
GSSTD 1.0e-5
GWSTD 1.0e-8
IHGOM 1
HGLIM 5.e17
ERANGE 0.20
";
let params = nstpar(input);
assert_eq!(params.iatref, 2);
assert!((params.bergfc - 1.5).abs() < 1e-10);
assert_eq!(params.nd, 30);
assert_eq!(params.nfreqs, 100);
assert_eq!(params.ibfac, 1);
assert_eq!(params.ifeos, 1);
assert_eq!(params.iophmi, 0);
assert!((params.vtb - 3.5).abs() < 1e-10);
assert_eq!(params.ifmol, 0);
assert!((params.tmolim - 8000.0).abs() < 1e-10);
assert!((params.gsstd - 1.0e-5).abs() < 1e-15);
assert!((params.gwstd - 1.0e-8).abs() < 1e-18);
assert_eq!(params.ihgom, 1);
assert!((params.hglim - 5.0e17).abs() < 1e10);
assert!((params.erange - 0.20).abs() < 1e-10);
}
}