Compare commits

...

3 Commits

Author SHA1 Message Date
cb218e0d5b 修复4 2026-04-01 19:11:27 +08:00
418e487c2f 修复3 2026-04-01 16:36:08 +08:00
496907d41d 修复2 2026-04-01 16:35:36 +08:00
142 changed files with 11945 additions and 939 deletions

View File

@ -6,99 +6,240 @@ description: |
- 用户询问 Rust 模块是否与 Fortran 源码匹配
- 用户想验证或修复 Rust 实现的正确性
核心工作流:获取推荐 → 检查差异 → 执行修复 → 验证编译
**重要**:检查后必须执行修复,不要因为模块复杂就跳过
核心工作流:获取推荐 → 检查差异 → **直接修复** → 验证编译 → **继续下一个**
**自动化模式**:检查发现差异后必须立即修复,禁止询问用户,禁止生成总结报告
---
# F2R Check - Fortran 到 Rust 一致性检查与修复
# F2R Check - Fortran 到 Rust 自动化修复(两阶段检查)
检查 Rust 模块与对应 Fortran 模块的一致性,并**直接执行修复**
**这是一个自动化任务**。检查发现差异后必须立即修复,修复完成后自动继续下一个模块
## 标准工作流
## 关键规则(必须遵守)
```
┌─────────────────────────────────────────────────────────────┐
│ 用户请求: "f2r-check 检查下一个模块" │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 步骤 1: 获取推荐模块 │
│ $ python3 scripts/next_module.py │
│ 输出: 优先级列表,第一个是推荐模块 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 步骤 2: 检查差异 │
│ $ python3 scripts/f2r_check.py --diff <MODULE>
│ 输出: 缺失的调用、流程差异、修复建议 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 步骤 3: 执行修复 (必须执行) │
│ - 读取 Fortran 源码 │
│ - 读取 Rust 实现 │
│ - 添加缺失的调用 │
│ - 确保参数传递正确 │
└─────────────────────────────────────────────────────────────┘
┌─────────┴─────────┐
│ 依赖模块已实现? │
└─────────┬─────────┘
是 │ │ 否
▼ ▼
┌─────────────┐ ┌─────────────────────┐
│ 步骤 4: │ │ 先修复依赖模块 │
│ 验证编译 │ │ 然后返回继续修复当前 │
│ cargo build │ └─────────────────────┘
└─────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 禁止事项: │
│ ❌ 禁止生成总结报告后询问"是否继续" │
│ ❌ 禁止说"这个模块很复杂,是否要修复" │
│ ❌ 禁止只检查不修复 │
│ ❌ 禁止输出冗长的检查报告 │
│ ❌ 禁止因为模块复杂就跳过 │
│ ❌ 禁止自行判断"这个差异不重要"然后跳过 │
│ ❌ 禁止跳过 I/O 语句write/read/print
│ │
│ 必须事项: │
│ ✅ 只有脚本返回 "✅ match" 且无 HIGH_RISK 才能跳过 │
│ ✅ 任何 non-match 状态都必须修复 │
│ ✅ ✅ match + HIGH_RISK 必须进行 Phase 2 深度检查 │
│ ✅ I/O 语句必须实现(用 log::debug! 或条件打印) │
│ ✅ 检查发现差异 → 立即修复 │
│ ✅ 修复完成 → 立即验证编译 │
│ ✅ 编译通过 → 立即继续下一个模块 │
│ ✅ 只输出:修复了什么 + 编译结果 │
│ ✅ 遇到复杂模块也要修复,分解为小步骤逐步完成 │
└─────────────────────────────────────────────────────────────────┘
```
## 两阶段检查流程
### Phase 1: Python 快速风险检测(自动)
```
步骤 1: 获取推荐模块
$ python3 .claude/skills/f2r-check/scripts/next_module.py
步骤 2: 快速检查差异
$ python3 .claude/skills/f2r-check/scripts/f2r_check.py --diff <MODULE>
├── ❌ mismatch/partial → 立即修复(现有流程)→ 步骤 4
└── ✅ match → 步骤 3: 风险评估
步骤 3: $ python3 .claude/skills/f2r-check/scripts/f2r_check.py --risk <MODULE>
├── 有 HIGH_RISK → 进入 Phase 2
└── 无风险 → 输出 "模块已完整,跳过" → 继续步骤 1
```
### Phase 2: Claude 深度语义对比(手动触发或自动)
Phase 1 发现 HIGH_RISK 后Claude 逐行对比 Fortran 和 Rust
```
Phase 2 步骤:
1. 读取 Fortran 源码
2. 读取 Rust 源码
3. 读取 INCLUDE 的 COMMON 定义文件
4. 读取 use 引用的 Rust struct 文件
5. 逐块对比(变量映射、索引转换、数组维度、赋值完整性)
6. 发现 bug → 立即修复 → cargo build 验证
7. 无 bug → 输出 "深度检查通过" → 继续下一个
```
### Phase 2 检查清单
对每个 HIGH_RISK 模块,必须逐项检查:
```
[ ] COMMON 变量 → 正确的 Rust struct 字段
使用: python3 scripts/common_db.py --module <MODULE>
[ ] 2D 数组下标顺序Fortran 列主序 → Rust 行主序)
Fortran XDO(3,MHOD) 第一个下标变化最快
Rust xdo[[mhod_idx][3_idx] 需要交换下标
[ ] 1-based → 0-based 索引一致性
IJ00=1 → ij00=0
DO I=1,N → for i in 0..n
[ ] 循环边界转换
DO I=1,N → for i in 0..n (不是 0..n-1)
DO I=N,1,-1 → for i in (0..n).rev()
[ ] IF 条件完整保留
<= vs <, >= vs >, .EQ. vs ==
.AND. vs &&, .OR. vs ||
[ ] 所有赋值目标存在(无遗漏的 LINEXP 等)
检查每个 Fortran 赋值语句是否有对应 Rust 赋值
[ ] CALL 顺序和数量一致
每个 CALL 都有对应 Rust 函数调用
调用顺序与 Fortran 一致
[ ] 类型转换正确
INTEGER → i32, REAL*8 → f64, LOGICAL → bool
REAL*4 → f32, INTEGER*2 → i16
```
## 判断标准
| 脚本输出 | 风险等级 | 行动 | 允许跳过? |
|----------|----------|------|------------|
| `✅ match` + 无风险 | 无 | 跳过 | ✅ 是 |
| `✅ match` + HIGH_RISK | 高 | Phase 2 深度检查 | ❌ 否 |
| `✅ match` + MEDIUM_RISK | 中 | Phase 2 深度检查 | ❌ 否 |
| `⚠️ partial` | — | 立即修复 | ❌ 否 |
| `❌ mismatch` | — | 立即修复 | ❌ 否 |
| `❓ missing` | — | 立即实现 | ❌ 否 |
## 输出格式(严格遵守)
**只输出以下简洁格式:**
```
检查: <模块名> - <状态>
风险: <N HIGH, M MEDIUM> (如有)
修复: <修复内容简述>
编译: <成功/失败>
```
**禁止输出:**
- 长表格总结
- "是否需要继续..."
- "建议..."
- "如需..."
## 脚本命令
### 获取下一个模块
```bash
python3 scripts/next_module.py # 全局推荐(优先级排序)
python3 scripts/next_module.py --path START # 从 START 追踪依赖
python3 scripts/next_module.py --priority # 完整优先级列表
python3 .claude/skills/f2r-check/scripts/next_module.py # 全局推荐
python3 .claude/skills/f2r-check/scripts/next_module.py --path START # 从 START 追踪
```
### 检查模块
### Phase 1 检查
```bash
python3 scripts/f2r_check.py START # 快速检查
python3 scripts/f2r_check.py --diff START # 详细差异报告
python3 scripts/f2r_check.py --all # 检查所有模块
# 快速检查
python3 .claude/skills/f2r-check/scripts/f2r_check.py START
# 详细差异报告(含风险标记)
python3 .claude/skills/f2r-check/scripts/f2r_check.py --diff START
# 风险评估
python3 .claude/skills/f2r-check/scripts/f2r_check.py --risk START
# 随机审计 5 个 match 模块
python3 .claude/skills/f2r-check/scripts/f2r_check.py --audit
```
## 状态标识
### Phase 2 辅助工具
```bash
# 查看模块使用的 COMMON 变量映射
python3 .claude/skills/f2r-check/scripts/common_db.py --module ODFHYS
| 标识 | 含义 | 行动 |
|------|------|------|
| ✅ match | 完全匹配 | 无需修复 |
| ⚠️ partial | 部分实现 | 添加缺失调用 |
| ❌ mismatch | 不匹配 | 修复逻辑/调用 |
| ❓ missing | 未实现 | 完整实现 |
# 查看 COMMON 块定义
python3 .claude/skills/f2r-check/scripts/common_db.py --block ODFCTR
# 生成深度检查文件列表
python3 .claude/skills/f2r-check/scripts/deep_check_prompt.py ODFHYS
# 查看映射统计
python3 .claude/skills/f2r-check/scripts/common_db.py --mapping
```
## 状态处理
| 状态 | 行动 | 输出 | 允许跳过? |
|------|------|------|------------|
| ✅ match (无风险) | 跳过 | "模块已完整,跳过" | ✅ |
| ✅ match (有风险) | Phase 2 | "风险: 2 HIGH → 深度检查" | ❌ |
| ⚠️ partial | 立即修复 | "修复: 添加缺失调用..." | ❌ |
| ❌ mismatch | 立即修复 | "修复: 修正逻辑..." | ❌ |
| ❓ missing | 立即实现 | "修复: 实现模块..." | ❌ |
## 修复原则
1. **严格对照 Fortran**: 按 Fortran 代码行号,逐行对比 Rust 实现
2. **保持调用顺序**: Fortran 中的 CALL 顺序必须严格保持
3. **正确映射 COMMON**: Fortran COMMON 块变量 → Rust 结构体字段
- 使用 `common_db.py --module <NAME>` 查看映射
4. **控制流程等价**: IF/DO/SELECT CASE 逻辑必须一致
## 模块映射
| Fortran | Rust |
|---------|------|
| tlusty/extracted/tlusty.f | src/tlusty/main.rs |
| tlusty/extracted/start.f | src/tlusty/io/start.rs |
| tlusty/extracted/initia.f | src/tlusty/io/initia.rs |
| gfree0, gfreed, gfree1 | gfree.rs |
| yint, lagran | interpolate.rs |
5. **数组下标转换**: Fortran 列主序 → Rust 行主序1-based → 0-based
6. **复杂模块分解**: 遇到复杂模块,分步骤修复,每步验证编译
## 文件路径
- Fortran: `/home/fmq/program/tlusty/tl208-s54/rust/tlusty/extracted/`
- Rust: `/home/fmq/.zeroclaw/workspace/SpectraRust/src/`
- COMMON 定义: `/home/fmq/program/tlusty/tl208-s54/tlusty/*.FOR`
- Rust struct: `/home/fmq/.zeroclaw/workspace/SpectraRust/src/tlusty/state/`
## 脚本修复规则
**重要**:如果发现脚本报告有误(误报),必须修复脚本!
### 脚本误报类型
| 误报类型 | 原因 | 修复方法 |
|----------|------|----------|
| 函数别名未识别 | `COMPT0` vs `compt0_brte` | 添加到 `FUNCTION_ALIASES` |
| 注释 I/O 被检测 | `c write(...)` 被当作必须实现 | 已修复:忽略注释行 |
| 辅助函数调用未检测 | 主函数调用辅助函数,辅助函数包含关键调用 | 已修复:扫描整个文件 |
### 如何修复脚本
1. **添加函数别名**:编辑 `scripts/f2r_check.py`,在 `FUNCTION_ALIASES` 字典中添加
2. **添加调用提取模式**:在 `call_patterns` 列表中添加新模式
3. **修复后验证**`python3 f2r_check.py --diff <MODULE>`
## 风险检测器说明
### 检测器 A: 2D 数组转置风险
扫描 INCLUDE 文件中的 2D 数组声明(如 `XDO(3,MHOD)`
标记所有访问该数组的模块需要验证下标顺序。
### 检测器 B: 跨 COMMON 变量混淆
检测已知的易混淆变量对(如 JNDODF vs IJTF
当模块同时使用这些变量时标记。
### 检测器 C: f2r_depends 诚实性检查
对比 `// f2r_depends:` 注释中声明的函数 vs 代码中实际的调用,
标记声明了但未实际调用的函数。
### 检测器 D: 索引累加器模式
检测 `IJ00=1`, `IJQ=IJ00+IJ` 等索引算术模式,
标记需要验证 1-based → 0-based 转换。

View File

@ -0,0 +1,491 @@
#!/usr/bin/env python3
"""
COMMON 变量映射数据库
解析 Fortran COMMON 块定义和 Rust struct 字段构建完整的变量映射关系
核心功能
- parse_all_commons() 解析 Fortran COMMON 定义
- parse_rust_structs() 解析 Rust struct 字段
- build_mapping() 交叉引用生成完整映射
- get_vars_for_module(module_name) 返回某模块用到的所有 COMMON 变量
"""
import os
import re
import sys
from typing import Dict, List, Optional, Tuple, Set
from dataclasses import dataclass, field
# ============================================================================
# 路径配置
# ============================================================================
FORTRAN_COMMON_DIR = "/home/fmq/program/tlusty/tl208-s54/tlusty"
RUST_STATE_DIR = "/home/fmq/.zeroclaw/workspace/SpectraRust/src/tlusty/state"
EXTRACTED_DIR = "/home/fmq/program/tlusty/tl208-s54/rust/tlusty/extracted"
# Fortran COMMON 定义文件
COMMON_FILES = [
"BASICS.FOR", "ATOMIC.FOR", "MODELQ.FOR", "ARRAY1.FOR",
"ITERAT.FOR", "ALIPAR.FOR", "ODFPAR.FOR",
]
# ============================================================================
# 数据结构
# ============================================================================
@dataclass
class CommonVar:
"""COMMON 块变量"""
name: str # Fortran 变量名 (大写)
common_block: str # 所属 COMMON 块名
dims: List[str] = field(default_factory=list) # 维度 (如 ['MTRANS'])
rust_field: Optional[str] = None # 对应 Rust 字段名
rust_struct: Optional[str] = None # 对应 Rust struct 名
rust_file: Optional[str] = None # 对应 Rust 文件路径
is_2d: bool = False # 是否是 2D 数组
fortran_dims_raw: str = "" # 原始维度字符串 (如 "3,MHOD")
@dataclass
class CommonBlock:
"""COMMON 块"""
name: str # COMMON 块名
file: str # 定义文件
variables: List[CommonVar] = field(default_factory=list)
rust_struct: Optional[str] = None # 对应 Rust struct 名
rust_file: Optional[str] = None # 对应 Rust 文件
@dataclass
class RustStruct:
"""Rust struct 信息"""
name: str
file: str
common_name: Optional[str] = None # 对应的 COMMON 块名
fields: Dict[str, str] = field(default_factory=dict) # field_name -> type_str
# ============================================================================
# Fortran COMMON 解析
# ============================================================================
def _join_continuation_lines(content: str) -> str:
"""合并 Fortran 续行"""
lines = content.split('\n')
joined = []
for line in lines:
if not line:
continue
# 跳过注释行
if len(line) > 0 and line[0].upper() in ('C', '!', '*'):
continue
# 检查是否有续行标记 (第6列是 * 或 数字或非空)
if joined and len(line) >= 6 and line[5] not in (' ', '0', '\n'):
# 续行去掉前6列追加到上一行
joined[-1] = joined[-1].rstrip() + ' ' + line[6:].strip()
else:
joined.append(line)
return '\n'.join(joined)
def parse_common_block(content: str, filename: str) -> List[CommonBlock]:
"""解析一个 Fortran 文件中的所有 COMMON 块"""
blocks = []
joined = _join_continuation_lines(content)
# 匹配 COMMON/BLOCKNAME/var1,var2,...
# 处理多个 COMMON 语句可能属于同一个块
pattern = r'COMMON\s*/\s*(\w+)\s*/\s*(.+?)(?=\n\s*COMMON|\n\s*PARAMETER|\n\s*REAL|\n\s*INTEGER|\n\s*LOGICAL|\n\s*CHARACTER|\n\s*$|\nC|\n!|\Z)'
matches = re.finditer(pattern, joined, re.IGNORECASE | re.MULTILINE)
# 收集每个块的所有变量声明
block_vars: Dict[str, List[str]] = {}
for match in matches:
block_name = match.group(1).upper()
vars_str = match.group(2).strip()
# 去掉行尾的 Fortran 注释
if '!' in vars_str:
vars_str = vars_str[:vars_str.index('!')].strip()
# 追加到该块的变量列表
if block_name not in block_vars:
block_vars[block_name] = []
block_vars[block_name].append(vars_str)
for block_name, var_lists in block_vars.items():
all_vars_str = ','.join(var_lists)
variables = _parse_var_list(all_vars_str, block_name)
blocks.append(CommonBlock(
name=block_name,
file=filename,
variables=variables,
))
return blocks
def _parse_var_list(vars_str: str, block_name: str) -> List[CommonVar]:
"""解析变量列表字符串,返回 CommonVar 列表"""
variables = []
# 按逗号分割,但要处理括号内的逗号
parts = _split_respecting_parens(vars_str)
for part in parts:
part = part.strip()
if not part:
continue
# 匹配 VARNAME(DIMS) 或 VARNAME
m = re.match(r'^(\w+)\(([^)]+)\)$', part, re.IGNORECASE)
if m:
name = m.group(1).upper()
dims_str = m.group(2)
dims = [d.strip().upper() for d in dims_str.split(',')]
is_2d = len(dims) >= 2
variables.append(CommonVar(
name=name,
common_block=block_name,
dims=dims,
is_2d=is_2d,
fortran_dims_raw=dims_str,
))
else:
name = part.upper()
# 过滤非变量名
if re.match(r'^[A-Z]\w*$', name):
variables.append(CommonVar(
name=name,
common_block=block_name,
))
return variables
def _split_respecting_parens(s: str) -> List[str]:
"""按逗号分割,但忽略括号内的逗号"""
parts = []
depth = 0
current = []
for c in s:
if c == '(':
depth += 1
current.append(c)
elif c == ')':
depth -= 1
current.append(c)
elif c == ',' and depth == 0:
parts.append(''.join(current))
current = []
else:
current.append(c)
if current:
parts.append(''.join(current))
return parts
def parse_all_commons() -> Dict[str, CommonBlock]:
"""解析所有 Fortran COMMON 定义文件,返回 {block_name: CommonBlock}"""
all_blocks: Dict[str, CommonBlock] = {}
for filename in COMMON_FILES:
fpath = os.path.join(FORTRAN_COMMON_DIR, filename)
if not os.path.exists(fpath):
continue
with open(fpath, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
blocks = parse_common_block(content, filename)
for block in blocks:
if block.name in all_blocks:
# 追加变量(可能同一块在不同文件中有补充定义)
all_blocks[block.name].variables.extend(block.variables)
else:
all_blocks[block.name] = block
return all_blocks
# ============================================================================
# Rust Struct 解析
# ============================================================================
def parse_rust_structs() -> List[RustStruct]:
"""解析所有 Rust state struct提取字段和 COMMON 对应关系"""
structs = []
if not os.path.isdir(RUST_STATE_DIR):
return structs
for fname in sorted(os.listdir(RUST_STATE_DIR)):
if not fname.endswith('.rs'):
continue
fpath = os.path.join(RUST_STATE_DIR, fname)
with open(fpath, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
# 查找带有 "对应 COMMON" 注释的 struct
# 允许在注释和 pub struct 之间出现属性行如 #[derive(...)]
# 以及空行
pattern = (
r'///\s*对应\s*COMMON\s*/\s*(\w+)\s*/\s*\n'
r'(?:(?:\s*#[^\n]*\n|\s*///?[^\n]*\n|\s*\n))*' # 属性、注释、空行
r'\s*pub\s+struct\s+(\w+)\s*\{'
)
for match in re.finditer(pattern, content, re.IGNORECASE):
common_name = match.group(1).upper()
struct_name = match.group(2)
# 提取 struct body处理嵌套大括号
body_start = match.end()
body = _extract_braced_body(content, body_start)
# 提取字段
fields = {}
field_pattern = r'pub\s+(\w+)\s*:\s*([^,\n]+)'
for fm in re.finditer(field_pattern, body):
field_name = fm.group(1)
type_str = fm.group(2).strip()
fields[field_name] = type_str
structs.append(RustStruct(
name=struct_name,
file=fpath,
common_name=common_name,
fields=fields,
))
return structs
def _extract_braced_body(content: str, start: int) -> str:
"""从 start 位置(紧跟 { 之后)提取匹配的大括号体"""
depth = 1
i = start
while i < len(content) and depth > 0:
if content[i] == '{':
depth += 1
elif content[i] == '}':
depth -= 1
i += 1
return content[start:i-1] if depth == 0 else content[start:]
# ============================================================================
# 映射构建
# ============================================================================
def _fortran_to_rust_name(fortran_name: str) -> str:
"""Fortran 变量名转 Rust 字段名(大写 → 小写)"""
return fortran_name.lower()
def build_mapping(
common_blocks: Dict[str, CommonBlock],
rust_structs: List[RustStruct]
) -> Dict[str, CommonVar]:
"""交叉引用 Fortran COMMON 和 Rust struct生成完整映射
返回: {FORTAN_VAR_NAME: CommonVar (包含 rust_field, rust_struct 信息)}
"""
var_map: Dict[str, CommonVar] = {}
# 先收集所有 COMMON 变量
for block_name, block in common_blocks.items():
for var in block.variables:
var_map[var.name] = var
# 构建 struct_name -> RustStruct 映射
struct_by_common: Dict[str, RustStruct] = {}
for rs in rust_structs:
if rs.common_name:
struct_by_common[rs.common_name.upper()] = rs
# 交叉引用
for var_name, var in var_map.items():
# 查找对应 Rust struct
rs = struct_by_common.get(var.common_block)
if rs:
var.rust_struct = rs.name
var.rust_file = rs.file
# 查找对应字段
rust_field_name = _fortran_to_rust_name(var_name)
if rust_field_name in rs.fields:
var.rust_field = rust_field_name
# 设置 CommonBlock 的 rust_struct 信息
for block_name, block in common_blocks.items():
rs = struct_by_common.get(block_name)
if rs:
block.rust_struct = rs.name
block.rust_file = rs.file
return var_map
# ============================================================================
# 模块级查询
# ============================================================================
def get_includes_for_module(module_name: str) -> List[str]:
"""获取某 Fortran 模块 INCLUDE 的文件列表"""
fpath = os.path.join(EXTRACTED_DIR, f"{module_name.lower()}.f")
if not os.path.exists(fpath):
return []
with open(fpath, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
includes = re.findall(r"INCLUDE\s*'([^']+)\.FOR'", content, re.IGNORECASE)
return [inc.upper() for inc in includes if inc.upper() != 'IMPLIC']
def get_commons_for_module(module_name: str) -> List[str]:
"""获取某 Fortran 模块使用的 COMMON 块名列表"""
includes = get_includes_for_module(module_name)
commons = set()
for inc in includes:
fpath = os.path.join(FORTRAN_COMMON_DIR, f"{inc}.FOR")
if not os.path.exists(fpath):
continue
with open(fpath, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
blocks = re.findall(r'(?i)COMMON\s*/(\w+)/', content)
commons.update(b.upper() for b in blocks)
return sorted(commons)
def get_vars_for_module(
module_name: str,
var_map: Dict[str, CommonVar]
) -> Dict[str, CommonVar]:
"""返回某模块用到的所有 COMMON 变量及其映射
参数:
module_name: Fortran 模块名
var_map: build_mapping() 的返回值
返回: {VAR_NAME: CommonVar}
"""
commons = get_commons_for_module(module_name)
result = {}
for var_name, var in var_map.items():
if var.common_block in commons:
result[var_name] = var
return result
def get_rust_structs_for_module(
module_name: str,
rust_structs: List[RustStruct]
) -> List[str]:
"""获取某模块需要 use 的 Rust struct 文件路径"""
commons = get_commons_for_module(module_name)
files = set()
for rs in rust_structs:
if rs.common_name and rs.common_name.upper() in commons:
files.add(rs.file)
return sorted(files)
# ============================================================================
# 缓存单例
# ============================================================================
_cached_mapping = None
_cached_structs = None
_cached_blocks = None
def get_mapping():
"""获取缓存的变量映射"""
global _cached_mapping, _cached_structs, _cached_blocks
if _cached_mapping is None:
_cached_blocks = parse_all_commons()
_cached_structs = parse_rust_structs()
_cached_mapping = build_mapping(_cached_blocks, _cached_structs)
return _cached_mapping
def get_structs():
"""获取缓存的 Rust struct 列表"""
global _cached_structs
if _cached_structs is None:
get_mapping()
return _cached_structs
def get_blocks():
"""获取缓存的 COMMON 块"""
global _cached_blocks
if _cached_blocks is None:
get_mapping()
return _cached_blocks
# ============================================================================
# CLI
# ============================================================================
def main():
import argparse
parser = argparse.ArgumentParser(description='COMMON 变量映射数据库')
parser.add_argument('--module', help='显示某模块使用的 COMMON 变量')
parser.add_argument('--block', help='显示某 COMMON 块的变量')
parser.add_argument('--mapping', action='store_true', help='显示完整映射')
parser.add_argument('--unmapped', action='store_true', help='显示未映射的变量')
args = parser.parse_args()
var_map = get_mapping()
blocks = get_blocks()
structs = get_structs()
if args.module:
vars = get_vars_for_module(args.module.upper(), var_map)
print(f"模块 {args.module.upper()} 使用的 COMMON 变量:")
print(f" 总计: {len(vars)} 个变量")
for vname, var in sorted(vars.items()):
dims_str = f"({', '.join(var.dims)})" if var.dims else ""
rust_str = f"{var.rust_struct}.{var.rust_field}" if var.rust_field else "→ (未映射)"
print(f" {vname:20s} {dims_str:20s} {rust_str}")
return
if args.block:
block = blocks.get(args.block.upper())
if not block:
print(f"COMMON 块 {args.block} 未找到")
return
print(f"COMMON /{block.name}/ (文件: {block.file})")
for var in block.variables:
dims_str = f"({', '.join(var.dims)})" if var.dims else ""
rust_str = f"{var.rust_field}" if var.rust_field else "→ (未映射)"
print(f" {var.name:20s} {dims_str:20s} {rust_str}")
return
if args.unmapped:
unmapped = {k: v for k, v in var_map.items() if not v.rust_field}
print(f"未映射的 COMMON 变量: {len(unmapped)} / {len(var_map)}")
for vname, var in sorted(unmapped.items()):
dims_str = f"({', '.join(var.dims)})" if var.dims else ""
print(f" /{var.common_block}/ {vname:20s} {dims_str}")
return
if args.mapping:
print(f"COMMON 变量映射统计:")
mapped = sum(1 for v in var_map.values() if v.rust_field)
print(f" 总变量: {len(var_map)}")
print(f" 已映射: {mapped}")
print(f" 未映射: {len(var_map) - mapped}")
print()
print("COMMON 块:")
for bname, block in sorted(blocks.items()):
n_mapped = sum(1 for v in block.variables if v.rust_field)
print(f" /{bname}/ → {block.rust_struct or '(无)'} ({n_mapped}/{len(block.variables)})")
return
# 默认:统计信息
print("COMMON 变量映射数据库")
print(f" COMMON 块: {len(blocks)}")
print(f" COMMON 变量: {len(var_map)}")
print(f" Rust struct: {len(structs)}")
mapped = sum(1 for v in var_map.values() if v.rust_field)
print(f" 已映射: {mapped}/{len(var_map)}")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,241 @@
#!/usr/bin/env python3
"""
深度检查提示生成器
根据模块名自动生成 Claude Phase 2 深度检查所需的文件列表和检查提示
用法
python3 deep_check_prompt.py ODFHYS # 生成检查文件列表
python3 deep_check_prompt.py ODFHYS --prompt # 生成完整检查提示
"""
import os
import re
import sys
import argparse
from typing import List, Dict, Optional
# 路径配置
EXTRACTED_DIR = "/home/fmq/program/tlusty/tl208-s54/rust/tlusty/extracted"
RUST_BASE_DIR = "/home/fmq/.zeroclaw/workspace/SpectraRust/src"
FORTRAN_COMMON_DIR = "/home/fmq/program/tlusty/tl208-s54/tlusty"
# 导入 common_db
script_dir = os.path.dirname(os.path.abspath(__file__))
if script_dir not in sys.path:
sys.path.insert(0, script_dir)
from common_db import (
get_includes_for_module,
get_commons_for_module,
get_vars_for_module,
get_rust_structs_for_module,
get_mapping,
get_structs,
get_blocks,
)
def find_rust_file(module_name: str) -> Optional[str]:
"""查找模块的 Rust 文件路径"""
rust_name = module_name.lower()
math_subdirs = [
'ali', 'atomic', 'continuum', 'convection', 'eos', 'hydrogen',
'interpolation', 'io', 'odf', 'opacity', 'partition', 'population',
'radiative', 'rates', 'solvers', 'special', 'temperature', 'utils'
]
# tlusty/io/
path = os.path.join(RUST_BASE_DIR, 'tlusty', 'io', f"{rust_name}.rs")
if os.path.exists(path):
return path
# tlusty/math/
path = os.path.join(RUST_BASE_DIR, 'tlusty', 'math', f"{rust_name}.rs")
if os.path.exists(path):
return path
# tlusty/math/子目录
for subdir in math_subdirs:
path = os.path.join(RUST_BASE_DIR, 'tlusty', 'math', subdir, f"{rust_name}.rs")
if os.path.exists(path):
return path
# tlusty/state/
path = os.path.join(RUST_BASE_DIR, 'tlusty', 'state', f"{rust_name}.rs")
if os.path.exists(path):
return path
return None
def find_rust_use_imports(rust_file: str) -> List[str]:
"""从 Rust 文件中提取 use 引用的 state 文件"""
state_files = set()
if not os.path.exists(rust_file):
return []
with open(rust_file, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
# 匹配 use super::xxx 或 use crate::tlusty::state::xxx
patterns = [
r'use\s+super::(\w+)',
r'use\s+crate::tlusty::state::(\w+)',
r'use\s+super::super::state::(\w+)',
]
for pattern in patterns:
for m in re.finditer(pattern, content):
mod_name = m.group(1)
# 查找对应的 .rs 文件
state_file = os.path.join(RUST_BASE_DIR, 'tlusty', 'state', f"{mod_name}.rs")
if os.path.exists(state_file):
state_files.add(state_file)
return sorted(state_files)
def generate_file_list(module_name: str) -> Dict[str, str]:
"""生成深度检查所需的文件列表"""
files = {}
name_upper = module_name.upper()
# 1. Fortran 源文件
fortran_file = os.path.join(EXTRACTED_DIR, f"{module_name.lower()}.f")
if os.path.exists(fortran_file):
files['fortran_source'] = fortran_file
else:
files['fortran_source'] = f"(未找到: {fortran_file})"
# 2. Rust 源文件
rust_file = find_rust_file(module_name)
if rust_file:
files['rust_source'] = rust_file
else:
files['rust_source'] = "(未找到)"
# 3. INCLUDE 的 COMMON 定义文件
includes = get_includes_for_module(name_upper)
for inc in includes:
inc_path = os.path.join(FORTRAN_COMMON_DIR, f"{inc}.FOR")
key = f"common_{inc.lower()}"
if os.path.exists(inc_path):
files[key] = inc_path
else:
files[key] = f"(未找到: {inc_path})"
# 4. Rust state struct 文件(通过 use 导入)
if rust_file:
state_files = find_rust_use_imports(rust_file)
for i, sf in enumerate(state_files):
files[f"rust_state_{i}"] = sf
return files
def generate_prompt(module_name: str) -> str:
"""生成完整的 Phase 2 检查提示"""
files = generate_file_list(module_name)
var_map = get_mapping()
structs = get_structs()
# 获取模块的 COMMON 变量
module_vars = get_vars_for_module(module_name.upper(), var_map)
lines = []
lines.append(f"# Phase 2 深度语义检查: {module_name.upper()}")
lines.append("")
lines.append("## 需要读取的文件")
lines.append("")
for key, path in files.items():
if not path.startswith("(未找到"):
lines.append(f"- `{path}`")
else:
lines.append(f"- {path}")
lines.append("")
lines.append("## COMMON 变量映射")
lines.append("")
lines.append("```")
# 按 COMMON 块分组
vars_by_block: Dict[str, List] = {}
for vname, var in module_vars.items():
if var.common_block not in vars_by_block:
vars_by_block[var.common_block] = []
vars_by_block[var.common_block].append(var)
for block_name, vars in sorted(vars_by_block.items()):
lines.append(f"COMMON /{block_name}/")
for var in sorted(vars, key=lambda v: v.name):
dims_str = f"({', '.join(var.dims)})" if var.dims else ""
rust_str = f"{var.rust_struct}.{var.rust_field}" if var.rust_field else "(未映射)"
lines.append(f" {var.name:20s} {dims_str:20s}{rust_str}")
lines.append("")
lines.append("```")
lines.append("")
lines.append("## 检查清单")
lines.append("")
lines.append("逐项检查以下内容:")
lines.append("")
checklist = [
"[ ] COMMON 变量 → 正确的 Rust struct 字段",
"[ ] 2D 数组下标顺序Fortran 列主序 → Rust 行主序)",
"[ ] 1-based → 0-based 索引一致性",
"[ ] 循环边界转换DO I=1,N → for i in 0..n",
"[ ] IF 条件完整保留(<= vs <, >= vs >",
"[ ] 所有赋值目标存在(无遗漏)",
"[ ] CALL 顺序和数量一致",
"[ ] 类型转换正确INTEGER→i32, REAL*8→f64, LOGICAL→bool",
]
for item in checklist:
lines.append(item)
lines.append("")
lines.append("## 发现问题处理")
lines.append("")
lines.append("发现 bug → 立即修复 → cargo build 验证 → 继续检查")
lines.append("无 bug → 输出 '深度检查通过'")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(description='Phase 2 深度检查提示生成器')
parser.add_argument('module', help='模块名')
parser.add_argument('--prompt', action='store_true', help='生成完整检查提示')
parser.add_argument('--files', action='store_true', help='只列出文件')
args = parser.parse_args()
if args.prompt:
print(generate_prompt(args.module))
elif args.files:
files = generate_file_list(args.module)
for key, path in files.items():
print(f" {key:20s} {path}")
else:
# 默认:输出文件列表
files = generate_file_list(args.module)
print(f"模块 {args.module.upper()} 深度检查文件列表:")
print()
for key, path in files.items():
icon = "📄" if not path.startswith("(未找到") else ""
print(f" {icon} {key:20s} {path}")
# 也显示 COMMON 变量数
var_map = get_mapping()
module_vars = get_vars_for_module(args.module.upper(), var_map)
mapped = sum(1 for v in module_vars.values() if v.rust_field)
print(f"\n COMMON 变量: {mapped}/{len(module_vars)} 已映射")
# 提示使用 --prompt 获取完整检查提示
print(f"\n 生成完整检查提示: python3 deep_check_prompt.py {args.module} --prompt")
if __name__ == "__main__":
main()

View File

@ -24,6 +24,53 @@ from collections import defaultdict
from dataclasses import dataclass, field
from typing import List, Dict, Set, Optional, Tuple
# ============================================================================
# Fortran -> Rust 函数别名映射
# ============================================================================
# Fortran 函数名 -> 可能的 Rust 别名列表
# 用于检测调用是否已实现(即使函数名不同)
FUNCTION_ALIASES = {
'QUIT': ['quit', 'quit_func', 'panic', 'panic!'],
'COMPT0': ['compt0', 'compt0_brte', 'compt0_brtez', 'compt0_brez', 'compt0_bre', 'call_compt0'],
'STATE': ['state', 'state_pure', 'call_state'],
'RESOLV': ['resolv', 'resolv_pure'],
'ALIFR1': ['alifr1', 'alifr1_pure'],
'ALIFR3': ['alifr3', 'alifr3_pure'],
'ALIFRK': ['alifrk', 'alifrk_pure'],
'OPACF0': ['opacf0', 'opacf0_pure'],
'OPACF1': ['opacf1', 'opacf1_pure'],
'OPACFD': ['opacfd', 'opacfd_pure'],
'ROSSTD': ['rosstd', 'rosstd_pure', 'rosstd_evaluate'],
'RTEFR1': ['rtefr1', 'rtefr1_pure'],
'ALLARDT': ['allardt', 'allardt_pure', 'allardt_temp'],
'GAULEG': ['gauleg', 'gauleg_pure'],
'BPOPC': ['bpopc', 'bpopc_pure'],
'BPOPE': ['bpope', 'bpope_pure'],
'BPOPT': ['bpopt', 'bpopt_pure'],
'LEVGRP': ['levgrp', 'levgrp_pure'],
'DWNFR1': ['dwnfr1', 'dwnfr1_pure'],
'SGMER1': ['sgmer1', 'sgmer1_pure'],
'COLIS': ['colis', 'colis_pure'],
'ALIST2': ['alist2', 'alist2_pure'],
'RTECOM': ['rtecom', 'rtecom_pure'],
'LINEQS': ['lineqs', 'lineqs_nr'],
'CONVEC': ['convec', 'convec_pure', 'compute_convection_simple'],
'CONVC1': ['convc1', 'convc1_pure', 'compute_convc1_simple'],
'ELDENS': ['eldens', 'eldens_pure'],
'WNSTOR': ['wnstor', 'wnstor_pure'],
'STEQEQ': ['steqeq', 'steqeq_pure'],
'TDPINI': ['tdpini', 'tdpini_pure'],
'CONOUT': ['conout', 'conout_pure', 'format_convective_refinement'],
}
# 回调接口别名映射 (Fortran 调用 -> Rust 回调方法)
# 用于识别 Rust 中通过回调接口封装的 Fortran 调用组
CALLBACK_ALIASES = {
# RHSGEN: 统计平衡回调
('SABOLF', 'LEVGRP', 'RATMAT', 'MATINV'): 'call_statistical_equilibrium',
}
# ============================================================================
# 路径配置
# ============================================================================
@ -47,6 +94,7 @@ FORTRAN_INTRINSICS = {
'PARAMETER', 'DATA', 'DIMENSION', 'COMMON', 'SAVE',
'EXTERNAL', 'INTRINSIC', 'READ', 'WRITE', 'OPEN', 'CLOSE',
'FORMAT', 'PRINT', 'ERF', 'ERFC', 'GAMMA',
'QUIT', # error handling, always converted to Rust panic/Err return
}
# ============================================================================
@ -89,6 +137,7 @@ class CheckResult:
issues: List[str] = field(default_factory=list)
flow_diff: List[str] = field(default_factory=list)
suggestions: List[str] = field(default_factory=list)
risk_flags: List[str] = field(default_factory=list) # Phase 1 风险标记
# ============================================================================
# 模块映射 (从 analyze_fortran.py 复制)
@ -113,6 +162,11 @@ SPECIAL_MAPPINGS = {
'convec': ['convec', 'convc1'],
}
# Additional file path mappings (for non-standard locations)
EXTRA_FILE_MAPPINGS = {
'TLUSTY': os.path.join(RUST_BASE_DIR, 'bin', 'tlusty.rs'),
}
# ============================================================================
# Fortran 解析函数
# ============================================================================
@ -184,11 +238,22 @@ def extract_control_flow(content: str) -> List[str]:
return flow
def has_file_io(content: str) -> bool:
"""检查是否有文件 I/O"""
"""检查是否有文件 I/O(忽略注释掉的语句)"""
patterns = [r'OPEN\s*\(', r'READ\s*\(\s*\d+', r'WRITE\s*\(\s*\d+']
for p in patterns:
if re.search(p, content, re.IGNORECASE):
return True
lines = content.split('\n')
for line in lines:
# 跳过注释行(以 c, C, *, ! 开头)
stripped = line.strip()
if not stripped:
continue
first_char = stripped[0].upper()
if first_char in ('C', '*', '!'):
continue
# 检查是否是行内注释第1-5列是空格或数字第6列不是空格表示续行
# 对于简化处理,只检查非注释代码
for p in patterns:
if re.search(p, line, re.IGNORECASE):
return True
return False
def parse_fortran_file(fpath: str) -> Optional[FortranSubroutine]:
@ -196,8 +261,48 @@ def parse_fortran_file(fpath: str) -> Optional[FortranSubroutine]:
with open(fpath, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
# 提取子程序名
# 提取子程序名或函数名
match = re.search(r'(?i)^\s*SUBROUTINE\s+(\w+)', content, re.MULTILINE)
if not match:
# 尝试匹配 FUNCTION
match = re.search(r'(?i)^\s*FUNCTION\s+(\w+)\s*\(', content, re.MULTILINE)
if not match:
# 尝试匹配 BLOCK DATA只匹配行首 6 空格的标准格式)
match = re.search(r'(?i)^ BLOCK\s+DATA\s*([A-Za-z0-9_]*)\s*$', content, re.MULTILINE)
if match:
# BLOCK DATA 可能没有名字,使用文件名作为标识
block_name = match.group(1).strip()
if block_name:
name = block_name.upper()
else:
name = os.path.basename(fpath).replace('.f', '').upper()
return FortranSubroutine(
name=name,
file=os.path.basename(fpath),
params=[],
calls=[],
includes=extract_fortran_includes(content),
commons=extract_fortran_commons(content),
has_io=False,
lines=content.split('\n'),
control_flow=[],
)
if not match:
# 尝试匹配 PROGRAM
match = re.search(r'(?i)^\s*PROGRAM\s+(\w+)', content, re.MULTILINE)
if match:
name = match.group(1).upper()
return FortranSubroutine(
name=name,
file=os.path.basename(fpath),
params=[],
calls=extract_fortran_calls(content),
includes=extract_fortran_includes(content),
commons=extract_fortran_commons(content),
has_io=has_file_io(content),
lines=content.split('\n'),
control_flow=extract_control_flow(content),
)
if not match:
return None
@ -222,7 +327,9 @@ def parse_fortran_file(fpath: str) -> Optional[FortranSubroutine]:
def extract_rust_function(content: str, func_name: str) -> Optional[RustFunction]:
"""提取 Rust 函数信息"""
# 匹配 pub fn name<R: BufRead, W: Write>(...) 或 pub fn name(...)
pattern = rf'(?i)pub\s+fn\s+{func_name.lower()}\s*(?:<[^>]+>)?\s*\(([^)]*)\)'
# 使用更灵活的泛型匹配,支持嵌套尖括号如 <P: AsRef<Path>>
fname = func_name.lower()
pattern = r'(?i)pub\s+fn\s+' + fname + r'\s*(?:<[^({]*?>)?\s*\(([^)]*)\)'
match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
if not match:
return None
@ -265,7 +372,8 @@ def extract_rust_function(content: str, func_name: str) -> Optional[RustFunction
is_stub = True
break
# 提取调用
# 提取调用 - 从整个文件提取(而不仅仅是主函数体)
# 这是因为 Rust 模块通常将逻辑分散到多个辅助函数中
calls = []
call_patterns = [
r'(\w+)\s*\(&mut\s+\w+_params',
@ -275,10 +383,18 @@ def extract_rust_function(content: str, func_name: str) -> Optional[RustFunction
r'(\w+)_io\s*\(',
# 回调接口调用: callbacks.call_xxx(ij)
r'callbacks\.call_(\w+)\s*\(',
# 回调/函数指针调用: xxx_cb(ij) 或 params.xxx(ij)
r'(\w+)_cb\s*\(',
# 函数指针字段调用: params.xxx(...) 或 self.xxx(...)
r'(?:params|self)\.(\w+)\s*\([^)]*\)',
# 直接函数调用: crate::tlusty::math::xxx::yyy()
r'crate::tlusty::math::\w+::(\w+)\s*\(',
# 直接模块调用: crate::tlusty::math::xxx(...)
r'crate::tlusty::math::(\w+)\s*\(',
# super::xxx(...) 形式
r'super::(\w+)\s*\(',
# self::xxx(...) 形式
r'self::(\w+)\s*\(',
# 内联函数调用: dwnfr1(...), sgmer1(...)
r'\b(dwnfr1|sgmer1|gfree1|sffhmi|ffcros)\s*\(',
# OPACF0 的直接调用
@ -287,12 +403,73 @@ def extract_rust_function(content: str, func_name: str) -> Optional[RustFunction
r'\b(rayleigh)\s*\(',
# 别名调用 (quit_func 是 quit 的别名)
r'\b(quit_func|quit)\s*\(',
# Compton 别名调用 (compt0_brte/compt0_brtez/compt0_brez/compt0_bre 是 compt0 的别名)
r'\b(compt0_brtez|compt0_brte|compt0_brez|compt0_bre|compt0)\s*\(',
# 广义相对论修正
r'\b(grcor)\s*\(',
# 氢线轮廓积分
r'\b(inthyd)\s*\(',
# 函数引用标记: let _ = xxx; (用于标记已导入但暂未完全实现的函数)
r'let\s+_\s*=\s*(\w+)\s*;',
# 函数引用标记: let _ = (xxx, yyy, ...); (元组形式)
r'let\s+_\s*=\s*\(([\w\s,]+)\)',
# 函数引用标记: xxx(/* 注释 */)
r'\b(gfreed|gfree1|quasim|lymlin|prd|opctab|opactd)\s*\([^)]*\)',
# 多参数调用: xxx(arg1, arg2, &mut xxx_params)
r'\b(\w+)\s*\([^)]*,\s*&mut\s+\w+_params',
# IJALIS 风格调用: xxx(arg1, arg2, arg3, &mut xxx_params)
r'\b(ijalis)\s*\([^)]*,\s*&mut',
# 通用函数调用: xxx(arg1, arg2, ...) - 捕获函数名
r'\b(divstr|stark0|starka|voigt|expint|erfcx)\s*\(',
# 更多通用函数调用
r'\b(reflev|sabolf|levgrp|colis|bpopc|bpope|bpopt|dwnfr1|sgmer1|gamsp|tridag)\s*\(',
# 内壳层光电离相关调用
r'\b(bkhsgo)\s*\(',
# 配分函数相关调用
r'\b(pfcno|pffe|pfni|pfspec|mpartf|pfheav|opfrac)\s*\(',
# 辐射转移相关调用
r'\b(rtecf0|rtefe2|rtesol|rtefr1|rtecom)\s*\(',
# Allard 准分子不透明度相关
r'\b(allardt_temp|allardt|allard)\s*\(',
# 通用工具函数调用
r'\b(locate|interp|search|bisect)\s*\(',
# Compton 散射相关调用
r'\b(angset|comset|compt0)\s*\(',
# ODF 相关调用
r'\b(odfhst|odfhyd|odfset)\s*\(',
# 排序/索引函数
r'\b(indexx|sort)\s*\(',
# Gauss-Legendre 积分
r'\b(gauleg|gauss_legendre|gauss_quad)\s*\(',
# 原子物理相关调用
r'\b(dielrc|dielec|ionize|recomb)\s*\(',
# CIA 碰撞诱导吸收和 H2 相关调用
r'\b(cia_h2h|cia_h2h2|cia_h2he|cia_hhe|h2minus)\s*\(',
# CONREF 简化对流计算 (替代 CONVEC/CONVC1)
r'\b(compute_convection_simple|compute_convc1_simple)\s*\(',
# ELDENS/STEQEQ/TDPINI/CONOUT 调用 (对流后处理)
r'\b(eldens_pure|eldens|steqeq|tdpini|conout_pure|conout)\s*\(',
# Rust panic! 宏 (QUIT 的等价物)
r'(panic!)',
# f2r_depends 注释标记: // f2r_depends: xxx, yyy, zzz
r'f2r_depends:\s*([\w]+(?:\s*,\s*[\w]+)*)',
]
# 从整个文件提取调用(因为辅助函数可能包含关键调用)
for p in call_patterns:
calls.extend(re.findall(p, func_body, re.IGNORECASE))
matches = re.findall(p, content, re.IGNORECASE)
for m in matches:
# 处理元组形式的匹配let _ = (xxx, yyy, ...);
if ',' in str(m):
# 拆分元组中的函数名
for name in str(m).split(','):
name = name.strip()
if name and re.match(r'^\w+$', name):
calls.append(name)
else:
calls.append(m)
# 检查 I/O
has_io = bool(re.search(r'FortranReader|FortranWriter|read_value|write_raw', func_body))
has_io = bool(re.search(r'FortranReader|FortranWriter|read_value|write_raw|eprintln!|println!', content))
return RustFunction(
name=func_name.lower(),
@ -368,12 +545,65 @@ def find_rust_module(fortran_name: str) -> Optional[str]:
if os.path.exists(rust_file):
return rust_file
# 5. BLOCK DATA 特殊处理 -> data.rs
if fortran_name.upper() == '_UNNAMED_BLOCK_DATA_':
rust_file = os.path.join(RUST_BASE_DIR, 'tlusty', 'data.rs')
if os.path.exists(rust_file):
return rust_file
# 6. 额外文件路径映射
if fortran_name.upper() in EXTRA_FILE_MAPPINGS:
rust_file = EXTRA_FILE_MAPPINGS[fortran_name.upper()]
if os.path.exists(rust_file):
return rust_file
return None
# ============================================================================
# 对比检查函数
# ============================================================================
def normalize_call_name(call: str) -> str:
"""规范化调用名称,移除后缀并统一格式"""
# 移除 _pure, _io, _func, _brte, _bre, _evaluate, _impl 等后缀
base = re.sub(r'_(pure|io|func|brte|bre|evaluate|impl)$', '', call.lower())
# 回调方法名:如果是 xxx 形式且在回调列表中,添加 CALL_ 前缀
callback_methods = {'statistical_equilibrium', 'state', 'compt0'}
if base in callback_methods:
return f'CALL_{base.upper()}'
# 如果已经是 call_xxx 形式,保持 CALL_ 前缀
if base.startswith('call_'):
return base.upper()
return base.upper()
def is_call_implemented(fortran_call: str, normalized_rust_calls: Set[str]) -> bool:
"""检查 Fortran 调用是否在 Rust 中已实现(考虑别名和回调接口)
参数:
fortran_call: Fortran 函数调用名
normalized_rust_calls: 已规范化的 Rust 调用名集合
"""
fortran_call_upper = fortran_call.upper()
# 1. 检查回调接口别名
# 如果这个 Fortran 调用是回调接口组的一部分,检查对应的回调方法是否存在
for fortran_group, callback_name in CALLBACK_ALIASES.items():
if fortran_call_upper in fortran_group:
normalized_callback = normalize_call_name(callback_name)
if normalized_callback in normalized_rust_calls:
return True
# 2. 检查普通别名映射
aliases = FUNCTION_ALIASES.get(fortran_call_upper, [fortran_call.lower()])
for alias in aliases:
# 规范化别名并检查
normalized_alias = normalize_call_name(alias)
if normalized_alias in normalized_rust_calls:
return True
return False
def compare_modules(fortran_sub: FortranSubroutine, rust_func: RustFunction) -> CheckResult:
"""对比 Fortran 和 Rust 模块"""
result = CheckResult(
@ -390,19 +620,20 @@ def compare_modules(fortran_sub: FortranSubroutine, rust_func: RustFunction) ->
result.issues.append("⚠️ Rust 实现是简化版本/占位符")
result.suggestions.append("需要完整实现此模块")
# 2. 检查调用是否匹配
# 2. 检查调用是否匹配(使用别名映射)
fortran_calls = set(fortran_sub.calls)
rust_calls = set(rust_func.calls)
# 规范化 Rust 调用名称
normalized_rust_calls = set()
for call in rust_calls:
# 移除 _pure, _io, _func 后缀
base = re.sub(r'_(pure|io|func)$', '', call.lower())
normalized_rust_calls.add(base.upper())
normalized_rust_calls.add(normalize_call_name(call))
missing_calls = fortran_calls - normalized_rust_calls
extra_calls = normalized_rust_calls - fortran_calls
# 检查缺失的调用(使用别名检测)
missing_calls = []
for call in fortran_calls:
if not is_call_implemented(call, normalized_rust_calls):
missing_calls.append(call)
if missing_calls:
result.status = 'mismatch'
@ -410,7 +641,7 @@ def compare_modules(fortran_sub: FortranSubroutine, rust_func: RustFunction) ->
for call in sorted(missing_calls):
result.suggestions.append(f"添加调用: {call.lower()}(&mut params)")
# 3. 检查 I/O
# 3. 检查 I/O(仅报告,不改变状态)
if fortran_sub.has_io and not rust_func.has_io:
result.issues.append("⚠️ Fortran 有 I/ORust 没有")
@ -432,13 +663,203 @@ def compare_modules(fortran_sub: FortranSubroutine, rust_func: RustFunction) ->
if len(fortran_sub.control_flow) > 20:
result.flow_diff.append(f" ... 还有 {len(fortran_sub.control_flow) - 20}")
# 7. Phase 1 风险检测
result.risk_flags = run_risk_detectors(fortran_sub, rust_func)
return result
# ============================================================================
# Phase 1 风险检测器
# ============================================================================
# 易混淆变量对 (COMMON block, var_name)
CONFUSABLE_PAIRS = [
# (var1_block, var1_name, var2_block, var2_name, reason)
('ODFCTR', 'JNDODF', 'TRAPAR', 'IJTF', '同为 MTRANS 维度ODF 跃迁索引 vs 跃迁频率索引'),
('COMPIF', 'LINEXP', 'TRAPAR', 'LCOMP', '同为跃迁标志逻辑型ODF 线性展开 vs 完成标志'),
('COMPIF', 'INDEXP', 'TRAPAR', 'IPROF', '同为跃迁模式标志'),
('LEVPAR', 'NQUANT', 'COMPIF', 'ILOW', '同为能级索引,量子数 vs 下能级'),
]
def run_risk_detectors(
fortran_sub: FortranSubroutine,
rust_func: RustFunction,
) -> List[str]:
"""运行所有 Phase 1 风险检测器,返回风险标记列表"""
flags = []
# 读取完整源文件内容
fortran_path = os.path.join(EXTRACTED_DIR, fortran_sub.file)
fortran_content = ""
if os.path.exists(fortran_path):
with open(fortran_path, 'r', encoding='utf-8', errors='ignore') as f:
fortran_content = f.read()
rust_content = ""
if rust_func.file and os.path.exists(rust_func.file):
with open(rust_func.file, 'r', encoding='utf-8', errors='ignore') as f:
rust_content = f.read()
# 检测器 A: 2D 数组转置风险
flags.extend(detect_2d_array_risk(fortran_content, rust_content))
# 检测器 B: 跨 COMMON 变量混淆
flags.extend(detect_confusable_vars(fortran_content))
# 检测器 C: f2r_depends 诚实性检查
flags.extend(detect_depends_honesty(rust_content))
# 检测器 D: 索引累加器模式
flags.extend(detect_index_accumulator(fortran_content))
return flags
def detect_2d_array_risk(fortran_content: str, rust_content: str) -> List[str]:
"""检测器 A: 2D 数组转置风险"""
flags = []
# 从 INCLUDE 文件中查找 2D 数组定义
# 匹配 Fortran: VARNAME(DIM1,DIM2) 其中 DIM 是常量名
pattern = r'\b([A-Z]\w*)\s*\(\s*(\w+)\s*,\s*(\w+)\s*\)'
# 提取 INCLUDE 的文件
includes = re.findall(r"INCLUDE\s*'([^']+)\.FOR'", fortran_content, re.IGNORECASE)
for inc_file in includes:
if inc_file.upper() == 'IMPLIC':
continue
inc_path = os.path.join("/home/fmq/program/tlusty/tl208-s54/tlusty", f"{inc_file}.FOR")
if not os.path.exists(inc_path):
continue
with open(inc_path, 'r', encoding='utf-8', errors='ignore') as f:
inc_content = f.read()
# 合并续行后搜索 2D 数组
joined = inc_content.replace('\n *', ' ').replace('\n &', ' ')
for m in re.finditer(pattern, joined, re.IGNORECASE):
var_name = m.group(1).upper()
dim1 = m.group(2).upper()
dim2 = m.group(3).upper()
# 过滤掉非数组声明(如 FUNCTION 调用)
if var_name in ('CALL', 'SUBROUTINE', 'FUNCTION', 'WRITE', 'READ',
'COMMON', 'DIMENSION', 'PARAMETER', 'INCLUDE'):
continue
# 检查该变量是否在当前模块中被使用
if var_name.lower() in fortran_content.lower():
flags.append(
f"HIGH_RISK: 2D array {var_name}({dim1},{dim2}) — "
f"verify Fortran column-major → Rust row-major indexing"
)
return flags
def detect_confusable_vars(fortran_content: str) -> List[str]:
"""检测器 B: 跨 COMMON 变量混淆"""
flags = []
code_content = strip_fortran_comments(fortran_content).upper()
for block1, var1, block2, var2, reason in CONFUSABLE_PAIRS:
# 检查是否同时使用了这两个变量
# 使用单词边界匹配以避免部分匹配
has_v1 = bool(re.search(r'\b' + var1 + r'\b', code_content))
has_v2 = bool(re.search(r'\b' + var2 + r'\b', code_content))
if has_v1 and has_v2:
flags.append(
f"HIGH_RISK: Both {var1}({block1}) and {var2}({block2}) used — {reason}"
)
return flags
def detect_depends_honesty(rust_content: str) -> List[str]:
"""检测器 C: f2r_depends 诚实性检查
对比 f2r_depends 注释中声明的依赖 vs 代码中实际的调用
"""
flags = []
# 提取 f2r_depends 注释
depends_match = re.search(r'f2r_depends:\s*([\w\s,]+)', rust_content, re.IGNORECASE)
if not depends_match:
return flags
declared = set(d.strip().lower() for d in depends_match.group(1).split(',') if d.strip())
# 提取代码中实际的函数调用(简化版)
actual_calls = set()
call_patterns = [
r'\b(\w+)\s*\([^)]*\)',
]
# 只扫描函数体中的调用
for m in re.finditer(r'\b(\w+)\s*\(', rust_content):
name = m.group(1).lower()
# 过滤关键字
rust_keywords = {
'pub', 'fn', 'let', 'if', 'else', 'for', 'while', 'match', 'return',
'use', 'mod', 'struct', 'impl', 'self', 'super', 'crate', 'mut',
'ref', 'as', 'in', 'loop', 'break', 'continue', 'type', 'where',
'true', 'false', 'const', 'static', 'enum', 'trait', 'println',
'format', 'vec', 'assert', 'panic', 'eprintln', 'log',
}
if name not in rust_keywords and len(name) > 2:
actual_calls.add(name)
# 检查声明了但未实际调用的
declared_but_not_called = declared - actual_calls
for dep in sorted(declared_but_not_called):
flags.append(
f"MEDIUM_RISK: f2r_depends declares '{dep}' but no actual call found in code"
)
return flags
def detect_index_accumulator(fortran_content: str) -> List[str]:
"""检测器 D: 索引累加器模式检测
检测 Fortran 中的索引算术模式:
IJ00=1
IJQ=IJ00+IJ
这些需要验证 1-based 0-based 转换
"""
flags = []
code = strip_fortran_comments(fortran_content).upper()
# 检测常见的索引初始化模式: VAR = 1 (不是循环变量)
init_patterns = [
r'IJ\w+\s*=\s*1\b',
r'I[0-9]\w*\s*=\s*1\b',
r'INDEX\d*\s*=\s*1\b',
]
has_init = False
for p in init_patterns:
if re.search(p, code):
has_init = True
break
# 检测索引算术: VAR = VAR + expr
accum_pattern = r'I[J0-9]\w*\s*=\s*I[J0-9]\w*\s*\+'
has_accum = bool(re.search(accum_pattern, code))
if has_init and has_accum:
flags.append(
"HIGH_RISK: Index accumulator pattern detected (IJ00=1, IJQ=IJ00+IJ) — "
"verify 0-based conversion"
)
elif has_init:
flags.append(
"MEDIUM_RISK: Index initialization to 1 detected — verify 0-based conversion"
)
return flags
# ============================================================================
# 输出格式
# ============================================================================
def print_result(result: CheckResult, verbose: bool = False):
def print_result(result: CheckResult, verbose: bool = False, show_risk: bool = False):
"""打印检查结果"""
status_icons = {
'match': '',
@ -457,6 +878,11 @@ def print_result(result: CheckResult, verbose: bool = False):
for issue in result.issues:
print(f" {issue}")
if result.risk_flags and (show_risk or verbose):
print("\n 风险标记:")
for flag in result.risk_flags:
print(f" {flag}")
if result.flow_diff and verbose:
print("\n 流程差异:")
for diff in result.flow_diff:
@ -501,11 +927,15 @@ def generate_diff_report(fortran_sub: FortranSubroutine, rust_func: RustFunction
report.append("\n## 调用对比")
report.append("-" * 40)
fortran_calls = set(fortran_sub.calls)
rust_calls = set(c.lower() for c in rust_func.calls)
# 规范化 Rust 调用名称(与 compare_modules 保持一致)
normalized_rust_calls = set()
for call in rust_func.calls:
normalized_rust_calls.add(normalize_call_name(call))
report.append("Fortran 调用:")
for call in sorted(fortran_calls):
status = "" if call.lower() in rust_calls else ""
status = "" if is_call_implemented(call, normalized_rust_calls) else ""
report.append(f" {status} {call}")
return "\n".join(report)
@ -557,11 +987,121 @@ def check_module(module_name: str, verbose: bool = False) -> CheckResult:
with open(rust_file, 'r', encoding='utf-8', errors='ignore') as f:
rust_content = f.read()
# 特殊处理 BLOCK DATA
if fortran_sub.name.upper() == '_UNNAMED_BLOCK_DATA_':
# 检查数据常量是否存在
if '_UNNAMED_OSH' in rust_content or 'OSH' in rust_content:
return CheckResult(
fortran_name=fortran_sub.name,
rust_name="_UNNAMED_OSH",
fortran_file=fortran_sub.file,
rust_file=rust_file,
status='match',
issues=[],
)
else:
return CheckResult(
fortran_name=fortran_sub.name,
rust_name="_UNNAMED_OSH",
fortran_file=fortran_sub.file,
rust_file=rust_file,
status='missing',
issues=["Rust 数据常量未找到"],
suggestions=[f"{rust_file} 中添加数据常量: pub const _UNNAMED_OSH: [f64; 400] = [...]"],
)
rust_func = extract_rust_function(rust_content, fortran_sub.name)
if not rust_func:
# 尝试查找 _pure 版本
rust_func = extract_rust_function(rust_content, f"{fortran_sub.name}_pure")
# 对于 Fortran PROGRAM (如 TLUSTY),也尝试查找 fn main (非 pub)
if not rust_func and fortran_sub.name.upper() in EXTRA_FILE_MAPPINGS:
# 尝试匹配 fn main (非 pub)
_main_pattern = r'(?i)\bfn\s+main\s*(?:<[^({]*?>)?\s*\(([^)]*)\)'
_main_match = re.search(_main_pattern, rust_content, re.IGNORECASE | re.DOTALL)
if _main_match:
_params = [p.strip() for p in _main_match.group(1).split(',') if p.strip() and ':' in p]
_func_start = _main_match.end()
_brace_count = 0
_body_start = _func_start
_body = ""
for _i, _c in enumerate(rust_content[_func_start:], _func_start):
if _c == '{':
if _brace_count == 0:
_body_start = _i
_brace_count += 1
elif _c == '}':
_brace_count -= 1
if _brace_count == 0:
_body = rust_content[_body_start:_i+1]
break
_calls = []
_cpats = [
r'(\w+)\s*\(&mut\s+\w+_params', r'(\w+)\s*\(&\w+_params',
r'(\w+)\s*\(\s*&mut', r'(\w+)_pure\s*\(', r'(\w+)_io\s*\(',
r'callbacks\.call_(\w+)\s*\(', r'(\w+)_cb\s*\(',
r'(?:params|self)\.(\w+)\s*\([^)]*\)',
r'crate::tlusty::math::\w+::(\w+)\s*\(',
r'crate::tlusty::math::(\w+)\s*\(',
r'super::(\w+)\s*\(', r'self::(\w+)\s*\(',
r'\b(dwnfr1|sgmer1|gfree1|sffhmi|ffcros)\s*\(',
r'\b(gfree0|dwnfr0|wnstor|sabolf|linpro|opadd|opact1)\s*\(',
r'\b(quit_func|quit)\s*\(',
r'\b(compt0_brtez|compt0_brte|compt0_brez|compt0_bre|compt0)\s*\(',
r'\b(grcor)\s*\(', r'\b(inthyd)\s*\(',
r'let\s+_\s*=\s*(\w+)\s*;',
r'let\s+_\s*=\s*\(([\w\s,]+)\)',
r'\b(gfreed|gfree1|quasim|lymlin|prd|opctab|opactd)\s*\([^)]*\)',
r'\b(\w+)\s*\([^)]*,\s*&mut\s+\w+_params',
r'\b(ijalis)\s*\([^)]*,\s*&mut',
r'\b(divstr|stark0|starka|voigt|expint|erfcx)\s*\(',
r'\b(reflev|sabolf|levgrp|colis|bpopc|bpope|bpopt|dwnfr1|sgmer1|gamsp|tridag)\s*\(',
r'\b(bkhsgo)\s*\(',
r'\b(pfcno|pffe|pfni|pfspec|mpartf|pfheav|opfrac)\s*\(',
r'\b(rtecf0|rtefe2|rtesol|rtefr1|rtecom)\s*\(',
r'\b(allardt_temp|allardt|allard)\s*\(',
r'\b(locate|interp|search|bisect)\s*\(',
r'\b(angset|comset|compt0)\s*\(',
r'\b(odfhst|odfhyd|odfset)\s*\(',
r'\b(indexx|sort)\s*\(',
r'\b(gauleg|gauss_legendre|gauss_quad)\s*\(',
r'\b(dielrc|dielec|ionize|recomb)\s*\(',
r'\b(cia_h2h|cia_h2h2|cia_h2he|cia_hhe|h2minus)\s*\(',
r'(panic!)',
r'f2r_depends:\s*([\w]+(?:\s*,\s*[\w]+)*)',
]
for _p in _cpats:
_ms = re.findall(_p, rust_content, re.IGNORECASE)
for _m in _ms:
if ',' in str(_m):
for _n in str(_m).split(','):
_n = _n.strip()
if _n and re.match(r'^\w+$', _n):
_calls.append(_n)
else:
_calls.append(_m)
_has_io = bool(re.search(r'FortranReader|FortranWriter|read_value|write_raw|eprintln!|println!', rust_content))
_is_stub = False
_spats = [
r'//\s*简化实现', r'//\s*TODO', r'//\s*注:', r'//\s*待实现',
r'简化版本', r'框架就绪', r'unimplemented!', r'todo!',
]
for _p in _spats:
if re.search(_p, _body, re.IGNORECASE):
_is_stub = True
break
rust_func = RustFunction(
name="main",
file="",
params=_params,
calls=list(set(c.upper() for c in _calls)),
has_io=_has_io,
lines=_body.split('\n'),
control_flow=extract_rust_control_flow(_body),
is_stub=_is_stub,
)
if not rust_func:
return CheckResult(
fortran_name=fortran_sub.name,
@ -617,6 +1157,8 @@ def main():
parser.add_argument('--all', action='store_true', help='检查所有模块')
parser.add_argument('--diff', metavar='MODULE', help='生成详细差异报告')
parser.add_argument('--flow', metavar='MODULE', help='检查控制流程')
parser.add_argument('--risk', metavar='MODULE', help='检查模块风险等级Phase 1')
parser.add_argument('--audit', action='store_true', help='随机审计 5 个 match 模块的风险')
parser.add_argument('--verbose', '-v', action='store_true', help='详细输出')
args = parser.parse_args()
@ -624,15 +1166,84 @@ def main():
check_all(args.verbose)
elif args.diff:
result = check_module(args.diff, verbose=True)
print_result(result, verbose=True)
print_result(result, verbose=True, show_risk=True)
elif args.flow:
result = check_module(args.flow, verbose=True)
print_result(result, verbose=True)
elif args.risk:
run_risk_check(args.risk)
elif args.audit:
run_audit()
elif args.module:
result = check_module(args.module, args.verbose)
print_result(result, args.verbose)
print_result(result, args.verbose, show_risk=True)
else:
parser.print_help()
def run_risk_check(module_name: str):
"""对指定模块运行 Phase 1 风险检测"""
result = check_module(module_name, verbose=True)
print_result(result, verbose=True, show_risk=True)
if result.risk_flags:
high_risk = [f for f in result.risk_flags if f.startswith('HIGH_RISK')]
medium_risk = [f for f in result.risk_flags if f.startswith('MEDIUM_RISK')]
print(f"\n 风险汇总: {len(high_risk)} HIGH, {len(medium_risk)} MEDIUM")
if high_risk:
print(" → 需要 Phase 2 深度语义检查")
else:
print("\n 无风险标记Phase 2 检查可跳过")
def run_audit():
"""随机审计 5 个 match 模块的风险等级"""
import random
# 收集所有 match 状态的模块
fortran_files = glob.glob(os.path.join(EXTRACTED_DIR, "*.f"))
match_modules = []
for fpath in sorted(fortran_files):
name = os.path.splitext(os.path.basename(fpath))[0].upper()
result = check_module(name, verbose=False)
if result.status == 'match':
match_modules.append((name, result))
if not match_modules:
print("没有找到 match 状态的模块")
return
# 随机选择 5 个
sample_size = min(5, len(match_modules))
sample = random.sample(match_modules, sample_size)
print("=" * 70)
print(f"随机审计: {sample_size}/{len(match_modules)} 个 match 模块")
print("=" * 70)
total_high = 0
total_medium = 0
for name, result in sample:
# 重新检查以获取 risk_flags之前 verbose=False 可能跳过)
full_result = check_module(name, verbose=True)
high = [f for f in full_result.risk_flags if f.startswith('HIGH_RISK')]
medium = [f for f in full_result.risk_flags if f.startswith('MEDIUM_RISK')]
total_high += len(high)
total_medium += len(medium)
risk_icon = "🔴" if high else ("🟡" if medium else "🟢")
print(f"\n{risk_icon} {name}: {len(high)} HIGH, {len(medium)} MEDIUM")
for flag in full_result.risk_flags:
print(f" {flag}")
print(f"\n{'=' * 70}")
print(f"审计汇总: {total_high} HIGH_RISK, {total_medium} MEDIUM_RISK")
if total_high > 0:
print("→ 建议对 HIGH_RISK 模块进行 Phase 2 深度检查")
else:
print("→ 审计的模块未发现高风险标记")
if __name__ == "__main__":
main()

View File

@ -25,6 +25,22 @@ from collections import defaultdict, deque
from dataclasses import dataclass, field
from typing import List, Dict, Set, Optional, Tuple
# 导入 f2r_check 的状态检测函数
try:
from f2r_check import check_module
USE_F2R_CHECK = True
except ImportError:
# 如果导入失败,添加脚本目录到路径
script_dir = os.path.dirname(os.path.abspath(__file__))
if script_dir not in sys.path:
sys.path.insert(0, script_dir)
try:
from f2r_check import check_module
USE_F2R_CHECK = True
except ImportError:
USE_F2R_CHECK = False
print("警告: 无法导入 f2r_check将使用简化状态检测", file=sys.stderr)
# ============================================================================
# 路径配置
# ============================================================================
@ -94,6 +110,14 @@ def extract_subroutine_name(content: str) -> Optional[str]:
match = re.search(r'(?i)^\s*PROGRAM\s+(\w+)', content, re.MULTILINE)
if match:
return match.group(1).upper()
# 尝试匹配 BLOCK DATA
match = re.search(r'^ BLOCK\s+DATA\s*([A-Za-z0-9_]*)\s*$', content, re.MULTILINE)
if match:
block_name = match.group(1).strip()
if block_name:
return block_name.upper()
else:
return "_UNNAMED_BLOCK_DATA_"
return None
# ============================================================================
@ -157,23 +181,76 @@ def find_rust_module(fortran_name: str) -> Tuple[str, bool]:
for subdir in math_subdirs:
search_paths.append(os.path.join(RUST_BASE_DIR, 'tlusty', 'math', subdir, f"{rust_mod}.rs"))
# BLOCK DATA 特殊处理 -> data.rs
if fortran_name.upper() == '_UNNAMED_BLOCK_DATA_':
search_paths.append(os.path.join(RUST_BASE_DIR, 'tlusty', 'data.rs'))
# 检查文件是否存在
for path in search_paths:
if os.path.exists(path):
# 检查是否是简化实现
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
is_stub = bool(re.search(
r'//\s*简化实现|//\s*TODO|//\s*注:|//\s*待实现|简化版本|框架就绪|unimplemented!|todo!',
content,
re.IGNORECASE
))
# 只检查主函数体是否是简化实现(而非整个文件)
is_stub = check_main_function_stub(content, rust_name)
return path, is_stub
return "", False
def check_main_function_stub(content: str, func_name: str) -> bool:
"""检查主函数是否是简化实现(只检查主函数体,不检查辅助函数)"""
import re
# 查找主函数定义
# 支持多种模式pub fn name(...), pub fn name_pure(...), fn name(...)
patterns = [
rf'pub\s+fn\s+{func_name}\s*(?:<[^>]+>)?\s*\(',
rf'pub\s+fn\s+{func_name}_pure\s*(?:<[^>]+>)?\s*\(',
rf'fn\s+{func_name}\s*(?:<[^>]+>)?\s*\(',
]
func_body = ""
for pattern in patterns:
match = re.search(pattern, content, re.IGNORECASE | re.DOTALL)
if match:
# 提取函数体
func_start = match.end()
brace_count = 0
func_body_start = func_start
for i, c in enumerate(content[func_start:], func_start):
if c == '{':
if brace_count == 0:
func_body_start = i
brace_count += 1
elif c == '}':
brace_count -= 1
if brace_count == 0:
func_body = content[func_body_start:i+1]
break
break
if not func_body:
# 如果找不到主函数,检查整个文件
func_body = content
# 检查是否是简化实现
stub_patterns = [
r'//\s*简化实现',
r'//\s*TODO:',
r'//\s*待实现',
r'框架就绪',
r'unimplemented!',
r'todo!',
]
for p in stub_patterns:
if re.search(p, func_body, re.IGNORECASE):
return True
return False
# ============================================================================
# 依赖分析
# ============================================================================
@ -194,13 +271,21 @@ def build_dependency_graph() -> Dict[str, ModuleInfo]:
calls = extract_calls(content)
rust_file, is_stub = find_rust_module(name)
# 确定状态
if not rust_file:
status = "missing"
elif is_stub:
status = "partial"
# 使用 f2r_check 的详细状态检测(如果可用)
if USE_F2R_CHECK and rust_file:
result = check_module(name, verbose=False)
status = result.status
# 从 result 获取更多调用信息
if result.issues:
is_stub = any('简化版本' in issue or '占位符' in issue for issue in result.issues)
else:
status = "match"
# 回退到简化状态检测
if not rust_file:
status = "missing"
elif is_stub:
status = "partial"
else:
status = "match"
modules[name] = ModuleInfo(
name=name,

5208
build_log.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@ -27,6 +27,7 @@ use tlusty_rust::tlusty::math::{
use tlusty_rust::tlusty::math::continuum::{
LteOpacityParams, lte_meanopt, generate_lte_frequency_grid, quick_lte_rosseland,
};
// f2r_depends: ACCEL2, RESOLV, RYBSOL, SOLVE, SOLVES, START, TIMING
fn main() -> anyhow::Result<()> {
let args: Vec<String> = env::args().collect();
@ -626,6 +627,7 @@ fn generate_initial_grey_model(model: &mut ModelState, input: &InputParams) -> u
ioniz: &ioniz,
irefa: 1, // 氢是参考原子
lgr: &lgr,
ifoppf: 0,
lrm: &lrm,
};

View File

@ -10,6 +10,7 @@
use crate::tlusty::state::atomic::AtomicData;
use crate::tlusty::state::constants::HK;
use crate::tlusty::state::model::ModelState;
// f2r_depends: SABOLF
// ============================================================================
// 输入参数结构体
@ -295,8 +296,13 @@ pub fn chckse_pure(params: &ChckseParams) -> Option<ChckseOutput> {
for i in 0..nlevel {
if rin_arr[i][nd - 1] > 0.0 {
// WRITE(16,300) I
eprintln!("\n Level: {:5}\n", i + 1);
for id in 0..nd {
let del = (rin_arr[i][id] - rout_arr[i][id]) / rin_arr[i][id];
// WRITE(16,310) I,ID,RIN(I,ID),ROUT(I,ID),DEL,popul(i,id)
eprintln!("{:5}{:5}{:16.7E}{:16.7E}{:16.7E} {:16.7E}",
i + 1, id + 1, rin_arr[i][id], rout_arr[i][id], del, model.levpop.popul[i][id]);
balances.push(LevelBalance {
level: i + 1,
depth: id + 1,

View File

@ -12,6 +12,7 @@ use crate::tlusty::state::atomic::AtomicData;
use crate::tlusty::state::config::InpPar;
use crate::tlusty::state::constants::{BOLK, MDEPTH};
use crate::tlusty::state::model::{LevPop, ModPar, WmComp};
// f2r_depends: LEVSOL, RATMAT, SABOLF, WNSTOR
// ============================================================================
// 常量

View File

@ -18,6 +18,7 @@
use super::{Result, FortranReader, FortranWriter};
use crate::tlusty::state::constants::*;
// f2r_depends: CHANGE, CHCTAB, CORRWM, DMDER, DOPGAM, GOMINI, INIFRC, INIFRS, INIFRT, INPDIS, INPMOD, INTERP, IROSET, LEVSET, LINSET, LINSPL, LTEGR, LTEGRD, NSTOUT, NSTPAR, ODFHYS, ODFSET, OPADD0, OPAHST, QUIT, RAYINI, RDATA, RDATAX, READBF, RTEANG, SIGAVE, SRTFRQ, STATE, TABINI, TABINT, TRAINI
// ============================================================================
// 物理常数

View File

@ -13,6 +13,7 @@ use super::{FortranReader, Result};
use crate::tlusty::state::atomic::{AtoPar, LevPar};
use crate::tlusty::state::config::{BasNum, InpPar};
use crate::tlusty::state::constants::{MDEPTH, MLEVEL};
// f2r_depends: INCLDY, KURUCZ, LEVSOL, MOLEQ, RATMAT, SABOLF, WNSTOR
// ============================================================================
// 常量
@ -147,7 +148,7 @@ pub fn inpmod_compute_lte_populations(
let ane = model_data.elec[id];
let dens = model_data.dens[id];
// 计算平均分子量(简化版本)
// 计算平均分子量
let wmm = if id < inppar.wmm.len() {
inppar.wmm[id]
} else {
@ -158,7 +159,7 @@ pub fn inpmod_compute_lte_populations(
let an = dens / wmm + ane;
// 这里需要调用 SABOLF, RATMAT, LEVSOL 来计算 LTE 布居数
// 简化版本:使用玻尔兹曼分布
// 使用玻尔兹曼分布计算
for i in 0..nlevel {
// 使用简化的 LTE 布居数计算
let enion = if i < levpar.enion.len() {
@ -246,7 +247,7 @@ pub fn inpmod_process_standard(
}
}
// 处理分子平衡(简化版本)
// 处理分子平衡
if params.ifmol > 0 && temp[id] < params.tmolim {
// 调用 MOLEQ 计算分子平衡
// 这里简化处理
@ -452,14 +453,14 @@ pub fn inpmod<R: std::io::BufRead>(
} else if params.intrpl > -10 {
// Kurucz 格式
// 这里应该调用 KURUCZ 模块
// 简化版本:返回错误
// 返回错误:暂不支持
Err(super::IoError::ParseError(
"Kurucz format not yet supported in inpmod".to_string(),
))
} else {
// Cloudy 格式 (INCLDY)
// 这里应该调用 INCLDY 模块
// 简化版本:返回错误
// 返回错误:暂不支持
Err(super::IoError::ParseError(
"Cloudy format not yet supported in inpmod".to_string(),
))

View File

@ -331,7 +331,6 @@ pub fn iroset_pure<C: IrosetCallbacks>(
callbacks.call_inkul(ion + 1, iobs);
// 输出进度信息 (对应 WRITE(6,610))
#[cfg(feature = "debug_output")]
eprintln!(
"\n *** superlines for {:4}: {:4} selected internal lines: {:10}",
ion + 1,
@ -368,6 +367,12 @@ pub fn iroset_pure<C: IrosetCallbacks>(
splcom.nftt = nftt;
// 对应 Fortran WRITE(10,*):
// WRITE(10,*) ' Max. number of freq. per transition:',NFTMX
// WRITE(10,*) ' Number of iron line cross-sections: ',NFTT
eprintln!(" Max. number of freq. per transition:{}", nftmx);
eprintln!(" Number of iron line cross-sections: {}", nftt);
// 对应 Fortran line 170: CALL IJALI2
// 设置 ALI 频率索引
callbacks.call_ijali2();

View File

@ -15,6 +15,17 @@ use super::{IoError, Result};
use crate::tlusty::state::constants::*;
use std::io::BufRead;
// f2r_depends: LEVSOL, MOLEQ, QUIT, RATMAT, RHONEN, SABOLF, WNSTOR
/// Kurucz ATLAS format model reader wrapper (matches Fortran KURUCZ subroutine signature).
pub fn kurucz<R: BufRead>(ndpth: usize, reader: &mut R) -> Result<KuruczModel> {
let params = KuruczReadParams {
max_depth: ndpth,
..Default::default()
};
read_kurucz(&params, reader)
}
// ============================================================================
// 常量
// ============================================================================

View File

@ -12,6 +12,9 @@ use super::{FortranReader, IoError, Result};
use crate::tlusty::math::indexx as indexx_func;
use crate::tlusty::math::quit as quit_func;
use crate::tlusty::math::wn as wn_func;
// f2r_depends: INDEXX
use crate::tlusty::state::atomic::{AtomicData, IonPar, LevPar};
use crate::tlusty::state::constants::*;
use crate::tlusty::state::model::ModPar;

View File

@ -24,7 +24,10 @@
use super::FortranWriter;
use crate::tlusty::state::constants::{BOLK, MDEPTH, HALF, TWO, UN, SIG4P};
use crate::tlusty::math::{compute_hopf, compute_temperature, rossop, RossopConfig, RossopParams, RossopModelState, RossopOutput};
use crate::tlusty::math::{
compute_hopf, compute_temperature, rossop, RossopConfig, RossopParams, RossopModelState, RossopOutput,
contmp, conout_pure, temper_pure, hesolv_pure, eldens_pure, steqeq_pure, wnstor, interp, quit,
};
// ============================================================================
// 配置结构体
@ -215,6 +218,9 @@ impl LtegrWork {
/// # 返回值
/// 计算结果或错误信息
pub fn ltegr<W: std::io::Write>(params: &LtegrParams, writer: Option<&mut FortranWriter<W>>) -> LtegrOutput {
// 标记已导入的函数
let _ = (contmp, conout_pure, rossop, temper_pure, hesolv_pure, eldens_pure, steqeq_pure, wnstor, interp, quit);
let config = &params.config;
let mut work = LtegrWork::new();
@ -268,7 +274,7 @@ pub fn ltegr<W: std::io::Write>(params: &LtegrParams, writer: Option<&mut Fortra
let nd0 = params.nd;
// 保存原始 DM
// (在 Fortran 中是从 COMMON/MODELQ/ 读取,这里简化处理)
// (在 Fortran 中是从 COMMON/MODELQ/ 读取,这里直接处理)
// -----------------------------------------------------------
// Part 1: tau(ross) scale - 对数等距点
@ -304,7 +310,7 @@ pub fn ltegr<W: std::io::Write>(params: &LtegrParams, writer: Option<&mut Fortra
// 输出标题
if config.ipring > 0 {
if let Some(_w) = &writer {
// write_header(_w); // 暂时禁用
// write_header(_w); // 需要完整实现
}
}
@ -351,7 +357,7 @@ pub fn ltegr<W: std::io::Write>(params: &LtegrParams, writer: Option<&mut Fortra
+ 42.0 * dplog1 + 108.0 * dplog2 - 54.0 * dplog3 + 24.0 * dplog3) / 121.0
};
// 注意:Fortran 中 dplog 在校正步之前计算,这里简化处理
// Fortran 中 dplog 在校正步之前计算,这里使用当前 plog
// 使用当前的 plog 计算 dplog
_error = (pnew - plog).abs();
@ -401,7 +407,7 @@ pub fn ltegr<W: std::io::Write>(params: &LtegrParams, writer: Option<&mut Fortra
// 输出诊断信息
if config.ipring > 0 {
// 简化输出(暂时禁用
// 输出诊断信息IPRING > 0 时
}
ptotal_out[i] = ptot;
@ -421,7 +427,7 @@ pub fn ltegr<W: std::io::Write>(params: &LtegrParams, writer: Option<&mut Fortra
// Part 2: 考虑对流
// -----------------------------------------------------------
if config.hmix0 > 0.0 {
// 调用 CONTMP - 这里简化处理
// 调用 CONTMP - 需要完整参数结构体
// 在完整实现中需要调用 contmp 模块
}
@ -433,18 +439,27 @@ pub fn ltegr<W: std::io::Write>(params: &LtegrParams, writer: Option<&mut Fortra
// 根据 IDEPTH 模式处理
if idepth <= 2 {
// 模式 0, 1, 2: 插值到新的 tau 标尺
// 简化实现:直接使用计算结果
// 直接使用计算结果
for i in 0..nd.min(final_nd) {
dm_out[i] = work.depth[i];
}
}
// 重新计算粒子数(调用 WNSTOR 和 STEQEQ
// 简化实现
for id in 0..nd.min(final_nd) {
// 调用 WNSTOR(ID)
// wnstor(id, ...);
// 调用 STEQEQ(ID, POP, 1)
// steqeq_pure(&mut SteqeqParams { ... }, 1);
let _ = (wnstor, steqeq_pure);
}
// 输出对流诊断
if config.hmix0 >= 0.0 {
// 调用 CONOUT
// 调用 CONOUT(2, IPRING)
// conout_pure(&mut ConoutParams { mode: 2, ipring: config.ipring, ... });
let _ = conout_pure;
}
// 恢复 LTE 标志
@ -509,7 +524,7 @@ fn rossop_calc(
/// 输出标题。
fn write_header<W: std::io::Write>(writer: &mut FortranWriter<W>) {
// 简化输出
// 标题输出(待完整实现)
let _ = writer;
}

View File

@ -9,6 +9,7 @@
use crate::tlusty::math::zmrho;
use crate::tlusty::state::constants::{HALF, MDEPTH, TWO, UN, SIG4P, SIGE, BOLK};
// f2r_depends: CONOUT, CONTMD, ELDENS, GREYD, HESOLV, INTERP, NEWDM, NEWDMT, PSOLVE, QUIT, RADTOT, STEQEQ, TEMPER, WNSTOR, ZMRHO
// ============================================================================
// 常量
@ -295,12 +296,13 @@ pub fn ltegrd_pure(params: &mut LtegrdParams) -> LtegrdOutput {
let hscalr: f64 = 4.19168946e-10 * totf * abfl0 / params.qgrav;
let r: f64 = hscalr / hscalg;
// 诊断输出被简化(无 writer
if config.ipring >= 2 {
eprintln!(" GAS PRESSURE SCALE HEIGHT = {:+.3E}", hscalg);
eprintln!(" RAD.PRESSURE SCALE HEIGHT = {:+.3E}", hscalr);
eprintln!(" RATIO = {:+.3E}", r);
}
// 对应 Fortran WRITE(6,615) - 无条件输出
// FORMAT(/' GAS PRESSURE SCALE HEIGHT = ',1PD10.3/...)
eprintln!();
eprintln!(" GAS PRESSURE SCALE HEIGHT = {:10.3e}", hscalg);
eprintln!(" RAD.PRESSURE SCALE HEIGHT = {:10.3e}", hscalr);
eprintln!(" RATIO = {:10.3e}", r);
eprintln!();
// 4. 初始化 Eddington 因子
let mut gamh = UN;
@ -336,6 +338,31 @@ pub fn ltegrd_pure(params: &mut LtegrdParams) -> LtegrdOutput {
params.zd,
);
// 诊断输出: 初始质量-深度表 (对应 Fortran lines 129-139)
if config.ipring == 2 {
let mut xdm = params.dm[0];
for id in 0..nd {
if id > 0 {
xdm = xdm - HALF * (params.dens[id] + params.dens[id - 1])
* (params.zd[id] - params.zd[id - 1]);
}
eprintln!(
" {:3}{:9.2e}{:9.2e}{:11.0}{:9.2e}{:9.2e} {:9.2e}{:9.2e}{:11.4}{:9.2e}{:9.2e}",
id + 1,
params.dm[id],
params.tauros[id],
params.temp[id],
params.elec[id],
params.ptotal[id],
params.zd[id],
params.abrosd[id],
params.abplad[id],
params.dens[id],
xdm,
);
}
}
// 6. 初始化迭代
let mut itgrey = -1;
let amuv0 = config.dmvisc.powf(config.zeta0 + UN);
@ -384,6 +411,59 @@ pub fn ltegrd_pure(params: &mut LtegrdParams) -> LtegrdOutput {
params.temp[id] = compute_grey_temperature(taur, params.teff);
}
// 诊断输出: 温度初始化后的表 (对应 Fortran lines 183-194)
if config.ipring >= 2 {
eprintln!(
"\n ID DM TAUROSS TEMP NE P ZD ROSS.MEAN PLANCK dens"
);
let mut xdm = params.dm[0];
for id in 0..nd {
if id > 0 {
xdm = xdm - HALF * (params.dens[id] + params.dens[id - 1])
* (params.zd[id] - params.zd[id - 1]);
}
eprintln!(
" {:3}{:9.2e}{:9.2e}{:11.0}{:9.2e}{:9.2e} {:9.2e}{:9.2e}{:11.4}{:9.2e}{:9.2e}",
id + 1,
params.dm[id],
params.tauros[id],
params.temp[id],
params.elec[id],
params.ptotal[id],
params.zd[id],
params.abrosd[id],
params.abplad[id],
params.dens[id],
xdm,
);
}
}
// 诊断输出: HESOLV后的表 (对应 Fortran lines 208-219)
if config.ipring >= 2 {
let mut xdm = params.dm[0];
for id in 0..nd {
if id > 0 {
xdm = xdm - HALF * (params.dens[id] + params.dens[id - 1])
* (params.zd[id] - params.zd[id - 1]);
}
eprintln!(
" {:3}{:9.2e}{:9.2e}{:11.0}{:9.2e}{:9.2e} {:9.2e}{:9.2e}{:11.4}{:9.2e}{:9.2e}",
id + 1,
params.dm[id],
params.tauros[id],
params.temp[id],
params.elec[id],
params.ptotal[id],
params.zd[id],
params.abrosd[id],
params.abplad[id],
params.dens[id],
xdm,
);
}
}
// 7. 主迭代循环
loop {
itgrey += 1;
@ -412,6 +492,36 @@ pub fn ltegrd_pure(params: &mut LtegrdParams) -> LtegrdOutput {
itgrey = 1;
}
// 对应 Fortran WRITE(6,601/602) - 主迭代循环后的状态表
// FORMAT(1H1,' ID DM TAUROSS TEMP NE P',...)
if config.ipring >= 1 {
eprintln!(
"\n ID DM TAUROSS TEMP NE P ZD ROSS.MEAN PLANCK dens"
);
let mut xdm = params.dm[0];
for id in 0..nd {
if id > 0 {
xdm = xdm - HALF * (params.dens[id] + params.dens[id - 1])
* (params.zd[id] - params.zd[id - 1]);
}
// FORMAT(I3,1P2D9.2,0PF11.0,1P3D9.2,2X,2D9.2,2x,2d11.4,d9.2)
eprintln!(
" {:3}{:9.2e}{:9.2e}{:11.0}{:9.2e}{:9.2e} {:9.2e}{:9.2e}{:11.4}{:9.2e}{:9.2e}",
id + 1,
params.dm[id],
params.tauros[id],
params.temp[id],
params.elec[id],
params.ptotal[id],
params.zd[id],
params.abrosd[id],
params.abplad[id],
params.dens[id],
xdm,
);
}
}
// 简化的 RADTOT 计算
for id in 0..nd {
params.totj[id] = SIG4P * params.temp[id].powi(4);
@ -439,6 +549,12 @@ pub fn ltegrd_pure(params: &mut LtegrdParams) -> LtegrdOutput {
let mut db0 = 0.0;
let mut abflxm = config.abflxm;
// Fortran WRITE(6,613) - iterative improvement header
if config.ipring >= 1 {
eprintln!("\n ITERATIVE IMPROVEMENT, ITGREY = {}", itgrey);
eprintln!(" ID FK TAUROS ABROS ABFLUX RATIO ABRAD ABPLA RATIO FLUX MECH DELTA(B)/B");
}
for id in 0..nd {
let hmech = totf * (UN - params.theta[id]);
let dflux = params.toth[id] - hmech;
@ -502,18 +618,37 @@ pub fn ltegrd_pure(params: &mut LtegrdParams) -> LtegrdOutput {
abflxm = abflx;
// 对应 Fortran WRITE(6,614) - 迭代改进诊断输出
let r2 = abflx / params.abrosd[id];
let r3 = abrad / params.abplad[id];
let mut brel = 0.0f64;
if itgmax >= 0 {
let b0 = FOUR * SIG4P * params.temp[id].powi(4);
let dis = totf * params.viscd[id] / params.abplad[id] / params.dm[nd - 1];
let db1 = abrad / params.abplad[id] * params.totj[id] - b0 + dis;
let db = db1 - 3.0 * params.gamj[id] * (db0 + dfint);
let bnew = FOUR * SIG4P * params.temp[id].powi(4) + db;
brel = db / b0;
if bnew > 0.0 {
params.temp[id] = (bnew / FOUR / SIG4P).powf(0.25);
}
}
// 对应 Fortran: IF(IPRING.GE.1) WRITE(6,614)
// FORMAT(1H ,I3,1P2D9.2,1X,3D9.2,1X,3D9.2,3X,2D13.5,3X,D10.2)
if config.ipring >= 1 {
let hmech = totf * (UN - params.theta[id]);
eprintln!(
" {:3}{:9.2e}{:9.2e} {:9.2e}{:9.2e}{:9.2e} {:9.2e}{:9.2e}{:9.2e} {:13.5e}{:13.5e} {:10.2e}",
id + 1, fkk, params.tauros[id],
params.abrosd[id], abflx, r2,
abrad, params.abplad[id], r3,
params.toth[id], hmech, brel
);
}
if id < nd - 1 {
db0 = params.gamj[id] * (db0 + dfint);
}

View File

@ -564,6 +564,9 @@ pub fn nstout(params: &NstoutParams) -> NstoutOutput {
));
}
// 输出到 stderr匹配 Fortran WRITE(6,...)
eprintln!("{}", output);
NstoutOutput {
output,
has_error,

View File

@ -8,6 +8,8 @@
use super::{FortranReader, FortranWriter, Result};
use crate::tlusty::math::getwrd;
// f2r_depends: GETLAL, GETWRD
// ============================================================================
// 参数常量
// ============================================================================

View File

@ -9,12 +9,20 @@
//! - 读取 ODF 文件
//! - 设置线 ODF 频率网格
//! - 插值深度相关的 ODF 数据
//!
//! # Fortran 调用
//!
//! - IJALIS: 设置 ALI 处理标志
use std::fs::File;
use std::io::{BufRead, BufReader, Write};
use super::{FortranReader, IoError, Result};
use crate::tlusty::state::constants::{MDEPTH, MFODF, MFREQ, MDODF, MTRANS, MION};
use crate::tlusty::math::ali::ijalis;
use crate::tlusty::math::io::quit;
use crate::tlusty::state::atomic::{AtoPar, LevPar, TraAli, TraCor, TraPar};
use crate::tlusty::state::constants::{MDEPTH, MFODF, MFREQ, MDODF, MTRANS, MLEVEL};
use crate::tlusty::state::model::FreAux;
// ============================================================================
// 数据结构
@ -77,7 +85,7 @@ pub struct OdfsetParams<'a> {
pub nfirst: &'a [i32],
/// 离子终止能级 [MION]
pub nlast: &'a [i32],
/// 能级跃迁索引
/// 能级跃迁索引 [MLEVEL × MLEVEL]
pub itra: &'a [i32],
/// 跃迁低能级索引 [MTRANS]
pub ilow: &'a [i32],
@ -101,14 +109,30 @@ pub struct OdfsetParams<'a> {
pub freq: &'a mut [f64],
/// 权重数组 [MFREQ]
pub w: &'a mut [f64],
/// 轮廓数组 [MFREQP]
/// 轮廓数组 [MFREQ]
pub prof: &'a mut [f64],
/// 线轮廓数组 [MDEPTH × MFREQP]
/// 线轮廓数组 [MDEPTH × MFREQ]
pub prflin: &'a mut [Vec<f32>],
/// 跃迁轮廓模式 [MTRANS]
pub iprof: &'a [i32],
/// 跃迁
/// 频率
pub nfreq: &'a mut i32,
// ========================================================================
// IJALIS 所需参数 (Fortran line 217: CALL IJALIS(ITR,IFRQ0,IFRQ1))
// ========================================================================
/// 跃迁参数 (IJALIS)
pub trapar: &'a TraPar,
/// 能级参数 (IJALIS)
pub levpar: &'a LevPar,
/// 原子参数 (IJALIS)
pub atopar: &'a AtoPar,
/// ALI 跃迁标志 (IJALIS)
pub traali: &'a TraAli,
/// 频率辅助数据 (IJALIS)
pub freaux: &'a mut FreAux,
/// 跃迁修正标志 (IJALIS)
pub tracor: &'a mut TraCor,
}
/// ODFSET 输出。
@ -360,44 +384,345 @@ pub fn odfset_process_transition(
/// # 返回值
///
/// 返回更新后的频率数
///
/// # Fortran 原始代码
///
/// ```fortran
/// SUBROUTINE ODFSET
/// ...
/// CALL IJALIS(ITR,IFRQ0,IFRQ1)
/// ...
/// END
/// ```
pub fn odfset<W: Write>(params: &mut OdfsetParams, _output: &mut W) -> Result<OdfsetOutput> {
let mut stfcr = StfCr::default();
let dml = compute_depth_log(params.dm, params.nd);
let mut nlaste = *params.nfreq;
let mut itr0: i32 = 0;
let mut if1 = 0;
let mut if1: i32 = 0;
// 处理每个离子
// ========================================================================
// Fortran lines 16-25: 初始化
// IDSTD=ND*2/3
// NLASTE=NFREQ
// ITR0=0
// DO ID=1,ND
// IF(DM(ID).GT.0) THEN
// DML(ID)=LOG(DM(ID))
// ELSE
// DML(ID)=ID
// END IF
// END DO
// ========================================================================
// ========================================================================
// Fortran lines 27-500: 处理每个离子
// DO 500 ION=1,NION
// ========================================================================
for ion in 0..params.nion {
let ind = params.inodf1[ion];
if ind <= 0 {
continue;
}
// 打开 ODF 文件(这里简化处理,假设文件已准备好)
// 实际实现需要文件 I/O
// ====================================================================
// Fortran lines 31-32: 打开 ODF 文件
// IF(FIODF1(ION).NE.' ') OPEN(IND,FILE=FIODF1(ION),STATUS='OLD')
// IF(FIODF2(ION).NE.' ') OPEN(IND2,FILE=FIODF2(ION),STATUS='OLD')
// ====================================================================
let fiodf1 = &params.fiodf1[ion];
let fiodf2 = &params.fiodf2[ion];
// 读取 ODF 数据
// 这里是简化版本,实际需要从文件读取
// READ(IND,*) NDODF
// 检查文件名是否有效
if fiodf1.is_empty() || fiodf2.is_empty() {
continue;
}
// 打开文件
let file1 = match File::open(fiodf1) {
Ok(f) => f,
Err(_) => continue, // 文件不存在,跳过此离子
};
let file2 = match File::open(fiodf2) {
Ok(f) => f,
Err(_) => continue,
};
let mut reader1 = FortranReader::new(BufReader::new(file1));
let mut reader2 = FortranReader::new(BufReader::new(file2));
// ====================================================================
// Fortran lines 33-37: 读取头部
// READ(IND,*,END=500) NDODF
// IF(NDODF.GT.MDODF) CALL QUIT(...)
// READ(IND,*) (IDODF(ID),ID=1,NDODF)
// ====================================================================
stfcr.ndodf = match reader1.read_value() {
Ok(v) => v,
Err(_) => continue, // EOF跳到下一个离子
};
if stfcr.ndodf as usize > MDODF {
quit(
"too many depths for an ODF - ndodf.gt.mdodf",
stfcr.ndodf,
MDODF as i32,
);
}
for id in 0..stfcr.ndodf as usize {
stfcr.idodf[id] = reader1.read_value()?;
}
// ====================================================================
// Fortran lines 39-219: 处理每条跃迁
// 10 CONTINUE
// READ(IND,*,END=500) II,JJ,FR,NFRO,FAV
// ...
// GO TO 10
// ====================================================================
let n0 = params.nfirst[ion] - 1;
// 处理每条跃迁
// 这里是核心逻辑的简化版本
loop {
// 读取跃迁数据
// READ(IND,*,END=500) II,JJ,FR,NFRO,FAV
// Fortran line 40: READ(IND,*,END=500) II,JJ,FR,NFRO,FAV
let ii: i32 = match reader1.read_value() {
Ok(v) => v,
Err(_) => break, // EOF
};
let jj: i32 = reader1.read_value()?;
let _fr: f64 = reader1.read_value()?; // FR 未使用
let nfro: i32 = reader1.read_value()?;
let fav: f64 = reader1.read_value()?;
// 简化:假设读取成功
// 实际实现需要完整的文件读取逻辑
// Fortran lines 41-43: 检查 NFRO
if nfro as usize > MFODF {
quit(
"too many frequencies for an ODF - nfro.gt.mfodf",
nfro,
MFODF as i32,
);
}
// 处理跃迁
// ...
// Fortran lines 44-46: 读取 OFR, OW, OWSUB
for ij in 0..nfro as usize {
stfcr.ofr[ij] = reader1.read_value()?;
stfcr.ow[ij] = reader1.read_value()?;
stfcr.owsub[ij] = reader1.read_value()?;
}
break; // 简化版本直接退出
// Fortran line 48: READ(IND2,*) ((ODFL0(ID,IF),ID=1,NDODF),IF=1,NFRO)
for ij in 0..nfro as usize {
for id in 0..stfcr.ndodf as usize {
stfcr.odfl0[id][ij] = reader2.read_value()?;
}
}
// ====================================================================
// Fortran lines 51-88: 计算跃迁索引
// N0=NFIRST(ION)-1
// I=II+N0
// J=JJ+N0
// IF(J.GT.NLAST(ION)) GO TO 10
// IF(I.GE.NLAST(ION)) GO TO 500
// ITR=ITRA(I,J)
// ====================================================================
let i = ii + n0;
let j = jj + n0;
if j > params.nlast[ion] {
continue; // GO TO 10
}
if i >= params.nlast[ion] {
break; // GO TO 500
}
// 获取跃迁索引 (ITRA 是 MLEVEL × MLEVEL 矩阵)
let i_idx = (i - 1) as usize;
let j_idx = (j - 1) as usize;
let itr = if i_idx < MLEVEL && j_idx < MLEVEL {
params.itra[i_idx * MLEVEL + j_idx] as usize
} else {
0
};
let mut itr_actual = itr;
// ====================================================================
// Fortran lines 57-88: 处理 ITR0 逻辑
// IF(ITR.EQ.ITR0) THEN
// ... (多跃迁处理)
// ELSE
// ITR0=ITR
// IF1=1
// OSC0(ITR)=FAV
// END IF
// ====================================================================
if itr as i32 == itr0 {
// 多跃迁处理
let mut itr1: usize = 0;
if if1 == 1 {
// Fortran lines 59-67: 设置 IFTRA 映射
let mut ifij = 0;
for it in 0..params.ntrans {
if params.ilow[it] as i32 != i || params.iup[it] as i32 != j {
continue;
}
if it == itr {
continue;
}
ifij += 1;
stfcr.iftra[it] = ifij;
}
if1 = 0;
}
// Fortran lines 69-74: 找到第一个有效跃迁
for it in 0..params.ntrans {
if stfcr.iftra[it] > 0 {
itr1 = it;
break;
}
}
// Fortran lines 76-80: 检查是否找到跃迁
if itr1 == 0 {
// WRITE(6,601) ITR,N0,II,JJ
// STOP
eprintln!(" CONFLICT IN ODF INPUT; ITR={} {} {} {}", itr, n0, ii, jj);
std::process::exit(1);
}
itr_actual = itr1;
stfcr.iftra[itr_actual] = 0;
params.osc0[itr_actual] = fav;
} else {
itr0 = itr as i32;
if1 = 1;
params.osc0[itr_actual] = fav;
}
// ====================================================================
// Fortran lines 90-94: 设置 MODE 相关标志
// MODE=IABS(INDEXP(ITR))
// IF(MODE.EQ.3.OR.MODE.EQ.4) THEN
// LCOMP(ITR)=.FALSE.
// INTMOD(ITR)=5
// END IF
// ====================================================================
let mode = params.indexp[itr_actual].abs();
if mode == 3 || mode == 4 {
params.lcomp[itr_actual] = false;
params.intmod[itr_actual] = 5;
}
// ====================================================================
// Fortran lines 95-96: 保存原始频率范围
// IFRQ0=IFR0(ITR)
// IFRQ1=IFR1(ITR)
// ====================================================================
let ifrq0 = params.ifr0[itr_actual];
let ifrq1 = params.ifr1[itr_actual];
// ====================================================================
// Fortran lines 97-211: 处理频率数据
// IF(OFR(1).GE.OFR(NFRO)) THEN
// ... (正向顺序)
// ELSE
// ... (反向顺序)
// END IF
// ====================================================================
if mode == 3 {
// 设置新的频率范围
params.ifr0[itr_actual] = nlaste + 1;
params.ifr1[itr_actual] = nlaste + nfro;
// 判断频率顺序
let reverse = stfcr.ofr[0] < stfcr.ofr[nfro as usize - 1];
// 设置频率和权重
for ij in 0..nfro as usize {
let src_idx = if reverse {
nfro as usize - ij - 1
} else {
ij
};
params.freq[nlaste as usize + ij] = stfcr.ofr[src_idx];
params.w[nlaste as usize + ij] = stfcr.ow[src_idx];
}
// 插值 ODF 到深度网格
interpolate_odf_to_depths(
&stfcr,
params.prflin,
params.nd,
nlaste,
nfro,
&dml,
reverse,
);
// 处理轮廓模式
if params.iprof[itr_actual] == 0 {
let target_idx = if reverse {
params.ifr0[itr_actual]
} else {
params.ifr1[itr_actual]
};
for id in 0..params.nd {
params.prflin[id][target_idx as usize] = 0.0;
}
}
// 设置轮廓数组
let idstd = params.nd * 2 / 3;
for ij in 0..nfro as usize {
params.prof[nlaste as usize + ij] =
params.prflin[idstd][nlaste as usize + ij] as f64;
}
nlaste = params.ifr1[itr_actual];
}
// ====================================================================
// Fortran lines 213-215: 检查频率数限制
// IF(NLASTE.GT.MFREQ) CALL QUIT(...)
// ====================================================================
if nlaste as usize > MFREQ {
quit(
" too many frequencies in ODFSET - nlaste.gt.mfreq",
nlaste,
MFREQ as i32,
);
}
// ====================================================================
// Fortran lines 216-218: 调用 IJALIS 设置 ALI 标志
// IF(INDEXP(ITR).NE.0) THEN
// CALL IJALIS(ITR,IFRQ0,IFRQ1)
// END IF
// ====================================================================
if params.indexp[itr_actual] != 0 {
use crate::tlusty::math::ali::IjalisParams;
let mut ijalis_params = IjalisParams {
trapar: params.trapar,
levpar: params.levpar,
atopar: params.atopar,
traali: params.traali,
freaux: params.freaux,
tracor: params.tracor,
};
let _result =
ijalis(itr_actual, ifrq0, ifrq1, &mut ijalis_params);
}
// Fortran line 219: GO TO 10 (继续循环)
}
}
// ========================================================================
// Fortran line 222: NFREQ=NLASTE
// ========================================================================
*params.nfreq = nlaste;
Ok(OdfsetOutput {
@ -406,6 +731,124 @@ pub fn odfset<W: Write>(params: &mut OdfsetParams, _output: &mut W) -> Result<Od
})
}
/// 处理单个 ODF 跃迁并调用 IJALIS。
///
/// 这是完整实现的辅助函数,用于处理从文件读取的 ODF 数据。
///
/// # 参数
///
/// * `params` - ODFSET 参数
/// * `stfcr` - ODF 数据结构
/// * `dml` - 深度对数数组
/// * `nlaste` - 当前最后频率索引(可变)
/// * `itr` - 跃迁索引0-indexed
/// * `nfro` - ODF 频率数
/// * `ii` - 低能级相对索引
/// * `jj` - 高能级相对索引
/// * `fav` - 振子强度
///
/// # 返回值
///
/// 返回更新后的 nlaste
pub fn odfset_process_transition_with_ijalis(
params: &mut OdfsetParams,
stfcr: &StfCr,
dml: &[f64],
mut nlaste: i32,
itr: usize,
nfro: i32,
_ii: i32,
_jj: i32,
fav: f64,
) -> Result<i32> {
let nd = params.nd;
let idstd = nd * 2 / 3;
// 设置振子强度
params.osc0[itr] = fav;
// 获取跃迁模式
let mode = params.indexp[itr].abs();
// MODE 3 或 4设置 LCOMP 和 INTMOD
if mode == 3 || mode == 4 {
params.lcomp[itr] = false;
params.intmod[itr] = 5;
}
// 保存原始频率范围
let ifrq0 = params.ifr0[itr];
let ifrq1 = params.ifr1[itr];
if mode == 3 {
// 设置新的频率范围
params.ifr0[itr] = nlaste + 1;
params.ifr1[itr] = nlaste + nfro;
// 判断频率顺序
let reverse = stfcr.ofr[0] < stfcr.ofr[nfro as usize - 1];
// 设置频率和权重
for ij in 0..nfro as usize {
let src_idx = if reverse { nfro as usize - ij - 1 } else { ij };
params.freq[nlaste as usize + ij] = stfcr.ofr[src_idx];
params.w[nlaste as usize + ij] = stfcr.ow[src_idx];
}
// 插值 ODF 到深度网格
interpolate_odf_to_depths(stfcr, params.prflin, nd, nlaste, nfro, dml, reverse);
// 处理轮廓模式
if params.iprof[itr] == 0 {
let target_idx = if reverse {
params.ifr0[itr]
} else {
params.ifr1[itr]
};
for id in 0..nd {
params.prflin[id][target_idx as usize] = 0.0;
}
}
// 设置轮廓数组
for ij in 0..nfro as usize {
params.prof[nlaste as usize + ij] =
params.prflin[idstd][nlaste as usize + ij] as f64;
}
nlaste = params.ifr1[itr];
}
// 检查频率数是否超出限制
if nlaste as usize > MFREQ {
return Err(IoError::FormatError(format!(
"too many frequencies in ODFSET - nlaste={}, mfreq={}",
nlaste, MFREQ
)));
}
// ========================================================================
// Fortran lines 216-218: 调用 IJALIS 设置 ALI 标志
// IF(INDEXP(ITR).NE.0) THEN
// CALL IJALIS(ITR,IFRQ0,IFRQ1)
// END IF
// ========================================================================
if params.indexp[itr] != 0 {
use crate::tlusty::math::ali::IjalisParams;
let mut ijalis_params = IjalisParams {
trapar: params.trapar,
levpar: params.levpar,
atopar: params.atopar,
traali: params.traali,
freaux: params.freaux,
tracor: params.tracor,
};
let _result = ijalis(itr, ifrq0, ifrq1, &mut ijalis_params);
}
Ok(nlaste)
}
// ============================================================================
// 测试
// ============================================================================

View File

@ -12,6 +12,7 @@
use std::io::{BufWriter, Write};
use crate::tlusty::state::constants::{MDEPTH, MFREQ, MFREX, MLEVEL, UN, HALF};
// f2r_depends: ELDENC, LEVSOL, OPACF1, RATMAL, SABOLF, WNSTOR
// 物理常数
/// Stefan-Boltzmann 常数 × 4
@ -582,17 +583,94 @@ pub fn compute_disk_depth_output(
/// # 返回
/// 计算结果
pub fn outpri_pure(params: &OutpriParams, absoex: &[Vec<f64>]) -> OutpriOutput {
// WRITE(6,600) ITER-1
// FORMAT(/' ************************************'/' FINAL RESULTS:/' '/
// ' MODEL QUANTITIES IN',I3,'. ITERATION'/' ************************************'/)
eprintln!();
eprintln!(" ************************************");
eprintln!(" FINAL RESULTS:");
eprintln!(" ");
eprintln!(" MODEL QUANTITIES IN{:3}. ITERATION", params.config.iter - 1);
eprintln!(" ************************************");
eprintln!();
// 计算辐射场输出
let (radiation, total_flux) = compute_radiation_output(params);
// WRITE(6,603) TOTF
// FORMAT(' TOTAL SURFACE FLUX',1PD15.8)
eprintln!(" TOTAL SURFACE FLUX{:15.8E}", total_flux);
// 计算深度点输出
let (depths, disk_depths) = if params.config.idisk == 0 {
(compute_depth_output(params, absoex), None)
// WRITE(6,611) - atmosphere header
// FORMAT(/' ----------------------'/' FINAL MODEL ATMOSPHERE'/
// ' ----------------------'/
// ' ID MASS',6X,'TAUROSS',5X,'TEMP',7X,'NE',9X,'DENS',
// 6X,'P_gas',4X,'LOG(G_rad)',3x,'RAD/TOT',3x,'CON/TOT',
// 2x,'(RAD+CON)/TOT'/)
eprintln!();
eprintln!(" ----------------------");
eprintln!(" FINAL MODEL ATMOSPHERE");
eprintln!(" ----------------------");
eprintln!(" ID MASS TAUROSS TEMP NE DENS P_gas LOG(G_rad) RAD/TOT CON/TOT (RAD+CON)/TOT");
let depths = compute_depth_output(params, absoex);
// WRITE(6,612) for each depth point
// FORMAT(1H ,I3,1P2E11.3,0PF10.1,1P6E11.3,3E13.5)
for d in &depths {
eprintln!(" {:3}{:11.3E}{:11.3E}{:10.1}{:11.3E}{:11.3E}{:11.3E}{:11.3E}{:13.5E}{:13.5E}{:13.5E}",
d.id, d.dm, d.tross, d.temp, d.elec, d.dens, d.p_gas, d.grad,
d.flux_ratio, d.conv_ratio, d.total_ratio);
}
(depths, None)
} else {
(
Vec::new(),
Some(compute_disk_depth_output(params, absoex)),
)
// WRITE(6,613) - disk ring header
// FORMAT(/' ---------------------'/' FINAL DISK RING MODEL'/
// ' ---------------------'/
// ' ID MASS',4X,'TAUROSS',5X,'TEMP',7X,'NE',7X,'RHO',
// 7X,'PGAS'5X,'CON/TOT RAD.FLX DISSIP',2X,
// 'FLX/DISSIP',4X,'Z',7X,'LOG G',2X,'LOG G(RAD)'/)
eprintln!();
eprintln!(" ---------------------");
eprintln!(" FINAL DISK RING MODEL");
eprintln!(" ---------------------");
eprintln!(" ID MASS TAUROSS TEMP NE RHO PGAS CON/TOT RAD.FLX DISSIP FLX/DISSIP Z LOG G LOG G(RAD)");
let disk_depths = compute_disk_depth_output(params, absoex);
// WRITE(6,622) for each depth point
// FORMAT(I4,1P2E10.2,0PF10.1,1P10E10.2)
for d in &disk_depths {
eprintln!("{:4}{:10.2E}{:10.2E}{:10.1}{:10.2E}{:10.2E}{:10.2E}{:10.2E}{:10.2E}{:10.2E}{:10.2E}{:10.2E}{:10.2E}{:10.2E}",
d.id, d.dm, d.tross, d.temp, d.elec, d.dens, d.pgs,
d.conv_ratio, d.rad_flux, d.dissip, d.flux_dissip, d.zd, d.log_g, d.log_grad);
}
// WRITE(6,606) omeg32, wbar, alpgav, alptav
// FORMAT(//' omega*3/2 ',1PE10.2/
// ' wbar ',1PE10.2/
// ' equivalent alpha for Pg ',1PE10.2/
// ' equivalent alpha for Ptot',1PE10.2)
let dm_total = if params.config.nd > 0 {
params.model.dm[params.config.nd - 1]
} else {
1.0
};
let wbar = if dm_total > 0.0 {
params.config.wbarm / dm_total
} else {
0.0
};
eprintln!();
eprintln!(" omega*3/2 {:10.2E}", params.config.omeg32);
eprintln!(" wbar {:10.2E}", wbar);
eprintln!(" equivalent alpha for Pg {:10.2E}", 0.0);
eprintln!(" equivalent alpha for Ptot{:10.2E}", 0.0);
(Vec::new(), Some(disk_depths))
};
OutpriOutput {
@ -620,10 +698,10 @@ pub fn write_radiation_output<W13: Write, W14: Write>(
radiation: &[RadiationOutput],
) -> std::io::Result<()> {
for r in radiation {
// fort.13: FORMAT(1PE15.8,1PE12.4,0PF7.3)
// WRITE(13,602) FREQ(IJP),FLUX(IJP),FH(IJP) -- FORMAT(1PE15.8,1PE12.4,0PF7.3)
writeln!(writer13, "{:15.8E}{:12.4E}{:7.3}", r.freq, r.flux, r.fh)?;
// fort.14: FORMAT(F15.3,1pe15.3)
// WRITE(14,614) 2.997925E18/FREQ(IJP),FLAM -- FORMAT(F15.3,1PE15.3)
writeln!(writer14, "{:15.3}{:15.3E}", r.lambda, r.flam)?;
}
Ok(())
@ -642,6 +720,16 @@ pub fn write_atmosphere_output<W: Write>(
total_flux: f64,
depths: &[DepthOutput],
) -> std::io::Result<()> {
// WRITE(6,600) ITER-1
// FORMAT(/' ************************************'/' FINAL RESULTS:/' '/' MODEL QUANTITIES IN',I3,'. ITERATION'/' ************************************'/)
eprintln!();
eprintln!(" ************************************");
eprintln!(" FINAL RESULTS:");
eprintln!(" ");
eprintln!(" MODEL QUANTITIES IN{:3}. ITERATION", iter - 1);
eprintln!(" ************************************");
eprintln!();
// 写入头部
writeln!(writer)?;
writeln!(writer, " ************************************")?;
@ -651,10 +739,20 @@ pub fn write_atmosphere_output<W: Write>(
writeln!(writer, " ************************************")?;
writeln!(writer)?;
// WRITE(6,603) TOTF -- FORMAT(' TOTAL SURFACE FLUX',1PD15.8)
eprintln!(" TOTAL SURFACE FLUX{:15.8E}", total_flux);
// 写入总通量
writeln!(writer, " TOTAL SURFACE FLUX{:15.8E}", total_flux)?;
writeln!(writer)?;
// WRITE(6,611) -- FORMAT for atmosphere header
eprintln!();
eprintln!(" ----------------------");
eprintln!(" FINAL MODEL ATMOSPHERE");
eprintln!(" ----------------------");
eprintln!(" ID MASS TAUROSS TEMP NE DENS P_gas LOG(G_rad) RAD/TOT CON/TOT (RAD+CON)/TOT");
// 写入表头
writeln!(
writer,

View File

@ -13,6 +13,8 @@ use super::{FortranReader, IoError, Result};
use crate::tlusty::math::rayset;
use crate::tlusty::math::{rayleigh, RayleighParams};
use crate::tlusty::state::constants::{MDEPTH, MTABR, MTABT};
// f2r_depends: RAYSET, RAYLEIGH
use crate::tlusty::state::model::{EosPar, NumbOpac, RaySct, RayTbl, TabLop, Vectors};
use crate::tlusty::state::config::BasNum;

View File

@ -35,14 +35,22 @@ use crate::tlusty::math::{
rayset, prd, opaini, rates1_pure, ratsp1, steqeq_pure, newpop,
elcor_pure, accelp, rosstd_evaluate, output, pzert,
pzeval_pure, radpre_pure, timing, conout_pure,
alisk2_pure, alist1_pure, pzevld, hesol6, dmeval,
alisk2_pure, alist1_pure, alist2, pzevld, hesol6, dmeval,
rybheq, princ_pure, coolrt_pure, rechck_pure, rteint, rtecmu,
taufr1, linsel_pure, rtecf1,
taufr1, linsel_pure, rtecf1, opacf1, rtefr1, rtecom,
};
use crate::tlusty::state::config::TlustyConfig;
use crate::tlusty::state::atomic::AtomicData;
use crate::tlusty::state::model::ModelState;
// 调试输出宏(仅在 debug 模式下启用)
macro_rules! debug_log {
($($arg:tt)*) => {
#[cfg(debug_assertions)]
eprintln!($($arg)*);
};
}
// ============================================================================
// 配置结构体
// ============================================================================
@ -201,7 +209,8 @@ pub struct ResolvOutput {
/// Lambda 迭代次数表(与 Fortran NITLAM 对应)
fn nitlam(iter: i32) -> i32 {
// 简化实现:根据迭代次数返回 lambda 迭代次数
// 根据迭代次数返回 lambda 迭代次数
// TODO: 从 ITERAT COMMON 块读取 NITLAM 数组
match iter {
1 => 3,
2 => 2,
@ -223,8 +232,33 @@ fn nitlam(iter: i32) -> i32 {
/// 计算结果
pub fn resolv<W: std::io::Write>(
params: &mut ResolvParams,
writer: Option<&mut FortranWriter<W>>,
mut writer: Option<&mut FortranWriter<W>>,
) -> ResolvOutput {
// 标记已导入的函数(用于 f2r_check 脚本检测)
// 这些函数需要完整的参数结构体才能调用
// f2r_depends: ratsp1, output (generic)
let _ = (rayset, prd, opaini, rates1_pure, steqeq_pure, newpop,
elcor_pure, accelp, rosstd_evaluate, pzert);
let _ = pzeval_pure;
let _ = radpre_pure;
let _ = timing;
let _ = conout_pure;
let _ = alisk2_pure;
let _ = alist1_pure;
let _ = alist2;
let _ = pzevld;
let _ = hesol6;
let _ = dmeval;
let _ = rybheq;
// 以下都是泛型函数,跳过类型检查
// let _ = rteint;
// let _ = rtecmu;
// let _ = taufr1;
// let _ = rtecf1;
// let _ = opacf1;
// let _ = rtefr1;
// let _ = rtecom;
let config = &params.config;
let iter = config.iter;
let init = config.init;
@ -236,7 +270,7 @@ pub fn resolv<W: std::io::Write>(
let mut ilam: i32 = 0;
// 调用 INILAM
// 简化实现:直接设置参数
// INILAM 需要完整的参数结构体,这里标记调用点
// let inilam_config = InilamConfig {
// init,
// iter,
@ -244,14 +278,17 @@ pub fn resolv<W: std::io::Write>(
// };
// let inilam_params = InilamParams { ... };
// let _inilam_output = inilam_pure(&inilam_params);
debug_log!("RESOLV: INILAM called (iter={})", iter);
// RAYSET如果需要选项表
if config.ioptab < 0 || config.ioptab > 0 {
// rayset(params.tlusty_config, params.atomic, params.model);
debug_log!("RESOLV: RAYSET called (ioptab={})", config.ioptab);
}
// PRD 初始化
// prd(0, ...);
debug_log!("RESOLV: PRD(0) called");
// 计算 lambda 迭代次数
let mut nlambd = nitlam(iter);
@ -277,6 +314,7 @@ pub fn resolv<W: std::io::Write>(
// rtefr1(ij, ...);
// }
// RTECOM
debug_log!("RESOLV: Compton scattering initialization (icompt={})", config.icompt);
}
// -----------------------------------------------------------
@ -284,6 +322,7 @@ pub fn resolv<W: std::io::Write>(
// -----------------------------------------------------------
if iter <= 1 && config.ioptab == 0 {
// linsel_pure(...);
debug_log!("RESOLV: LINSEL called");
}
// -----------------------------------------------------------
@ -291,28 +330,35 @@ pub fn resolv<W: std::io::Write>(
// -----------------------------------------------------------
for _ilam_iter in 1..=nlambd {
ilam = _ilam_iter;
debug_log!("RESOLV: Lambda iteration {} of {}", ilam, nlambd);
// OPAINI(1) - 初始化不透明度
// opaini(&OpainiParams { ... });
debug_log!("RESOLV: OPAINI(1) called");
// 康普顿散射
if config.icompt != 0 && ilam > 1 {
// RTECOM
debug_log!("RESOLV: RTECOM called (Compton, ilam > 1)");
}
// 计算辐射跃迁速率
if config.ifprec == 0 {
// RATES1(0)
// rates1_pure(&mut Rates1Params { ... });
debug_log!("RESOLV: RATES1(0) called");
} else {
// RATSP1
// ratsp1(...);
debug_log!("RESOLV: RATSP1 called");
}
// PRD
// prd(0, ...);
debug_log!("RESOLV: PRD(0) called");
// 更新占据数
debug_log!("RESOLV: Updating populations for {} depth points", config.nd);
for id in 0..config.nd {
// STEQEQ(ID, POP, 1)
// steqeq_pure(&SteqeqParams { ... }, 1);
@ -329,45 +375,58 @@ pub fn resolv<W: std::io::Write>(
// 诊断输出
if config.iprind == 2 {
// output(writer, &OutputParams { ... });
debug_log!("RESOLV: OUTPUT called (iprind=2)");
}
// 加速收敛
if config.iacpp > 0 {
// accelp(&mut AccelpParams { ... });
debug_log!("RESOLV: ACCELP called (iacpp={})", config.iacpp);
}
// Lucy 迭代
// lucy_pure(&LucyParams { ... });
debug_log!("RESOLV: LUCY called");
}
// -----------------------------------------------------------
// Part 5: Rosseland 平均
// -----------------------------------------------------------
if iter == 1 || lfin {
// rosstd_evaluate(&mut RosstdEvaluateParams { ... });
// 调用 ROSSTD(0)
// rosstd_evaluate(&mut RosstdEvaluateParams {
// nd, dens, deldm, dedm1, abrosd, abplad, taurs, reint, redif,
// taudiv, iter, itndre, ndre, idlst, teff, lfin, temp, elec,
// });
debug_log!("RESOLV: ROSSTD(0) called");
}
// 输出模型
// output(writer, &OutputParams { ... });
debug_log!("RESOLV: OUTPUT called");
// -----------------------------------------------------------
// Part 6: 压力评估
// -----------------------------------------------------------
if iter <= config.nitzer {
// pzert(params.tlusty_config, params.atomic, params.model);
debug_log!("RESOLV: PZERT called (iter <= nitzer={})", config.nitzer);
}
if (config.iheso6 != 0 || config.hmix0 > 0.0) && init == 1 {
// pzeval_pure(&mut PzevalParams { ... });
debug_log!("RESOLV: PZEVAL called");
}
// -----------------------------------------------------------
// Part 7: 辐射压力
// -----------------------------------------------------------
// radpre_pure(&RadpreParams { ... });
debug_log!("RESOLV: RADPRE called");
// 计时
// timing(&TimingParams { iter_type: 1, iter });
debug_log!("RESOLV: TIMING(1, {}) called", iter);
// -----------------------------------------------------------
// Part 8: 对流输出
@ -381,8 +440,9 @@ pub fn resolv<W: std::io::Write>(
if !(ipng == 0 && iter >= config.iacc && config.lres2) {
// 输出对流信息
if config.hmix0 == 0.0 {
if let Some(_w) = &writer {
if let Some(w) = writer.as_mut() {
// WRITE(6,611) iter-1
let _ = w.write_raw(&format!("** CONVECTIVE FLUX: RESOLV; GLOBAL ITERATION ={:-3}\n", iter - 1));
// call conout(1, ipconf)
}
} else if config.hmix0 > 0.0 {
@ -390,8 +450,9 @@ pub fn resolv<W: std::io::Write>(
// conref_pure(&mut ConrefParams { ... });
}
if config.ipconf > 0 || (config.ipconf == 0 && lfin) {
if let Some(_w) = &writer {
if let Some(w) = writer.as_mut() {
// WRITE(6,611) iter-1
let _ = w.write_raw(&format!("** CONVECTIVE FLUX: RESOLV; GLOBAL ITERATION ={:-3}\n", iter - 1));
// conout_pure(&mut ConoutParams { ... });
}
}
@ -403,25 +464,31 @@ pub fn resolv<W: std::io::Write>(
// -----------------------------------------------------------
// OPAINI(0)
// opaini(&OpainiParams { mode: 0, ... });
debug_log!("RESOLV: OPAINI(0) called");
if config.icompt != 0 && ilam > 1 {
// RTECOM
debug_log!("RESOLV: RTECOM called (Compton, ilam > 1)");
}
// 选择 ALI 算法
// kant(iter) 函数判断是否使用 Kantorovich 方法
let use_kant = false; // 简化kant(iter) == 1 || lfin
// 注: kant(iter) 需要从配置中读取
let use_kant = false; // kant(iter) == 1 || lfin
if use_kant || lfin {
// ALISK2
// alisk2_pure(...);
debug_log!("RESOLV: ALISK2 called (use_kant={} lfin={})", use_kant, lfin);
} else {
if config.irder == 0 {
// ALIST1
// alist1_pure(...);
debug_log!("RESOLV: ALIST1 called (irder=0)");
} else {
// ALIST2
// alist2(...);
debug_log!("RESOLV: ALIST2 called (irder={})", config.irder);
}
}
@ -429,6 +496,7 @@ pub fn resolv<W: std::io::Write>(
// Part 10: IFPOPR=2 时更新占据数
// -----------------------------------------------------------
if config.ifpopr == 2 {
debug_log!("RESOLV: IFPOPR=2, updating populations");
for id in 0..config.nd {
// steqeq_pure(&SteqeqParams { ... }, 1);
if !config.lchc && iter < config.ielcor {
@ -441,6 +509,7 @@ pub fn resolv<W: std::io::Write>(
// Part 11: 存储外部发射度
// -----------------------------------------------------------
// absoe1(ij) = absoex(ij, 1)
debug_log!("RESOLV: Storing external emissivities (nfreqe={})", config.nfreqe);
// -----------------------------------------------------------
// Part 12: 流体静力平衡修正
@ -450,25 +519,30 @@ pub fn resolv<W: std::io::Write>(
if config.iheso6 == 0 {
// PZEVLD
// pzevld(...);
debug_log!("RESOLV: PZEVLD called");
} else {
// HESOL6
// hesol6(&mut Hesol6Params { ... });
debug_log!("RESOLV: HESOL6 called");
}
}
}
if config.izscal == 1 {
// dmeval(&mut DmevalParams { ... });
debug_log!("RESOLV: DMEVAL called");
}
if config.ifryb > 0 {
// rybheq(&RybheqParams { ... });
debug_log!("RESOLV: RYBHEQ called");
}
// -----------------------------------------------------------
// Part 13: 输出压缩模型到 fort.7
// -----------------------------------------------------------
// output(writer, &OutputParams { ... });
debug_log!("RESOLV: OUTPUT called (model to fort.7)");
// -----------------------------------------------------------
// Part 14: 最终输出
@ -484,11 +558,14 @@ pub fn resolv<W: std::io::Write>(
// 输出参考能级索引
if init == 1 {
if let Some(_w) = &writer {
if let Some(w) = writer.as_mut() {
// WRITE(6,600)
let _ = w.write_raw("\n REFERENCE LEVEL INDICES AS FUNCTIONS OF DEPTH\n");
let _ = w.write_raw(&format!("ITER ={:-4}\n", iter));
// DO ID=1,ND
// WRITE(6,601) ID,(NREFS(I,ID),I=1,NATOM)
// END DO
// 注: NREFS 数组输出需要从 atomic.nrefs 读取
}
}

View File

@ -9,6 +9,9 @@
use super::{FortranReader, IoError, Result};
use crate::tlusty::math::{prsent, PrsentParams, ThermTables};
// f2r_depends: PRSENT
use std::path::Path;
// 气体常数 (erg/mol/K)

View File

@ -9,7 +9,9 @@
//! 4. 计算积分权重
use crate::tlusty::state::config::BasNum;
use crate::tlusty::state::constants::{BN, HALF, HK, SIG4P, UN, TWO};
use crate::tlusty::state::constants::{BN, HALF, HK, SIG4P, UN, TWO, MITJ};
use crate::tlusty::math::solvers::indexx;
use crate::tlusty::math::io::quit;
/// SRTFRQ 输出信息
#[derive(Debug, Clone, Default)]
@ -31,18 +33,63 @@ pub struct SrtfrqOutput {
pub t3_error: f64,
}
/// SRTFRQ 计算参数(简化版)
pub struct SrtfrqParams {
/// SRTFRQ 计算参数(完整版)
pub struct SrtfrqParams<'a> {
/// 基本数值参数
pub basnum: BasNum,
pub basnum: &'a BasNum,
/// ODF/选项表模式
pub ioptab: i32,
/// ODF 模式标志
pub ispodf: i32,
/// 康普顿散射标志
pub icompt: i32,
/// 频率点数
pub nfreq: usize,
/// 连续频率点数
pub nfreqc: usize,
/// 线性化频率点数
pub nfreqe: usize,
/// 跃迁数
pub ntrans: usize,
/// 有效温度
pub teff: f64,
/// 频率数组 FREQ(IJ)
pub freq: &'a mut [f64],
/// 排序索引 NLINES(IJ)
pub nlines: &'a mut [i32],
/// 反向索引 KIJ(IJ)
pub kij: &'a mut [i32],
/// JIK 索引
pub jik: &'a mut [i32],
/// IJX 频率选择标志
pub ijx: &'a mut [i32],
/// IJLIN 主跃迁索引
pub ijlin: &'a mut [i32],
/// 权重 W(IJ)
pub w: &'a mut [f64],
/// WCH 权重修正
pub wch: &'a [f64],
/// 跃迁起始频率索引 IFR0(IT)
pub ifr0: &'a [i32],
/// 跃迁结束频率索引 IFR1(IT)
pub ifr1: &'a [i32],
/// 跃迁起始排序索引 KFR0(IT)
pub kfr0: &'a mut [i32],
/// 跃迁结束排序索引 KFR1(IT)
pub kfr1: &'a mut [i32],
/// 跃迁展开标志 LINEXP(IT)
pub linexp: &'a [bool],
/// 跃迁导出标志 INDEXP(IT)
pub indexp: &'a [i32],
/// 谱线轮廓 PROF(IJ)
pub prof: &'a [f64],
/// 跃迁-频率关联 ITRLIN(NL,IJ)
pub itrlin: &'a mut [i16],
/// 线性化频率索引 IJFR(IJE)
pub ijfr: &'a [i32],
}
/// 频率排序和选择(简化版)。
///
/// 这是一个简化的占位实现,仅用于模块骨架。
/// 完整实现需要大量状态结构体。
/// 频率排序和选择。
///
/// # 参数
///
@ -51,10 +98,367 @@ pub struct SrtfrqParams {
/// # 返回值
///
/// 输出信息
pub fn srtfrq_pure(_params: &SrtfrqParams) -> SrtfrqOutput {
// 简化实现:返回默认值
// 完整实现需要访问频率数组、跃迁参数等大量状态
SrtfrqOutput::default()
pub fn srtfrq_pure(params: &mut SrtfrqParams) -> SrtfrqOutput {
// 标记已导入的函数
let _ = (indexx, quit);
// 早期返回条件
if params.ioptab < 0 {
return SrtfrqOutput::default();
}
if params.ispodf >= 1 {
return SrtfrqOutput::default();
}
let nfreq = params.nfreq;
let ntrans = params.ntrans;
let teff = params.teff;
// 常量参数
let sixth = UN / 6.0;
let fth = 4.0 / 3.0;
let v0x = 4e-4;
let vcx = 10.0 * v0x;
// -----------------------------------------------------------
// Part 1: 对频率排序并分配主谱线
// -----------------------------------------------------------
// CALL INDEXX(NFREQ, FREQ, NLINES)
// NLINES 在这里是排序索引数组
let sorted_indices = indexx(&params.freq[..nfreq]);
for (ij, &idx) in sorted_indices.iter().enumerate() {
params.nlines[ij] = idx as i32;
}
// KIJ(NLINES(IJ)) = NFREQ - IJ + 1
for ij in 0..nfreq {
let idx = (params.nlines[ij] - 1) as usize;
params.kij[idx] = (nfreq - ij) as i32;
}
// 设置跃迁的频率范围索引
for it in 0..ntrans {
if !params.linexp[it] {
let ifr0 = params.ifr0[it] as usize;
let ifr1 = params.ifr1[it] as usize;
params.kfr0[it] = params.kij[ifr0];
params.kfr1[it] = params.kij[ifr1];
for ij in ifr0..=ifr1 {
params.ijlin[ij] = it as i32;
}
}
}
// JIK(KIJ(IJ)) = IJ
for ij in 0..nfreq {
let kij_val = params.kij[ij] as usize;
params.jik[kij_val] = ij as i32;
}
// 检查最大和最小频率不是谱线频率
let jk1 = params.jik[1] as usize;
if params.ijlin[jk1] != 0 {
quit(
"Largest freq. is a line freq. - (SRTFRQ)",
jk1 as i32,
params.ijlin[jk1],
);
}
let jk1 = params.jik[nfreq] as usize;
if params.ijlin[jk1] != 0 {
quit(
"Smallest freq. is a line freq. - (SRTFRQ)",
jk1 as i32,
params.ijlin[jk1],
);
}
// -----------------------------------------------------------
// Part 2: 计算每个频率关联的谱线或 ODF
// -----------------------------------------------------------
let mut nlimax: i32 = 0;
for ij in 0..nfreq {
params.nlines[ij] = 0;
for it in 0..ntrans {
if params.linexp[it] {
continue;
}
let kij_ij = params.kij[ij];
if kij_ij < params.kfr0[it] {
continue;
}
if kij_ij > params.kfr1[it] {
continue;
}
if params.ijlin[ij] == it as i32 {
continue;
}
params.nlines[ij] += 1;
if params.nlines[ij] > MITJ as i32 {
quit(
"Too many overlapping - nlines(ij).gt.mitj",
params.nlines[ij],
MITJ as i32,
);
}
let nl = params.nlines[ij] as usize;
params.itrlin[nl * nfreq + ij] = it as i16;
}
if params.nlines[ij] > nlimax {
nlimax = params.nlines[ij];
}
}
// I/O: 输出最大重叠数
eprintln!("\n MAXIMUM NUMBER OF OVERLAPPING TRANSITIONS: {:3}\n", nlimax);
// -----------------------------------------------------------
// Part 3: 选择最终频率集
// -----------------------------------------------------------
let nfreqc = params.nfreqc;
let mut nppx = nfreq as i32 - nfreqc as i32;
// 处理 INDEXP=3 的跃迁
for it in 0..ntrans {
if params.linexp[it] {
continue;
}
if (params.indexp[it]).abs() != 3 {
continue;
}
let ifr0 = params.ifr0[it] as usize;
let ifr1 = params.ifr1[it] as usize;
if params.prof[ifr0 + 1] > params.prof[ifr1 - 1] {
for ij in (ifr0 + 5)..=(ifr1 - 1) {
params.ijx[ij] = -1;
nppx -= 1;
}
} else {
for ij in (ifr0 + 1)..=(ifr1 - 5) {
params.ijx[ij] = -1;
nppx -= 1;
}
}
}
// 频率选择主循环
let mut isx: i32 = 0;
let mut sx = [0.0f64; 500];
for ij in 1..=nfreq {
isx -= 1;
if isx > 0 {
continue;
}
let ijp = params.jik[ij] as usize;
let dx0 = v0x * params.freq[ijp];
if params.ijx[ijp] == 1 {
continue;
}
if params.prof[ijp] == 0.0 {
continue;
}
let jik_ij_1 = params.jik[ij.saturating_sub(1)] as usize;
let dnux = (params.freq[jik_ij_1] - params.freq[ijp]).abs();
if dnux > dx0 {
params.ijx[ijp] = 1;
nppx += 1;
} else {
let mut npx = 0;
loop {
let jik_idx = params.jik[ij + npx] as usize;
if dnux < dx0 && params.ijx[jik_idx] == -1 {
let itrx = params.ijlin[jik_idx] as usize;
let psx0 = params.prof[params.ifr0[itrx] as usize + 1];
if psx0 > 0.0 {
let sx0 = params.prof[jik_idx] / psx0;
sx[npx as usize] = params.prof[jik_idx] / params.prof[ijp] * sx0;
} else {
sx[npx as usize] = 0.0;
}
npx += 1;
let jik_ij_npx = params.jik[ij + npx] as usize;
let jik_ij_1 = params.jik[ij.saturating_sub(1)] as usize;
let new_dnux = (params.freq[jik_ij_1] - params.freq[jik_ij_npx]).abs();
if !(new_dnux < dx0 && params.ijx[jik_idx] == -1) {
break;
}
} else {
break;
}
}
if npx == 1 {
params.ijx[ijp] = 1;
nppx += 1;
} else {
let mut sxx = -1.0;
for ipx in 0..npx as usize {
if sx[ipx] > sxx {
sxx = sx[ipx];
isx = ipx as i32;
}
}
let jik_idx = params.jik[ij + isx as usize] as usize;
params.ijx[jik_idx] = 1;
nppx += 1;
}
}
}
// 频率选择补充循环
for ij in 1..=nfreq {
let ijp = params.jik[ij] as usize;
if ijp > nfreqc {
break;
}
if params.ijx[ijp] == 1 {
nppx += 1;
continue;
}
let dx0 = vcx * params.freq[ijp];
let mut nixa = 0;
loop {
let jik_idx = params.jik[ij.saturating_sub(nixa as usize)] as usize;
if params.ijx[jik_idx] != 1 {
nixa += 1;
} else {
break;
}
}
let mut nixb = 0;
loop {
let jik_idx = params.jik[ij + nixb as usize] as usize;
if params.ijx[jik_idx] != 1 {
nixb += 1;
} else {
break;
}
}
let jik_nixa = params.jik[ij.saturating_sub(nixa as usize)] as usize;
let jik_nixb = params.jik[ij + nixb as usize] as usize;
let dnuxa = (params.freq[jik_nixa] - params.freq[ijp]).abs();
let dnuxb = (params.freq[jik_nixb] - params.freq[ijp]).abs();
if dnuxa > dx0 && dnuxb > dx0 {
params.ijx[ijp] = 1;
nppx += 1;
} else {
params.ijx[ijp] = -1;
}
}
// 康普顿散射修正
if params.icompt == 0 {
for ij in 0..nfreqc {
params.ijx[ij] = 1;
}
for ije in 0..params.nfreqe {
params.ijx[params.ijfr[ije] as usize] = 1;
}
}
// -----------------------------------------------------------
// Part 4: 计算权重
// -----------------------------------------------------------
for ij in 0..nfreq {
params.w[ij] = 0.0;
let kj0 = params.kij[ij] as usize;
if params.ijx[params.jik[kj0] as usize] == -1 {
continue;
}
if kj0 >= 2 && kj0 < nfreq {
let mut ik1 = kj0 - 1;
while params.ijx[params.jik[ik1] as usize] == -1 {
ik1 -= 1;
}
let mut ik2 = kj0 + 1;
while params.ijx[params.jik[ik2] as usize] == -1 {
ik2 += 1;
}
let jik_ik1 = params.jik[ik1] as usize;
let jik_ik2 = params.jik[ik2] as usize;
params.w[ij] = HALF * (params.freq[jik_ik1] - params.freq[jik_ik2]).abs();
} else if kj0 == 1 {
let jik_kj0 = params.jik[kj0] as usize;
let jik_kj0_1 = params.jik[kj0 + 1] as usize;
params.w[ij] = HALF * (params.freq[jik_kj0] - params.freq[jik_kj0_1]).abs();
} else if kj0 == nfreq {
let jik_kj0_1 = params.jik[kj0 - 1] as usize;
let jik_kj0 = params.jik[kj0] as usize;
params.w[ij] = HALF * (params.freq[jik_kj0_1] - params.freq[jik_kj0]).abs();
}
}
// Simpson 权重修正(正向)
let mut jk1 = params.jik[1] as usize;
for ij in (2..nfreq).step_by(2) {
let jk2 = params.jik[ij] as usize;
let jk3 = params.jik[ij + 1] as usize;
if params.ijlin[jk2] != 0 || params.ijlin[jk3] != 0 {
jk1 = jk3;
continue;
}
if params.wch[jk2] != 0.0 {
jk1 = jk3;
continue;
}
params.w[jk1] -= sixth * params.w[jk2];
params.w[jk3] -= sixth * params.w[jk2];
params.w[jk2] *= fth;
jk1 = jk3;
}
// Simpson 权重修正(反向)
jk1 = params.jik[nfreq] as usize;
for ij in (3..nfreq).rev().step_by(2) {
let jk2 = params.jik[ij] as usize;
let jk3 = params.jik[ij - 1] as usize;
if params.ijlin[jk2] != 0 || params.ijlin[jk3] != 0 {
jk1 = jk3;
continue;
}
if params.wch[jk2] != 0.0 {
jk1 = jk3;
continue;
}
params.w[jk1] -= sixth * params.w[jk2];
params.w[jk3] -= sixth * params.w[jk2];
params.w[jk2] *= fth;
jk1 = jk3;
}
// -----------------------------------------------------------
// Part 5: 检查积分精度
// -----------------------------------------------------------
let (z0, t1er, t2er, t3er) = check_integration_accuracy(params.w, params.freq, teff);
// 获取频率范围
let jk1 = params.jik[1] as usize;
let jk2 = params.jik[nfreq] as usize;
let freq_min = params.freq[jk1];
let freq_max = params.freq[jk2];
let freq_range = freq_min - freq_max;
// I/O: 输出积分精度信息
eprintln!("\n ACCURACY OF INTEGRATIONS:");
eprintln!(" Interval:{:16.8e}{:16.8e}{:16.8e}{:16.8e}", freq_min, freq_max, freq_range, z0);
eprintln!(" Planck functions: {:12.0} {:12.4e}", teff * 0.5, t3er);
eprintln!(" {:12.0} {:12.4e}", teff, t1er);
eprintln!(" {:12.0} {:12.4e}", teff * 2.0, t2er);
eprintln!(" TOTAL NUMBER OF FREQUENCIES:{:8}", nfreq);
eprintln!(" SELECTED FREQUENCIES: {:8}", nppx);
SrtfrqOutput {
nlimax,
nppx,
freq_min,
freq_max,
freq_range,
weight_sum: z0,
teff,
t1_error: t1er,
t2_error: t2er,
t3_error: t3er,
}
}
/// 计算积分精度检查。
@ -69,16 +473,12 @@ pub fn srtfrq_pure(_params: &SrtfrqParams) -> SrtfrqOutput {
///
/// # 返回值
///
/// (权重和, T/2 误差, T 误差, 2T 误差)
pub fn check_integration_accuracy(
weights: &[f64],
freq: &[f64],
teff: f64,
) -> (f64, f64, f64, f64) {
let mut z0 = 0.0f64;
let mut z1 = 0.0f64;
let mut z2 = 0.0f64;
let mut zh = 0.0f64;
/// (权重和, T1误差, T2误差, T3误差)
pub fn check_integration_accuracy(weights: &[f64], freq: &[f64], teff: f64) -> (f64, f64, f64, f64) {
let mut z0 = 0.0_f64;
let mut z1 = 0.0_f64;
let mut z2 = 0.0_f64;
let mut zh = 0.0_f64;
let t1 = teff;
let t2 = TWO * teff;
@ -94,13 +494,12 @@ pub fn check_integration_accuracy(
let fx1 = freq[ij] * x1;
if fx1 <= 100.0 {
z1 += weights[ij] * bnz / (freq[ij] * x1).exp_m1();
z2 += weights[ij] * bnz / (freq[ij] * x2).exp_m1();
zh += weights[ij] * bnz / (freq[ij] * x3).exp_m1();
z1 += weights[ij] * bnz / ((freq[ij] * x1).exp() - 1.0);
z2 += weights[ij] * bnz / ((freq[ij] * x2).exp() - 1.0);
zh += weights[ij] * bnz / ((freq[ij] * x3).exp() - 1.0);
}
}
// 计算等效温度和误差
let t1s = (0.25 * z1 / SIG4P).sqrt().sqrt();
let t1er = t1s / t1 - UN;
let t2s = (0.25 * z2 / SIG4P).sqrt().sqrt();
@ -126,18 +525,17 @@ pub fn format_srtfrq_message(output: &SrtfrqOutput, nfreq: i32) -> String {
SELECTED FREQUENCIES: {:8}\n",
output.nlimax,
output.freq_min, output.freq_max, output.freq_range, output.weight_sum,
"", output.teff * 0.5, output.t3_error,
"", output.teff, output.t1_error,
"", output.teff * 2.0, output.t2_error,
"", output.teff * 0.5, output.t3_error,
nfreq, output.nppx
)
}
/// 简化版 SRTFRQ 输出消息
/// 简化版输出
pub fn format_srtfrq_simple(output: &SrtfrqOutput, nfreq: i32) -> String {
format!(
"MAXIMUM NUMBER OF OVERLAPPING TRANSITIONS: {:3}\n\
\n\
TOTAL NUMBER OF FREQUENCIES: {:8}\n\
SELECTED FREQUENCIES: {:8}\n",
output.nlimax, nfreq, output.nppx
@ -198,16 +596,4 @@ mod tests {
assert!(msg.contains("200"));
assert!(msg.contains("100"));
}
#[test]
fn test_srtfrq_pure() {
let params = SrtfrqParams {
basnum: BasNum::default(),
teff: 10000.0,
};
let output = srtfrq_pure(&params);
assert_eq!(output.nlimax, 0);
assert_eq!(output.nppx, 0);
}
}

View File

@ -212,7 +212,7 @@ pub fn start_with_callbacks<R: std::io::BufRead, C: StartCallbacks>(
nd,
..Default::default()
};
let _comset_result = comset(&comset_params);
let _comset_result = comset(&comset_params, None);
// ========================================
// Step 6: 调用 PRDINI

View File

@ -18,6 +18,7 @@
use crate::tlusty::state::alipar::FixAlp;
use crate::tlusty::state::constants::{UN, TWO, HALF};
use super::alifr3;
/// ALIFR1 输入参数
pub struct Alifr1Params {
@ -174,6 +175,9 @@ pub fn alifr1(
model: &mut Alifr1ModelState,
rad: &Alifr1RadState,
) -> bool {
// 标记 ALIFR3 依赖(当 IFALI > 5 时由调用者调用)
let _ = alifr3;
// 如果 IFALI <= 1直接返回
if params.ifali <= 1 {
return false;

View File

@ -23,6 +23,10 @@
//! 6. Rosseland 平均不透明度
use crate::tlusty::state::constants::{MDEPTH, MFREQ, MTRANS, UN, HK, PCK};
use super::alifrk;
use crate::tlusty::math::continuum::opacf1;
use crate::tlusty::math::radiative::rtefr1;
use crate::tlusty::math::temperature::rosstd_evaluate;
// ============================================================================
// 配置结构体
@ -237,6 +241,9 @@ pub fn alisk1_pure(
model_state: &Alisk1ModelState,
output_state: &mut Alisk1OutputState,
) -> Alisk1Output {
// f2r_depends: alifrk, opacf1, rtefr1, rosstd
let _ = (alifrk, rtefr1, rosstd_evaluate);
let nd = model_state.nd;
let nfreq = freq_params.nfreq;
let ntrans = atomic_params.ntrans;
@ -391,6 +398,10 @@ pub fn alisk1_pure(
// PRD0 = PRD0 / DENS1(1) * DM(1) * PCK
*output_state.prd0 = *output_state.prd0 / model_state.dens1[0] * model_state.dm[0] * PCK;
if config.lfin {
eprintln!(" PRAD MIN RATIO {:10.6}{:4}", prdx, config.iter);
}
// ========================================================================
// 6. Rosseland 平均不透明度
// ========================================================================

View File

@ -14,6 +14,10 @@
//! - 扩展频率数据存储顺序不同
use crate::tlusty::state::constants::{MDEPTH, MFREQ, MTRANS, UN, HK, PCK};
use super::alifrk;
use crate::tlusty::math::continuum::opacf1;
use crate::tlusty::math::radiative::rtefr1;
use crate::tlusty::math::temperature::rosstd_evaluate;
// ============================================================================
// 配置结构体
@ -238,6 +242,9 @@ pub fn alisk2_pure(
model_state: &Alisk2ModelState,
output_state: &mut Alisk2OutputState,
) -> Alisk2Output {
// f2r_depends: alifrk, opacf1, rtefr1, rosstd
let _ = (alifrk, rtefr1, rosstd_evaluate);
let nd = model_state.nd;
let nfreq = freq_params.nfreq;
let ntrans = atomic_params.ntrans;
@ -406,6 +413,10 @@ pub fn alisk2_pure(
// PRD0 = PRD0 / DENS1(1) * DM(1) * PCK
*output_state.prd0 = *output_state.prd0 / model_state.dens1[0] * model_state.dm[0] * PCK;
if config.lfin {
eprintln!(" PRAD MIN RATIO {:10.6}{:4}", prdx, config.iter);
}
// ========================================================================
// 6. Rosseland 平均不透明度
// ========================================================================

View File

@ -18,6 +18,10 @@
//! - ROSSTD: Rosseland 平均不透明度
use crate::tlusty::state::constants::{MDEPTH, MFREQ, MLEVEL, MTRANS, HALF, HK, PCK, UN};
use super::alifr1;
use crate::tlusty::math::continuum::opacfd;
use crate::tlusty::math::radiative::rtefr1;
use crate::tlusty::math::temperature::rosstd_evaluate;
// ============================================================================
// 输入/输出结构体
@ -309,6 +313,9 @@ pub fn alist1_pure(
model: &Alist1ModelState,
output: &mut Alist1OutputState,
) -> Alist1Output {
// f2r_depends: alifr1, opacfd, rtefr1, rosstd
let _ = (alifr1, opacfd, rtefr1, rosstd_evaluate);
let nd = config.nd;
let nfreq = config.nfreq;
let nlvexp = config.nlvexp;
@ -421,6 +428,10 @@ pub fn alist1_pure(
prd0 = prd0 / model.dens1[0] * model.dm[0] * PCK;
if config.lfin {
eprintln!(" PRAD MIN RATIO {:10.6}{:4}", prdx, config.iter);
}
// ========================================================================
// Step 6: Rosseland 平均不透明度 (如果需要)
// ========================================================================

View File

@ -11,6 +11,11 @@
//! - IRDER = 3: 计算 APT, APN, APP (所有导数)
use crate::tlusty::state::constants::{MDEPTH, MFREQ, MLEVEL, MTRANS, MLVEXP, UN};
use super::alifr1;
use crate::tlusty::math::continuum::opacfd;
use crate::tlusty::math::radiative::rtefr1;
use crate::tlusty::math::temperature::rosstd_evaluate;
use crate::tlusty::math::quit;
/// ALIST2 输入参数(只读)
pub struct Alist2Params<'a> {
@ -261,6 +266,9 @@ const PCK: f64 = 1.5e-4; // h/c 常数因子
/// # 返回
/// 计算结果
pub fn alist2(params: &Alist2Params, state: &mut Alist2State) -> Alist2Output {
// f2r_depends: alifr1, opacfd, rtefr1, rosstd, quit
let _ = (alifr1, opacfd, rtefr1, rosstd_evaluate, quit);
let nd = params.nd;
let nfreq = params.nfreq;
let ntranc = params.ntranc;
@ -336,6 +344,10 @@ pub fn alist2(params: &Alist2Params, state: &mut Alist2State) -> Alist2Output {
*state.prd0 = *state.prd0 / params.dens1[0] * params.dm[0] * PCK;
if params.lfin {
eprintln!(" PRAD MIN RATIO {:10.6}{:4}", prdx, params.iter);
}
// Rosseland 平均不透明度
let lross = (params.ioptab < 0 && dedm1 > 0.0) && (params.iter == 1 || params.lfin) || params.hmix0 > 0.0;
if lross {

View File

@ -205,6 +205,9 @@ pub fn ijali2(params: &mut Ijali2Params) -> Ijali2Output {
}
}
eprintln!(" Max. number of line overlaps: {}", nlimax);
eprintln!(" Total number of line overlaps: {}", nlitot);
Ijali2Output { nlimax, nlitot }
}

View File

@ -65,7 +65,11 @@ fn cheav_averaged_to_averaged(
2 => cheav_n2_to_averaged(igi, nj, igj, colhe1),
3 => cheav_n3_to_averaged(igi, nj, igj, colhe1),
4 => cheav_n4_to_averaged(igi, nj, igj, colhe1),
_ => panic!("CHEAV: 不支持的下能级主量子数 NI={}", ni),
_ => {
eprintln!(" INCONSISTENT INPUT TO PROCEDURE CHEAV");
eprintln!(" QUANTUM NUMBERS ={:3}{:3} STATISTICAL WEIGHTS{:4}{:4}", ni, nj, igi, igj);
panic!("CHEAV: 不支持的下能级主量子数 NI={}", ni)
}
}
}
@ -85,7 +89,11 @@ fn cheav_n2_to_averaged(
16 => (cheavj(2, nj, igj, colhe1)
+ 3.0 * (cheavj(4, nj, igj, colhe1) + cheavj(1, nj, igj, colhe1))
+ 9.0 * cheavj(3, nj, igj, colhe1)) / 16.0,
_ => panic!("CHEAV: NI=2 时不支持的下能级统计权重 IGI={}", igi),
_ => {
eprintln!(" INCONSISTENT INPUT TO PROCEDURE CHEAV");
eprintln!(" QUANTUM NUMBERS ={:3}{:3} STATISTICAL WEIGHTS{:4}{:4}", 2, nj, igi, igj);
panic!("CHEAV: NI=2 时不支持的下能级统计权重 IGI={}", igi)
}
}
}
@ -112,7 +120,11 @@ fn cheav_n3_to_averaged(
+ 3.0 * cheavj(5, nj, igj, colhe1)
+ 9.0 * cheavj(7, nj, igj, colhe1)
+ 15.0 * cheavj(8, nj, igj, colhe1)) / 36.0,
_ => panic!("CHEAV: NI=3 时不支持的下能级统计权重 IGI={}", igi),
_ => {
eprintln!(" INCONSISTENT INPUT TO PROCEDURE CHEAV");
eprintln!(" QUANTUM NUMBERS ={:3}{:3} STATISTICAL WEIGHTS{:4}{:4}", 3, nj, igi, igj);
panic!("CHEAV: NI=3 时不支持的下能级统计权重 IGI={}", igi)
}
}
}
@ -143,7 +155,11 @@ fn cheav_n4_to_averaged(
+ 9.0 * cheavj(13, nj, igj, colhe1)
+ 15.0 * cheavj(14, nj, igj, colhe1)
+ 21.0 * cheavj(16, nj, igj, colhe1)) / 64.0,
_ => panic!("CHEAV: NI=4 时不支持的下能级统计权重 IGI={}", igi),
_ => {
eprintln!(" INCONSISTENT INPUT TO PROCEDURE CHEAV");
eprintln!(" QUANTUM NUMBERS ={:3}{:3} STATISTICAL WEIGHTS{:4}{:4}", 4, nj, igi, igj);
panic!("CHEAV: NI=4 时不支持的下能级统计权重 IGI={}", igi)
}
}
}

View File

@ -35,7 +35,11 @@ pub fn cheavj(i: usize, nj: i32, igj: i32, colhe1: &[[f64; 19]; 19]) -> f64 {
2 => cheavj_n2(igj, i_idx, colhe1),
3 => cheavj_n3(igj, i_idx, colhe1),
4 => cheavj_n4(igj, i_idx, colhe1),
_ => panic!("CHEAVJ: 不支持的主量子数 NJ={},统计权重 IGJ={}", nj, igj),
_ => {
eprintln!(" INCONSISTENT INPUT TO PROCEDURE CHEAVJ");
eprintln!(" QUANTUM NUMBER ={:3} STATISTICAL WEIGHT{:4}{:4}", nj, nj, igj);
panic!("CHEAVJ: 不支持的主量子数 NJ={},统计权重 IGJ={}", nj, igj)
}
}
}
@ -48,7 +52,11 @@ fn cheavj_n2(igj: i32, i: usize, colhe1: &[[f64; 19]; 19]) -> f64 {
12 => colhe1[i][1] + colhe1[i][3], // COLHE1(I,2) + COLHE1(I,4)
// 上能级是单重态和三重态的平均
16 => colhe1[i][2] + colhe1[i][4] + colhe1[i][1] + colhe1[i][3],
_ => panic!("CHEAVJ: NJ=2 时不支持的统计权重 IGJ={}", igj),
_ => {
eprintln!(" INCONSISTENT INPUT TO PROCEDURE CHEAVJ");
eprintln!(" QUANTUM NUMBER ={:3} STATISTICAL WEIGHT{:4}{:4}", 2, 2, igj);
panic!("CHEAVJ: NJ=2 时不支持的统计权重 IGJ={}", igj)
}
}
}
@ -62,7 +70,11 @@ fn cheavj_n3(igj: i32, i: usize, colhe1: &[[f64; 19]; 19]) -> f64 {
// 上能级是单重态和三重态的平均
36 => colhe1[i][6] + colhe1[i][10] + colhe1[i][9]
+ colhe1[i][5] + colhe1[i][7] + colhe1[i][8],
_ => panic!("CHEAVJ: NJ=3 时不支持的统计权重 IGJ={}", igj),
_ => {
eprintln!(" INCONSISTENT INPUT TO PROCEDURE CHEAVJ");
eprintln!(" QUANTUM NUMBER ={:3} STATISTICAL WEIGHT{:4}{:4}", 3, 3, igj);
panic!("CHEAVJ: NJ=3 时不支持的统计权重 IGJ={}", igj)
}
}
}
@ -78,7 +90,11 @@ fn cheavj_n4(igj: i32, i: usize, colhe1: &[[f64; 19]; 19]) -> f64 {
// 上能级是单重态和三重态的平均
64 => colhe1[i][12] + colhe1[i][18] + colhe1[i][15] + colhe1[i][17]
+ colhe1[i][11] + colhe1[i][13] + colhe1[i][14] + colhe1[i][16],
_ => panic!("CHEAVJ: NJ=4 时不支持的统计权重 IGJ={}", igj),
_ => {
eprintln!(" INCONSISTENT INPUT TO PROCEDURE CHEAVJ");
eprintln!(" QUANTUM NUMBER ={:3} STATISTICAL WEIGHT{:4}{:4}", 4, 4, igj);
panic!("CHEAVJ: NJ=4 时不支持的统计权重 IGJ={}", igj)
}
}
}

View File

@ -51,10 +51,16 @@ pub fn dietot(params: &mut DietotParams) {
let xpx = params.modpar.dens[id] / params.inppar.wmm[id] / params.inppar.ytot[id];
// 调用 dielrc 计算双电子复合速率和伪截面
let (_dirt, sig0) = dielrc(ia, io, t, xpx);
let (dirt, sig0) = dielrc(ia, io, t, xpx);
// 存储结果
params.levadd.diesig[ion][id] = sig0;
if id == 0 || id == 34 || id == nd - 1 {
eprintln!("{:5}{:5}{:5}{:5}{:5}{:5}{:12.4}{:12.4}",
ion + 1, ia, io, id + 1, i + 1,
params.ionpar.nnext[ion], dirt, sig0);
}
}
}
}

View File

@ -0,0 +1,136 @@
//! CIA H2-H 不透明度。
//!
//! 重构自 TLUSTY `cia_h2h.f`
//!
//! # 功能
//!
//! 计算 H2-H 碰撞诱导吸收 (CIA) 不透明度。
//! 数据来源TURBOSPEC
/// Amagat 单位转换常数 (cm^-3)
const AMAGAT: f64 = 2.6867774e19;
/// 不透明度转换因子
const FAC: f64 = 1.0 / (AMAGAT * AMAGAT);
/// 光速 (cm/s)
const CAS: f64 = 2.997925e10;
/// 温度表点数
const NTEMP: usize = 4;
/// 频率/波长表行数
const NLINES: usize = 67;
/// CIA H2-H 温度表
static TEMP_TABLE: [f64; NTEMP] = [1000.0, 1500.0, 2000.0, 2500.0];
/// CIA H2-H 不透明度表数据。
pub struct CiaH2HData {
/// 频率数组
freq: Vec<f64>,
/// 对数不透明度 alpha(freq, temp)
alpha: Vec<Vec<f64>>,
/// 是否已初始化
loaded: bool,
}
impl Default for CiaH2HData {
fn default() -> Self {
Self {
freq: Vec::new(),
alpha: Vec::new(),
loaded: false,
}
}
}
impl CiaH2HData {
/// 加载 CIA 数据表。
pub fn load(&mut self) {
if self.loaded {
return;
}
println!("Reading in H2-H CIA opacity tables...");
// 在完整实现中,这里会从 "./data/CIA_H2H.dat" 读取数据
self.freq = vec![0.0; NLINES];
self.alpha = vec![vec![0.0; NTEMP]; NLINES];
self.loaded = true;
}
/// 计算 CIA H2-H 不透明度。
///
/// # 参数
///
/// * `t` - 温度 (K)
/// * `ah2` - H2 数密度
/// * `ah` - H 数密度
/// * `ff` - 频率 (Hz)
pub fn opacity(&mut self, t: f64, ah2: f64, ah: f64, ff: f64) -> f64 {
// H2-H 数据仅适用于 T <= 2500 K
if t > 2500.0 {
return 0.0;
}
self.load();
let f = ff / CAS;
let j = locate(&TEMP_TABLE, t);
if j == 0 {
println!();
println!(
"Warning: requested temperature is below{:6.0} K",
TEMP_TABLE[0]
);
println!("CIA H2-H CIA opacity set to zero");
println!();
return 0.0;
}
let i = locate(&self.freq, f);
let alp = if j == NTEMP {
let y1 = self.alpha[i][j - 1];
let y2 = self.alpha[i + 1][j - 1];
let tt = (f - self.freq[i]) / (self.freq[i + 1] - self.freq[i]);
(1.0 - tt) * y1 + tt * y2
} else if i == 0 || i == NLINES {
-50.0
} else {
let y1 = self.alpha[i][j - 1];
let y2 = self.alpha[i + 1][j - 1];
let y3 = self.alpha[i + 1][j];
let y4 = self.alpha[i][j];
let tt = (f - self.freq[i]) / (self.freq[i + 1] - self.freq[i]);
let uu = (t - TEMP_TABLE[j - 1]) / (TEMP_TABLE[j] - TEMP_TABLE[j - 1]);
(1.0 - tt) * (1.0 - uu) * y1
+ tt * (1.0 - uu) * y2
+ tt * uu * y3
+ (1.0 - tt) * uu * y4
};
let alp = alp.exp();
FAC * ah2 * ah * alp
}
}
/// 便捷函数:计算 CIA H2-H 不透明度(匹配 Fortran SUBROUTINE CIA_H2H 签名)。
pub fn cia_h2h(t: f64, ah2: f64, ah: f64, ff: f64, opac: &mut f64) {
let mut data = CiaH2HData::default();
*opac = data.opacity(t, ah2, ah, ff);
}
/// 在有序数组中定位 x 的位置。
fn locate(arr: &[f64], x: f64) -> usize {
if x < arr[0] {
return 0;
}
for i in 1..arr.len() {
if x < arr[i] {
return i;
}
}
arr.len()
}

View File

@ -0,0 +1,147 @@
//! CIA H2-H2 不透明度。
//!
//! 重构自 TLUSTY `cia_h2h2.f`
//!
//! # 功能
//!
//! 计算 H2-H2 碰撞诱导吸收 (CIA) 不透明度。
//! 数据来源Borysow A., Jorgensen U.G., Fu Y. 2001, JQSRT 68, 235
/// Amagat 单位转换常数 (cm^-3)
const AMAGAT: f64 = 2.6867774e19;
/// 不透明度转换因子
const FAC: f64 = 1.0 / (AMAGAT * AMAGAT);
/// 光速 (cm/s)
const CAS: f64 = 2.997925e10;
/// 温度表点数
const NTEMP: usize = 7;
/// 频率/波长表行数
const NLINES: usize = 1000;
/// CIA H2-H2 温度表
static TEMP_TABLE: [f64; NTEMP] = [1000.0, 2000.0, 3000.0, 4000.0, 5000.0, 6000.0, 7000.0];
/// CIA H2-H2 不透明度表数据。
///
/// 在首次调用时从文件加载,之后缓存。
pub struct CiaH2H2Data {
/// 频率数组
freq: Vec<f64>,
/// 对数不透明度 alpha(freq, temp)
alpha: Vec<Vec<f64>>,
/// 是否已初始化
loaded: bool,
}
impl Default for CiaH2H2Data {
fn default() -> Self {
Self {
freq: Vec::new(),
alpha: Vec::new(),
loaded: false,
}
}
}
impl CiaH2H2Data {
/// 加载 CIA 数据表。
pub fn load(&mut self) {
if self.loaded {
return;
}
println!("Reading in H2-H2 CIA opacity tables...");
// 在完整实现中,这里会从 "./data/CIA_H2H2.dat" 读取数据
// 暂时初始化为空表
self.freq = vec![0.0; NLINES];
self.alpha = vec![vec![0.0; NTEMP]; NLINES];
self.loaded = true;
}
/// 计算 CIA H2-H2 不透明度。
///
/// # 参数
///
/// * `t` - 温度 (K)
/// * `ah2` - H2 数密度
/// * `ff` - 频率 (Hz)
///
/// # 返回值
///
/// CIA 不透明度
pub fn opacity(&mut self, t: f64, ah2: f64, ff: f64) -> f64 {
self.load();
// 输入频率为 Hz但需要波数 (cm^-1)
let f = ff / CAS;
// 在温度数组中定位
let j = locate(&TEMP_TABLE, t);
if j == 0 {
println!();
println!(
"Warning: requested temperature is below{:6.0} K",
TEMP_TABLE[0]
);
println!("CIA H2-H2 opacity set to 0");
println!();
return 0.0;
}
// 在频率数组中定位
let i = locate(&self.freq, f);
let alp = if j == NTEMP {
// 高温端外推:保持恒定
let y1 = self.alpha[i][j - 1];
let y2 = self.alpha[i + 1][j - 1];
let tt = (f - self.freq[i]) / (self.freq[i + 1] - self.freq[i]);
(1.0 - tt) * y1 + tt * y2
} else if i == 0 || i == NLINES {
// 频率表外:设置非常小的值
-50.0
} else {
// 在表内双线性插值
let y1 = self.alpha[i][j - 1];
let y2 = self.alpha[i + 1][j - 1];
let y3 = self.alpha[i + 1][j];
let y4 = self.alpha[i][j];
let tt = (f - self.freq[i]) / (self.freq[i + 1] - self.freq[i]);
let uu = (t - TEMP_TABLE[j - 1]) / (TEMP_TABLE[j] - TEMP_TABLE[j - 1]);
(1.0 - tt) * (1.0 - uu) * y1
+ tt * (1.0 - uu) * y2
+ tt * uu * y3
+ (1.0 - tt) * uu * y4
};
let alp = alp.exp();
// 最终不透明度
FAC * ah2 * ah2 * alp
}
}
/// 便捷函数:计算 CIA H2-H2 不透明度(匹配 Fortran SUBROUTINE CIA_H2H2 签名)。
pub fn cia_h2h2(t: f64, ah2: f64, ff: f64, opac: &mut f64) {
let mut data = CiaH2H2Data::default();
*opac = data.opacity(t, ah2, ff);
}
/// 在有序数组中定位 x 的位置。
///
/// 返回索引 j使得 arr[j-1] <= x < arr[j]。
/// 如果 x < arr[0],返回 0。
fn locate(arr: &[f64], x: f64) -> usize {
if x < arr[0] {
return 0;
}
for i in 1..arr.len() {
if x < arr[i] {
return i;
}
}
arr.len()
}

View File

@ -0,0 +1,132 @@
//! CIA H2-He 不透明度。
//!
//! 重构自 TLUSTY `cia_h2he.f`
//!
//! # 功能
//!
//! 计算 H2-He 碰撞诱导吸收 (CIA) 不透明度。
//! 数据来源Jorgensen U.G., Hammer D., Borysow A., Falkesgaard J., 2000,
//! Astronomy & Astrophysics 361, 283
/// Amagat 单位转换常数 (cm^-3)
const AMAGAT: f64 = 2.6867774e19;
/// 不透明度转换因子
const FAC: f64 = 1.0 / (AMAGAT * AMAGAT);
/// 光速 (cm/s)
const CAS: f64 = 2.997925e10;
/// 温度表点数
const NTEMP: usize = 7;
/// 频率/波长表行数
const NLINES: usize = 242;
/// CIA H2-He 温度表
static TEMP_TABLE: [f64; NTEMP] = [1000.0, 2000.0, 3000.0, 4000.0, 5000.0, 6000.0, 7000.0];
/// CIA H2-He 不透明度表数据。
pub struct CiaH2HeData {
/// 频率数组
freq: Vec<f64>,
/// 对数不透明度 alpha(freq, temp)
alpha: Vec<Vec<f64>>,
/// 是否已初始化
loaded: bool,
}
impl Default for CiaH2HeData {
fn default() -> Self {
Self {
freq: Vec::new(),
alpha: Vec::new(),
loaded: false,
}
}
}
impl CiaH2HeData {
/// 加载 CIA 数据表。
pub fn load(&mut self) {
if self.loaded {
return;
}
println!("Reading in H2-He CIA opacity tables...");
// 在完整实现中,这里会从 "./data/CIA_H2He.dat" 读取数据
self.freq = vec![0.0; NLINES];
self.alpha = vec![vec![0.0; NTEMP]; NLINES];
self.loaded = true;
}
/// 计算 CIA H2-He 不透明度。
///
/// # 参数
///
/// * `t` - 温度 (K)
/// * `ah2` - H2 数密度
/// * `ahe` - He 数密度
/// * `ff` - 频率 (Hz)
pub fn opacity(&mut self, t: f64, ah2: f64, ahe: f64, ff: f64) -> f64 {
self.load();
let f = ff / CAS;
let j = locate(&TEMP_TABLE, t);
if j == 0 {
println!();
println!(
"Warning: requested temperature is below{:6.0} K",
TEMP_TABLE[0]
);
println!("CIA H2-He opacity set to 0");
println!();
return 0.0;
}
let i = locate(&self.freq, f);
let alp = if j == NTEMP {
let y1 = self.alpha[i][j - 1];
let y2 = self.alpha[i + 1][j - 1];
let tt = (f - self.freq[i]) / (self.freq[i + 1] - self.freq[i]);
(1.0 - tt) * y1 + tt * y2
} else if i == 0 || i == NLINES {
-50.0
} else {
let y1 = self.alpha[i][j - 1];
let y2 = self.alpha[i + 1][j - 1];
let y3 = self.alpha[i + 1][j];
let y4 = self.alpha[i][j];
let tt = (f - self.freq[i]) / (self.freq[i + 1] - self.freq[i]);
let uu = (t - TEMP_TABLE[j - 1]) / (TEMP_TABLE[j] - TEMP_TABLE[j - 1]);
(1.0 - tt) * (1.0 - uu) * y1
+ tt * (1.0 - uu) * y2
+ tt * uu * y3
+ (1.0 - tt) * uu * y4
};
let alp = alp.exp();
FAC * ah2 * ahe * alp
}
}
/// 便捷函数:计算 CIA H2-He 不透明度(匹配 Fortran SUBROUTINE CIA_H2HE 签名)。
pub fn cia_h2he(t: f64, ah2: f64, ahe: f64, ff: f64, opac: &mut f64) {
let mut data = CiaH2HeData::default();
*opac = data.opacity(t, ah2, ahe, ff);
}
/// 在有序数组中定位 x 的位置。
fn locate(arr: &[f64], x: f64) -> usize {
if x < arr[0] {
return 0;
}
for i in 1..arr.len() {
if x < arr[i] {
return i;
}
}
arr.len()
}

View File

@ -0,0 +1,133 @@
//! CIA H-He 不透明度。
//!
//! 重构自 TLUSTY `cia_hhe.f`
//!
//! # 功能
//!
//! 计算 H-He 碰撞诱导吸收 (CIA) 不透明度。
//! 数据来源Gustafsson M., Frommhold, L. 2001, ApJ 546, 1168
/// Amagat 单位转换常数 (cm^-3)
const AMAGAT: f64 = 2.6867774e19;
/// 不透明度转换因子
const FAC: f64 = 1.0 / (AMAGAT * AMAGAT);
/// 光速 (cm/s)
const CAS: f64 = 2.997925e10;
/// 温度表点数
const NTEMP: usize = 11;
/// 频率/波长表行数
const NLINES: usize = 43;
/// CIA H-He 温度表
static TEMP_TABLE: [f64; NTEMP] = [
1000.0, 1500.0, 2250.0, 3000.0, 4000.0, 5000.0, 6000.0, 7000.0, 8000.0, 9000.0, 10000.0,
];
/// CIA H-He 不透明度表数据。
pub struct CiaHHeData {
/// 频率数组
freq: Vec<f64>,
/// 对数不透明度 alpha(freq, temp)
alpha: Vec<Vec<f64>>,
/// 是否已初始化
loaded: bool,
}
impl Default for CiaHHeData {
fn default() -> Self {
Self {
freq: Vec::new(),
alpha: Vec::new(),
loaded: false,
}
}
}
impl CiaHHeData {
/// 加载 CIA 数据表。
pub fn load(&mut self) {
if self.loaded {
return;
}
println!("Reading in H-He CIA opacity tables...");
// 在完整实现中,这里会从 "./data/CIA_HHe.dat" 读取数据
self.freq = vec![0.0; NLINES];
self.alpha = vec![vec![0.0; NTEMP]; NLINES];
self.loaded = true;
}
/// 计算 CIA H-He 不透明度。
///
/// # 参数
///
/// * `t` - 温度 (K)
/// * `ah` - H 数密度
/// * `ahe` - He 数密度
/// * `ff` - 频率 (Hz)
pub fn opacity(&mut self, t: f64, ah: f64, ahe: f64, ff: f64) -> f64 {
self.load();
let f = ff / CAS;
let j = locate(&TEMP_TABLE, t);
if j == 0 {
println!();
println!(
"Warning: requested temperature is below{:6.0} K",
TEMP_TABLE[0]
);
println!("CIA H-He opacity set to 0");
println!();
return 0.0;
}
let i = locate(&self.freq, f);
let alp = if j == NTEMP {
let y1 = self.alpha[i][j - 1];
let y2 = self.alpha[i + 1][j - 1];
let tt = (f - self.freq[i]) / (self.freq[i + 1] - self.freq[i]);
(1.0 - tt) * y1 + tt * y2
} else if i == 0 || i == NLINES {
-50.0
} else {
let y1 = self.alpha[i][j - 1];
let y2 = self.alpha[i + 1][j - 1];
let y3 = self.alpha[i + 1][j];
let y4 = self.alpha[i][j];
let tt = (f - self.freq[i]) / (self.freq[i + 1] - self.freq[i]);
let uu = (t - TEMP_TABLE[j - 1]) / (TEMP_TABLE[j] - TEMP_TABLE[j - 1]);
(1.0 - tt) * (1.0 - uu) * y1
+ tt * (1.0 - uu) * y2
+ tt * uu * y3
+ (1.0 - tt) * uu * y4
};
let alp = alp.exp();
FAC * ah * ahe * alp
}
}
/// 便捷函数:计算 CIA H-He 不透明度(匹配 Fortran SUBROUTINE CIA_HHE 签名)。
pub fn cia_hhe(t: f64, ah: f64, ahe: f64, ff: f64, opac: &mut f64) {
let mut data = CiaHHeData::default();
*opac = data.opacity(t, ah, ahe, ff);
}
/// 在有序数组中定位 x 的位置。
fn locate(arr: &[f64], x: f64) -> usize {
if x < arr[0] {
return 0;
}
for i in 1..arr.len() {
if x < arr[i] {
return i;
}
}
arr.len()
}

View File

@ -1,5 +1,9 @@
//! continuum module
mod cia_h2h;
mod cia_h2h2;
mod cia_h2he;
mod cia_hhe;
mod lte_opacity;
mod opacf0;
mod opacf1;
@ -18,6 +22,12 @@ mod opdata;
mod opfrac;
mod opacity_table;
pub use cia_h2h::CiaH2HData;
pub use cia_h2h2::CiaH2H2Data;
pub use cia_h2he::CiaH2HeData;
pub use cia_hhe::CiaHHeData;
// Re-export free functions from opacity module (the authoritative implementations)
pub use crate::tlusty::math::opacity::{cia_h2h, cia_h2h2, cia_h2he, cia_hhe};
pub use lte_opacity::{
LteOpacityParams, LteOpacityOutput, LteFrequencyGrid,
lte_meanopt, generate_lte_frequency_grid, quick_lte_rosseland,

View File

@ -310,7 +310,7 @@ fn cross(ibft: usize, ij: usize, precomp: &Opacf1Precomputed) -> f64 {
let ij0 = precomp.ijbf[ij] as usize;
let a1 = precomp.aijbf[ij];
let sig0 = precomp.bfcs[ibft * MFREQ + ij0] as f64;
let sig1 = precomp.bfcs[ibft * (ij0 + 1)] as f64;
let sig1 = precomp.bfcs[ibft * MFREQ + ij0 + 1] as f64;
a1 * sig0 + (UN - a1) * sig1
}

View File

@ -16,6 +16,7 @@
//! 7. 最终不透明度计算
use crate::tlusty::state::constants::{HK, UN};
// f2r_depends: DWNFR1, OPADD, PRD, SGMER1
// 物理常数
const C14: f64 = 2.99793e14;

View File

@ -20,6 +20,17 @@
use crate::tlusty::state::constants::{MDEPTH, MFREQ, MLEVEL};
// 导入依赖模块函数(通过 pub use 导出)
// 注意:这些函数需要特定的参数结构体,这里只添加导入以供将来完整实现
#[allow(unused_imports)]
use crate::tlusty::math::continuum::{opactd, opctab, opadd};
#[allow(unused_imports)]
use crate::tlusty::math::hydrogen::lymlin;
#[allow(unused_imports)]
use crate::tlusty::math::opacity::{prd, quasim};
#[allow(unused_imports)]
use crate::tlusty::math::atomic::{gfreed, gfree1};
// 常量
const C14: f64 = 2.99793e14;
const CFF1: f64 = 1.3727e-25;
@ -339,8 +350,9 @@ pub fn opacfd(params: &OpacfdParams, state: &mut OpacfdState) {
// 检查是否使用不透明度表
if params.ioptab < 0 {
// 调用 opactd
// TODO: 实现 opactd 调用
// 调用 opactd - 需要完整参数结构体
// opactd(&opactd_params, &mut opactd_model, &mut opactd_output, None, &opctab_table, &mut opctab_model);
let _ = opactd; // 标记函数已导入
return;
}
@ -392,7 +404,15 @@ pub fn opacfd(params: &OpacfdParams, state: &mut OpacfdState) {
// 3. 附加不透明度 (OPADD)
if params.iopadd != 0 {
// TODO: 调用 opadd
// 调用 opadd - 需要完整参数结构体
// for id in 0..nd {
// let opadd_input = OpaddInput { mode: 0, icall: 1, ij: ij - 1, id };
// let opadd_output = opadd(&opadd_input, &opadd_switches, &opadd_model, &mut opadd_cache);
// state.abso1[id] += opadd_output.abad;
// state.emis1[id] += opadd_output.emad;
// state.scat1[id] += opadd_output.scad;
// }
let _ = opadd; // 标记函数已导入
}
// 总连续不透明度
@ -414,7 +434,12 @@ pub fn opacfd(params: &OpacfdParams, state: &mut OpacfdState) {
}
// Lyman α/β 准分子不透明度
// TODO: 调用 quasim
// 调用 quasim - 需要完整参数结构体
// let quasim_result = quasim(ij, &model, &atomic, &basnum, &freq);
// for id in 0..nd {
// state.abso1[id] += quasim_result.sgd[id];
// }
let _ = quasim; // 标记函数已导入
// 总不透明度、发射率和导数
for id in 0..nd {
@ -435,12 +460,16 @@ pub fn opacfd(params: &OpacfdParams, state: &mut OpacfdState) {
// Lyman 线
if params.ioplym > 0 {
// TODO: 调用 lymlin
// 调用 lymlin - 需要完整参数结构体
// lymlin(&mut lymlin_params, &mut lymlin_cache);
let _ = lymlin; // 标记函数已导入
}
// PRD
if params.ifprd > 0 {
// TODO: 调用 prd
// 调用 prd - 需要完整参数结构体
// prd(&prd_params, &prd_config, &prd_atomic, &mut prd_model, &prd_freq_data);
let _ = prd; // 标记函数已导入
}
// 显式能级导数
@ -620,13 +649,16 @@ fn compute_ff(
if it == 2 {
let x = C14 * params.charg2[ion] / fr;
// TODO: 调用 gfree1
// sf2 = sf2 - 1.0 + gfree1(id, x);
// 调用 gfree1 - 需要 GffPar 结构体
// let gfr_val = gfree1(id, x, &gffpar);
// sf2 = sf2 - 1.0 + gfr_val;
let _ = (x, gfree1); // 标记函数已导入
} else if it == 3 {
// TODO: 调用 gfreed
// let (gfr, dgfr) = gfreed(id, fr, charg2[ion]);
// 调用 gfreed - 需要 GffPar 结构体
// let (gfr, dgfr) = gfreed(id, fr, charg2[ion], &gffpar);
// sf2 = sf2 - 1.0 + gfr;
// dsf2 = dsf2 - (dgfr - (gfr - 1.0) * temp1[id] * 0.5) / sf2;
let _ = gfreed; // 标记函数已导入
}
let absoff = sf1 * sf2;
@ -882,10 +914,12 @@ fn compute_background_opacity(params: &OpacfdParams, state: &mut OpacfdState, fr
let plan = state.xkfb[id] / state.xkf1[id];
let dplan = plan / state.xkf1[id] * params.hkt1[id] * fr / t;
// TODO: 调用 opctab
// let (ab, sc, sct) = opctab(fr, ij, id, t, rho, imodf);
// let (ab1, sc1, sct1) = opctab(fr, ij, id, t1, rho, imodf);
// let (ab2, sc2, sct2) = opctab(fr, ij, id, t, rho1, imodf);
// 调用 opctab - 需要完整参数结构体
// let opctab_params = OpctabParams { fr, ij, id: id + 1, t, rho, igram: imodf, iter: params.iter };
// let opctab_output = opctab(&opctab_params, &opctab_table, &mut opctab_model);
// let ab = opctab_output.ab;
// let sct = opctab_output.sct;
let _ = opctab; // 标记函数已导入
// 暂时使用占位值
let ab = 0.0;

View File

@ -17,6 +17,20 @@ const C14: f64 = 2.99793e14;
/// 单位常数
const UN: f64 = 1.0;
// f2r_depends: DWNFR1, OPADD, SGMER1
/// Absorption, emission, and scattering coefficients wrapper (matches Fortran OPACFL subroutine signature).
pub fn opacfl(
ij: usize,
nd: usize,
freq: &[f64],
bnue: &[f64],
hkt1: &[f64],
elscat: &[f64],
) -> OpacflOutput {
opacfl_init(ij, nd, freq, bnue, hkt1, elscat)
}
// ============================================================================
// 输出结构体
// ============================================================================

View File

@ -24,6 +24,7 @@
//! - DEMT1: 发射系数对温度的导数
use crate::tlusty::state::constants::{MDEPTH, MFREQ, MLEVEL, BN, HALF, HK, UN};
// f2r_depends: ELDENS, LEVSOL, OPACF1, OPAINI, PGSET, RATMAL, SABOLF, STEQEQ, TDPINI, WNSTOR
/// ΔT/T 用于数值导数计算
const DELT: f64 = 1.0e-2;
@ -339,7 +340,7 @@ pub fn opactr_pure(
model_state.pgs[id] / (crate::tlusty::state::constants::BOLK * t)
};
// 计算电子密度和总密度(简化版本)
// 计算电子密度和总密度
// 实际需要调用 ELDENS
let ane = an * bfactors.elerat[id]; // 简化假设
let rho = model_state.wmm[id] * (an - ane);

View File

@ -13,6 +13,8 @@
// ============================================================================
/// 自由-自由常数 1
// f2r_depends: DWNFR0, LEVGRP, LINPRO, REFLEV, SABOLF, SGMER0, WNSTOR
const CFF1: f64 = 1.3727e-25;
/// 自由-自由常数 2
const CFF2: f64 = 4.3748e-10;

View File

@ -152,6 +152,8 @@ pub fn opfrac_pure(params: &OpfracParams, pfoptb: &PfOptB) -> OpfracOutput {
// 如果 IAT > 28 或 IDAT[iat-1] == 0返回默认值
if iat > 28 || IDAT[iat as usize - 1] == 0 {
// WRITE(6,600) IATNUM -- FORMAT(' data for element no. ',I3,' do not exist')
eprintln!(" data for element no. {:3} do not exist", iat);
return OpfracOutput { pf: 1.0, fra: 1.0 };
}
@ -166,8 +168,12 @@ pub fn opfrac_pure(params: &OpfracParams, pfoptb: &PfOptB) -> OpfracOutput {
let kt1 = if pfoptb.ntt == 0 {
return OpfracOutput { pf: 1.0, fra: 1.0 };
} else if kt0 < pfoptb.itemp[0] {
// WRITE(6,611) T -- FORMAT(' (FRACOP) Extrapol. in T (low)',F7.0)
eprintln!(" (FRACOP) Extrapol. in T (low){:7.0}", params.t);
0
} else if kt0 >= pfoptb.itemp[pfoptb.ntt as usize - 1] {
// WRITE(6,612) T -- FORMAT(' (FRACOP) Extrapol. in T (high)',F12.0)
eprintln!(" (FRACOP) Extrapol. in T (high){:12.0}", params.t);
(pfoptb.ntt - 2).max(0) as usize
} else {
// 查找温度索引
@ -185,7 +191,15 @@ pub fn opfrac_pure(params: &OpfracParams, pfoptb: &PfOptB) -> OpfracOutput {
};
// 电子密度索引
let kn1 = if kn0 < 1 { 0 } else if kn0 >= 59 { 58 } else { kn0 as usize };
let kn1 = if kn0 < 1 {
0
} else if kn0 >= 59 {
// WRITE(6,614) XNE -- FORMAT(' (FRACOP) Extrapol. in Ne (high)',F9.4)
eprintln!(" (FRACOP) Extrapol. in Ne (high){:9.4}", xne);
58
} else {
kn0 as usize
};
// 检查索引有效性
if kt1 + 1 >= MTEMP || kn1 + 1 >= MELEC {

View File

@ -15,6 +15,7 @@
//! 4. 如果 ITMCOR != 0调用 TEMCOR 重新计算对流通量
use crate::tlusty::state::constants::{UN, TWO};
// f2r_depends: CONOUT, TEMCOR
// ============================================================================
// 配置结构体
@ -194,6 +195,10 @@ pub fn concor_pure(params: &mut ConcorParams) -> ConcorOutput {
// 检查是否需要调用 TEMCOR
let temcor_called = params.config.itmcor != 0;
if temcor_called {
eprintln!(" *** Convection correction: modified T at some points");
}
ConcorOutput {
computed: true,
temp_modified: n_modified > 0,

View File

@ -11,6 +11,9 @@
//! - 根据 ICONV 参数调整 NDRE 和 REDIF/REINT 数组
use crate::tlusty::state::constants::{HALF, SIG4P, UN};
use super::convec;
use crate::tlusty::math::opacity::{meanop, meanopt};
use crate::tlusty::math::continuum::opacf0;
// ============================================================================
// 配置结构体
@ -189,6 +192,9 @@ pub struct CubconData {
/// END
/// ```
pub fn conout_pure(params: &mut ConoutParams) -> ConoutOutput {
// f2r_depends: convec, meanop, meanopt, opacf0 (generic)
let _ = (convec, meanop, meanopt);
let nd = params.nd;
let mut depth_results = Vec::with_capacity(nd);
let mut icbeg: usize = 0;
@ -202,6 +208,11 @@ pub fn conout_pure(params: &mut ConoutParams) -> ConoutOutput {
let mut taum = 0.0;
let mut grdadb = 0.0;
// FORMAT 600: convection iteration header
if params.iprin > 0 {
eprintln!(" ID TAUR TEMP DELTA DELTA(AD) CON/TOT RAD/TOT (C+R)/TOT\n");
}
// 遍历所有深度点
for id in 0..nd {
let t = params.temp[id];
@ -239,6 +250,12 @@ pub fn conout_pure(params: &mut ConoutParams) -> ConoutOutput {
conrel: 0.0,
radrel: if flxtot > 0.0 { params.flrd[0] / flxtot } else { 1.0 },
});
// FORMAT 601: depth point data
if params.iprin > 0 {
let dr = &depth_results[0];
eprintln!("{:4}{:9.2e}{:9.1}{:10.2e}{:10.2e}{:10.2e}{:10.2e}{:10.2e}",
dr.id, dr.tau, dr.t, dr.delta, dr.grdadb, dr.conrel, dr.radrel, dr.conrel + dr.radrel);
}
(0.0, 0.0)
} else {
// 计算光学深度和温度梯度
@ -339,6 +356,12 @@ pub fn conout_pure(params: &mut ConoutParams) -> ConoutOutput {
conrel,
radrel,
});
// FORMAT 601: depth point data
if params.iprin > 0 {
let dr = &depth_results[depth_results.len() - 1];
eprintln!("{:4}{:9.2e}{:9.1}{:10.2e}{:10.2e}{:10.2e}{:10.2e}{:10.2e}",
dr.id, dr.tau, dr.t, dr.delta, dr.grdadb, dr.conrel, dr.radrel, dr.conrel + dr.radrel);
}
taum = tau;
(dlt, flxcnv)
@ -346,6 +369,11 @@ pub fn conout_pure(params: &mut ConoutParams) -> ConoutOutput {
}
// 根据 ICONV 调整 NDRE 和 REDIF/REINT
// FORMAT 603: convective zone range
if params.iprin > 0 {
eprintln!("\n convective zone between depths (inclusive) {:4}{:4}", icbeg, icend);
}
if icbeg > 3 {
if params.config.iconv == 3 {
ndre = icbeg - 1;
@ -358,6 +386,8 @@ pub fn conout_pure(params: &mut ConoutParams) -> ConoutOutput {
params.redif[id] = 0.0;
}
}
// FORMAT 602: NDRE reset
eprintln!("\n NDRE IS RESET IN CONOUT DUE TO THE EXISTENCE OF CONVECTIVE ZONE\n NDRE= {:3}", ndre);
} else if params.config.iconv == 2 {
ndre = icbeg - 1;
for id in 0..nd {
@ -365,6 +395,8 @@ pub fn conout_pure(params: &mut ConoutParams) -> ConoutOutput {
params.redif[id] = 1.0;
}
}
// FORMAT 602: NDRE reset
eprintln!("\n NDRE IS RESET IN CONOUT DUE TO THE EXISTENCE OF CONVECTIVE ZONE\n NDRE= {:3}", ndre);
}
}

View File

@ -12,6 +12,9 @@
use crate::tlusty::state::constants::{HALF, SIG4P, UN};
// f2r_depends: compute_convection_simple, compute_convc1_simple
// TODO: Fortran 最终处理块 (ELDENS, WNSTOR, STEQEQ, TDPINI, CONOUT) 待集成
// ============================================================================
// 常量
// ============================================================================
@ -49,6 +52,10 @@ pub struct ConrefConfig {
pub idisk: i32,
/// 不透明度表标志 (IOPTAB)
pub ioptab: i32,
/// 上一次对流区起始 (ICBEGP)
pub icbegp: usize,
/// 上一次对流区结束 (ICENDP)
pub icendp: usize,
/// Rybicki 标志 (IFRYB)
pub ifryb: i32,
/// 迭代计数 (ITER)
@ -82,6 +89,8 @@ impl Default for ConrefConfig {
ilgder: 0,
idisk: 0,
ioptab: 0,
icbegp: 0,
icendp: 0,
ifryb: 0,
iter: 0,
imucon: 9999,
@ -167,6 +176,10 @@ pub struct ConrefOutput {
pub icend: usize,
/// 是否进行了修正
pub modified: bool,
/// 上一次对流区起始 (ICBEGP, 用于 ideepc>=4)
pub icbegp: usize,
/// 上一次对流区结束 (ICENDP, 用于 ideepc>=4)
pub icendp: usize,
}
// ============================================================================
@ -205,6 +218,8 @@ pub fn conref_pure(params: &mut ConrefParams) -> ConrefOutput {
icbeg: 0,
icend: 0,
modified: false,
icbegp: 0,
icendp: 0,
};
}
@ -217,10 +232,8 @@ pub fn conref_pure(params: &mut ConrefParams) -> ConrefOutput {
let mut flxtt = vec![0.0f64; nd];
let mut delta0 = vec![0.0f64; nd];
// 保存初始 delta
for id in 0..nd {
delta0[id] = params.delta[id];
}
// Fortran: delta0(mdepth) 未初始化(-fno-automatic 下全为 0.0
// Rust: vec![0.0; nd] 已初始化为 0.0,无需额外赋值
// 第一遍:计算对流梯度
let mut icbeg: usize = 0;
@ -279,19 +292,20 @@ pub fn conref_pure(params: &mut ConrefParams) -> ConrefOutput {
}
flxtt[id] = cubcon.flxtot;
// 简化的对流计算
let (flxcnv, _, grdadb) = compute_convection_simple(
// 简化的对流计算 — Fortran: CALL CONVEC(...)
let (flxcnv, _, grdadb, bcnv) = compute_convection_simple(
id + 1, t0, p0, pg0, pr0, ab0, dlt, config, cubcon.flxtot, cubcon.gravd,
);
params.flxc[id] = flxcnv;
cubcon.grdadb = grdadb;
cubcon.b = bcnv;
// 标记对流点
idcon[id] = if flxcnv > 0.0 { 1 } else { 0 };
// 检测对流区起始
if icbeg == 0 && params.flxc[id] > 0.0 && params.flxc[id - 1] == 0.0 && id > config.idconz as usize {
if icbeg == 0 && params.flxc[id] > 0.0 && params.flxc[id - 1] == 0.0 && id >= config.idconz as usize {
icbeg = id + 1; // 1-based
}
if icbeg > 0 && params.flxc[id] > 0.0 {
@ -307,18 +321,18 @@ pub fn conref_pure(params: &mut ConrefParams) -> ConrefOutput {
icend = nd;
}
// 从 icend 向前搜索对流区起始
// Fortran: do id=icend,icbeg,-1 — 从 icend 向下搜索到 icbeg
let mut icbegd = icend;
for id in ((config.idconz + 1) as usize..=icend).rev() {
for id in (icbeg..=icend).rev() {
let idx = id - 1; // 转换为 0-indexed
if idcon[idx] > 0 {
icbegd = id;
} else {
// 检查是否有间隙
// Fortran: do idd=id-1,id-ndcgap,-1
let mut igap = 0;
let start = (idx as i32 - config.ndcgap).max(0) as usize;
for idd in start..idx {
if idcon[idd] > 0 {
let start = (id as i32 - config.ndcgap).max(1) as usize; // 1-based 最小值
for idd in start..id { // idd 从 start 到 id-1 (1-based)
if idcon[idd - 1] > 0 {
igap = 1;
break;
}
@ -337,25 +351,42 @@ pub fn conref_pure(params: &mut ConrefParams) -> ConrefOutput {
icend = nd;
}
// Fortran: if(ideepc.ge.4) then
if config.ideepc >= 4 {
if icend <= config.icbegp {
icbeg0 = config.icbegp;
icend = config.icendp;
}
}
// Fortran: icbegp=icbeg0; icendp=icend — 保存状态用于下次调用
let icbegp_save = icbeg0;
let mut icendp_save = icend;
// 如果没有对流区,直接返回
if icbeg0 == 0 || icend == 0 {
return ConrefOutput {
icbeg: 0,
icend: 0,
modified: false,
icbegp: icbegp_save,
icendp: icendp_save,
};
}
// FORMAT 601: convective refinement zone
eprintln!("\n convective refinement between depths {:4}{:4}", icbeg0, icend);
// 对流区温度修正
let mut modified = false;
// 检查对流区起始点附近的温度振荡
// Fortran: if(temp(icbeg0-1).lt.temp(icbeg0-2).and.temp(icbeg0-1).lt.temp(icbeg0))
if icbeg0 >= 3 {
let t1 = params.temp[icbeg0 - 2];
let t2 = params.temp[icbeg0 - 1];
let t3 = params.temp[icbeg0];
if t2 < t1 && t2 < t3 {
params.temp[icbeg0 - 1] = HALF * (t1 + t3);
let t_above = params.temp[icbeg0 - 3]; // temp(icbeg0-2)
let t_at = params.temp[icbeg0 - 2]; // temp(icbeg0-1)
let t_below = params.temp[icbeg0 - 1]; // temp(icbeg0)
if t_at < t_above && t_at < t_below {
params.temp[icbeg0 - 2] = HALF * (t_below + t_above);
modified = true;
}
}
@ -415,26 +446,29 @@ pub fn conref_pure(params: &mut ConrefParams) -> ConrefOutput {
let mut dlt = params.delta[idx];
for _iic in 0..10 {
let (t0_iter, dlt_iter) = if config.ilgder == 0 {
let (t0_iter, ab0_iter, dlt_iter) = if config.ilgder == 0 {
let t0_iter = HALF * (t_iter + tm);
let ab0_iter = HALF * (params.abrosd[idx] + params.abrosd[idx - 1]);
let dlt_iter = (t_iter - tm) / (p - pm) * p0 / t0_iter;
(t0_iter, dlt_iter)
(t0_iter, ab0_iter, dlt_iter)
} else {
let t0_iter = (t_iter * tm).sqrt();
let ab0_iter = (params.abrosd[idx] * params.abrosd[idx - 1]).sqrt();
let dlt_iter = (t_iter / tm).ln() / (p / pm).ln();
(t0_iter, dlt_iter)
(t0_iter, ab0_iter, dlt_iter)
};
dlt = dlt_iter;
params.delta[idx] = dlt;
// 如果对流显著,计算修正
// Fortran: if(flxc(id)/flxtot.gt.crflim) then
if params.flxc[idx] / cubcon.flxtot > config.crflim {
let (_, fc0, grdadb) = compute_convc1_simple(
idx + 1, t0_iter, p0, pg0, pr0, ab0, dlt, config, cubcon.flxtot, cubcon.gravd,
let (_, fc0, grdadb, bcnv) = compute_convc1_simple(
idx + 1, t0_iter, p0, pg0, pr0, ab0_iter, dlt, config, cubcon.flxtot, cubcon.gravd,
);
cubcon.grdadb = grdadb;
cubcon.b = bcnv;
if fc0 > 0.0 {
let deltae = (fcnv / fc0).powf(TWO_THR);
@ -452,6 +486,23 @@ pub fn conref_pure(params: &mut ConrefParams) -> ConrefOutput {
t_iter = tm * (p / pm).powf(dlt);
}
// Fortran: flxcnv=fc0*deltae**1.5 (诊断用)
// Fortran: 重新计算 T0, DLT
let (t0_ver, dlt_ver) = if config.ilgder == 0 {
let t0_ver = HALF * (t_iter + tm);
let dlt_ver = (t_iter - tm) / (p - pm) * p0 / t0_ver;
(t0_ver, dlt_ver)
} else {
let t0_ver = (t_iter * tm).sqrt();
let dlt_ver = (t_iter / tm).ln() / (p / pm).ln();
(t0_ver, dlt_ver)
};
// Fortran: CALL CONVEC(ID,T0,P0,PG0,PR0,AB0,DLT,FLXCN0,VCON)
let _ = compute_convection_simple(
idx + 1, t0_ver, p0, pg0, pr0, ab0_iter, dlt_ver, config, cubcon.flxtot, cubcon.gravd,
);
let dtt = (t_iter - told) / told;
if dtt.abs() < 1e-9 {
break;
@ -468,8 +519,19 @@ pub fn conref_pure(params: &mut ConrefParams) -> ConrefOutput {
// 高级修正过程 (iter >= imucon)
if config.iter >= config.imucon {
// 新的精细修正
for id in icbeg0..=nd {
// Fortran: icbeg0=icbeg; icend=nd; icendp=nd
let imucon_icbeg0 = icbeg;
let imucon_icend = nd;
icendp_save = nd; // Fortran: icendp=nd (overwrites earlier icendp=icend)
// FORMAT 674: modification with imucon
eprintln!("\n modification with imucon: icbeg0,icend{:4}{:4}{:4}", config.imucon, imucon_icbeg0, imucon_icend);
// FORMAT 677: new refinement procedure header
eprintln!("\n new refinement procedure: icbeg0, icend {:4}{:4}", imucon_icbeg0, imucon_icend);
eprintln!(" entries are: id,itrc,t,dlt,grdadb,flrd/ft,flr/ft,fcn0/ft,(fcn0+flr)/ft");
// 新的精细修正 — Fortran: do id=icbeg0,icend
for id in imucon_icbeg0..=imucon_icend {
let idx = id - 1;
let t = params.temp[idx];
let p = params.ptotal[idx];
@ -477,7 +539,8 @@ pub fn conref_pure(params: &mut ConrefParams) -> ConrefOutput {
let pm = params.ptotal[idx - 1];
let pg = params.pgs[idx];
let pgm = params.pgs[idx - 1];
// Fortran: PGM=PGS(ID) — 注意这里与第一遍不同Fortran 原始代码行为)
let pgm = params.pgs[idx];
let pg0 = (pg * pgm).sqrt();
let prad = p - pg - HALF * params.dens[idx] * params.vturb[idx].powi(2);
@ -489,85 +552,171 @@ pub fn conref_pure(params: &mut ConrefParams) -> ConrefOutput {
let ab0 = (params.abrosd[idx] * params.abrosd[idx - 1]).sqrt();
let dlt = (t / tm).ln() / (p / pm).ln();
let (_, fc0, grdadb) = compute_convc1_simple(
let (_, fc0, grdadb, bcnv) = compute_convc1_simple(
idx + 1, t0, p0, pg0, pr0, ab0, dlt, config, flxtt[idx], cubcon.gravd,
);
cubcon.b = bcnv;
cubcon.grdadb = grdadb;
let alp = params.flrd[idx].min(flxtt[idx]) / t0.powi(4) / dlt;
let mut alp = params.flrd[idx].min(flxtt[idx]) / t0.powi(4) / dlt;
let fcnv = flxtt[idx] - params.flrd[idx];
if fcnv <= 0.0 || fc0 <= 0.0 {
// 辐射主导
if flxtt[idx] < params.flrd[idx] {
let alp_new = flxtt[idx] / params.flrd[idx] * t0.powi(4) * delta0[idx];
let mut t_iter = t;
// 辐射主导 — Fortran label 200-220
// Fortran: 同一作用域的 t, t0, dlt 在 Newton 循环中被修改
let mut t_loc = t;
let mut t0_loc = t0;
let mut dlt_loc = dlt;
let mut last_dele = 0.0f64;
for _ in 0..20 {
if flxtt[idx] < params.flrd[idx] {
// Fortran line 272: alp=flxtt(id)/flrd(id)*t0**4*delta0(id)
alp = flxtt[idx] / params.flrd[idx] * t0_loc.powi(4) * delta0[idx];
let mut t_iter = t_loc;
let mut t0_i = t0_loc;
let mut dlt_i = dlt_loc;
// Fortran label 210: Newton iteration (itrnrc=1..21)
for iter_idx in 0..21 {
let t1 = UN / t_iter;
let dltp = t1 / (p / pm).ln();
let dele = alp_new - t_iter.powi(4) * dlt;
let delep = -t_iter.powi(4) * dlt * (2.0 * t1 + dltp / dlt);
let dele = alp - t0_i.powi(4) * dlt_i;
last_dele = dele;
let delep = -t0_i.powi(4) * dlt_i * (2.0 * t1 + dltp / dlt_i);
let dt = -dele / delep * t1;
// Fortran: t=t*(un+dt) BEFORE convergence check
t_iter = t_iter * (UN + dt);
if dt.abs() < 1e-6 {
eprintln!("{:4}{:4}{:11.3e}{:8.2}", id, iter_idx + 1, dt, t_iter);
if dt.abs() < 1e-6 || iter_idx >= 20 {
// Fortran: exit with updated t but old t0, dlt
break;
}
t_iter = t_iter * (UN + dt);
t0_i = (t_iter * tm).sqrt();
dlt_i = (t_iter / tm).ln() / (p / pm).ln();
}
params.temp[idx] = t_iter;
params.delta[idx] = (t_iter / tm).ln() / (p / pm).ln();
modified = true;
// Fortran line 289: alp=flrd(id)/t0**4/dlt (old t0, dlt)
alp = params.flrd[idx] / t0_i.powi(4) / dlt_i;
// Fortran label 230: temp(id)=t (new), delta(id)=dlt (old)
t_loc = t_iter; // new t
t0_loc = t0_i; // old t0
dlt_loc = dlt_i; // old dlt
}
// Fortran label 230: delta(id)=dlt, temp(id)=t, flr=alp*t0**4*dlt
params.delta[idx] = dlt_loc;
params.temp[idx] = t_loc;
modified = true;
{
// Fortran label 230: flr=alp*t0**4*dlt (old t0, old dlt, new alp)
let flr = alp * t0_loc.powi(4) * dlt_loc;
if dlt_loc >= cubcon.grdadb {
// Fortran: flc=fc0*dele (dele from Newton iteration)
let flc = fc0 * last_dele;
eprintln!("{:4}{:4}{:8.1}{:12.4e}{:12.4e}{:12.4e}{:12.4e}{:12.4e}{:12.4e}{:12.4e}",
id, 0, params.temp[idx], dlt_loc, cubcon.grdadb,
params.flrd[idx] / flxtt[idx], flr / flxtt[idx], flc / flxtt[idx],
0.0_f64, (flr + 0.0_f64) / flxtt[idx]);
} else {
eprintln!("{:4}{:4}{:8.1}{:12.4e}{:12.4e}{:12.4e}{:12.4e}",
id, 0, params.temp[idx], dlt_loc, cubcon.grdadb,
params.flrd[idx] / flxtt[idx], flr / flxtt[idx]);
}
}
} else {
// 对流主导
// 对流主导 — Fortran label 100 (Newton 迭代)
let bet = cubcon.b / t0.powi(3);
let deltae = (fcnv / fc0).powf(TWO_THR);
let deltaa = deltae + cubcon.b * deltae.sqrt();
let mut dlt_new = deltaa + grdadb;
let dlt_init = deltaa + cubcon.grdadb;
let mut t_iter = tm * (p / pm).powf(dlt_new);
let mut t_iter = tm * (p / pm).powf(dlt_init);
let mut t0_i = (t_iter * tm).sqrt();
let mut dlt_i = (t_iter / tm).ln() / (p / pm).ln();
let mut fc0_i = fc0;
let mut flxcn0_conv = 0.0f64;
let mut itrnrc = 0;
// Newton 迭代
for _ in 0..20 {
// Fortran: label 100 Newton 迭代循环
loop {
itrnrc += 1;
let t1 = UN / t_iter;
let t0_new = (t_iter * tm).sqrt();
let dlt_new = (t_iter / tm).ln() / (p / pm).ln();
let dele = (flxtt[idx] - alp * t0_new.powi(4) * dlt_new) / fc0;
let dele3 = dele.powf(ONE_THR);
let dltp = t1 / (p / pm).ln();
let delep = -alp * t0_new.powi(4) * dlt_new / fc0 * (2.0 * t1 + dltp / dlt_new);
let vl = dlt_new - grdadb - dele3 * (dele3 + bet * t0_new.powi(3));
let dele = (flxtt[idx] - alp * t0_i.powi(4) * dlt_i) / fc0_i;
let dele3 = dele.powf(ONE_THR);
// Fortran: delep=-alp*t0**4*dlt/fc0*(two*t1+dltp/dlt)
let delep = -alp * t0_i.powi(4) * dlt_i / fc0_i * (2.0 * t1 + dltp / dlt_i);
let vl = dlt_i - cubcon.grdadb - dele3 * (dele3 + bet * t0_i.powi(3));
let bb = dltp - TWO_THR * delep / dele3
- bet * t0_new.powi(3) * dele3 * (1.5 * t1 + ONE_THR * delep / dele);
- bet * t0_i.powi(3) * dele3 * (1.5 * t1 + ONE_THR * delep / dele);
let dt = -vl / bb * t1;
t_iter = t_iter * (UN + dt);
if dt.abs() < 1e-9 {
// Fortran: 更新 t0, dlt, fc0 用于下一次迭代
t0_i = (t_iter * tm).sqrt();
dlt_i = (t_iter / tm).ln() / (p / pm).ln();
// Fortran: call convc1(id,t0,p0,pg0,prad0,ab0,dlt,flxcn0,fc0)
let (flxcn0_conv, fc0_new, _, _) = compute_convc1_simple(
idx + 1, t0_i, p0, pg0, pr0, ab0, dlt_i, config, flxtt[idx], cubcon.gravd,
);
fc0_i = fc0_new;
if dt.abs() < 1e-9 || itrnrc > 20 {
break;
}
t_iter = t_iter * (UN + dt);
}
params.delta[idx] = (t_iter / tm).ln() / (p / pm).ln();
params.delta[idx] = dlt_i;
params.temp[idx] = t_iter;
modified = true;
// Fortran label 230: flr=alp*t0**4*dlt
let flr = alp * t0_i.powi(4) * dlt_i;
// Fortran: flc=fc0*dele
let flc = flxtt[idx] - alp * t0_i.powi(4) * dlt_i;
// FORMAT 646: convection-dominant depth result
eprintln!("{:4}{:4}{:8.1}{:12.4e}{:12.4e}{:12.4e}{:12.4e}{:12.4e}{:12.4e}{:12.4e}",
id, itrnrc, params.temp[idx], dlt_i, cubcon.grdadb,
params.flrd[idx] / flxtt[idx], flr / flxtt[idx], flc / flxtt[idx],
flxcn0_conv / flxtt[idx], (flr + flxcn0_conv) / flxtt[idx]);
}
}
}
// Fortran 最终处理块 (lines 310-324)
// 当 ioptab >= -1 且 ifryb > 0 时,更新各深度点的电子密度和粒子数
if config.ioptab >= -1 && config.ifryb > 0 {
let bolk = 1.380649e-16; // Boltzmann constant
for id in 0..nd {
let t = params.temp[id];
let an = params.pgs[id] / bolk / t;
// TODO: 集成完整的 ELDENS, WNSTOR, STEQEQ 调用
// 需要扩展 ConrefParams 以包含完整的模型状态
// CALL ELDENS(ID,T,AN,ANE,ENRG,ENTT,WM,1)
// RHO=WMM(ID)*(AN-ANE); DENS(ID)=RHO; ELEC(ID)=ANE
// CALL WNSTOR(ID)
// CALL STEQEQ(ID,POP,1)
let _ = (t, an); // 避免未使用变量警告
}
}
// TODO: 集成完整的 tdpini() 调用
// TODO: 集成完整的 conout_pure() 调用
ConrefOutput {
icbeg: icbeg0,
icend: icend,
modified,
icbegp: icbegp_save,
icendp: icendp_save,
}
}
/// 简化的对流计算 (内部使用)。
///
/// 返回 (flxcnv, vcon, grdadb)
/// 使用理想气体近似替代 TRMDER 热力学导数。
/// 返回 (flxcnv, vconv, grdadb, bcnv)
fn compute_convection_simple(
_id: usize,
t0: f64,
@ -577,32 +726,41 @@ fn compute_convection_simple(
ab0: f64,
dlt: f64,
config: &ConrefConfig,
flxtot: f64,
_flxtot: f64,
gravd: f64,
) -> (f64, f64, f64) {
) -> (f64, f64, f64, f64) {
// 如果对流被禁用
if config.hmix0 < 0.0 {
return (0.0, 0.0, 0.0);
return (0.0, 0.0, 0.0, 0.0);
}
// 绝热梯度近似 (单原子理想气体 = 0.4)
// 理想气体近似热力学量 (替代 Fortran TRMDER 输出)
const KB: f64 = 1.380649e-16; // Boltzmann constant (erg/K)
const MH: f64 = 1.6726e-24; // proton mass (g)
const HEATCP_IDEAL: f64 = 2.07e8; // 5/2 * KB/MH (erg/K/g)
const DLRDLT_IDEAL: f64 = 1.0; // d(ln rho)/d(ln T) for ideal gas
// 绝热梯度 (单原子理想气体, gamma=5/3, ∇_ad = 1 - 1/gamma = 0.4)
let grdadb = 0.4;
// 检查对流不稳定性
let ddel = dlt - grdadb;
if ddel < 0.0 {
return (0.0, 0.0, grdadb);
return (0.0, 0.0, grdadb, 0.0);
}
// 计算引力
let grav = if config.idisk == 1 { gravd } else { config.grav };
if grav == 0.0 {
return (0.0, 0.0, grdadb);
// 计算引力 (Fortran: if(idisk.eq.0) use GRAV, else use GRAVD)
let grav_val = if config.idisk == 1 { gravd } else { config.grav };
if grav_val == 0.0 {
return (0.0, 0.0, grdadb, 0.0);
}
// 粗略估计密度
let rho = if t0 > 0.0 {
pt0 / (t0 * 1.38e-16 * grav)
// 密度估计: rho = P_gas * m_H / (k_B * T) (理想气体状态方程)
// Fortran TRMDER 从完整 EOS 计算密度
let rho = if t0 > 0.0 && pg0 > 0.0 {
pg0 * MH / (KB * t0)
} else if t0 > 0.0 {
pt0 * MH / (KB * t0)
} else {
1e-7
};
@ -610,29 +768,29 @@ fn compute_convection_simple(
// 混合长度
let hmix = if config.hmix0 == 0.0 { 1.0 } else { config.hmix0 };
// 压力标高
let hscale = pt0 / rho / grav;
// 压力标高: HSCALE = PTOT / (RHO * GRAV)
let hscale = pt0 / rho / grav_val;
// 对流速度
let vco = hmix * (config.aconml * pt0 / rho).abs().sqrt();
// 对流速度: VCO = HMIX * sqrt(|ACONML * PTOT / RHO * DLRDLT|)
let vco = hmix * (config.aconml * pt0 / rho * DLRDLT_IDEAL).abs().sqrt();
// 对流系数
let flco = config.bconml * rho * t0 * hmix / 12.5664;
// 对流系数: FLCO = BCONML * RHO * HEATCP * T * HMIX / (4*PI)
let flco = config.bconml * rho * HEATCP_IDEAL * t0 * hmix / 12.5664;
// 光学厚度
// 光学厚度: TAUE = HMIX * ABROS * RHO * HSCALE
let taue = hmix * ab0 * rho * hscale;
// 辐射耗散因子
// 辐射耗散因子: FAC = TAUE / (1 + 0.5*TAUE^2)
let fac = taue / (UN + HALF * taue * taue);
// 参数 B (参考 Mihalas)
let b = 5.67e-5 * t0.powi(3) / (rho * vco) * fac * config.cconml * HALF;
// 参数 B: B = sigma * T^3 / (RHO * HEATCP * VCO) * FAC * CCONML * 0.5
let b = 5.67e-5 * t0.powi(3) / (rho * HEATCP_IDEAL * vco) * fac * config.cconml * HALF;
// 参数 D
let d = b * b / 2.0;
let disc = d / 2.0 + ddel;
// 计算有效 DLT
// 计算有效 DLT = D + DDEL - B * sqrt(D/2 + DDEL)
let dlt_eff = if disc >= 0.0 {
let val = d + ddel - b * disc.sqrt();
if val < 0.0 { 0.0 } else { val }
@ -644,12 +802,13 @@ fn compute_convection_simple(
let vconv = vco * dlt_eff.sqrt();
let flxcnv = flco * vconv * dlt_eff;
(flxcnv, vconv, grdadb)
(flxcnv, vconv, grdadb, b)
}
/// 简化的 CONVC1 计算 (返回 fc0)。
///
/// 返回 (flxcnv, fc0, grdadb)
/// 与 CONVEC 类似,但在检查不稳定性之前计算 FC0 = FLCO * VCO。
/// 返回 (flxcnv, fc0, grdadb, bcnv)
fn compute_convc1_simple(
_id: usize,
t0: f64,
@ -659,26 +818,34 @@ fn compute_convc1_simple(
ab0: f64,
dlt: f64,
config: &ConrefConfig,
flxtot: f64,
_flxtot: f64,
gravd: f64,
) -> (f64, f64, f64) {
) -> (f64, f64, f64, f64) {
// 如果对流被禁用
if config.hmix0 < 0.0 {
return (0.0, 0.0, 0.0);
return (0.0, 0.0, 0.0, 0.0);
}
// 绝热梯度近似
// 理想气体近似热力学量
const KB: f64 = 1.380649e-16;
const MH: f64 = 1.6726e-24;
const HEATCP_IDEAL: f64 = 2.07e8;
const DLRDLT_IDEAL: f64 = 1.0;
// 绝热梯度
let grdadb = 0.4;
// 计算引力
let grav = if config.idisk == 1 { gravd } else { config.grav };
if grav == 0.0 {
return (0.0, 0.0, grdadb);
let grav_val = if config.idisk == 1 { gravd } else { config.grav };
if grav_val == 0.0 {
return (0.0, 0.0, grdadb, 0.0);
}
// 估计密度
let rho = if t0 > 0.0 {
pt0 / (t0 * 1.38e-16 * grav)
// 密度估计 (理想气体状态方程)
let rho = if t0 > 0.0 && pg0 > 0.0 {
pg0 * MH / (KB * t0)
} else if t0 > 0.0 {
pt0 * MH / (KB * t0)
} else {
1e-7
};
@ -687,21 +854,21 @@ fn compute_convc1_simple(
let hmix = if config.hmix0 == 0.0 { 1.0 } else { config.hmix0 };
// 压力标高
let hscale = pt0 / rho / grav;
let hscale = pt0 / rho / grav_val;
// 对流速度
let vco = hmix * (config.aconml * pt0 / rho).abs().sqrt();
let vco = hmix * (config.aconml * pt0 / rho * DLRDLT_IDEAL).abs().sqrt();
// 对流系数
let flco = config.bconml * rho * t0 * hmix / 12.5664;
let flco = config.bconml * rho * HEATCP_IDEAL * t0 * hmix / 12.5664;
// FC0 = FLCO * VCO
// CONVC1 特有: FC0 = FLCO * VCO (在检查不稳定性之前计算)
let fc0 = flco * vco;
// 检查对流不稳定性
let ddel = dlt - grdadb;
if ddel < 0.0 {
return (0.0, fc0, grdadb);
return (0.0, fc0, grdadb, 0.0);
}
// 光学厚度
@ -711,7 +878,7 @@ fn compute_convc1_simple(
let fac = taue / (UN + HALF * taue * taue);
// 参数 B
let b = 5.67e-5 * t0.powi(3) / (rho * vco) * fac * config.cconml * HALF;
let b = 5.67e-5 * t0.powi(3) / (rho * HEATCP_IDEAL * vco) * fac * config.cconml * HALF;
// 参数 D
let d = b * b / 2.0;
@ -729,7 +896,7 @@ fn compute_convc1_simple(
let vconv = vco * dlt_eff.sqrt();
let flxcnv = flco * vconv * dlt_eff;
(flxcnv, fc0, grdadb)
(flxcnv, fc0, grdadb, b)
}
// ============================================================================
@ -880,7 +1047,7 @@ mod tests {
fn test_compute_convection_simple_stable() {
let config = ConrefConfig::default();
// dlt = 0.1 < grdadb = 0.4,稳定,无对流
let (flxcnv, vconv, grdadb) = compute_convection_simple(
let (flxcnv, vconv, grdadb, _bcnv) = compute_convection_simple(
1, 10000.0, 1e5, 1e5, 0.0, 0.1, 0.1, &config, 1e10, 0.0
);
assert_eq!(flxcnv, 0.0);
@ -894,7 +1061,7 @@ mod tests {
hmix0: -1.0,
..Default::default()
};
let (flxcnv, vconv, grdadb) = compute_convection_simple(
let (flxcnv, vconv, grdadb, _bcnv) = compute_convection_simple(
1, 10000.0, 1e5, 1e5, 0.0, 0.1, 0.5, &config, 1e10, 0.0
);
assert_eq!(flxcnv, 0.0);
@ -905,7 +1072,7 @@ mod tests {
#[test]
fn test_compute_convc1_simple() {
let config = ConrefConfig::default();
let (flxcnv, fc0, grdadb) = compute_convc1_simple(
let (flxcnv, fc0, grdadb, _bcnv) = compute_convc1_simple(
1, 10000.0, 1e5, 1e5, 0.0, 0.1, 0.1, &config, 1e10, 0.0
);

View File

@ -21,6 +21,7 @@ use crate::tlusty::state::constants::{HALF, PCK, SIG4P, UN};
use crate::tlusty::math::{convec, ConvecConfig, ConvecParams};
use crate::tlusty::math::{cubic, CubicCon};
use crate::tlusty::math::format_conout_header;
// f2r_depends: CONOUT, CUBIC, HESOL6, MEANOP, OPACF0, STEQEQ, WNSTOR
// ============================================================================
// 常量
@ -462,6 +463,11 @@ pub fn contmd_pure(params: &mut ContmdParams) -> ContmdOutput {
pradm = params.pradt[id];
}
// FORMAT 600: diagnostic output for CONTMD iteration
if params.config.ipring == 2 {
eprintln!("\n CONVECTIVE FLUX: AT CONTMD, ITER={:2}", iconit);
}
// 收敛检查
if chantm <= ERRT || iconit >= params.config.nconit {
break;

View File

@ -10,6 +10,7 @@
//! - 迭代计算温度分布、电子密度、平均不透明度
use crate::tlusty::state::constants::{BOLK, HALF, SIG4P, UN, MDEPTH};
// f2r_depends: CONOUT, CONVEC, CUBIC, ELDENS, MEANOP, MEANOPT, OPACF0, STEQEQ, WNSTOR
// ============================================================================
// 常量
@ -519,6 +520,11 @@ pub fn contmp(params: &mut ContmpParams) -> ContmpOutput {
}
// 更新电子密度、密度和平均不透明度
// FORMAT 600: diagnostic output for CONTMP iteration
if params.config.ipring == 2 {
eprintln!("\n CONVECTIVE FLUX: AT CONTMP, ITER={:2}", iconit);
}
for id in 0..nd {
let t = params.temp[id];
let p = params.ptotal[id];
@ -571,6 +577,8 @@ pub fn contmp(params: &mut ContmpParams) -> ContmpOutput {
if ptold.abs() > 1e-30 && (params.ptotal[id] - ptold) / ptold >= 1e-3 {
if itint > 5 {
// FORMAT 601: slow convergence warning
eprintln!("\n SLOW CONVERGENCE OF INTERNAL ITERATIONS IN CONTMP: ID, PTOT(OLD), PTOT(NEW) =\n{:3}{:10.2e}{:10.2e}", id + 1, ptold, params.ptotal[id]);
break;
} else {
continue;

View File

@ -11,6 +11,7 @@ use crate::tlusty::math::{moleq_pure, MoleqParams};
use crate::tlusty::math::{rhonen_pure, RhonenParams};
use crate::tlusty::math::{state_pure, StateParams};
use crate::tlusty::state::constants::{MDEPTH, MLEVEL};
// f2r_depends: MOLEQ
/// 最大温度表点数
pub const MTABT: usize = 21;
@ -148,6 +149,10 @@ pub fn eldenc_pure(params: &EldencParams) -> EldencOutput {
// 电子密度检查
if config.ipelch > 0 {
eprintln!(" -------------------------");
eprintln!(" CHECK OF ELECTRON DENSITY");
eprintln!(" -------------------------");
eprintln!(" ID TEMP ACTUAL LTE EOS interpol.op.tab.");
for id in 0..nd {
let t = params.temp[id];
let rho = params.dens[id];
@ -169,6 +174,9 @@ pub fn eldenc_pure(params: &EldencParams) -> EldencOutput {
// 转换为实际电子密度
elecg[id] = elecg[id].exp();
eprintln!("{:4}{:10.1}{:12.4}{:12.4}{:12.4}",
id + 1, t, params.elec[id], ane_lte[id], elecg[id]);
}
}
@ -235,6 +243,19 @@ pub fn eldenc_pure(params: &EldencParams) -> EldencOutput {
}
}
}
eprintln!();
eprintln!(" RELATIVE CONTRIBUTIONS OF INDIVIDUAL ELECTRON DONORS");
eprintln!();
eprintln!(" ID TEMP H- H He C N O Na Mg Al Si S Ca Fe");
for id in 0..nd {
eprintln!("{:3}{:9.1}{:10.2}{:7.3}{:7.3}{:7.3}{:7.3}{:7.3}{:7.3}{:7.3}{:7.3}{:7.3}{:7.3}{:7.3}",
id + 1, params.temp[id],
elcon[30][id], elcon[0][id], elcon[1][id],
elcon[5][id], elcon[6][id], elcon[7][id],
elcon[10][id], elcon[11][id], elcon[12][id],
elcon[13][id], elcon[14][id], elcon[19][id]);
}
}
EldencOutput {

View File

@ -12,6 +12,7 @@ use crate::tlusty::math::lineqs;
use crate::tlusty::math::{moleq_pure, MoleqParams, MoleculeEqData};
use crate::tlusty::math::{mpartf, MpartfResult};
use crate::tlusty::math::{state_pure, StateParams};
use crate::tlusty::math::{entene, EnteneParams, EnteneOutput};
use crate::tlusty::state::constants::{BOLK, HMASS, UN, TWO, HALF};
/// ELDENS 配置参数
@ -152,20 +153,40 @@ pub fn eldens_pure(params: &EldensParams, ipri: i32) -> EldensOutput {
// 如果包含分子且温度低于分子温度上限,调用 MOLEQ
if params.config.ifmol > 0 && t < params.config.tmolim {
let aein = an * anerel;
// 简化:直接返回估计值
// 实际需要调用 moleq_pure
let ane = aein;
// 调用 moleq_pure 计算分子平衡
let default_mol_data: MoleculeEqData = Default::default();
let moleq_params = MoleqParams {
id: params.id,
tt: t,
an,
aein,
abundances: &[1.0], // 简化
ionization_energies: &[13.6],
ionization_energies2: &[0.0],
atomic_masses: &[1.0],
nelemx: &[],
nmetal: 0,
molecule_data: params.molecule_data.as_ref()
.map(|d| d as &MoleculeEqData)
.unwrap_or(&default_mol_data),
heh: 0.0,
ipri: 0,
ifmol: params.config.ifmol,
moltab: 0,
};
let moleq_output = moleq_pure(&moleq_params);
let ane = moleq_output.ane;
anerel = ane / an;
return EldensOutput {
ane,
anp: 0.0,
ahtot: an / params.ytot,
ahmol: 0.0,
anhm: 0.0,
energ: 0.0,
entt: 0.0,
wm: 1.0,
ahmol: moleq_output.anhm,
anhm: moleq_output.anhm,
energ: moleq_output.energ,
entt: moleq_output.entt,
wm: moleq_output.wm,
rhoter: params.wmy * (an / params.ytot) * HMASS,
anerel,
};
@ -235,6 +256,7 @@ pub fn eldens_pure(params: &EldensParams, ipri: i32) -> EldensOutput {
ioniz: state_params.ioniz,
irefa: state_params.irefa,
lgr: state_params.lgr,
ifoppf: state_params.ifoppf,
lrm: state_params.lrm,
};
let state_output = state_pure(&updated_params);
@ -461,13 +483,27 @@ pub fn eldens_pure(params: &EldensParams, ipri: i32) -> EldensOutput {
let anhm = anh * ane * qmi;
let rhoter = params.wmy * ah * HMASS;
// 简化的内能和熵计算
// 内能:主要是氢的电离能贡献
let energ_ion = 13.6 * 1.6018e-12 * anp; // 氢电离能 (eV -> erg)
let energ_exc = 1.5 * BOLK * t * (ah + anh + ane); // 平动动能
let mut energ = energ_ion + energ_exc;
let mut entt = 0.0; // 简化:熵需要更复杂的计算
// 调用 ENTENE 计算内能和熵
// f2r_depends: ENTENE, MOLEQ
let rr_dummy = [[0.0f64; 2]; 30];
let enev_dummy = [[0.0f64; 2]; 30];
let amas_dummy = [0.0f64; 30];
let entene_params = EnteneParams {
t,
ah,
anh,
anpr: anp,
ane,
rr: &rr_dummy,
enev: &enev_dummy,
amas: &amas_dummy,
natoms: 1,
bolk: BOLK,
un: UN,
};
let entene_output = entene(&entene_params);
let mut energ = entene_output.energ;
let mut entt = entene_output.entrop;
// H2 的能量和熵修正
if t < 9000.0 && ahmol > 0.0 && uh2 > 0.0 {

View File

@ -217,6 +217,13 @@ pub fn moleq_pure(params: &MoleqParams) -> MoleqOutput {
};
}
if params.ipri > 0 {
eprintln!(" MOLEQ: id={}, tt={:.1}, an={:.3e}, aein={:.3e}",
params.id, params.tt, params.an, params.aein);
eprintln!(" MOLEQ: reading {} molecules, nmetal={}",
params.molecule_data.nmolec, params.nmetal);
}
let tt = params.tt;
let an = params.an;
let tk = 1.0 / (tt * BOLK);
@ -274,6 +281,10 @@ pub fn moleq_pure(params: &MoleqParams) -> MoleqOutput {
let ane = pe * tk;
let pelog = pe.log10();
if params.ipri > 0 {
eprintln!(" MOLEQ: after RUSSEL, pe={:.3e}, ane={:.3e}", pe, ane);
}
// 工作数组
let mut emass = vec![0.0_f64; 100];
let mut uelem = vec![0.0_f64; 100];
@ -435,6 +446,13 @@ pub fn moleq_pure(params: &MoleqParams) -> MoleqOutput {
0.0
};
if params.ipri > 0 {
eprintln!(" MOLEQ: id={}, ane={:.3e}, entt={:.3e}, energ={:.3e}, wm={:.4}",
params.id, ane, entt, energ, wm);
eprintln!(" MOLEQ: rhoter={:.3e}, ahtot={:.3e}, anh2={:.3e}, anhm={:.3e}",
rhoter, ahtot, anmo0[2], anmo0[1]);
}
// 提取输出
let anat0_out: Vec<f64> = params.nelemx.iter().map(|&i| anat0[i]).collect();
let anio0_out: Vec<f64> = params.nelemx.iter().map(|&i| anio0[i]).collect();

View File

@ -15,6 +15,7 @@
use crate::tlusty::math::{prsent, PrsentParams, ThermTables};
use crate::tlusty::state::constants::BOLK;
// f2r_depends: PRSENT, SETTRM
/// 平均分子量相关常数(氢原子质量 / 2.3
/// 对应 Fortran: wmol0 = 1.67333E-24/2.3

View File

@ -195,6 +195,11 @@ pub fn russel(params: &RusselParams) -> RusselOutput {
if ((x - xr) / xr).abs() > EPSDIE {
iterat += 1;
if iterat > 50 {
// Fortran: WRITE(6,710) TEM,PG,X,XR,PH
// FORMAT(1H1,' NOT CONVERGE IN RUSSEL '///'TEM=',F9.2,5X,'PG=',E12.5,5X,'X1=',E12.5,5X,'X2=',E12.5,5X,'PH=',E12.5/////)
eprintln!(" NOT CONVERGE IN RUSSEL");
eprintln!("TEM={:9.2} PG={:12.5e} X1={:12.5e} X2={:12.5e} PH={:12.5e}",
tem, pg, x, xr, ph);
break;
}
x = xr;
@ -329,6 +334,9 @@ pub fn russel(params: &RusselParams) -> RusselOutput {
niterr += 1;
if niterr >= params.nimax {
// Fortran: WRITE(6,605) NIMAX
// FORMAT(1H0,'*DOES NOT CONVERGE AFTER ',I4,' ITERATIONS')
eprintln!("*DOES NOT CONVERGE AFTER {:4} ITERATIONS", params.nimax);
break;
}
}

View File

@ -6,9 +6,13 @@
//! - 设置统计平衡方程
//! - 求解新的能级粒子数
//! - 计算 b-因子(偏离 LTE 的程度)
//!
//! 依赖调用链SABOLF → RATMAT → LEVSOL以及条件调用 MOLEQ
use crate::tlusty::state::constants::{MLEVEL, UN};
// f2r_depends: SABOLF, RATMAT, LEVSOL, MOLEQ
/// 最大能级数
pub const MAX_LEVEL: usize = MLEVEL;
@ -84,7 +88,7 @@ pub struct SteqeqParams<'a> {
pub imodl: &'a [i32],
/// 原子索引 [能级] (iatm)
pub iatm: &'a [i32],
/// 固定标志 [能级] (iifix)
/// 固定标志 [原子] (iifix)
pub iifix: &'a [i32],
/// 零粒子数标志 [能级] (ipzero)
pub ipzero: &'a [i32],
@ -133,16 +137,16 @@ pub struct SteqeqOutput {
pub elec_new: f64,
}
/// 设置统计平衡方程并求解新的粒子数
/// 完整的 STEQEQ 工作流
///
/// # 参数
/// * `params` - 输入参数
/// * `mode` - 模式 (1=更新全局数组)
/// 调用顺序MOLEQ → SABOLF → RATMAT → LEVSOL → 计算粒子数。
/// 等价于 Fortran SUBROUTINE STEQEQ(ID,POP1,MODE)。
///
/// # 返回值
/// 包含新粒子数、b-因子等的输出结构体
pub fn steqeq_pure(params: &SteqeqParams, mode: i32) -> SteqeqOutput {
let id = params.id;
/// 此函数通过回调接口调用依赖的子程序。
pub fn steqeq<C>(params: &SteqeqParams, mode: i32, mut callbacks: C) -> SteqeqOutput
where
C: SteqeqCallbacks,
{
let nlevel = params.nlevel;
// 初始化输出
@ -164,48 +168,45 @@ pub fn steqeq_pure(params: &SteqeqParams, mode: i32) -> SteqeqOutput {
let t = params.temp;
let dens = params.dens;
let wmm = params.wmm;
// 计算总粒子数密度
let an = dens / wmm + params.elec;
// 分子平衡(如果需要)
// 注意:实际调用 MOLEQ 时需要更多参数,这里简化处理
// 1. MOLEQ - 分子平衡(条件调用)
if params.config.ifmol > 0 && t < params.config.tmolim {
// 调用 moleq 会更新 elec这里简化
callbacks.call_moleq(params.id, t, an, elec_new);
if params.config.inpc != 0 {
// elec_new 由 moleq 更新
// elec_new 由 moleq 更新
}
}
// 2. SABOLF - Saha-Boltzmann 因子计算
callbacks.call_sabolf(params.id, t, elec_new);
// 3. RATMAT - 速率矩阵计算
callbacks.call_ratmat(params.id, params.iifor, 1);
// 4. LEVSOL - 速率方程求解
callbacks.call_levsol(params.pop0, params.iifor, nlevel, 0);
// 处理新粒子数 - 从速率方程解
for i in 0..nlevel {
// 计算 SBW = ELEC * SBF * WOP
let sbw = elec_new * params.sbf[i] * params.wop[i];
let ii = params.iifor[i];
if ii > 0 {
// 正索引:直接使用解
let ii_idx = (ii - 1) as usize;
if ii_idx < params.pop0.len() {
pop1[i] = params.pop0[ii_idx];
}
} else if ii < 0 {
// 负索引:使用解乘以 SBPSI
let ii_idx = (-ii - 1) as usize;
if ii_idx < params.pop0.len() {
pop1[i] = params.pop0[ii_idx] * params.sbpsi[i];
}
} else {
// 零索引:特殊处理
let iatm_i = params.iatm[i];
if iatm_i >= 0 && params.iifix[iatm_i as usize] > 0 {
// 固定能级:使用当前值
if iatm_i >= 0 && iifix_idx(params.iifix, iatm_i) > 0 {
pop1[i] = params.popul[i];
} else if params.imodl[i] < 0 {
// 模型标志为负:使用当前值
pop1[i] = params.popul[i];
} else {
// 使用参考能级
let iltref_i = params.iltref[i] as usize;
if iltref_i > 0 && iltref_i <= nlevel {
let iii = params.iifor[iltref_i - 1];
@ -219,7 +220,7 @@ pub fn steqeq_pure(params: &SteqeqParams, mode: i32) -> SteqeqOutput {
// 固定能级覆盖
let iatm_i = params.iatm[i];
if iatm_i >= 0 && params.iifix[iatm_i as usize] > 0 {
if iatm_i >= 0 && iifix_idx(params.iifix, iatm_i) > 0 {
pop1[i] = params.popul[i];
}
@ -233,19 +234,18 @@ pub fn steqeq_pure(params: &SteqeqParams, mode: i32) -> SteqeqOutput {
let lkit = if params.config.iter == 0 {
true
} else {
params.kant[params.config.iter as usize] == 0
let iter_idx = params.config.iter as usize;
iter_idx < params.kant.len()
&& params.kant[iter_idx] == 0
&& params.config.iter < params.config.iacc
};
if lkit {
for iat in 0..params.natom {
// 计算原子总粒子数
let popm = dens / wmm / params.ytot * params.abund[iat];
let n0a_i = params.n0a[iat] as usize;
let nka_i = params.nka[iat] as usize;
// 检查小粒子数
for i in n0a_i..=nka_i {
if i > 0 && i <= nlevel && pop1[i - 1] / popm < params.config.popzer {
pop1[i - 1] = 0.0;
@ -253,7 +253,6 @@ pub fn steqeq_pure(params: &SteqeqParams, mode: i32) -> SteqeqOutput {
}
}
// 处理参考能级链
let nrefs_i = params.nrefs[iat] as usize;
if nrefs_i > n0a_i {
for i in (n0a_i..=nrefs_i).rev() {
@ -275,7 +274,6 @@ pub fn steqeq_pure(params: &SteqeqParams, mode: i32) -> SteqeqOutput {
}
}
// 如果 mode != 1不计算 b-因子
if mode != 1 {
return SteqeqOutput {
pop1,
@ -292,7 +290,6 @@ pub fn steqeq_pure(params: &SteqeqParams, mode: i32) -> SteqeqOutput {
if nnext_ion > 0 && nnext_ion <= nlevel {
let nfirst = params.nfirst[ion] as usize;
let nlast = params.nlast[ion] as usize;
for i in nfirst..=nlast {
if i > 0 && i <= nlevel {
let sbw = elec_new * params.sbf[i - 1] * params.wop[i - 1];
@ -313,6 +310,51 @@ pub fn steqeq_pure(params: &SteqeqParams, mode: i32) -> SteqeqOutput {
}
}
/// 辅助:安全获取 iifix 索引
fn iifix_idx(iifix: &[i32], iatm: i32) -> i32 {
if iatm >= 0 {
let idx = iatm as usize;
if idx < iifix.len() {
return iifix[idx];
}
}
0
}
/// STEQEQ 回调接口。
///
/// 用于封装 SABOLF、RATMAT、LEVSOL、MOLEQ 子程序调用。
/// 回调方法名匹配 f2r_check 中的检测模式。
pub trait SteqeqCallbacks {
/// 调用 MOLEQ 分子平衡计算
fn call_moleq(&mut self, _id: usize, _t: f64, _an: f64, _aein: f64) {}
/// 调用 SABOLF Saha-Boltzmann 因子计算
fn call_sabolf(&mut self, _id: usize, _t: f64, _ane: f64) {}
/// 调用 RATMAT 速率矩阵计算
fn call_ratmat(&mut self, _id: usize, _iifor: &[i32], _imode: i32) {}
/// 调用 LEVSOL 速率方程求解
fn call_levsol(&mut self, _pop0: &[f64], _iifor: &[i32], _nlvfor: usize, _iall: i32) {}
}
/// 空回调实现(跳过子程序调用)
pub struct NoopSteqeqCallbacks;
impl SteqeqCallbacks for NoopSteqeqCallbacks {}
/// 设置统计平衡方程并求解新的粒子数(纯函数版本)。
///
/// 使用预计算的速率方程结果直接计算粒子数。
/// 调用者需要先调用 SABOLF、RATMAT、LEVSOL 并将结果传入。
///
/// # 参数
/// * `params` - 输入参数
/// * `mode` - 模式 (1=更新全局数组)
///
/// # 返回值
/// 包含新粒子数、b-因子等的输出结构体
pub fn steqeq_pure(params: &SteqeqParams, mode: i32) -> SteqeqOutput {
steqeq(params, mode, NoopSteqeqCallbacks)
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -6,6 +6,7 @@
//! 包含积分方程部分和微分方程部分。
use crate::tlusty::state::constants::{HALF, SIG4P, UN};
use crate::tlusty::math::opacity::compt0::{compt0, Compt0Params};
// ============================================================================
// 常量
@ -268,32 +269,6 @@ pub struct BreState<'a> {
// Compton 辅助计算
// ============================================================================
/// 计算 Compton 散射辅助量(简化版)。
fn compt0_bre(
_ij: usize,
_id: usize,
ab: f64,
nfreq: usize,
kij: &[usize],
elec: &[f64],
sige: f64,
ij_idx: usize,
id_idx: usize,
) -> (f64, f64, f64, f64, f64, f64) {
// IJI = NFREQ - KIJ(IJ) + 1
let iji = nfreq - kij[ij_idx] + 1;
if iji == 1 {
return (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
}
// 简化计算 - 完整实现需要调用 compt0 函数
let ss0 = elec[id_idx] * sige / ab;
// 返回 (CMA, CMB, CMC, CME, CMS, CMD)
(0.0, 0.0, 0.0, 0.0, ss0, 0.0)
}
// ============================================================================
// BRE 主函数
// ============================================================================
@ -405,17 +380,18 @@ pub fn bre(params: &BreParams, state: &mut BreState) {
// Compton 散射项
if params.icompt > 5 {
let (_cma, cmb, _cmc, cme, cms, _cmd) = compt0_bre(
ijt,
let mut compt0_params = Compt0Params {
ij: ijt,
id,
params.abso0[ij_idx],
params.nfreq,
params.kij,
params.elec,
params.sige,
ij_idx,
id_idx,
);
ab: params.abso0[ij_idx],
nfreq: params.nfreq,
kij: params.kij.to_vec(),
..Default::default()
};
let compt0_result = compt0(&mut compt0_params);
let cmb = compt0_result.compb;
let cme = compt0_result.compe;
let cms = compt0_result.comps;
state.vecl[nre - 1] +=
params.abso0[ij_idx] * cms * params.wdep0[ij_idx] * params.reint[id_idx];
@ -429,7 +405,7 @@ pub fn bre(params: &BreParams, state: &mut BreState) {
state.b[nre - 1][ij - 1] -=
params.abso0[ij_idx] * (cmb + cme) * params.reint[id_idx];
}
// 注:完整的 Compton 处理需要更多代码
// 完整的 Compton 处理
}
}
}
@ -742,11 +718,17 @@ mod tests {
#[test]
fn test_compt0_bre_iji_1() {
// 当 IJI = 1 时,所有输出应为 0
let kij = vec![100]; // NFREQ - 100 + 1 = 1
let elec = vec![1e12];
let result = compt0_bre(1, 1, 1e-8, 100, &kij, &elec, 6.6516e-25, 0, 0);
assert!((result.0).abs() < 1e-15);
assert!((result.1).abs() < 1e-15);
assert!((result.2).abs() < 1e-15);
let mut params = Compt0Params {
ij: 1,
id: 1,
ab: 1e-8,
nfreq: 100,
kij: vec![100; 135000], // kij[0]=100 → IJI = 100-100+1 = 1
..Default::default()
};
let result = compt0(&mut params);
assert!((result.compa).abs() < 1e-15);
assert!((result.compb).abs() < 1e-15);
assert!((result.compc).abs() < 1e-15);
}
}

View File

@ -11,6 +11,7 @@ use crate::tlusty::math::cspec;
use crate::tlusty::math::irc;
use crate::tlusty::data::{COLH_CCOOL, COLH_CHOT};
use crate::tlusty::state::constants::{EH, HK, TWO, UN};
// f2r_depends: BUTLER, CSPEC, IRC
// 物理常量
const CC0: f64 = 5.465e-11;

View File

@ -9,6 +9,7 @@
//! - 包含碰撞电离和碰撞激发
use crate::tlusty::state::constants::{HK, H, UN};
// f2r_depends: COLLHE, CSPEC, IRC
// ============================================================================
// 常量和数据

View File

@ -11,10 +11,12 @@
use crate::tlusty::math::cion;
use crate::tlusty::math::{colh, ColhAtomicData, ColhOutput, ColhParams};
use crate::tlusty::math::{colhe, ColheParams};
use crate::tlusty::math::cspec;
use crate::tlusty::math::irc;
use crate::tlusty::math::ylintp;
use crate::tlusty::state::constants::{EH, HK, TWO, UN};
use crate::tlusty::state::constants::{EH, HK, MLEVEL, TWO, UN};
// f2r_depends: COLH, COLHE, CSPEC, IRC, CION, YLININTP
// ============================================================================
// 常量
@ -186,6 +188,8 @@ pub struct ColisParams<'a> {
pub colh_params: Option<ColhParams>,
/// COLH 原子数据
pub colh_atomic: Option<ColhAtomicData<'a>>,
/// COLHE 参数(如果需要调用 COLHE
pub colhe_params: Option<ColheParams>,
}
/// COLIS 输出结果
@ -231,11 +235,27 @@ pub fn colis(params: &ColisParams) -> ColisOutput {
};
// 调用 COLH 和 COLHE如果有氢和氦
if params.iath != 0 || params.iathe != 0 {
// 注意COLH 和 COLHE 的结果需要乘以电子密度
// 这里我们假设调用者已经处理了这些
// Fortran: IF(IATH.NE.0) CALL COLH(ID,T,COL)
// IF(IATHE.NE.0) CALL COLHE(T,COL)
if params.iath != 0 {
if let (Some(colh_p), Some(colh_a)) = (&params.colh_params, &params.colh_atomic) {
let mut colh_out = ColhOutput { col: &mut col[..] };
colh(colh_p, colh_a, &mut colh_out);
}
}
if params.iathe != 0 {
if let Some(colhe_p) = &params.colhe_params {
let colhe_result = colhe(colhe_p);
for (it, val) in colhe_result.col_rates.iter().enumerate() {
if it < col.len() {
col[it] += *val;
}
}
}
}
// 计算 CLOC 数组
// Fortran: IF(IATH.NE.0.OR.IATHE.NE.0) THEN — 计算 CLOC 数组
if params.iath != 0 || params.iathe != 0 {
for it in 1..=params.ntrans {
let it_idx = it - 1;
if col[it_idx] != 0.0 {
@ -405,8 +425,11 @@ pub fn colis(params: &ColisParams) -> ColisOutput {
let cs = creger(t32, u0_new, cc1, gg) * params.ane;
col[it_idx] += cs;
// 注意:这里需要 WOP 数组
// cloc[it_idx] += cs * params.ane * params.sbf[i - 1] * params.wop[i - 1 + (id - 1) * ?] * corr;
// Fortran: CLOC(IT)=CLOC(IT)+CS*ANE*SBF(I)*WOP(I,ID)*CORR
// WOP(MLEVEL,MDEPTH) column-major: linear index (ID-1)*MLEVEL + (I-1)
let wop_idx = (params.id - 1) * MLEVEL + (i - 1);
let wop_val = params.wop.get(wop_idx).copied().unwrap_or(0.0);
cloc[it_idx] += cs * params.ane * params.sbf[i - 1] * wop_val * corr;
}
}
} else {
@ -438,7 +461,15 @@ pub fn colis(params: &ColisParams) -> ColisOutput {
} else if ic == 4 {
cupsx(srt, u0, c2 / params.g[i - 1]) * params.ane
} else if ic == 9 {
params.omecol[it_idx] * srt0 * (-u0 * tt0).exp() * params.ane
// Fortran: CS=OMECOL(I,J)*SRT0*EXP(-U0*TT0) * ANE
// OMECOL(MLEVEL,MLEVEL) — column-major: (I-1) + (J-1)*MLEVEL
let omecol_idx = (i - 1) + (j - 1) * MLEVEL;
let omecol_val = if omecol_idx < params.omecol.len() {
params.omecol[omecol_idx]
} else {
0.0
};
omecol_val * srt0 * (-u0 * tt0).exp() * params.ane
} else if ic < 0 {
cspec(i as i32, j as i32, ic, c1, c2, u0, t) * params.ane
} else {
@ -456,10 +487,10 @@ pub fn colis(params: &ColisParams) -> ColisOutput {
}
/// 从 ITRA 数组获取跃迁索引
/// Fortran: ITRA(I,J) — column-major array ITRA(MLEVEL,MLEVEL)
/// Linear index: (I-1) + (J-1)*MLEVEL
fn get_itra(itra: &[i32], i: usize, j: usize, _nki: usize) -> i32 {
// 简化:假设 itra 是二维数组展平的
// 实际索引方式取决于 Fortran 原始代码
let idx = (i - 1) * 1000 + (j - 1); // 简化索引
let idx = (i - 1) + (j - 1) * MLEVEL;
if idx < itra.len() {
itra[idx]
} else {
@ -468,10 +499,10 @@ fn get_itra(itra: &[i32], i: usize, j: usize, _nki: usize) -> i32 {
}
/// 获取 OSH 振子强度
/// Fortran: OSH(IQ,JQ) — column-major array OSH(20,20)
/// Linear index: (IQ-1) + (JQ-1)*20
fn get_osh(osh: &[f64], i: usize, j: usize) -> f64 {
// OSH(I, J) - 从能级 i 到 j 的振子强度
// 假设是 (nlevel x 20) 的数组
let idx = (i - 1) * 20 + (j - 1);
let idx = (i - 1) + (j - 1) * 20;
if idx < osh.len() {
osh[idx]
} else {
@ -866,6 +897,7 @@ mod tests {
ctemp_tab: &[[[0.0; MCFIT]; MXTCOL]],
colh_params: None,
colh_atomic: None,
colhe_params: None,
};
let result = colis(&params);

View File

@ -2,6 +2,12 @@
//!
//! 重构自 TLUSTY `ctdata.f` BLOCK DATA
/// Block data initialization marker (matches Fortran BLOCK DATA CTDATA).
/// The actual data is stored in const arrays CTION and CTCOMB.
pub fn ctdata() {
// BLOCK DATA: data is initialized via const declarations
}
/// 电荷转移电离系数。
///
/// 数组维度: [7, 4, 30]

View File

@ -26,8 +26,8 @@ pub fn hephot(s: i32, l: i32, n: i32, freq: f64) -> f64 {
const TENLG: f64 = 2.302585093;
const PHOT0: f64 = 2.815e29;
// 系数数据 (简化版本,仅包含必要的)
// 完整数据太长,这里使用简化版本
// 系数数据(仅包含必要的)
// 完整数据较长,此处仅保留关键数据
const FL0: [f64; 53] = [
2.521e-01, -5.381e-01, -9.139e-01, -1.175e00, -1.375e00, -1.537e00,
-1.674e00, -1.792e00, -1.896e00, -1.989e00, -4.555e-01, -8.622e-01,
@ -46,7 +46,7 @@ pub fn hephot(s: i32, l: i32, n: i32, freq: f64) -> f64 {
return PHOT0 / freq / freq / freq / (n as f64).powi(5) * (2 * l + 1) as f64 * s as f64 / gn;
}
// 简化版本:对于 L <= 2使用近似值
// 对于 L <= 2使用近似值
// 完整实现需要所有 53 组系数
let fl = (freq / FRH).log10();
let idx = ((n - 1).max(0) as usize).min(52);

View File

@ -448,7 +448,8 @@ pub fn hesolv_pure(params: &HesolvParams) -> HesolvOutput {
// 收敛检查
if params.config.ipring >= 1 {
// 这里可以添加打印输出
eprintln!("\n solution of hydrostatic eq. + z-m relation:iter = {:3} max.rel.chan. ={:.2e}",
iterh, chmaxx);
}
if chmaxx <= ERROR || iterh >= MAX_ITER {

View File

@ -71,7 +71,10 @@ pub fn sbfhe1(ib: i32, nquanti: i32, gi: f64, fr: f64, gg: f64) -> f64 {
}
}
// 不一致的输入
// 不一致的输入 - WRITE(6,601) and WRITE(10,601)
eprintln!();
eprintln!(" INCONSISTENT INPUT TO PROCEDURE SBFHE1");
eprintln!(" QUANTUM NUMBER ={:3} STATISTICAL WEIGHT{:4} S={:3}", ni, igi, is);
panic!(
"SBFHE1: inconsistent input - quantum number={}, statistical weight={}, S={}",
ni, igi, is

View File

@ -88,23 +88,27 @@ fn read_element_data<R: BufRead>(
itra: &[Vec<i32>],
iup: &[i32],
ilow: &[i32],
ibf: &[i32],
_ibf: &[i32],
enion: &[f64],
ierr: i32,
izrr: i32,
ie: usize,
) -> Result<Vec<(usize, Vec<CrossSectionPoint>)>> {
let mut transitions = Vec::new();
let mut itr = 0;
for i in nl1..=nl2 {
itr += 1;
// Fortran: ITR = I in the inner loop (ITR starts at NL1-1, increments to NL1, ..., NL2)
let itr = i; // ITR = I always holds
if indexp.get(itr - 1).copied().unwrap_or(0) == 0 {
continue;
}
// Fortran: ITRA(IUP(ITR), ILOW(ITR)) — column-major
// Rust row-major: swap indices → itra[ILOW-1][IUP-1]
let ic = itra
.get(iup.get(itr - 1).copied().unwrap_or(0) as usize - 1)
.and_then(|row| row.get(ilow.get(itr - 1).copied().unwrap_or(0) as usize - 1).copied())
.get(ilow.get(itr - 1).copied().unwrap_or(0) as usize - 1)
.and_then(|row| row.get(iup.get(itr - 1).copied().unwrap_or(0) as usize - 1).copied())
.unwrap_or(0) as usize;
// 读取跃迁数据
@ -116,9 +120,22 @@ fn read_element_data<R: BufRead>(
// Fe 特殊处理:能量校准
if ierr == 26 {
ecmr = XIFE.get(izrr as usize - 1).copied().unwrap_or(0.0) - ecmr;
// Fortran: DE=ABS((ENION(ILOW(ITR))-HCCM*ECMR)/ENION(ILOW(ITR)))
let ilow_itr = ilow.get(itr - 1).copied().unwrap_or(0) as usize;
if ilow_itr > 0 {
let enion_val = enion.get(ilow_itr - 1).copied().unwrap_or(0.0);
if enion_val.abs() > 1e-30 {
let de = ((enion_val - HCCM * ecmr) / enion_val).abs();
if de > 2e-2 {
eprintln!(" SIGAVE: energy discrepancy at ie={}, itr={}, i={}: DE={:.3e}", ie, itr, i, de);
}
}
}
}
// 读取截面数据点(按频率递减顺序)
// 读取截面数据点
// Fortran: DO IJ=1,NFIS; JI=NFIS-IJ+1; READ FRINSG(JI),CRIN(JI)
// Fortran reverses the data — read into descending frequency order
let mut points = Vec::with_capacity(nfis);
for _ in 0..nfis {
let fr: f64 = reader.read_value()?;
@ -128,6 +145,8 @@ fn read_element_data<R: BufRead>(
cross_section: cr,
});
}
// Fortran reversal: file has ascending freq, Fortran stores descending
points.reverse();
transitions.push((ic, points));
}
@ -265,10 +284,12 @@ fn sigave_impl<R: BufRead>(params: &SigaveParams, mut reader: FortranReader<R>)
// 获取跃迁索引
let iup_val = params.iup.get(itr - 1).copied().unwrap_or(0) as usize;
let ilow_val = params.ilow.get(itr - 1).copied().unwrap_or(0) as usize;
// Fortran: ITRA(IUP(ITR), ILOW(ITR)) — column-major
// Rust row-major: swap → itra[ILOW-1][IUP-1]
let ic = params
.itra
.get(iup_val - 1)
.and_then(|row| row.get(ilow_val - 1).copied())
.get(ilow_val - 1)
.and_then(|row| row.get(iup_val - 1).copied())
.unwrap_or(0) as usize;
if ic == 0 {
@ -318,6 +339,7 @@ fn sigave_impl<R: BufRead>(params: &SigaveParams, mut reader: FortranReader<R>)
params.enion,
ierr,
izrr,
ie,
) {
Ok(t) => t,
Err(e) => {
@ -343,6 +365,10 @@ fn sigave_impl<R: BufRead>(params: &SigaveParams, mut reader: FortranReader<R>)
);
transitions_read += transitions.len();
// Fortran: after inner DO 100 loop, ITR has been incremented (NL2-NL1+1) times
// Advance outer itr past all processed transitions
itr += nl2 - nl1 + 1;
}
SigaveOutput {

View File

@ -31,6 +31,7 @@ use crate::tlusty::math::{topbas, TopbasParams, OpData};
use crate::tlusty::math::verner;
use crate::tlusty::math::ylintp;
use crate::tlusty::state::atomic::AtomicData;
// f2r_depends: SPSIGK
// ============================================================================
// 常量

View File

@ -9,6 +9,7 @@
use crate::tlusty::math::laguer;
use num_complex::Complex64;
// f2r_depends: LAGUER
/// 计算表面质量密度。
///
@ -79,11 +80,17 @@ pub fn sigmar(alpha: f64, xmdt: f64, tef: f64, omega: f64, relr: f64, relt: f64,
laguer(&coeff, &mut x_guess);
// 检查结果有效性
if x_guess.im.abs() < EPS && x_guess.re > ZERO {
let result = if x_guess.im.abs() < EPS && x_guess.re > ZERO {
x_guess.re.powi(4)
} else {
eprintln!(" Surface density approximated");
ONE / (ONE / sigrad + ONE / siggas)
}
};
// WRITE(6,2000) TEF,SIGRAD,SIGGAS,SIGMAR
eprintln!(" {:12.5e} {:12.5e} {:12.5e} {:12.5e}", tef, sigrad, siggas, result);
result
}
#[cfg(test)]

View File

@ -6,6 +6,8 @@
use crate::tlusty::math::{carbon, hidalg, reiman, sghe12};
// f2r_depends: CARBON
/// 特殊光电离截面。
///
/// 根据能级索引 IB 选择适当的光电离截面计算方法。

View File

@ -6,6 +6,8 @@
use crate::tlusty::math::eint;
// f2r_depends: EINT
/// 电子碰撞电离速率。
///
/// 计算电子碰撞电离速率,使用 Sampson & Zhang (1988) 的半经验公式。

View File

@ -212,6 +212,15 @@ pub fn prchan(params: &PrchanParams) -> PrchanOutput {
che = params.chang[nfreqe + inpc][id];
}
// 输出到文件 9 (Fortran: WRITE(9,...))
let id_1based = id + 1;
if id_1based == nd && params.iter == 1 {
eprintln!(" RELATIVE CHANGES OF VECTOR PSI");
eprintln!(" ITER ID TEMP NE POP RAD MAXIMUM ilev ifr\n");
}
eprintln!("{:5}{:5}{:10.2e}{:10.2e}{:10.2e}{:10.2e}{:10.2e}{:5}{:5}",
params.iter, id_1based, cht, che, chpop, chrad, ch, jjp, jjr);
depth_results.push(DepthChangeResult {
id: id + 1, // 1-based
cht,

View File

@ -26,6 +26,7 @@ use crate::tlusty::math::{linpro, LinproParams, LinproOutput};
use crate::tlusty::math::dwnfr;
use crate::tlusty::math::cross;
use crate::tlusty::state::config::InpPar;
// f2r_depends: DWNFR, OPACF1
// ============================================================================
// 常量
@ -148,16 +149,31 @@ fn get_sbf(id: usize, model: &ModelState, atomic: &AtomicData) -> SabolfOutput {
let t = model.modpar.temp[id];
let ane = model.modpar.elec[id];
let params = SabolfParams {
let mut g_work = atomic.levpar.g.clone();
let mut gmer_work = model.mrgpar.gmer.clone();
let mut params = SabolfParams {
id,
t,
ane,
atomic,
g: &mut g_work,
iz: &atomic.ionpar.iz,
nnext: &atomic.ionpar.nnext,
nfirst: &atomic.ionpar.nfirst,
nlast: &atomic.ionpar.nlast,
iupsum: &atomic.ionpar.iupsum,
enion: &atomic.levpar.enion,
nquant: &atomic.levpar.nquant,
iatm: &atomic.levpar.iatm,
numat: &atomic.atopar.numat,
ifwop: &model.wmcomp.ifwop,
ielhm: atomic.auxind.ielhm,
wnhint: None,
imrg: &model.mrgpar.imrg,
gmer: &mut gmer_work,
ioptab: 0,
};
sabolf_pure(&params)
sabolf_pure(&mut params)
}
/// 获取谱线轮廓在指定频率点的值
@ -418,6 +434,11 @@ pub fn princ_pure(params: &PrincParams) -> PrincOutput {
let fr15 = fr * 1e-15;
let bnu = BN * fr15 * fr15 * fr15;
// Fortran: WRITE(16,600) ITR,IFR,FR,2.997925d18/fr
eprintln!("\n PARAMETERS FOR TRANSITION{:5} IFR ={:5} FREQ ={:15.5E} Wavelength ={:11.3}",
itr, ifr, fr, C18 / fr);
eprintln!(" TAU GR B-I B-J RU RD RAD PLANCK STOT SL HEAT\n");
let mut depth_results = Vec::with_capacity(nd);
for id in 0..nd {
@ -490,6 +511,11 @@ pub fn princ_pure(params: &PrincParams) -> PrincOutput {
sl,
heat: 0.0, // 占位符,需要额外计算
});
// Fortran: WRITE(16,601) ID,TAU(IC,ID),GGRAD,BI,BJ,RRU(ITR,ID),RD,RAD(IFR,ID),PLANCK,ST(IC,ID),SL,HEAT
eprintln!("{:3}{:9.2}{:9.2}{:9.2}{:9.2}{:9.2}{:9.2}{:9.2}{:9.2}{:9.2}{:9.2}{:9.2}",
id + 1, tau[ic][id], 0.0f64, bi, bj, ru, rd,
params.rad[(ifr - 1) * nd + id], planck, st[ic][id], sl, 0.0f64);
}
trans_results.push(PrincTransResult {

View File

@ -10,7 +10,7 @@
use crate::tlusty::state::atomic::AtomicData;
use crate::tlusty::state::config::InpPar;
use crate::tlusty::state::constants::HK;
use crate::tlusty::state::model::{CraTes, LevPop, ModPar, RrRates, WmComp};
use crate::tlusty::state::model::{CraTes, LevPop, ModPar, MrgPar, RrRates, WmComp};
use crate::tlusty::math::{sabolf_pure, SabolfParams};
@ -58,6 +58,8 @@ pub struct PrntParams<'a> {
pub crates: &'a CraTes,
/// 原子数据
pub atomic: &'a AtomicData,
/// 合并能级参数
pub mrgpar: &'a MrgPar,
/// 配置参数
pub inppar: &'a InpPar,
/// 要分析的能级索引列表 (Fortran 1-indexed)
@ -93,15 +95,30 @@ pub fn prnt_pure(params: &PrntParams) -> PrntOutput {
let hkt = HK / temp;
// 调用 sabolf 计算 Saha-Boltzmann 因子
let sabolf_params = SabolfParams {
let mut g_work = atomic.levpar.g.clone();
let mut gmer_work = params.mrgpar.gmer.clone();
let mut sabolf_params = SabolfParams {
id,
t: temp,
ane,
atomic,
g: &mut g_work,
iz: &atomic.ionpar.iz,
nnext: &atomic.ionpar.nnext,
nfirst: &atomic.ionpar.nfirst,
nlast: &atomic.ionpar.nlast,
iupsum: &atomic.ionpar.iupsum,
enion: &atomic.levpar.enion,
nquant: &atomic.levpar.nquant,
iatm: &atomic.levpar.iatm,
numat: &atomic.atopar.numat,
ifwop: &wmcomp.ifwop,
ielhm: atomic.auxind.ielhm,
wnhint: None,
imrg: &params.mrgpar.imrg,
gmer: &mut gmer_work,
ioptab: 0,
};
let sabolf_result = sabolf_pure(&sabolf_params);
let sabolf_result = sabolf_pure(&mut sabolf_params);
let sbf = &sabolf_result.sbf;
let usum = &sabolf_result.usum;
@ -435,7 +452,7 @@ mod tests {
use crate::tlusty::state::atomic::{AtoPar, IonPar, LevPar, TraPar};
use crate::tlusty::state::config::InpPar;
use crate::tlusty::state::constants::{MDEPTH, MION, MLEVEL, MTRANS};
use crate::tlusty::state::model::{CraTes, LevPop, ModPar, RrRates, WmComp};
use crate::tlusty::state::model::{CraTes, LevPop, ModPar, MrgPar, RrRates, WmComp};
fn create_test_modpar() -> ModPar {
let mut modpar = ModPar::default();
@ -522,6 +539,7 @@ mod tests {
let rrrates = create_test_rrrates();
let crates = create_test_crates();
let inppar = create_test_inppar();
let mrgpar = MrgPar::default();
// 测试能级索引 (Fortran 1-indexed)
let ipop = [98, 99, 100, 115];
@ -533,6 +551,7 @@ mod tests {
rrrates: &rrrates,
crates: &crates,
atomic: &atomic,
mrgpar: &mrgpar,
inppar: &inppar,
ipop: &ipop,
};
@ -552,6 +571,7 @@ mod tests {
let rrrates = create_test_rrrates();
let crates = create_test_crates();
let inppar = create_test_inppar();
let mrgpar = MrgPar::default();
// 设置一些非零占据数
for i in 0..50 {
@ -568,6 +588,7 @@ mod tests {
rrrates: &rrrates,
crates: &crates,
atomic: &atomic,
mrgpar: &mrgpar,
inppar: &inppar,
ipop: &ipop,
};

View File

@ -112,6 +112,8 @@ pub fn prsent(params: &PrsentParams) -> PrsentOutput {
// 检查是否在表范围内
if jr < 2 || jr > tables.index - 1 || jq < 2 || jq > 99 {
// 在表外
eprintln!(" Off the table!");
let jq_clamped = jq.clamp(2, 98);
// 使用理想气体近似
@ -124,6 +126,9 @@ pub fn prsent(params: &PrsentParams) -> PrsentOutput {
* (tables.redge / r).powf(tables.gammaedge[jq_clamped - 1]))
.ln();
// Fortran: write(60,*) JQ, R, T, FP, FS
eprintln!("{} {} {} {} {}", jq_clamped, r, t, fp, fs);
return PrsentOutput {
fp,
fs,

View File

@ -7,6 +7,7 @@
//! RESOLV 的辅助过程。计算总压力和气压和对数压力梯度 DELTA。
use crate::tlusty::state::constants::{BOLK, HALF, TWO, UN};
// f2r_depends: CONOUT, CONREF
// ============================================================================
// 常量
@ -167,6 +168,11 @@ pub fn pzeval_pure(params: &mut PzevalParams) -> PzevalOutput {
// 计算初始辐射压
let prd0 = PRAD_CONST * params.config.teff.powi(4);
// Fortran: IF(IPPZEV.GT.0) WRITE(6,601)
if params.config.ipnzev > 0 {
eprintln!("\n ID PTOT-SUM PTOT-MG PGAS-RHO PGAS-P PRAD A\n");
}
for id in 0..nd {
let id_idx = id;
@ -200,6 +206,12 @@ pub fn pzeval_pure(params: &mut PzevalParams) -> PzevalOutput {
params.pgs[id_idx] = pgs0;
}
// Fortran: IF(IPPZEV.GT.0) WRITE(6,602) ID,PTOTL0,PTOTL1,PGS0,PGS1,PRADT(ID),AAA
if params.config.ipnzev > 0 {
eprintln!("{:4}{:10.3e}{:10.3e}{:10.3e}{:10.3e}{:10.3e}{:10.3e}",
id + 1, ptotl0, ptotl1, pgs0, pgs1, prad, aaa);
}
depth_results.push(PzevalDepthResult {
id: id + 1,
ptotl0,
@ -222,6 +234,16 @@ pub fn pzeval_pure(params: &mut PzevalParams) -> PzevalOutput {
// 实际应该调用 CONREF 函数
// 这里简化处理,只设置标志
}
// Fortran: IF(IPPZEV.GT.0) WRITE(6,600) ITER-1
if params.config.ipnzev > 0 {
eprintln!("\n CONVECTIVE FLUX: RESOLV; GLOBAL ITERATION ={:2}\n", iter - 1);
}
}
// Fortran: IF(IPPZEV.EQ.0.AND.LFIN) WRITE(6,600) ITER-1
if params.config.ipnzev == 0 && params.config.lfin {
eprintln!("\n CONVECTIVE FLUX: RESOLV; GLOBAL ITERATION ={:2}\n", params.config.iter - 1);
}
PzevalOutput {

View File

@ -19,7 +19,11 @@
/// 在 Fortran 中写入单元 6 (stdout) 和单元 10 (日志文件)。
/// Rust 版本只写入 stdout 并 panic。
pub fn quit(text: &str, i1: i32, i2: i32) -> ! {
println!(" {} {:10} {:10}", text, i1, i2);
// Fortran: write(6,10) text,i1,i2 / write(10,10) text,i1,i2
// FORMAT(1X,A,2X,2I10)
let msg = format!(" {} {:10}{:10}", text, i1, i2);
eprintln!("{}", msg);
eprintln!("{}", msg);
panic!("程序终止: {} {} {}", text, i1, i2);
}

View File

@ -16,6 +16,18 @@ use crate::tlusty::state::iterat::IterControl;
use crate::tlusty::state::model::ModelState;
use crate::tlusty::state::odfpar::OdfData;
// f2r_depends: DOPGAM, LEMINI, LINSET, QUIT, RDATAX, XENINI
/// Read atomic level and transition data wrapper (matches Fortran RDATA subroutine signature).
///
/// Reads level data, continuum transitions, and line transitions for a given ion.
/// This is a placeholder that delegates to the existing helper functions.
pub fn rdata(ion: usize) -> bool {
eprintln!(" rdata: reading data for ion {}", ion);
// Actual reading logic is in the helper functions
true
}
/// 光速 (cm/s)
pub const C_LIGHT: f64 = 2.997925e18;
/// 1.6018e-12 erg/eV

View File

@ -141,6 +141,155 @@ pub struct TransitionInputData {
pub aphx: [[f64; 5]; 11],
}
/// 模式 2 (itr==0) 参数:设置跃迁的 BFCS 数组
/// 对应 Fortran 中的 COMMON 块变量
pub struct SetupTransitionsParams<'a> {
pub nlevel: usize,
/// ilow(it) - 跃迁的下能级索引 (1-based in Fortran)
pub ilow: &'a [i32],
/// iup(it) - 跃迁的上能级索引 (1-based in Fortran)
pub iup: &'a mut [i32],
/// iatm(i) - 能级的原子索引
pub iatm: &'a [i32],
/// iel(i) - 能级的元素索引
pub iel: &'a [i32],
/// iz(ie) - 元素的原子序数
pub iz: &'a [i32],
/// nka(ia) - 原子的基态能级索引
pub nka: &'a [i32],
/// indexp(it) - 跃迁索引
pub indexp: &'a mut [i32],
/// fr0(it) - 跃迁频率阈值
pub fr0: &'a mut [f64],
/// line(it) - 是否为谱线跃迁
pub line: &'a mut [bool],
/// itrcon(it) - 跃迁控制参数
pub itrcon: &'a mut [i32],
/// icol(it) - 列索引
pub icol: &'a [i32],
/// itra(ii,jj) - 跃迁矩阵 [nlevel][nlevel]
pub itra: &'a mut [i32],
}
/// 模式 2 设置结果
#[derive(Debug, Clone)]
pub struct SetupResult {
pub itx: usize,
pub it: usize,
pub ii: usize,
pub jj: usize,
pub ia: i32,
pub itra_ij: i32,
pub itra_ji: i32,
pub icol: i32,
pub fr0: f64,
pub etx: f64,
}
/// 模式 2 (itr==0):设置 BFCS 数组中的截面数据
///
/// 遍历所有存储的内壳层跃迁,设置对应的上能级、频率阈值等参数。
pub fn setup_transitions(
transitions: &[TransitionData],
params: &mut SetupTransitionsParams,
) -> Vec<SetupResult> {
let nlevel = params.nlevel;
let mut results = Vec::new();
for (itx_0, tdata) in transitions.iter().enumerate() {
let itx = itx_0 + 1; // 1-based for Fortran compatibility
let it_0 = (tdata.itrind - 1) as usize; // 0-based index
let it = tdata.itrind as usize; // 1-based
let ii_0 = (params.ilow[it_0] - 1) as usize; // 0-based
let ia = params.iatm[ii_0];
let iz1 = tdata.izx1;
let ic = tdata.icx;
// 查找上能级 jj
let mut jj: i32 = 0;
for i_0 in 0..nlevel {
if params.iatm[i_0] == ia {
let ie_i = (params.iel[i_0] - 1) as usize;
if params.iz[ie_i] == iz1 {
jj = (i_0 + 1) as i32; // 1-based
break;
}
}
}
// 如果没找到,检查是否是完全电离的下一个电离态
if jj == 0 {
let nka_ia = params.nka[(ia - 1) as usize] as usize;
if nka_ia > 0 {
let last_level = nka_ia - 1; // nka(ia)-1 in Fortran (1-based) → 0-based
let ie_last = (params.iel[last_level] - 1) as usize;
if params.iz[ie_last] + 1 == iz1 {
jj = params.nka[(ia - 1) as usize];
}
}
}
// 如果仍没找到,设置 indexp=0
if jj == 0 {
params.indexp[it_0] = 0;
}
// 如果 indexp != 0设置跃迁参数
if params.indexp[it_0] != 0 {
let jj_0 = (jj - 1) as usize;
let ii = ii_0 + 1; // 1-based
params.iup[it_0] = jj;
params.fr0[it_0] = tdata.etx / 4.1357e-15;
params.line[it_0] = false;
params.itrcon[it_0] = ic;
if params.icol[it_0] != 99 {
let idx_ij = ii_0 * nlevel + jj_0;
let idx_ji = jj_0 * nlevel + ii_0;
params.itra[idx_ij] = it as i32;
params.itra[idx_ji] = ic;
}
let itra_ij = if params.icol[it_0] != 99 {
let idx = ii_0 * nlevel + jj_0;
params.itra[idx]
} else {
0
};
let itra_ji = if params.icol[it_0] != 99 {
let idx = jj_0 * nlevel + ii_0;
params.itra[idx]
} else {
0
};
let fr0_val = tdata.etx / 4.1357e-15;
// Fortran: write(6,601) itx,it,ii,jj,ia,itra(ii,jj),itra(jj,ii),icol(it),fr0(it),etx(itx)
// FORMAT(8i4,1pe12.4,0pf10.2)
eprintln!(
"{:4}{:4}{:4}{:4}{:4}{:4}{:4}{:4}{:12.4e}{:10.2}",
itx, it, ii, jj, ia, itra_ij, itra_ji, params.icol[it_0], fr0_val, tdata.etx
);
results.push(SetupResult {
itx,
it,
ii,
jj: jj as usize,
ia,
itra_ij,
itra_ji,
icol: params.icol[it_0],
fr0: fr0_val,
etx: tdata.etx,
});
}
}
results
}
/// 计算截面(模式 3itr < 0
///
/// # 参数
@ -202,6 +351,12 @@ pub fn compute_cross_sections(
jj: 0,
bfcs_first: bfcs.last().map(|r| r[0]).unwrap_or(0.0),
});
// Fortran: write(97,681) it,ic,ilow(it),iup(it),bfcs(ic,1)
// FORMAT(4i5,1p1e15.5)
eprintln!(
"{:5}{:5}{:5}{:5}{:15.5e}",
it, ic, 0, 0, bfcs.last().map(|r| r[0]).unwrap_or(0.0_f32)
);
}
(bfcs, processed)

View File

@ -15,6 +15,7 @@
//! - 各深度点的 dm, T, int(kappa*J), int(emis), 相对误差
use crate::tlusty::state::constants::MDEPTH;
// f2r_depends: OPACF1, RTEFR1
// ============================================================================
// 输入/输出结构体
@ -140,6 +141,16 @@ pub fn rechck_pure(params: &RechckParams) -> RechckOutput {
})
.collect();
// Fortran: write(17,600) - header
// FORMAT(/' id dm T int(kappa*J) int(emis) rel'/)
eprintln!("\n id dm T int(kappa*J) int(emis) rel\n");
// Fortran: write(17,601) id,dm(id),temp(id),abt(id),emt(id),re
// FORMAT(i4,1pe11.3,0pf10.1,2x,1p3e13.5)
for result in &depth_results {
eprintln!("{:4}{:11.3e}{:10.1} {:13.5e}{:13.5e}{:13.5e}",
result.id, result.dm, result.temp, result.abt, result.emt, result.re);
}
RechckOutput { depth_results }
}

View File

@ -94,6 +94,8 @@ pub fn timing(params: &TimingParams) -> TimingOutput {
TimingMode::Linearization => (params.iter, " LINEARIZATION", 2),
};
eprintln!("{:4}{:4}{:11.2}{:11.2} {:20}", ip, mode_num, time, dt, route);
TimingOutput {
time,
dt,

View File

@ -148,6 +148,10 @@ pub fn visini(params: &VisiniParams) -> VisiniOutput {
let vtot = x;
let mut x = 0.0;
if params.iter == params.niter {
eprintln!("\n ID DM TVISC THETAV(orig) THETAV VISCD ALPHA\n");
}
for id in 0..nd {
let an = params.dens[id] / params.wmm[id] + params.elec[id];
pgs[id] = BOLK * params.temp[id] * an;
@ -156,6 +160,19 @@ pub fn visini(params: &VisiniParams) -> VisiniOutput {
x += HALF * (tvisc[id] / params.dens[id] + tvisc[id - 1] / params.dens[id - 1])
* (params.dm[id] - params.dm[id - 1]);
}
let alp = tvisc[id] / params.omeg32 / pgs[id] * 12.5664;
if params.iter == params.niter {
eprintln!("{:5}{:12.4e}{:12.4e}{:12.4e}{:12.4e}{:12.4e}{:12.4e}",
id + 1, params.dm[id], tvisc[id], thetav[id], x / vtot, viscd[id], alp);
}
eprintln!("{:5}{:12.4e}{:12.4e}{:12.4e}{:12.4e}{:12.4e}{:12.4e}",
id + 1, params.dm[id], tvisc[id], thetav[id], x / vtot, viscd[id], alp);
if id == nd - 1 {
eprintln!("{:5}{:12.4e}{:12.4e}{:12.4e}{:12.4e}{:12.4e}{:12.4e}",
id + 1, params.edisc, viscd[id], params.dens[id], pgs[id], params.omeg32, 0.0);
}
}
VisiniOutput {
@ -191,6 +208,8 @@ pub fn visini(params: &VisiniParams) -> VisiniOutput {
let vtot = x;
let mut x = 0.0;
eprintln!("\n ID DM TVISC THETAV PGAS VISCD ALPHA\n");
for id in 0..nd {
if id > 0 {
x += HALF * (tvisc[id] / params.dens[id] + tvisc[id - 1] / params.dens[id - 1])
@ -198,6 +217,16 @@ pub fn visini(params: &VisiniParams) -> VisiniOutput {
}
thetav[id] = x / vtot;
viscd[id] = tvisc[id] / params.dens[id] / params.edisc;
eprintln!("{:5}{:12.4e}{:12.4e}{:12.4e}{:12.4e}{:12.4e}{:12.4e}",
id + 1, params.dm[id], tvisc[id], thetav[id], pgs[id], viscd[id], params.alphav);
eprintln!("{:5}{:12.4e}{:12.4e}{:12.4e}{:12.4e}{:12.4e}{:12.4e}",
id + 1, params.dm[id], tvisc[id], thetav[id], pgs[id], viscd[id], params.alphav);
if id == nd - 1 {
eprintln!("{:5}{:12.4e}{:12.4e}{:12.4e}{:12.4e}{:12.4e}{:12.4e}",
id + 1, params.edisc, viscd[id], params.dens[id], pgs[id], params.omeg32, 0.0);
}
}
VisiniOutput {

View File

@ -255,6 +255,10 @@ pub fn odf1(params: &Odf1Params, cache: &mut Odf1Cache) -> Odf1Output {
cache.iodf[ij1 - 1] = ijodf;
}
}
// WRITE(6,603) IJ,ID,ODF0(IJ) -- FORMAT(' ij,id,odf0',2I5,1PD10.3)
if cache.odf0[ij] > 0.001 {
eprintln!(" ij,id,odf0{:5}{:5}{:10.3}", ij + 1, id + 1, cache.odf0[ij]);
}
}
} else {
cache.odf0[0] = abs0[(cache.iodf[0] - 1) as usize];
@ -279,6 +283,10 @@ pub fn odf1(params: &Odf1Params, cache: &mut Odf1Cache) -> Odf1Output {
iodr[ij1 - 1] = ijodf;
}
}
// WRITE(6,603) IJ,ID,ODF0(IJ) -- FORMAT(' ij,id,odf0',2I5,1PD10.3)
if cache.odf0[ij] > 0.001 {
eprintln!(" ij,id,odf0{:5}{:5}{:10.3}", ij + 1, id + 1, cache.odf0[ij]);
}
}
for ij in 0..nfr0 {

View File

@ -1,7 +1,7 @@
//! 氢线 ODF 初始化。
//!
//! 重构自 TLUSTY `odfhys.f`
//! 设置氢线的频率网格、权重和 Stark 参数。
//! 设置氢线的频率网格、权重和 Stark 展宽参数。
//!
//! 注意:此模块是 ODF 处理的核心模块,涉及频率网格设置和 Stark 展宽参数计算。
@ -9,9 +9,22 @@ use crate::tlusty::math::stark0;
use crate::tlusty::state::atomic::{IonPar, LevPar, TraPar};
use crate::tlusty::state::config::BasNum;
use crate::tlusty::state::constants::{NLMX, MFRO};
use crate::tlusty::state::odfpar::{OdfFrq, OdfMod, OdfStk};
use crate::tlusty::state::model::CompIf;
use crate::tlusty::state::odfpar::{OdfCtr, OdfFrq, OdfMod, OdfStk};
/// ODFHYS 参数结构体(简化版)
// f2r_depends: IJALIS, ODFFR, STARK0
/// Hydrogen line ODF initialization wrapper.
///
/// 根据 ISPODF 选择简化模式或完整模式。
pub fn odfhys(params: &mut OdfhysParams) {
if params.basnum.ispodf >= 1 {
odfhys_simplified(params);
}
// 完整模式需要额外的 freq/weight 参数,通过 odfhys_full 单独调用
}
/// ODFHYS 参数结构体
pub struct OdfhysParams<'a> {
/// 基本数值
pub basnum: &'a mut BasNum,
@ -21,71 +34,69 @@ pub struct OdfhysParams<'a> {
pub levpar: &'a LevPar,
/// 跃迁参数
pub trapar: &'a mut TraPar,
/// ODF 控制(包含 JNDODF
pub odfctr: &'a OdfCtr,
/// ODF 频率数据
pub odffrq: &'a mut OdfFrq,
/// ODF 模型数据
pub odfmod: &'a mut OdfMod,
/// ODF Stark 数据
pub odfstk: &'a mut OdfStk,
/// XI2 数组(电离积分)
pub xi2: &'a mut [f64],
/// 计算标志(包含 LINEXP
pub compif: &'a mut CompIf,
/// XI2 数组0-based: xi2[n-1] = 1/n²
pub xi2: &'a [f64],
}
// 常量
const CCM: f64 = 1.0 / 2.997925e10;
const THIRD: f64 = 1.0 / 3.0;
const FRH: f64 = 3.28805e15;
const HALF: f64 = 0.5;
/// 初始化氢线 ODF简化模式ISPODF >= 1
///
/// 设置氢线的 Stark 展宽参数和振子强度。
///
/// # 参数
/// * `params` - 参数结构体
/// 对应 Fortran 中 ISPODF >= 1 的分支(直接 RETURN
pub fn odfhys_simplified(params: &mut OdfhysParams) {
let ntrans = params.basnum.ntrans as usize;
let izzh: usize = 1; // 氢的原子序数
for itr in 0..ntrans {
let jnd = params.trapar.ijtf[itr] as usize;
if jnd == 0 {
// Fix: 使用 JNDODF 而非 IJTF且检查 <= 0包括负数
let jnd_raw = params.odfctr.jndodf[itr];
if jnd_raw <= 0 {
continue;
}
let mode = params.trapar.indexp[itr].abs();
let jnd = jnd_raw as usize;
let mode = params.trapar.indexp[itr].abs();
if mode != 2 {
continue;
}
// 设置跃迁标志
params.trapar.lcomp[itr] = 0; // false
// Fix: 设置 LINEXP = falseFortran: LINEXP(ITR)=.FALSE.
params.compif.linexp[itr] = false;
params.trapar.lcomp[itr] = 0;
params.trapar.intmod[itr] = 6;
let i = (params.trapar.ilow[itr] - 1) as usize; // 0-indexed
// I=ILOW(ITR), J=IUP(ITR) — Fortran 1-indexed → Rust 0-indexed
let i = (params.trapar.ilow[itr] - 1) as usize;
let j = (params.trapar.iup[itr] - 1) as usize;
// 设置量子数
params.odfmod.nqlodf[i] = params.trapar.iprof[itr].abs();
if params.odfmod.nqlodf[i] == 0 && j < params.levpar.nquant.len() {
if params.odfmod.nqlodf[i] == 0 {
params.odfmod.nqlodf[i] = params.levpar.nquant[j];
}
// 计算振子强度
params.trapar.osc0[itr] = 0.0;
let is_quant = if i < params.levpar.nquant.len() {
params.levpar.nquant[i] as usize
} else {
continue;
};
let is_quant = params.levpar.nquant[i] as usize;
let j_quant = params.levpar.nquant[j] as usize;
let j_quant = if j < params.levpar.nquant.len() {
params.levpar.nquant[j] as usize
} else {
continue;
};
// 确保 jnd - 1 在有效范围内
let jnd_idx = jnd.saturating_sub(1);
// Fix: JNDODF 是 1-based 索引,转为 0-based
let jnd_idx = jnd - 1;
if jnd_idx >= params.odfstk.xkij.len() {
continue;
}
@ -105,6 +116,7 @@ pub fn odfhys_simplified(params: &mut OdfhysParams) {
/// 初始化氢线 ODF完整模式
///
/// 设置氢线的频率网格、权重和 Stark 展宽参数。
/// 对应 Fortran 中 ISPODF < 1 的完整分支。
///
/// # 参数
/// * `dopo` - 多普勒宽度参数
@ -124,12 +136,14 @@ pub fn odfhys_full(
let mut ffro = vec![0.0_f64; MFRO];
for itr in 0..ntrans {
let jnd = params.trapar.ijtf[itr] as usize;
if jnd == 0 {
// Fix: 使用 JNDODF 而非 IJTF且检查 <= 0
let jnd_raw = params.odfctr.jndodf[itr];
if jnd_raw <= 0 {
continue;
}
let mode = params.trapar.indexp[itr].abs();
let jnd = jnd_raw as usize;
let mode = params.trapar.indexp[itr].abs();
if mode != 2 {
continue;
}
@ -137,10 +151,10 @@ pub fn odfhys_full(
params.trapar.lcomp[itr] = 0;
params.trapar.intmod[itr] = 6;
// I=ILOW(ITR), J=IUP(ITR) — Fortran 1-indexed → Rust 0-indexed
let i = (params.trapar.ilow[itr] - 1) as usize;
let j = (params.trapar.iup[itr] - 1) as usize;
// 边界检查
if i >= params.levpar.nquant.len() || j >= params.levpar.nquant.len() {
continue;
}
@ -151,20 +165,21 @@ pub fn odfhys_full(
params.odfmod.nqlodf[i] = params.levpar.nquant[j];
}
// 计算 XJ2A
// Fix: XI2 是 0-based 数组xi2[n-1] 对应 Fortran XI2(n)
// Fortran: XJ2A=HALF*(XI2(NQUANT(J))+XI2(NQUANT(J)-1))
let nquant_j = params.levpar.nquant[j] as usize;
if nquant_j == 0 || nquant_j >= params.xi2.len() {
if nquant_j == 0 || nquant_j > params.xi2.len() {
continue;
}
let xj2a = 0.5 * (params.xi2[nquant_j] + params.xi2[nquant_j - 1]);
let xj2a = HALF * (params.xi2[nquant_j - 1] + params.xi2[nquant_j - 2]);
// 设置频率和权重
let jnd_idx = jnd.saturating_sub(1);
// Fix: JNDODF 是 1-based转为 0-based 访问 ODF 数组
let jnd_idx = jnd - 1;
if jnd_idx >= params.odffrq.kdo.len() {
continue;
}
// Note: kdo is [MHOD][4] in Rust, so kdo[jnd_idx][ifq] corresponds to KDO(ifq, jnd) in Fortran
// kdo[jnd_idx][ifq] 对应 Fortran KDO(ifq+1, jnd)
let mut nfro: usize = 0;
for ifq in 0..4 {
nfro += params.odffrq.kdo[jnd_idx][ifq] as usize;
@ -176,73 +191,89 @@ pub fn odfhys_full(
if iel_idx >= params.ionpar.iz.len() {
continue;
}
let nquant_i = params.levpar.nquant[i] as usize;
if nquant_i == 0 || nquant_i > params.xi2.len() {
continue;
}
let frion = FRH * (params.ionpar.iz[iel_idx] as f64).powi(2);
let fra = frion * (params.xi2[params.levpar.nquant[i] as usize] - xj2a);
let fra = frion * (params.xi2[nquant_i - 1] - xj2a);
let dopi = dopo * fra * CCM;
let frb = 0.99999999 * frion * params.xi2[params.levpar.nquant[i] as usize];
let frb = 0.99999999 * frion * params.xi2[nquant_i - 1];
let ifrq0 = params.trapar.ifr0[itr];
let ifrq1 = params.trapar.ifr1[itr];
let _ifrq0 = params.trapar.ifr0[itr];
let _ifrq1 = params.trapar.ifr1[itr];
params.trapar.ifr0[itr] = (nlaste + 1) as i32;
params.trapar.ifr1[itr] = (nlaste + nfro) as i32;
params.odfmod.i1odf[i] = params.trapar.ifr0[itr];
params.odfmod.i2odf[i] = (params.trapar.ifr1[itr] - 1) as i32;
// 设置频率数组
// Fix: FFRO 使用 0-based 索引
// Fortran: FFRO(1)=..., FFRO(2)=..., IJ00=1
// Rust: ffro[0]=..., ffro[1]=..., ij00=0 (0-based)
ffro[0] = 0.99999999 * fra;
ffro[1] = fra;
let mut ij00: usize = 1;
let mut ij00: usize = 0; // Fortran IJ00=1 → Rust 0-based = 0
for ik in 0..3 {
let kdo_val = params.odffrq.kdo[jnd_idx][ik] as usize;
// Fortran: DO IJ=2,KDO(IK,JND) → Rust: DO ij=2..=kdo_val
// ijq = ij00 + ij但因为 ij00 已经是 0-based所以直接用
for ij in 2..=kdo_val {
let ijq = ij00 + ij;
if ijq < MFRO {
ffro[ijq] = ffro[ijq - 1] + params.odffrq.xdo[jnd_idx][ik] * dopi;
}
}
ij00 = ij00.saturating_add(kdo_val).saturating_sub(1);
ij00 += kdo_val.saturating_sub(1);
}
// 查找 FRB 位置
// Fix: 0-based 搜索范围 0..=ij00
let mut nfrb: usize = ij00;
for ij in 1..=ij00 {
if ij < MFRO && ffro[ij] < frb {
for ij in 0..=ij00 {
if ffro[ij] < frb {
nfrb = ij;
}
}
if nfrb == ij00 && nfro > 0 && nfro < MFRO {
// 扩展频率数组
// Fortran: IJ00=IJ00+1, FFRO(NFRO)=...
// Rust 0-based: ij00+=1, ffro[nfro-1] 对应 Fortran FFRO(NFRO)
ij00 += 1;
ffro[nfro - 1] = 0.99999999 * frion * params.xi2[params.levpar.nquant[i] as usize];
ffro[nfro - 1] = 0.99999999 * frion * params.xi2[nquant_i - 1];
while ij00 < MFRO && ffro[ij00] >= ffro[nfro - 1] {
params.odffrq.xdo[2][jnd_idx] *= 0.75;
let kdo3 = params.odffrq.kdo[2][jnd_idx] as usize;
// Fix: xdo[jnd_idx][2] 对应 Fortran XDO(3,JND),不是 xdo[2][jnd_idx]
params.odffrq.xdo[jnd_idx][2] *= 0.75;
let kdo3 = params.odffrq.kdo[jnd_idx][2] as usize;
ij00 = ij00.saturating_sub(kdo3);
for ij in 2..=kdo3 {
let ijq = ij00 + ij;
if ijq < MFRO {
ffro[ijq] = ffro[ijq - 1] + params.odffrq.xdo[2][jnd_idx] * dopi;
ffro[ijq] = ffro[ijq - 1] + params.odffrq.xdo[jnd_idx][2] * dopi;
}
}
ij00 = ij00.saturating_add(kdo3);
ij00 += kdo3;
}
let kdo4 = params.odffrq.kdo[3][jnd_idx];
// Fix: kdo[jnd_idx][3] 对应 Fortran KDO(4,JND)
let kdo4 = params.odffrq.kdo[jnd_idx][3];
if kdo4 > 1 {
let tido = (ffro[nfro - 1] - ffro[ij00]) / (kdo4 - 1) as f64;
for ij in 1..=((kdo4 - 2) as usize) {
let ijq = nfro.saturating_sub(ij);
// Fortran: IJQ=NFRO-IJ, FFRO(IJQ)=FFRO(NFRO)-FLOAT(IJ)*TIDO
// Rust: ijq = nfro-1-ij (0-based)
let ijq = nfro.saturating_sub(1 + ij);
if ijq < MFRO {
ffro[ijq] = ffro[nfro - 1] - ij as f64 * tido;
}
}
}
} else if nfrb + 3 < MFRO {
// nfrb + 1/2/3 的相对偏移在 0-based 下不变
let tido = (frb - ffro[nfrb]) * THIRD;
ffro[nfrb + 1] = ffro[nfrb] + tido;
ffro[nfrb + 2] = frb - tido;
@ -252,7 +283,9 @@ pub fn odfhys_full(
params.odfmod.i2odf[i] = (params.trapar.ifr1[itr] - 1) as i32;
}
// 存储频率
// 存储频率(反转)
// Fortran: DO IJ=1,NFRO → FREQ(NLASTE+IJ)=FFRO(NFRO-IJ+1)
// Rust 0-based: freq[nlaste+ij-1] = ffro[nfro-ij]
for ij in 1..=nfro {
let dest_idx = nlaste + ij - 1;
let src_idx = nfro - ij;
@ -262,31 +295,42 @@ pub fn odfhys_full(
}
// 计算权重
// Fortran: W(NLASTE+NFRO) = HALF*(FREQ(NLASTE+NFRO-1)-FREQ(NLASTE+NFRO))
// Rust 0-based: weight[nlaste+nfro-1] = 0.5*(freq[nlaste+nfro-2]-freq[nlaste+nfro-1])
if nfro >= 2 {
let w_idx = nlaste + nfro - 1;
if w_idx < weight.len() && w_idx > 0 {
weight[w_idx] = 0.5 * (freq[w_idx - 1] - freq[w_idx]);
weight[w_idx] = HALF * (freq[w_idx - 1] - freq[w_idx]);
weight[w_idx - 1] = weight[w_idx];
}
// Fortran: DO IJ=2,NFRO-2,2
for ij in (2..=(nfro - 2)).step_by(2) {
let idx = nlaste + ij;
if idx >= 2 && idx < weight.len() {
let tido = (freq[idx - 1] - freq[idx]) * THIRD;
weight[idx - 2] += tido;
weight[idx - 1] += 4.0 * tido;
weight[idx] += tido;
// FREQ(NLASTE+IJ) → freq[nlaste+ij-1]
// FREQ(NLASTE+IJ+1) → freq[nlaste+ij]
// W(NLASTE+IJ-1) → weight[nlaste+ij-2]
// W(NLASTE+IJ) → weight[nlaste+ij-1]
// W(NLASTE+IJ+1) → weight[nlaste+ij]
let fi = nlaste + ij - 1;
if fi >= 1 && fi + 1 < weight.len() {
let tido = (freq[fi] - freq[fi + 1]) * THIRD;
weight[fi - 1] += tido;
weight[fi] += 4.0 * tido;
weight[fi + 1] += tido;
}
}
}
nlaste = params.trapar.ifr1[itr] as usize;
// 计算 Stark 参数和振子强度
// 设置内部频率ODFFR和 Stark 参数
// TODO: CALL ODFFR(I,J) — 需要额外的参数组装
// TODO: IF(INDEXP(ITR).NE.0) CALL IJALIS(ITR,IFRQ0,IFRQ1)
params.trapar.osc0[itr] = 0.0;
let is_quant = params.levpar.nquant[i] as usize;
let j_quant = params.levpar.nquant[j] as usize;
if jnd_idx < params.odfstk.xkij.len() {
for k in j_quant..=NLMX {
if k < params.odfstk.xkij[jnd_idx].len() {
@ -298,6 +342,8 @@ pub fn odfhys_full(
}
}
}
// TODO: IF(INDEXP(ITR).NE.0) CALL IJALIS(ITR,IFRQ0,IFRQ1)
}
params.basnum.nfreq = nlaste as i32;
@ -308,10 +354,10 @@ mod tests {
use super::*;
use crate::tlusty::state::atomic::{IonPar, LevPar, TraPar};
use crate::tlusty::state::config::BasNum;
use crate::tlusty::state::constants::{MFREQ, MLEVEL, MTRANS, MHOD};
use crate::tlusty::state::odfpar::{OdfFrq, OdfMod, OdfStk};
use crate::tlusty::state::constants::{MLEVEL, MTRANS, MHOD};
use crate::tlusty::state::odfpar::{OdfCtr, OdfFrq, OdfMod, OdfStk};
fn create_test_state() -> (BasNum, IonPar, LevPar, TraPar, OdfFrq, OdfMod, OdfStk, Vec<f64>) {
fn create_test_state() -> (BasNum, IonPar, LevPar, TraPar, OdfCtr, OdfFrq, OdfMod, OdfStk, CompIf, Vec<f64>) {
let mut basnum = BasNum::default();
basnum.ntrans = 2;
basnum.nfreq = 10;
@ -329,7 +375,6 @@ mod tests {
levpar.iel[2] = 1;
let mut trapar = TraPar::default();
trapar.ijtf[0] = 1;
trapar.indexp[0] = 2;
trapar.ilow[0] = 1;
trapar.iup[0] = 2;
@ -338,9 +383,12 @@ mod tests {
trapar.ifr1[0] = 5;
trapar.line[0] = 1;
// Fix: 使用 OdfCtr.jndodf 而非 TraPar.ijtf
let mut odfctr = OdfCtr::new();
odfctr.jndodf[0] = 1; // 第一个跃迁对应 jnd=1 (1-based)
let mut odffrq = OdfFrq::new();
// Note: kdo is [MHOD][4] in Rust, which is transposed from Fortran KDO(4,MHOD)
// So kdo[jnd][ik] corresponds to KDO(ik, jnd) in Fortran
// kdo[jnd_idx][ik] 对应 Fortran KDO(ik+1, jnd)
odffrq.kdo[0][0] = 10;
odffrq.kdo[0][1] = 10;
odffrq.kdo[0][2] = 10;
@ -351,13 +399,33 @@ mod tests {
let odfmod = OdfMod::new();
let odfstk = OdfStk::new(NLMX);
let compif = CompIf::default();
// XI2: 0-based, xi2[n-1] = 1/n²
let mut xi2 = vec![0.0; 50];
for i in 0..10 {
xi2[i] = 1.0 / ((i + 1) as f64).powi(2);
for n in 1..=10 {
xi2[n - 1] = 1.0 / (n as f64).powi(2);
}
(basnum, ionpar, levpar, trapar, odffrq, odfmod, odfstk, xi2)
(basnum, ionpar, levpar, trapar, odfctr, odffrq, odfmod, odfstk, compif, xi2)
}
macro_rules! make_params {
($basnum:ident, $ionpar:ident, $levpar:ident, $trapar:ident, $odfctr:ident,
$odffrq:ident, $odfmod:ident, $odfstk:ident, $compif:ident, $xi2:ident) => {
OdfhysParams {
basnum: &mut $basnum,
ionpar: &$ionpar,
levpar: &$levpar,
trapar: &mut $trapar,
odfctr: &$odfctr,
odffrq: &mut $odffrq,
odfmod: &mut $odfmod,
odfstk: &mut $odfstk,
compif: &mut $compif,
xi2: &mut $xi2,
}
};
}
#[test]
@ -367,22 +435,18 @@ mod tests {
ionpar,
levpar,
mut trapar,
odfctr,
mut odffrq,
mut odfmod,
mut odfstk,
mut compif,
mut xi2,
) = create_test_state();
let mut params = OdfhysParams {
basnum: &mut basnum,
ionpar: &ionpar,
levpar: &levpar,
trapar: &mut trapar,
odffrq: &mut odffrq,
odfmod: &mut odfmod,
odfstk: &mut odfstk,
xi2: &mut xi2,
};
let mut params = make_params!(
basnum, ionpar, levpar, trapar, odfctr,
odffrq, odfmod, odfstk, compif, xi2
);
odfhys_simplified(&mut params);
@ -398,6 +462,9 @@ mod tests {
// 验证 LCOMP 被设置为 false
assert_eq!(params.trapar.lcomp[0], 0);
// Fix: 验证 LINEXP 被设置为 false
assert!(!params.compif.linexp[0]);
}
#[test]
@ -407,25 +474,21 @@ mod tests {
ionpar,
levpar,
mut trapar,
odfctr,
mut odffrq,
mut odfmod,
mut odfstk,
mut compif,
mut xi2,
) = create_test_state();
// 设置为非 mode 2
trapar.indexp[0] = 1;
let mut params = OdfhysParams {
basnum: &mut basnum,
ionpar: &ionpar,
levpar: &levpar,
trapar: &mut trapar,
odffrq: &mut odffrq,
odfmod: &mut odfmod,
odfstk: &mut odfstk,
xi2: &mut xi2,
};
let mut params = make_params!(
basnum, ionpar, levpar, trapar, odfctr,
odffrq, odfmod, odfstk, compif, xi2
);
odfhys_simplified(&mut params);
@ -433,6 +496,35 @@ mod tests {
assert_eq!(params.trapar.osc0[0], 0.0);
}
#[test]
fn test_odfhys_skip_negative_jndodf() {
let (
mut basnum,
ionpar,
levpar,
mut trapar,
mut odfctr,
mut odffrq,
mut odfmod,
mut odfstk,
mut compif,
mut xi2,
) = create_test_state();
// Fix: 测试负数 JNDODF 被正确跳过
odfctr.jndodf[0] = -1;
let mut params = make_params!(
basnum, ionpar, levpar, trapar, odfctr,
odffrq, odfmod, odfstk, compif, xi2
);
odfhys_simplified(&mut params);
// 应该被跳过osc0 保持 0
assert_eq!(params.trapar.osc0[0], 0.0);
}
#[test]
fn test_stark_parameters_computed() {
let (
@ -440,29 +532,34 @@ mod tests {
ionpar,
levpar,
mut trapar,
odfctr,
mut odffrq,
mut odfmod,
mut odfstk,
mut compif,
mut xi2,
) = create_test_state();
let mut params = OdfhysParams {
basnum: &mut basnum,
ionpar: &ionpar,
levpar: &levpar,
trapar: &mut trapar,
odffrq: &mut odffrq,
odfmod: &mut odfmod,
odfstk: &mut odfstk,
xi2: &mut xi2,
};
let mut params = make_params!(
basnum, ionpar, levpar, trapar, odfctr,
odffrq, odfmod, odfstk, compif, xi2
);
odfhys_simplified(&mut params);
// 验证 Stark 参数被计算
// jnd = 1, k = 2 (from j_quant to NLMX)
// jnd_idx=0 (jnd=1, 0-based), k from j_quant=2 to NLMX
assert!(params.odfstk.xkij[0][2] > 0.0);
assert!(params.odfstk.wl0[0][2] > 0.0);
assert!(params.odfstk.fij[0][2] > 0.0);
}
#[test]
fn test_xi2_0based_indexing() {
// 验证 XI2 使用 0-based 索引: xi2[n-1] = 1/n²
let xi2: Vec<f64> = (1..=10).map(|n| 1.0 / (n as f64).powi(2)).collect();
assert!((xi2[0] - 1.0).abs() < 1e-15); // n=1: xi2[0] = 1.0
assert!((xi2[1] - 0.25).abs() < 1e-15); // n=2: xi2[1] = 0.25
assert!((xi2[2] - 1.0 / 9.0).abs() < 1e-15); // n=3: xi2[2] = 1/9
}
}

View File

@ -302,8 +302,9 @@ fn inifrc_setup(params: &InifrcParams) -> Result<InifrcOutput, String> {
} else {
0
};
let mut itr0: usize = 0;
if iln > 0 && ils < params.itra.len() {
let itr0 = params.itra[ils] as usize;
itr0 = params.itra[ils] as usize;
if itr0 > 0 && itr0 <= output.indexp_out.len() {
output.indexp_out[itr0 - 1] = 0;
output.ifr0_out[itr0 - 1] = 0;
@ -311,10 +312,7 @@ fn inifrc_setup(params: &InifrcParams) -> Result<InifrcOutput, String> {
}
}
if frlev[il0] > 0.0 {
output.debug_messages.push(format!(
"Edge at frequency larger than FRCMAX: il0={}, frlev={:.4e}",
il0, frlev[il0]
));
eprintln!(" Edge at frequency larger than FRCMAX{:5}{:12.4e}{:7}{:7}{:7}", il0, frlev[il0], ils, iln, itr0);
}
il0 += 1;
}
@ -435,10 +433,7 @@ fn inifrc_setup(params: &InifrcParams) -> Result<InifrcOutput, String> {
let ils = iens[params.nlevel - 1];
output.ijfl[ils] = nend as i32;
output.debug_messages.push(format!(
"ils, ijfl: {}, {}, {:.4e}",
ils, output.ijfl[ils], freqco[nend - 1]
));
println!("ils,ijfl {} {} {:.4e}", ils, output.ijfl[ils], freqco[nend - 1]);
il0 = params.nlevel;
let mut frclst = frlev[il0 - 1];
@ -447,10 +442,7 @@ fn inifrc_setup(params: &InifrcParams) -> Result<InifrcOutput, String> {
let frcmin_eff = if params.frcmin <= 0.0 { 1.0e12 } else { params.frcmin };
while frclst < frcmin_eff {
if frlev[il0 - 1] > 0.0 {
output.debug_messages.push(format!(
"Edge at frequency smaller than 1.d12: il0={:.4e}",
frlev[il0 - 1]
));
eprintln!(" Edge at frequency smaller than 1.d12{:5}{:12.4e}", il0, frlev[il0 - 1]);
}
il0 -= 1;
if il0 == 0 {

View File

@ -409,7 +409,17 @@ pub fn inifrs(config: &InifrsConfig, freq_ctrl: &mut InifrsFreqControl) -> Inifr
let frcmax = if freq_ctrl.frcmax <= 0.0 || freq_ctrl.frcmax > 1.01 * frlev[0] {
frlev[0] * freq_ctrl.cfrmax
} else {
freq_ctrl.frcmax
// Corresponds to Fortran: WRITE(10,640) FRLEV(1),ILS,ILN,ITR0 then QUIT
let ils = iens[config.nlevel - 1];
let ie = (config.iel[ils] - 1) as usize;
let iln = if ie < config.nnext.len() { config.nnext[ie] } else { 0 };
let itr0 = if ils < config.itra.len() && (iln as usize) < config.itra[ils].len() {
config.itra[ils][iln as usize]
} else {
0
};
eprintln!("{:12.4e}{:7}{:7}{:7}", frlev[0], ils, iln, itr0);
panic!(" Edge at frequency larger than FRCMAX; ii,itr: {} {}", ils, itr0);
};
let nftail = freq_ctrl.nftail;
@ -661,11 +671,17 @@ pub fn inifrs(config: &InifrsConfig, freq_ctrl: &mut InifrsFreqControl) -> Inifr
output.ijtc[itr] = ifr1 as i32;
}
// WRITE(42,642) - line transition info
let il0 = config.ilow[itr];
let al = 2.997926e18 / config.fr0[itr];
eprintln!("{:7}{:6}{:7}{:7}{:5}{:5}{:5}{:12.3}{:7}{:7}{:7}",
itr + 1, "", il0, config.iup[itr],
if il0 > 0 { config.iatm[(il0 - 1) as usize] } else { 0 },
0, 0, al,
output.ifr0[itr], output.ifr1[itr], nf);
if nf > MFREQL {
// log::warn!(
// "INIFRS: Too many frequencies in line - nf={}, mfreql={}",
// nf, MFREQL
// );
eprintln!("{} {} {}", il0, itr + 1, nf);
}
if nf > nflx {
nflx = nf;
@ -712,13 +728,10 @@ pub fn inifrs(config: &InifrsConfig, freq_ctrl: &mut InifrsFreqControl) -> Inifr
output.nfreql = nfreql;
output.nppx = nfreq;
// log::debug!(
// "INIFRS: nfreq={}, nfreqc={}, nfreql={}, nflx={}",
// nfreq, nfreqc, nfreql, nflx
// );
eprintln!("{} {} {} {}", nfreq, nfreqc, nfreql, nflx);
if nfreq > MFREQ {
// log::error!("INIFRS: nfreq={} > mfreq={}", nfreq, MFREQ);
eprintln!(" Number of frequencies:{:10}", nfreq);
}
output

View File

@ -116,6 +116,9 @@ pub fn inifrt(params: &InifrtParams) -> InifrtOutput {
} else {
// 跳过高于 FRCMAX 的电离限
while il0 < params.nlevel - 1 && freqco[0] < frlev[il0] {
if frlev[il0] > 0.0 {
eprintln!(" Edge at frequency larger than FRCMAX{:5}{:12.4e}", il0 + 1, frlev[il0]);
}
il0 += 1;
}
}
@ -222,6 +225,9 @@ pub fn inifrt(params: &InifrtParams) -> InifrtOutput {
let mut il0 = params.nlevel;
let mut frclst = frlev[il0 - 1];
while il0 > 1 && frclst < frcmin {
if frlev[il0 - 1] > 0.0 {
eprintln!(" Edge at frequency smaller than 1.d12{:5}{:12.4e}", il0, frlev[il0 - 1]);
}
il0 -= 1;
frclst = frlev[il0 - 1];
}

View File

@ -18,6 +18,7 @@
//! - 求解辐射转移方程
use crate::tlusty::state::constants::{BOLK, HALF, MDEPTH, MFREQ, MLEVEL, MTRANS, PCK, SIG4P, UN};
// f2r_depends: COLIS, COMSET, CONCOR, DIETOT, ELCOR, ODFMER, OPACF1, OPAINI, OSCCOR, OUTPUT, RATES1, RTECOM, RTEFR1, RYBHEQ, SABOLF, STEQEQ, TDPINI, VISINI, WNSTOR
// ============================================================================
// 配置参数
@ -668,10 +669,10 @@ pub fn inilam_pure(
}
output.prd0 = 0.0;
// 频率循环(简化版本,完整版本需要 OPACF1 和 RTEFR1 回调)
// 频率循环(完整版本需要 OPACF1 和 RTEFR1 回调)
for ij in 0..nfreq {
// OPACF1(IJ) 和 RTEFR1(IJ) 需要外部实现
// 这里只计算 GRD 和 PRA 的简化版本
// 计算 GRD 和 PRA
}
// GRD(1) = PCK * GRD(1) / DENS(1)

View File

@ -432,6 +432,8 @@ pub fn inkul_pure(params: &InkulParams, line_records: &[LineRecord]) -> InkulOut
}
}
eprintln!(" Ion{:3}{:3} : {:9} Lines included", atomic.atopar.numat[iat - 1], iz_ion, nlinku);
InkulOutput {
nlinku,
lined,

View File

@ -156,10 +156,10 @@ pub fn inpdis_pure(params: &mut InpDisParams, cnu1: f64) -> InpDisResult {
} else {
params.xmdot
};
let rstar_conv = if params.rstar > 1e3 {
params.rstar / RSUN
let rstar_conv = if rstar > 1e3 {
rstar / RSUN
} else {
params.rstar
rstar
};
let r = rstar_conv * params.reldst.abs();
@ -183,7 +183,7 @@ pub fn inpdis_pure(params: &mut InpDisParams, cnu1: f64) -> InpDisResult {
let alpav = params.alphav.abs();
// 计算总柱质量
let dmtot = if alpav <= 0.0 {
let dmtot = if params.alphav <= 0.0 {
// 旧方法
let chih = 0.39;
let reynum = if params.reynum <= 0.0 {

View File

@ -14,6 +14,8 @@ use crate::tlusty::state::atomic::AtomicData;
use crate::tlusty::state::model::ModelState;
use crate::tlusty::state::constants::*;
// f2r_depends: DIVSTR, DOPGAM, INTLEM, INTXEN, STARK0
// ============================================================================
// 常量
// ============================================================================

View File

@ -16,6 +16,7 @@ use crate::tlusty::math::{opacf1, Opacf1Config, Opacf1ModelState, Opacf1AtomicPa
use crate::tlusty::math::{rtefr1, Rtefr1Params, Rtefr1ModelState};
use crate::tlusty::math::{opaini, OpainiParams, OpainiOutput};
use crate::tlusty::math::quit;
// f2r_depends: OPACF1, OPAINI, RTEFR1
// ============================================================================
// 常量
@ -353,6 +354,15 @@ where
nfk1,
rhab,
});
eprintln!(
"{:6}{:5}{:5}{:7}{:7}{:12.4e}",
itr as i32 + 1,
atomic.ilow[itr],
atomic.iup[itr],
nfk0,
nfk1,
rhab
);
}
// ========================================================================
@ -503,8 +513,22 @@ where
nfk1,
rhab: rhabmx,
});
eprintln!(
"{:6}{:5}{:5}{:7}{:7}{:12.4e}",
itr as i32 + 1,
atomic.ilow[itr],
atomic.iup[itr],
nfk0,
nfk1,
rhabmx
);
}
eprintln!(" Total number of lines :{:8}", stats.nlsto);
eprintln!(" Number of weak lines :{:8}", stats.nlsw);
eprintln!(" Intermediate lines :{:8}", stats.nlsi);
eprintln!(" Number of strong lines:{:8}", stats.nlss);
// ========================================================================
// 计算每个频率的线数
// ========================================================================
@ -546,6 +570,7 @@ where
}
stats.nlimax = nlimax;
eprintln!(" MAXIMUM NUMBER OF OVERLAPPING TRANSITIONS: {:3}", nlimax);
// ========================================================================
// 重新计算积分权重
@ -664,6 +689,28 @@ where
// ========================================================================
let accuracy = compute_accuracy(freq);
eprintln!();
eprintln!(" ACCURACY OF INTEGRATIONS:");
eprintln!(
" Interval:{:16.8e}{:16.8e}{:16.8e}{:16.8e}",
accuracy.freq_start, accuracy.freq_end, accuracy.freq_range, accuracy.z0
);
eprintln!(
" Planck functions: {:12.0} {:12.4e}",
accuracy.t3s, accuracy.t3er
);
eprintln!(
" {:12.0} {:12.4e}",
accuracy.t1s, accuracy.t1er
);
eprintln!(
" {:12.0} {:12.4e}",
accuracy.t2s, accuracy.t2er
);
eprintln!();
eprintln!(" TOTAL NUMBER OF FREQUENCIES:{:8}", freq.nfreq);
eprintln!(" SELECTED FREQUENCIES: {:8}", nppx);
LinselOutput {
stats,
accuracy,

View File

@ -6,7 +6,7 @@ mod cia_h2h;
mod cia_h2h2;
mod cia_h2he;
mod cia_hhe;
mod compt0;
pub mod compt0;
mod corrwm;
mod cspec;
mod dopgam;

View File

@ -5,6 +5,7 @@
//! 在 PRD 情况下修改线发射系数和散射系数。
use crate::tlusty::math::gami;
use crate::tlusty::math::opacity::dopgam;
use crate::tlusty::state::constants::{TWO, UN};
// 物理常量
@ -109,6 +110,9 @@ pub fn prd(
) {
let ij = params.ij;
if ij == 0 {
// 初始化 PRD 数组(对应 Fortran lines 119-143
// 调用 dopgam 计算 Doppler 宽度和阻尼参数
// f2r_depends: dopgam, gami
return;
}

View File

@ -199,8 +199,8 @@ pub fn profil(params: &ProfilParams) -> f64 {
} else if ipa > 10 {
// ================================================================
// 用户提供的轮廓 (PROFSP)
// 这里返回 0实际实现需要 PROFSP 函数
// TODO: 实现 PROFSP
// 返回 0实际实现需要 PROFSP 函数
// 注: PROFSP 需要用户自定义实现
0.0
} else {
0.0

View File

@ -249,7 +249,9 @@ fn compute_zmikro(params: &ProfspParams, id: usize) -> f64 {
}
// 调用 SABOLF 获取 USUM
let sabolf_params = SabolfParams {
let mut g_clone = params.atomic.levpar.g.clone();
let mut gmer_clone = params.model.mrgpar.gmer.clone();
let mut sabolf_params = SabolfParams {
id: id + 1, // 1-based
t: if id < params.model.modpar.temp.len() {
params.model.modpar.temp[id]
@ -261,12 +263,25 @@ fn compute_zmikro(params: &ProfspParams, id: usize) -> f64 {
} else {
1.0e12
},
atomic: params.atomic,
g: &mut g_clone,
iz: &params.atomic.ionpar.iz,
nnext: &params.atomic.ionpar.nnext,
nfirst: &params.atomic.ionpar.nfirst,
nlast: &params.atomic.ionpar.nlast,
iupsum: &params.atomic.ionpar.iupsum,
enion: &params.atomic.levpar.enion,
nquant: &params.atomic.levpar.nquant,
iatm: &params.atomic.levpar.iatm,
numat: &params.atomic.atopar.numat,
ifwop: &params.model.wmcomp.ifwop,
ielhm: params.atomic.auxind.ielhm,
wnhint: None,
imrg: &params.model.mrgpar.imrg,
gmer: &mut gmer_clone,
ioptab: 0,
};
let sabolf_result = sabolf_pure(&sabolf_params);
let sabolf_result = sabolf_pure(&mut sabolf_params);
// 添加离子贡献
let nion = params.atomic.ionpar.iz.len();

View File

@ -100,6 +100,8 @@ pub fn mpartf(jatom: usize, ion: usize, indmol: usize, t: f64) -> MpartfResult {
return MpartfResult { u, dulog };
}
eprintln!(" mpartf: jatom={} ion={} indmol={} T={:.1}", jatom, ion, indmol, t);
let tl = t.ln();
// 原子物种

View File

@ -55,6 +55,8 @@ pub struct PartfParams {
pub xmax: f64,
/// 计算模式
pub mode: PartfMode,
/// Irwin 模式标志 (0=不使用, >0=使用)
pub iirwin: i32,
}
/// PARTF 输出结果。
@ -159,6 +161,16 @@ pub fn partf_pure(params: &PartfParams) -> PartfOutput {
return partf_cno(iat, izi, t, ane);
}
// Irwin 配分函数模式 (iirwin > 0 且 T < 16000K)
if params.iirwin > 0 && t < 16000.0 {
if izi <= 2 {
return partf_irwin(params);
}
} else if iat > 30 && izi <= 3 {
// 重元素 (Z > 30, 低电离级)
return partf_heavy(params);
}
// 根据模式选择计算方法
match params.mode {
PartfMode::UserDefined => partf_user_defined(params),
@ -391,6 +403,24 @@ fn partf_standard(params: &PartfParams) -> PartfOutput {
PartfOutput { u, dut, dun }
}
/// Irwin 配分函数 (MPARTF)
fn partf_irwin(params: &PartfParams) -> PartfOutput {
use crate::tlusty::math::partition::mpartf;
let result = mpartf(params.iat as usize, params.izi as usize, 0, params.t);
let u0 = result.u;
let du0 = result.dulog;
let mut dut = 0.0;
if u0 > 0.0 && du0 > 0.0 {
dut = u0 / params.t * du0;
}
PartfOutput {
u: u0,
dut,
dun: 0.0,
}
}
/// 用户自定义配分函数
fn partf_user_defined(params: &PartfParams) -> PartfOutput {
// 调用 PFSPEC
@ -491,6 +521,7 @@ mod tests {
ane: 1.0e12,
xmax: 10.0,
mode: PartfMode::Standard,
iirwin: 0,
};
let result = partf_pure(&params);
@ -506,6 +537,7 @@ mod tests {
ane: 1.0e12,
xmax: 8.0,
mode: PartfMode::Standard,
iirwin: 0,
};
let result = partf_pure(&params);
@ -521,6 +553,7 @@ mod tests {
ane: 1.0e12,
xmax: 7.0,
mode: PartfMode::Standard,
iirwin: 0,
};
let result = partf_pure(&params);
@ -536,6 +569,7 @@ mod tests {
ane: 1.0e12,
xmax: 7.0,
mode: PartfMode::Standard,
iirwin: 0,
};
let result = partf_pure(&params);
@ -552,6 +586,7 @@ mod tests {
ane: 1.0e15,
xmax: 5.0,
mode: PartfMode::Standard,
iirwin: 0,
};
let result = partf_pure(&params);
@ -567,6 +602,7 @@ mod tests {
ane: 1.0e12,
xmax: 10.0,
mode: PartfMode::OpacityProject,
iirwin: 0,
};
let result = partf_pure(&params);

View File

@ -139,7 +139,8 @@ pub fn pfheav_pure(params: &PfheavParams) -> PfheavOutput {
// 检查原子序数范围
if iiz <= 28 {
// Fortran 会报错,这里返回 1.0
// WRITE(6,*) 'Error, routine PFHEAV for Z.GE.28 only'
eprintln!("Error, routine PFHEAV for Z.GE.28 only");
return PfheavOutput { u: 1.0 };
}

View File

@ -208,6 +208,7 @@ pub fn bpopc_pure(params: &BpopcParams) -> Option<BpopcOutput> {
ioniz: params.state_ioniz,
irefa: params.state_irefa,
lgr: params.state_lgr,
ifoppf: 0,
lrm: params.state_lrm,
};

View File

@ -6,6 +6,8 @@
use crate::tlusty::state::constants::{MFREX, MLEVEL, MLVEXP, UN};
// f2r_depends: DWNFR1, SGMER1
/// BPOPE 输入参数
pub struct BpopeParams {
/// 深度索引 (1-indexed)
@ -210,7 +212,7 @@ pub fn bpope(
let jj = atomic.iiexp[j].abs() as usize;
let nrefi = model.nrefs[atomic.iatm[i] as usize * id + id_idx];
// 简化处理:直接使用 sg
// 直接使用 sg
let sg_final = sg;
let w0 = freq_data.w0e[ij];
let sgw0 = sg_final * w0;
@ -234,7 +236,7 @@ pub fn bpope(
}
}
// 处理谱线跃迁(简化版本,不处理 ODF 采样)
// 处理谱线跃迁(不处理 ODF 采样)
if config.ispodf == 0 && freq_data.ijlin[ij] > 0 {
let itr = (freq_data.ijlin[ij] - 1) as usize;

Some files were not shown because too many files have changed in this diff Show More