物理正确性校验体系(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 报错
7.4 KiB
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.rsbuilds the router + background loops (offline detection, stale-task requeue).db/module (SQLite + r2d2):db/mod.rsholds theDatabasestruct, 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.rshold the per-domainimpl Databasemethods.scheduler.rsexpands 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.rsis the poll loop (heartbeat + claim + concurrent slot pool).executor.rsruns the physics chain in a per-task sandbox.reporter.rsuploads results +.7seed via multipart. Bootstrap pulls missing runtime deps from master.mq—SqliteTaskQueue(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_chainincommon/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.7asfort.8:seed_nc→nl→synspec.
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 3–11% success vs 42–54% 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=10forncare the result of documented measurements (seeworkflows/sdB_cno.yamlcomments anddocs/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_yamlpinned 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 (seedocs/database_refactor_design.md). - Commit messages follow Conventional Commits with scopes
common/server/node/mq/dashboard/all(seedocs/contributing.md).