Files
DCTS/CLAUDE.md
T
fmq 43b82b1ae2 feat(all): 物理正确性五重硬门槛、输入文件结构化与 fort.55 错位修复、conv 诊断 DB 化与阶段归因修复、ORELAX 收敛修复与导入工具下线
物理正确性校验体系(common/conv_check.rs +494 行)
- 新增 5 类硬门槛:能量守恒(.6)、温度结构(.7)、emflux 积分校验(.emflux,含全 NaN 判失败)、假收敛排查(itek 轨迹首末比)、b 因子合理性(.bfac)
- runner 在 TLUSTY 阶段结束后执行全部校验,任一失败判 final_converged=false
- GridConfig 新增 8 个可配阈值,经 scheduler→executor→runner 全链路透传

输入文件配置结构化重构(config.rs +1453 行)
- TlustyInput 拆为 dot5/nst 分层结构,字段名严格映射 tlusty208.f READ 语句;SynspecInput 重构为 9 个 Fort55Line 子结构体
- 移除 ChainStep.metals 字段,元素集改由 dot5.atoms/ions 显式声明(gen_input5/nst_writer 同步重写为三源融合 / 分层覆盖)
- fort.55 修复行结构 bug:补全分子表行(7→9 行),IDSTD 50→0 错位修正(影响全部光谱线强归一化,需重算 SYNSPEC 阶段)

conv 诊断 DB 化与阶段归因修复(server)
- 单点详情 conv 面板从磁盘 conv.json 改读 DB grid_points.summary_json;grid_points 新增 summary_json/last_elapsed_sec 两列(旧库幂等 ALTER)
- record_task_report 阶段归因列加 CASE 守卫 + clear_synspec 对称处理,修复 synspec-only/TLUSTY-only 重跑污染统计
- 新增 summary_merge.rs 点级增量合并,避免重跑覆盖诊断字段

收敛性 ORELAX 修复与 seed_chain 可配(sdB_cno.yaml + node)
- nl 阶段加 orelax=0.5、seed_nc 加 orelax=0.3,阻尼中温区 relc 振荡发散
- seed_chain 块可配,executor 优先采用用户配置而非内置默认链

导入工具下线
- 删除 import_results 客户端工具及 Windows 推送脚本;移除 /admin/import_seed 端点
- 改为服务端临时 migrate_conv 端点(扫 conv.json 增量合并入库,迁移后可删)

文档与分析
- 新增 1305 失败点根因分析、fort.14 全 NaN 物理含义分析两份深度文档
- spectrum_correctness_analysis 两次修订标注已修复项;fetch_results.sh 修 trap RETURN 的 set -u 报错
2026-08-09 12:09:48 +08:00

7.4 KiB
Raw Blame History

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

DCTS is a Rust-based distributed computing scheduler for stellar atmosphere modeling. It discretizes a multi-dimensional parameter grid (Teff, log g, log He, log C, log N, log O) into independent compute points, runs TLUSTY (atmosphere) + SYNSPEC (synthetic spectrum) physics binaries on distributed worker nodes, and orchestrates convergence via seed passing and divergence fallback. Fully documented (in Chinese) under docs/.

Commands

# Build / check
cargo build --release                 # binaries: server, node
cargo check --workspace --all-targets

# Tests (per crate)
cargo test --workspace
cargo test -p common                  # physics/input/conv-check unit tests
cargo test -p mq                      # SQLite queue concurrency + requeue tests
cargo test -p server                  # axum integration tests (api_tests.rs, wf_migration_isolation.rs)
cargo test -p server --test api_tests           # single integration test file
cargo test -p server --test api_tests test_name # single test by name filter

# Lint / format (must be clean before commit — see contributing.md)
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings

# Dashboard (Vite + native ESM, Node built-in test runner)
cd dashboard && npm install
npm run dev                           # dev server
npm run build                         # outputs dashboard/dist (served by server on /)
npm test                              # node --test "test/*.test.js"

Runtime

./target/release/server --workflow workflows/sdB_cno.yaml --port 8090
./target/release/node                 # worker; reads .env, DCTS_SERVER_URL

All config is via environment variables prefixed DCTS_ (see .env.example): DCTS_ADMIN_TOKEN, DCTS_NODE_ID, DCTS_SERVER_URL, DCTS_MAX_SLOTS, DCTS_HEARTBEAT_SEC, DCTS_DB_PATH, DCTS_QUEUE_DB_PATH, DCTS_SEEDS_DIR, etc. dotenvy loads .env. With DCTS_AUTH_DISABLE=1 auth is skipped (local debug only).

Architecture

Workspace crates (Cargo.toml): common (lib), server (bin), node (bin), mq (lib), plus dashboard/ (web UI).

