新增 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>
417 lines
11 KiB
Rust
417 lines
11 KiB
Rust
//! 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);
|
||
}
|
||
}
|