runops 2.7.0__tar.gz → 2.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {runops-2.7.0 → runops-2.8.0}/PKG-INFO +1 -1
- {runops-2.7.0 → runops-2.8.0}/SPEC.md +3 -2
- {runops-2.7.0 → runops-2.8.0}/docs/guide.md +6 -1
- {runops-2.7.0 → runops-2.8.0}/docs/reference.md +4 -2
- {runops-2.7.0 → runops-2.8.0}/pyproject.toml +1 -1
- {runops-2.7.0 → runops-2.8.0}/src/runops/__init__.py +1 -1
- {runops-2.7.0 → runops-2.8.0}/src/runops/ops/create.py +92 -9
- {runops-2.7.0 → runops-2.8.0}/src/runops/skills/runops-analyze/SKILL.md +9 -4
- runops-2.8.0/src/runops/skills/runops-analyze/example-report/README.md +97 -0
- runops-2.8.0/src/runops/skills/runops-analyze/example-report/out/fig1_maps.png +0 -0
- runops-2.8.0/src/runops/skills/runops-analyze/example-report/out/fig2_dependence.png +0 -0
- runops-2.8.0/src/runops/skills/runops-analyze/example-report/out/values.csv +6 -0
- runops-2.8.0/src/runops/skills/runops-analyze/example-report/report.html +149 -0
- runops-2.8.0/src/runops/skills/runops-analyze/example-report/scripts/build_html.py +72 -0
- runops-2.8.0/src/runops/skills/runops-analyze/example-report/scripts/make_figures.py +122 -0
- runops-2.8.0/src/runops/skills/runops-analyze/report-guide.md +91 -0
- {runops-2.7.0 → runops-2.8.0}/tests/test_safety.py +51 -0
- {runops-2.7.0 → runops-2.8.0}/tests/test_skills.py +43 -0
- {runops-2.7.0 → runops-2.8.0}/uv.lock +1 -1
- runops-2.7.0/.harness/manifest.toml +0 -15
- {runops-2.7.0 → runops-2.8.0}/.agents/skills/check/SKILL.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/.agents/skills/improve-harness/SKILL.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/.agents/skills/python-package-refactor/SKILL.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/.agents/skills/python-package-refactor/agents/openai.yaml +0 -0
- {runops-2.7.0 → runops-2.8.0}/.agents/skills/python-package-refactor/examples/refactor-request.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/.agents/skills/python-package-refactor/references/anti-patterns.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/.agents/skills/python-package-refactor/references/api-compatibility.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/.agents/skills/python-package-refactor/references/packaging-modernization.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/.agents/skills/python-package-refactor/references/refactor-playbook.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/.agents/skills/python-package-refactor/references/verification.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/.agents/skills/python-package-refactor/scripts/api_surface_snapshot.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/.agents/skills/python-package-refactor/scripts/import_smoke.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/.agents/skills/python-package-refactor/scripts/inspect_python_package.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/.agents/skills/python-package-refactor/scripts/refactor_quality_gate.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/.agents/skills/release/SKILL.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/.agents/skills/simulator-adapter/SKILL.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/.agents/skills/spec-reviewer/SKILL.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/.agents/skills/sync-docs/SKILL.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/.claude/agents/spec-reviewer.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/.claude/rules/architecture.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/.claude/rules/dev-workflow.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/.claude/rules/gotchas.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/.claude/settings.json +0 -0
- {runops-2.7.0 → runops-2.8.0}/.claude/skills/check/SKILL.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/.claude/skills/improve-harness/SKILL.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/.claude/skills/python-package-refactor/SKILL.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/.claude/skills/release/SKILL.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/.claude/skills/simulator-adapter/SKILL.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/.claude/skills/spec-reviewer/SKILL.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/.claude/skills/sync-docs/SKILL.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/.codex/README.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/.codex/agents/spec-reviewer.toml +0 -0
- {runops-2.7.0 → runops-2.8.0}/.codex/config.toml +0 -0
- {runops-2.7.0 → runops-2.8.0}/.codex/rules/runops.rules +0 -0
- {runops-2.7.0 → runops-2.8.0}/.dockerignore +0 -0
- {runops-2.7.0 → runops-2.8.0}/.gitattributes +0 -0
- {runops-2.7.0 → runops-2.8.0}/.github/assets/conversation-dark.svg +0 -0
- {runops-2.7.0 → runops-2.8.0}/.github/assets/conversation-light.svg +0 -0
- {runops-2.7.0 → runops-2.8.0}/.github/assets/hero-dark.svg +0 -0
- {runops-2.7.0 → runops-2.8.0}/.github/assets/hero-light.svg +0 -0
- {runops-2.7.0 → runops-2.8.0}/.github/assets/make_readme_art.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/.github/workflows/ci.yml +0 -0
- {runops-2.7.0 → runops-2.8.0}/.github/workflows/publish.yml +0 -0
- {runops-2.7.0 → runops-2.8.0}/.gitignore +0 -0
- {runops-2.7.0 → runops-2.8.0}/.pre-commit-config.yaml +0 -0
- {runops-2.7.0 → runops-2.8.0}/.vscode/settings.json +0 -0
- {runops-2.7.0 → runops-2.8.0}/AGENTS.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/CLAUDE.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/LICENSE +0 -0
- {runops-2.7.0 → runops-2.8.0}/README.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/docs/adapter.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/adapters/__init__.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/adapters/base.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/adapters/generic.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/adapters/registry.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/case.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/cli.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/config.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/errors.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/harness.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/jobgen.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/launchers.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/legacy_adapters/__init__.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/legacy_adapters/beach.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/legacy_adapters/emses.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/manifest.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/ops/__init__.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/ops/analysis.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/ops/common.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/ops/listing.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/ops/migrate.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/ops/notes.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/ops/progress.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/ops/setup.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/ops/skills.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/ops/submit.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/ops/sync.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/ops/tidy.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/project.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/runtime.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/skills/runops-check-status/SKILL.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/skills/runops-create-run/SKILL.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/skills/runops-maintain/SKILL.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/skills/runops-reference/SKILL.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/skills/runops-tidy/SKILL.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/slurm.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/src/runops/util.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/__init__.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/conftest.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/fixtures/v1-project/.codex/rules/a.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/fixtures/v1-project/.runops/test-runs/T20260701-0001/receipt.toml +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/fixtures/v1-project/.runops/work/beach-x-abc/bin/beach +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/fixtures/v1-project/AGENTS.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/fixtures/v1-project/CLAUDE.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/fixtures/v1-project/cases/beach/plane/beach.toml +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/fixtures/v1-project/cases/beach/plane/case.toml +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/fixtures/v1-project/cases/old/beach.toml +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/fixtures/v1-project/cases/old/case.toml +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/fixtures/v1-project/launchers.toml +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/fixtures/v1-project/research/CURRENT.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/fixtures/v1-project/runops.toml +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/fixtures/v1-project/runs/AGENTS.md +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/fixtures/v1-project/runs/_archive/plane/R20260601-0001/manifest.toml +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/fixtures/v1-project/runs/plane/R20260701-0001--a/input/beach.toml +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/fixtures/v1-project/runs/plane/R20260701-0001--a/manifest.toml +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/fixtures/v1-project/runs/plane/R20260702-0001/manifest.toml +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/fixtures/v1-project/runs/scan/R20260701-0002/manifest.toml +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/fixtures/v1-project/runs/scan/survey.toml +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/fixtures/v1-project/simulators.toml +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/fixtures/v1-project/site.toml +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/golden/mpi_standard.sh +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/golden/openmp_rsc.sh +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/test_adapters.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/test_analysis_layout.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/test_archive_series.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/test_build.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/test_dev_harness.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/test_jobgen.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/test_legacy_adapters.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/test_lifecycle.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/test_manifest_config.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/test_migrate.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/test_mv_naming.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/test_progress.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/test_project_scope.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/test_run_job.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/test_scratch_tidy.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/test_setup_misc.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/test_sweep.py +0 -0
- {runops-2.7.0 → runops-2.8.0}/tests/test_sync_evidence.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: runops
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.8.0
|
|
4
4
|
Summary: Run ledger and Slurm job submission for HPC simulation projects
|
|
5
5
|
Project-URL: Homepage, https://github.com/Nkzono99/runops
|
|
6
6
|
Project-URL: Documentation, https://github.com/Nkzono99/runops/blob/main/docs/reference.md
|
|
@@ -729,14 +729,14 @@ runops は `init` と `update` で次を行う。
|
|
|
729
729
|
runops は次の 6 skill だけを同梱し、simulator skill と同じ仕組みで展開する。
|
|
730
730
|
利用者が覚えるコマンドは `runo init` だけとし、それ以外(simulator の追加、更新、v1 移行を含む)は
|
|
731
731
|
エージェントへの依頼で行う。
|
|
732
|
-
各 SKILL.md は 60
|
|
732
|
+
各 SKILL.md は 60 行以内に収める。詳しい指針や例が要る skill は、同じ directory に別 file として置き、SKILL.md から相対リンクで参照する。
|
|
733
733
|
|
|
734
734
|
| skill | 内容 |
|
|
735
735
|
|---|---|
|
|
736
736
|
| `runops-create-run` | `new` / `sweep` の使い方。`--why` を必ず書く、scratch の使い分け |
|
|
737
737
|
| `runops-check-status` | `sync` → `list` → `log` の順。失敗時の `retry` と `tidy --purge-work` |
|
|
738
738
|
| `runops-tidy` | `tidy` を定期的に実行する判断基準。pin の使い方 |
|
|
739
|
-
| `runops-analyze` | 依存する run の範囲で調査の置き場所を決め、script・結果・log・README
|
|
739
|
+
| `runops-analyze` | 依存する run の範囲で調査の置き場所を決め、script・結果・log・README をまとめる。読み手に渡す比較レポートの書き方は同梱の `report-guide.md` と `example-report/` に置く |
|
|
740
740
|
| `runops-reference` | command 一覧(§7.3 の表をそのまま載せる) |
|
|
741
741
|
| `runops-maintain` | simulator の追加、runops と simulator package の更新、`migrate-v1`、`doctor` の読み方 |
|
|
742
742
|
|
|
@@ -1017,6 +1017,7 @@ v2 の文書は次の 4 ファイルだけにする。
|
|
|
1017
1017
|
|
|
1018
1018
|
| 項目 | 決定 | 理由 |
|
|
1019
1019
|
|---|---|---|
|
|
1020
|
+
| 比較レポートの書き方(2026-11-10) | `runops-analyze` に執筆ガイド `report-guide.md` と模擬データの例 `example-report/` を同梱する。README.md を本文の正本とし、例の `scripts/build_html.py` が図を埋め込んだ `report.html` を作る。command やレポート生成機構は足さない | 差分図と条件の列挙が中心のレポートは読み手に伝わらず、元の分布を同じ尺度で並べ、問い・観測・解釈をつなげて書き直すと伝わった(#119)。書き方は判断を伴うので、手順と例として skill に置く |
|
|
1020
1021
|
| 解析の配置 2.6.0(2026-10-23) | 依存する run の範囲に応じて run 内 `analysis/`、group の `_analysis/<topic>/`、project の `analysis/<topic>/` にまとめる。skill、log の tidy、group archive、context を対応させる(§9.1) | 報告・図・script・job log が分散・混在していた。group は既存の directory として扱い、新しい entity や command は足さない。既存ファイルの整理は移動一覧を提示してから行う |
|
|
1021
1022
|
| job の cwd | run_dir(v1 と同じ)。Slurm log は `work/` | v1 adapter の path 規約と実 project の job.sh をそのまま使える |
|
|
1022
1023
|
| adapter の追加 hook | `configure(sim_config)`、`job_prelude(run_dir)` | generic の `command` 取得と、EMSES の `rm -f *_0000.h5` など v1 の setup command を表す |
|
|
@@ -60,7 +60,8 @@ runops が扱う名詞は case、run、sweep の 3 つです。
|
|
|
60
60
|
作った run の `manifest.toml` と `input/` は変更しません。入力を直したいときは新しい run を作ります。
|
|
61
61
|
同じ入力の run が既にあると、作成時に警告が出ます。
|
|
62
62
|
`new --from` は元の run の params を引き継ぎますが、入力ファイルは今の case から作ります。
|
|
63
|
-
元の run を作った後に case
|
|
63
|
+
元の run を作った後に case の入力ファイルを直して値が変わると、作成時の警告に変わる key と値が並びます。
|
|
64
|
+
元の値のまま続けたいときは `--set` で指定します。
|
|
64
65
|
|
|
65
66
|
### 名前の付け方
|
|
66
67
|
|
|
@@ -232,6 +233,10 @@ topic は `speed-by-version` のように問いの内容で名付け、同じ問
|
|
|
232
233
|
|
|
233
234
|
エージェントは `runops-analyze` skill を使い、調査の README と、そこへのリンクを含む
|
|
234
235
|
`LOG.md` の判断を更新します。`notes/` は run に依存しない知見に使います。
|
|
236
|
+
比較の結果を人に見せるレポートを頼むと(「この 3 条件を比較してレポートにまとめて」)、
|
|
237
|
+
エージェントは skill 同梱の執筆ガイドと例に沿って、問いと結論、条件を同じ尺度で並べた図、
|
|
238
|
+
図の読み方と結果の意味、再現情報の順に書きます。README.md と、図を埋め込んで単体で
|
|
239
|
+
読める `report.html` の両方ができます。
|
|
235
240
|
解析の追加依存は `.runops/analysis-env/` に置き、重い読み込みや作図はサイトの規則に従って
|
|
236
241
|
計算ノードで実行します。sbatch は調査 directory から投入し、事前に `logs/` を作って
|
|
237
242
|
`#SBATCH -o logs/%x.%j.out` と `#SBATCH -e logs/%x.%j.err` を指定します。
|
|
@@ -129,8 +129,10 @@ program を起動せずに終わり、`stage = "setup"` と exit code を記録
|
|
|
129
129
|
|
|
130
130
|
`submit` は executable を hash し直して attempt に記録し、作成時と違えば警告します。
|
|
131
131
|
`new` と `sweep --apply` は同じ入力(`input_hash`)の run が既にあれば警告します。
|
|
132
|
-
`new --from RUN` は、元の run を作った後に case
|
|
133
|
-
|
|
132
|
+
`new --from RUN` は、元の run を作った後に case の入力ファイルが変わり、そのために入力の値が
|
|
133
|
+
元の run と違うとき、違う key と値を警告に並べます(入力は今の case から作るため)。TOML 以外の
|
|
134
|
+
入力は file 名だけを示します。`--set` した key と、adapter が `--continue` のために書いた key は
|
|
135
|
+
並べません。どれも作成や投入は拒否しません。
|
|
134
136
|
|
|
135
137
|
`submit`、`cancel`、`retry`、`sync` は lock の中で manifest を読み直してから更新します。
|
|
136
138
|
同じ run を 2 回指定しても、複数のエージェントが同時に投入しても sbatch は 1 回です。
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import contextlib
|
|
5
6
|
import itertools
|
|
6
7
|
import shutil
|
|
8
|
+
from collections.abc import Iterable
|
|
7
9
|
from dataclasses import dataclass, field
|
|
8
10
|
from datetime import datetime, timedelta
|
|
9
11
|
from pathlib import Path
|
|
@@ -28,7 +30,17 @@ from runops.ops.common import SimContext, resolve_dest, sim_context
|
|
|
28
30
|
from runops.ops.submit import write_job_for
|
|
29
31
|
from runops.project import ARCHIVE_DIR, SCRATCH_DIR, Project
|
|
30
32
|
from runops.runtime import resolve_runtime
|
|
31
|
-
from runops.util import
|
|
33
|
+
from runops.util import (
|
|
34
|
+
hash_file,
|
|
35
|
+
hash_tree,
|
|
36
|
+
load_toml,
|
|
37
|
+
move_noreplace,
|
|
38
|
+
now_iso,
|
|
39
|
+
parse_value,
|
|
40
|
+
tomllib,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
SHOWN_INPUT_CHANGES = 10
|
|
32
44
|
|
|
33
45
|
|
|
34
46
|
@dataclass
|
|
@@ -40,6 +52,7 @@ class RunSpec:
|
|
|
40
52
|
sweep: str = ""
|
|
41
53
|
job: dict[str, Any] = field(default_factory=dict)
|
|
42
54
|
job_changed: list[str] = field(default_factory=list)
|
|
55
|
+
input_changes: list[str] = field(default_factory=list) # set for a run derived from another
|
|
43
56
|
|
|
44
57
|
|
|
45
58
|
@dataclass
|
|
@@ -118,6 +131,60 @@ def _job_keys(job: dict[str, Any]) -> dict[str, Any]:
|
|
|
118
131
|
return {k: v for k, v in job.items() if k in JOB_KEYS}
|
|
119
132
|
|
|
120
133
|
|
|
134
|
+
def _flat(value: Any, prefix: str = "") -> dict[str, Any]:
|
|
135
|
+
"""The leaves of a TOML document under dotted keys, arrays of tables by index."""
|
|
136
|
+
items: Iterable[tuple[Any, Any]]
|
|
137
|
+
if isinstance(value, dict):
|
|
138
|
+
items = value.items()
|
|
139
|
+
elif isinstance(value, list) and any(isinstance(x, dict) for x in value):
|
|
140
|
+
items = enumerate(value)
|
|
141
|
+
else:
|
|
142
|
+
return {prefix: value}
|
|
143
|
+
flat: dict[str, Any] = {}
|
|
144
|
+
for key, item in items:
|
|
145
|
+
flat.update(_flat(item, f"{prefix}.{key}" if prefix else str(key)))
|
|
146
|
+
return flat
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _input_values(input_dir: Path) -> dict[tuple[str, str], Any]:
|
|
150
|
+
"""What a run's input files say: ``(file, dotted key)`` for TOML, ``(file, "")`` otherwise."""
|
|
151
|
+
values: dict[tuple[str, str], Any] = {}
|
|
152
|
+
for path in sorted(p for p in input_dir.rglob("*") if p.is_file()):
|
|
153
|
+
rel = path.relative_to(input_dir).as_posix()
|
|
154
|
+
document = None
|
|
155
|
+
if path.suffix == ".toml":
|
|
156
|
+
with contextlib.suppress(tomllib.TOMLDecodeError, UnicodeDecodeError):
|
|
157
|
+
document = load_toml(path)
|
|
158
|
+
if document is None:
|
|
159
|
+
values[(rel, "")] = hash_file(path)
|
|
160
|
+
else:
|
|
161
|
+
values.update({(rel, key): value for key, value in _flat(document).items()})
|
|
162
|
+
return values
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _differing(
|
|
166
|
+
before: dict[tuple[str, str], Any], after: dict[tuple[str, str], Any]
|
|
167
|
+
) -> list[tuple[str, str]]:
|
|
168
|
+
return sorted(k for k in before.keys() | after.keys() if before.get(k) != after.get(k))
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _input_changes(
|
|
172
|
+
before: dict[tuple[str, str], Any],
|
|
173
|
+
after: dict[tuple[str, str], Any],
|
|
174
|
+
explained: set[str],
|
|
175
|
+
) -> list[str]:
|
|
176
|
+
"""What differs between two runs' inputs, leaving out the keys in *explained*."""
|
|
177
|
+
lines = []
|
|
178
|
+
for key in _differing(before, after):
|
|
179
|
+
rel, name = key
|
|
180
|
+
if not name:
|
|
181
|
+
lines.append(rel)
|
|
182
|
+
elif not any(name == k or name.startswith(f"{k}.") for k in explained):
|
|
183
|
+
old, new = (repr(side[key]) if key in side else "-" for side in (before, after))
|
|
184
|
+
lines.append(f"{name}: {old} -> {new}")
|
|
185
|
+
return lines
|
|
186
|
+
|
|
187
|
+
|
|
121
188
|
def _diff(label: str, before: dict[str, Any], after: dict[str, Any]) -> str:
|
|
122
189
|
keys = [k for k in after if before.get(k) != after[k]]
|
|
123
190
|
keys += [k for k in before if k not in after]
|
|
@@ -263,14 +330,15 @@ def create_run(project: Project, request: CreateRequest) -> CreateResult:
|
|
|
263
330
|
line = _diff("job vs case", dict(case.job), job)
|
|
264
331
|
if line:
|
|
265
332
|
result.changes.append(line)
|
|
266
|
-
if source is not None:
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
333
|
+
if source is not None and spec.input_changes:
|
|
334
|
+
# Inputs come from the case as it is now, not as it was for the source.
|
|
335
|
+
shown = "; ".join(spec.input_changes[:SHOWN_INPUT_CHANGES])
|
|
336
|
+
hidden = len(spec.input_changes) - SHOWN_INPUT_CHANGES
|
|
337
|
+
more = f"; and {hidden} more" if hidden > 0 else ""
|
|
338
|
+
result.warnings.append(
|
|
339
|
+
f"case {case.name!r} input files changed since {source.id} was created; "
|
|
340
|
+
f"{result.manifests[0].id} differs from it beyond the params in: {shown}{more}"
|
|
341
|
+
)
|
|
274
342
|
return result
|
|
275
343
|
|
|
276
344
|
|
|
@@ -483,8 +551,13 @@ def _populate(
|
|
|
483
551
|
dst = input_dir / rel
|
|
484
552
|
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
485
553
|
shutil.copy2(src, dst)
|
|
554
|
+
recorded = str(source.section("origin").get("case_input_hash", "")) if source else ""
|
|
555
|
+
case_moved = bool(recorded) and recorded != case.input_hash()
|
|
556
|
+
rendered: dict[tuple[str, str], Any] = {}
|
|
486
557
|
try:
|
|
487
558
|
ctx.adapter.render_inputs(dict(spec.params), stage)
|
|
559
|
+
if case_moved:
|
|
560
|
+
rendered = _input_values(input_dir)
|
|
488
561
|
if request.continue_run and source is not None:
|
|
489
562
|
extend = getattr(ctx.adapter, "extend_inputs", None)
|
|
490
563
|
if not callable(extend):
|
|
@@ -497,6 +570,16 @@ def _populate(
|
|
|
497
570
|
) from None
|
|
498
571
|
except (ValueError, KeyError, OSError) as exc:
|
|
499
572
|
raise RunError(f"{run_id}: render_inputs failed: {exc}") from exc
|
|
573
|
+
if case_moved and source is not None:
|
|
574
|
+
values = _input_values(input_dir)
|
|
575
|
+
# Keys the user set, or the adapter wrote to continue, are not case changes.
|
|
576
|
+
explained = {
|
|
577
|
+
k.replace("[", ".").replace("]", "") for k in changed_keys(source.params, spec.params)
|
|
578
|
+
}
|
|
579
|
+
explained |= {name for _, name in _differing(rendered, values) if name}
|
|
580
|
+
spec.input_changes = _input_changes(
|
|
581
|
+
_input_values(source.run_dir / "input"), values, explained
|
|
582
|
+
)
|
|
500
583
|
|
|
501
584
|
now = now_iso()
|
|
502
585
|
run: dict[str, Any] = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: runops-analyze
|
|
3
|
-
description: Analyze or compare run results in a runops project. Use when plotting, investigating a series or sweep, writing an analysis report, or choosing where analysis scripts and outputs belong.
|
|
3
|
+
description: Analyze or compare run results in a runops project. Use when plotting, investigating a series or sweep, writing an analysis or comparison report for a reader, or choosing where analysis scripts and outputs belong.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Analyze run results
|
|
@@ -30,11 +30,16 @@ description: Analyze or compare run results in a runops project. Use when plotti
|
|
|
30
30
|
In the script use `#SBATCH -o logs/%x.%j.out` and `#SBATCH -e logs/%x.%j.err`,
|
|
31
31
|
the site's resource directives, and `srun` for the payload where required.
|
|
32
32
|
Keep the working directory at the investigation root so `scripts/` and `out/` resolve.
|
|
33
|
-
5.
|
|
33
|
+
5. When the result is a report someone will read (a comparison of runs, a dependence,
|
|
34
|
+
"what changed and why"), write it by [report-guide.md](report-guide.md): question and
|
|
35
|
+
answer first, original fields side by side on one scale, each figure explained
|
|
36
|
+
before and after, provenance and checks at the end, as `README.md` plus a
|
|
37
|
+
self-contained `report.html`. [example-report/](example-report/README.md) shows it.
|
|
38
|
+
6. Update the investigation README, then append the decision and its README link to
|
|
34
39
|
`LOG.md`. Put reusable formulas, methods and pitfalls in `notes/<topic>.md`.
|
|
35
|
-
|
|
40
|
+
7. `runo tidy --only analysis-logs` previews old job logs, retaining the newest pair per
|
|
36
41
|
job name in each logs directory. Apply with `runo tidy --apply --only analysis-logs`.
|
|
37
42
|
Archiving a whole group directory also moves its `_analysis/`; a partial archive
|
|
38
43
|
leaves shared analysis with the remaining runs. `restore RUN` restores only that run.
|
|
39
|
-
|
|
44
|
+
8. For an existing project's scattered files, show source → destination paths before
|
|
40
45
|
moving anything, then fix affected script paths and document links. Keep results.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# ヒーターを離して置いても、センサーの温度は上がるか
|
|
2
|
+
|
|
3
|
+
> これは `runops-analyze` skill に同梱した書き方の見本です。数値は
|
|
4
|
+
> `scripts/make_figures.py` が作る模擬データで、実在の計算結果ではありません。
|
|
5
|
+
> run id などの再現情報も、書く場所を示すための例です。
|
|
6
|
+
|
|
7
|
+
## 調べたことと結果
|
|
8
|
+
|
|
9
|
+
金属板の中央に温度センサーがあり、板の上にヒーターを 1 個置きます。ヒーターを
|
|
10
|
+
センサーの真上に置けばセンサーが温まるのは当然ですが、知りたいのは、ヒーターを
|
|
11
|
+
センサーから離して置いたときです。センサーの温度は上がるのか。上がるなら、それは
|
|
12
|
+
センサーだけが局所的に熱せられているのか、板の広い範囲が温まった結果なのか。
|
|
13
|
+
|
|
14
|
+
ヒーターの出力(10 W)と板の条件は固定し、ヒーターとセンサーの距離だけを 0 cm から
|
|
15
|
+
12 cm まで変えました。比較の基準は、ヒーターを切った計算です。
|
|
16
|
+
|
|
17
|
+
結果は次のとおりです。ヒーターを 8 cm 離しても、センサーの温度は基準より約 8 K
|
|
18
|
+
高くなりました。このときセンサーとその周囲の温度はほぼ同じで、センサーだけが熱い
|
|
19
|
+
状態にはなっていません。離れたヒーターは、板の広い範囲を温めることを通して
|
|
20
|
+
センサーの温度を上げています。
|
|
21
|
+
|
|
22
|
+
## 温度分布を並べて見る
|
|
23
|
+
|
|
24
|
+
まず、板全体の温度がどう変わるかを見ます。図 1 は、ヒーターを切った場合、センサーの
|
|
25
|
+
真上に置いた場合、8 cm 離して置いた場合の温度分布を、同じ範囲と同じ色尺度で並べた
|
|
26
|
+
ものです。色が明るいほど高温で、白い丸がセンサー、水色の三角がヒーターの位置です。
|
|
27
|
+
白い破線の円(半径 3 cm)は、あとで「センサーの周囲」として温度を平均する範囲を
|
|
28
|
+
示しています。どの分布も、計算が定常に達した後の最後の 1000 step の平均です。
|
|
29
|
+
|
|
30
|
+

|
|
31
|
+
|
|
32
|
+
左の図は一様に 20 °C で、これが比較の基準です。中央の図では、ヒーターを中心に
|
|
33
|
+
温度が上がり、センサーは 50 °C と板の中で最も高温になっています。右の図では高温の
|
|
34
|
+
中心がヒーターとともに右へ移りますが、温まった範囲はセンサーの位置まで広がって
|
|
35
|
+
おり、センサーは 27.9 °C を示します。破線の円の中を見ると、中央の図では円の中心が
|
|
36
|
+
周りより明らかに明るいのに対し、右の図では円の中に目立った明暗がありません。
|
|
37
|
+
|
|
38
|
+
つまり、離れたヒーターでもセンサーの温度は上がりますが、その上がり方は直上に置いた
|
|
39
|
+
場合と違い、センサーの位置が特別に熱いわけではないように見えます。ただし図 1 は
|
|
40
|
+
2 つの距離しか示していません。次に、距離を細かく変えてこの見方を確かめます。
|
|
41
|
+
|
|
42
|
+
## 距離を変えたときの変化
|
|
43
|
+
|
|
44
|
+
図 2 は、ヒーターとセンサーの距離を横軸にとり、2 つの量を示しています。(a) は
|
|
45
|
+
センサーの温度が基準(ヒーターなし)からどれだけ上がったか、(b) はセンサーの温度から
|
|
46
|
+
周囲(図 1 の破線の円周上)の平均温度を引いた値です。(b) が正で大きいほど、
|
|
47
|
+
センサーの位置だけが局所的に熱いことを表し、0 に近ければセンサーは周囲と同じ温度
|
|
48
|
+
です。赤い点は図 1 で分布を示した 2 つの距離です。
|
|
49
|
+
|
|
50
|
+

|
|
51
|
+
|
|
52
|
+
(a) の温度上昇は距離とともにゆるやかに小さくなり、8 cm で 7.9 K、12 cm でも 4.1 K
|
|
53
|
+
残ります。一方、(b) の周囲との差は 0 cm の 11.8 K から急に小さくなり、4 cm で 0.6 K、
|
|
54
|
+
8 cm 以上ではほぼ 0(−0.1 K)です。
|
|
55
|
+
|
|
56
|
+
| 距離 [cm] | センサーの温度上昇 [K] | センサー − 周囲 [K] |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| 0 | 30.0 | 11.8 |
|
|
59
|
+
| 2 | 21.5 | 3.9 |
|
|
60
|
+
| 4 | 15.4 | 0.6 |
|
|
61
|
+
| 8 | 7.9 | −0.1 |
|
|
62
|
+
| 12 | 4.1 | −0.1 |
|
|
63
|
+
|
|
64
|
+
2 つの量が違う速さで小さくなることが、最初の問いへの答えです。ヒーターを 4 cm 以上
|
|
65
|
+
離すと、センサーの温度はまだ 15 K 上がっているのに、周囲との差はほとんど消えます。
|
|
66
|
+
この距離では、センサーはヒーターに直接熱せられているのではなく、温まった板と同じ
|
|
67
|
+
温度になっています。
|
|
68
|
+
|
|
69
|
+
この結果からは判断できないこともあります。保存したのは板の温度分布だけなので、
|
|
70
|
+
熱が板の中を伝わってセンサーに届いたのか、板の上の空気を通って届いたのかは
|
|
71
|
+
区別できません。「板の広い範囲が温まる」という説明は分布の形から言えることで、
|
|
72
|
+
熱の経路ごとの寄与を分けた結果ではありません。
|
|
73
|
+
|
|
74
|
+
## 再現情報
|
|
75
|
+
|
|
76
|
+
| 条件 | run | 計算コード | 平均した区間 |
|
|
77
|
+
|---|---|---|---|
|
|
78
|
+
| ヒーターなし(基準) | `R20261101-0001` | plate-sim 1.4.2 | step 9001–10000 |
|
|
79
|
+
| 距離 0, 2, 4, 8, 12 cm | `R20261101-0002` – `0006` | plate-sim 1.4.2 | step 9001–10000 |
|
|
80
|
+
|
|
81
|
+
- 6 本とも同じ版・同じ格子・同じ step 数の計算で、違いはヒーターの有無と位置だけです。
|
|
82
|
+
センサーの温度が最後の 1000 step で 0.01 K 以内に収まっていることを確認し、
|
|
83
|
+
その区間を平均しました。
|
|
84
|
+
- 図に平滑化やマスクは使っていません。「周囲」は、センサーを中心とする半径 3 cm の
|
|
85
|
+
円周上の 720 点の平均です。
|
|
86
|
+
- 図と表は `scripts/make_figures.py` が作ります。図 2 と表の元の数値は
|
|
87
|
+
[out/values.csv](out/values.csv) にあります。
|
|
88
|
+
- `report.html` は `scripts/build_html.py` がこの README から作る、図を埋め込んだ
|
|
89
|
+
単体で読めるファイルです。
|
|
90
|
+
|
|
91
|
+
## 確認記録
|
|
92
|
+
|
|
93
|
+
- ファイル: `python scripts/build_html.py --check` が、`report.html` が README と一致し、
|
|
94
|
+
画像 2 枚を埋め込んでいると報告しました。
|
|
95
|
+
- 図の内容: 2 枚の PNG を開き、軸ラベル、色尺度、各列の条件名と温度の文字が読めること、
|
|
96
|
+
表の値と図 2 の点が一致することを確認しました。
|
|
97
|
+
- ブラウザ表示: `report.html` をブラウザで開いた確認はしていません。
|
|
Binary file
|