Master-Worker topology (pull-based)

  • server — Axum HTTP service. main.rs builds the router + background loops (offline detection, stale-task requeue). db/ module (SQLite + r2d2): db/mod.rs holds the Database struct, connection/migration infra, shared types & the consolidated unit tests; db/grid.rs/db/nodes.rs/db/tasks.rs/db/seeds.rs/db/workflows.rs/db/snapshots.rs hold the per-domain impl Database methods. scheduler.rs expands the grid and pushes pending points into the MQ queue. api/ submodules: node.rs (register/heartbeat), task.rs (claim/report), seed.rs (.7 download), data.rs (binary/runtime deps), workflow.rs (CRUD + start/stop), status.rs, admin.rs, auth.rs, rate_limit.rs.
  • node — stateless worker daemon. worker.rs is the poll loop (heartbeat + claim + concurrent slot pool). executor.rs runs the physics chain in a per-task sandbox. reporter.rs uploads results + .7 seed via multipart. Bootstrap pulls missing runtime deps from master.
  • mqSqliteTaskQueue (WAL mode): transactional Push/Claim/Report/Stale-Requeue. Claim is atomic so one task goes to one worker.
  • common — physics engine: config.rs (YAML parsing), gen_input5.rs/fort55_writer.rs/nst_writer.rs (TLUSTY input streams), conv_check.rs (parses fort.6 log, judges convergence), runner.rs (async subprocess with timeout), seed_finder.rs (nearest-neighbor seed matching), models.rs (shared types/enums), embedded.rs (bootstrap), logging.rs.

Task scheduling — deliberate pull model

The scheduler only pushes pending grid points into the MQ queue; it does NOT decide which node gets a task. Workers actively claim work whenever active_slots < max_slots and stop claiming when full — this achieves natural load balancing in steady state. Dequeue order is wave ASC, created_at ASC (low CNO hard-sum first, then FIFO). Do not "improve" this to server-side load distribution unless the sparse-state tradeoffs are explicitly accepted (see docs/architecture.md §5).

Task lifecycle

pending → queued → running → completed / failed. Statuses map to GridPointStatus in common/src/models.rs (completed supersedes the old converged alias, still parsed). Running tasks that exceed the stale timeout (~1800s) are requeued back to pending.

Physics pipeline (TLUSTY/SYNSPEC)

Each grid point runs a strategy chain (tlusty_strategies in the workflow YAML, e.g. ["cold_run", "seed_step"]):

  • Cold run (default_cold_chain in common/src/runner.rs): lte (grey LTE guess) → nc (NLTE continuum, ilvlin=0, niter=10) → nl (NLTE full lines, ilvlin=100, must converge) → synspec.
  • Seed step (default_seed_chain): skip the grey LTE stage, hot-start from a nearby converged atmosphere .7 as fort.8: seed_ncnlsynspec.

On nl non-convergence the server pops the chain head and retries with the next strategy (trigger_strategy_fallback). seed_step requires a resolvable neighboring seed before dispatch; if none exists the grid point stays pending and self-heals once a seed appears. Only failed tasks with an actual state transition trigger fallback (a 2026-08-02 fix prevents duplicate failure reports from re-triggering). Nodes are strategy-unaware — they only execute strategies[0] plus the injected seed_point_name.

Seed finding (common/src/seed_finder.rs)

The seed pool is a global shared resource (seeds table + in-memory cache, cross-workflow). exact_family buckets by quantized (teff/5000, logg, loghe); within a family, candidates are ranked by directed CNO distance — increasing metal abundance (target richer) is penalized 4×, decreasing (target poorer) 1×, because adding metals destabilizes NLTE radiation equilibrium (historically 311% success vs 4254% for metal-poor direction). Global fallback uses d = Δteff/5000 + Δlogg×2 + Δloghe×0.5 + Δcno×0.1 with d ≤ 3.0.

Web dashboard (dashboard/)

Native ESM modules (no framework), Vite build. main.js/router.js/state.js/api.js are the shell; utils/ (format, polling, yamlStage, errors) and components/, views/ (home, workflowDetail, detail/ for per-point panels). Tests are pure Node scripts in dashboard/test/ run via node --test. State is centralized in state.js with polling from utils/polling.js.

Conventions & gotchas

  • Convergence tuning is empirical — parameters like niter=10 for nc are the result of documented measurements (see workflows/sdB_cno.yaml comments and docs/spectrum_correctness_analysis.md). Don't change them casually; validate against the guide before altering.
  • Update docs when you change contracts: API changes → docs/api.md; DB schema → docs/database.md; physics/scheduling → docs/design.md, docs/task_engine_decoupling_design.md. The repo treats these as living specs.
  • Keep serde_yaml pinned to 0.9.34 (the upstream crate is archived; the workspace pins the final stable version deliberately).
  • DB migrations live in crates/server/src/migrations.rs; old SQLite DBs are migrated seamlessly at startup (see docs/database_refactor_design.md).
  • Commit messages follow Conventional Commits with scopes common/server/node/mq/dashboard/all (see docs/contributing.md).