runops 0.2.0__py3-none-any.whl

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.
Files changed (115) hide show
  1. runops/__init__.py +5 -0
  2. runops/_data/README.md +476 -0
  3. runops/adapters/__init__.py +29 -0
  4. runops/adapters/_utils/__init__.py +36 -0
  5. runops/adapters/_utils/toml_utils.py +81 -0
  6. runops/adapters/base.py +335 -0
  7. runops/adapters/contrib/__init__.py +5 -0
  8. runops/adapters/contrib/beach.py +837 -0
  9. runops/adapters/contrib/emses.py +1010 -0
  10. runops/adapters/generic.py +439 -0
  11. runops/adapters/registry.py +244 -0
  12. runops/cli/__init__.py +3 -0
  13. runops/cli/analyze.py +222 -0
  14. runops/cli/clone.py +104 -0
  15. runops/cli/config.py +217 -0
  16. runops/cli/context.py +56 -0
  17. runops/cli/create.py +263 -0
  18. runops/cli/dashboard.py +179 -0
  19. runops/cli/extend.py +204 -0
  20. runops/cli/history.py +105 -0
  21. runops/cli/init.py +1432 -0
  22. runops/cli/jobs.py +145 -0
  23. runops/cli/knowledge.py +1017 -0
  24. runops/cli/list.py +102 -0
  25. runops/cli/log.py +163 -0
  26. runops/cli/main.py +96 -0
  27. runops/cli/manage.py +231 -0
  28. runops/cli/new.py +343 -0
  29. runops/cli/notes.py +257 -0
  30. runops/cli/run_lookup.py +148 -0
  31. runops/cli/setup.py +174 -0
  32. runops/cli/status.py +187 -0
  33. runops/cli/submit.py +297 -0
  34. runops/cli/update.py +113 -0
  35. runops/cli/update_harness.py +245 -0
  36. runops/cli/update_refs.py +370 -0
  37. runops/core/__init__.py +3 -0
  38. runops/core/actions.py +1186 -0
  39. runops/core/analysis.py +1090 -0
  40. runops/core/campaign.py +156 -0
  41. runops/core/case.py +307 -0
  42. runops/core/context.py +426 -0
  43. runops/core/discovery.py +192 -0
  44. runops/core/environment.py +266 -0
  45. runops/core/exceptions.py +93 -0
  46. runops/core/knowledge.py +595 -0
  47. runops/core/knowledge_source.py +1204 -0
  48. runops/core/manifest.py +219 -0
  49. runops/core/project.py +171 -0
  50. runops/core/provenance.py +147 -0
  51. runops/core/retry.py +193 -0
  52. runops/core/run.py +170 -0
  53. runops/core/run_creation.py +456 -0
  54. runops/core/site.py +337 -0
  55. runops/core/state.py +197 -0
  56. runops/core/survey.py +380 -0
  57. runops/core/validation.py +40 -0
  58. runops/harness/__init__.py +27 -0
  59. runops/harness/builder.py +327 -0
  60. runops/harness/claude.py +189 -0
  61. runops/jobgen/__init__.py +3 -0
  62. runops/jobgen/generator.py +295 -0
  63. runops/launchers/__init__.py +17 -0
  64. runops/launchers/base.py +313 -0
  65. runops/launchers/mpiexec.py +131 -0
  66. runops/launchers/mpirun.py +132 -0
  67. runops/launchers/srun.py +126 -0
  68. runops/sites/__init__.py +0 -0
  69. runops/sites/camphor.md +98 -0
  70. runops/sites/camphor.toml +27 -0
  71. runops/slurm/__init__.py +3 -0
  72. runops/slurm/query.py +384 -0
  73. runops/slurm/submit.py +203 -0
  74. runops/templates/__init__.py +29 -0
  75. runops/templates/adapters/beach/agent_guide.md +50 -0
  76. runops/templates/adapters/beach/beach.toml +19 -0
  77. runops/templates/adapters/beach/case.toml +16 -0
  78. runops/templates/adapters/beach/summarize.py +272 -0
  79. runops/templates/adapters/emses/agent_guide.md +39 -0
  80. runops/templates/adapters/emses/case.toml +18 -0
  81. runops/templates/adapters/emses/plasma.toml +118 -0
  82. runops/templates/adapters/emses/summarize.py +413 -0
  83. runops/templates/adapters/generic/case.toml.j2 +13 -0
  84. runops/templates/adapters/generic/summarize.py +21 -0
  85. runops/templates/agent.md +156 -0
  86. runops/templates/rules/cookbook.md +22 -0
  87. runops/templates/scaffold/campaign.toml.j2 +10 -0
  88. runops/templates/scaffold/cases_claude.md +22 -0
  89. runops/templates/scaffold/facts.toml +2 -0
  90. runops/templates/scaffold/gitignore.txt +30 -0
  91. runops/templates/scaffold/notes/README.md +69 -0
  92. runops/templates/scaffold/rules/plan-before-act.md +17 -0
  93. runops/templates/scaffold/rules/runops-workflow.md +84 -0
  94. runops/templates/scaffold/rules/upstream-feedback.md +85 -0
  95. runops/templates/scaffold/runs_claude.md +24 -0
  96. runops/templates/scaffold/vscode_settings.json +9 -0
  97. runops/templates/skills/analyze/SKILL.md +40 -0
  98. runops/templates/skills/check-status/SKILL.md +29 -0
  99. runops/templates/skills/cleanup/SKILL.md +43 -0
  100. runops/templates/skills/create-run/SKILL.md +135 -0
  101. runops/templates/skills/debug-failed/SKILL.md +38 -0
  102. runops/templates/skills/learn/SKILL.md +54 -0
  103. runops/templates/skills/new-case/SKILL.md +108 -0
  104. runops/templates/skills/note/SKILL.md +107 -0
  105. runops/templates/skills/run-all/SKILL.md +47 -0
  106. runops/templates/skills/runops-reference/SKILL.md +203 -0
  107. runops/templates/skills/setup-campaign/SKILL.md +111 -0
  108. runops/templates/skills/setup-env/SKILL.md +32 -0
  109. runops/templates/skills/survey-design/SKILL.md +73 -0
  110. runops/templates/survey.toml.j2 +22 -0
  111. runops-0.2.0.dist-info/METADATA +491 -0
  112. runops-0.2.0.dist-info/RECORD +115 -0
  113. runops-0.2.0.dist-info/WHEEL +4 -0
  114. runops-0.2.0.dist-info/entry_points.txt +2 -0
  115. runops-0.2.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,272 @@
1
+ from __future__ import annotations
2
+
3
+ import csv
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+
8
+ def _append_figure(summary: dict[str, Any], rel_path: str, caption: str) -> None:
9
+ figures = summary.setdefault("figures", [])
10
+ if not isinstance(figures, list):
11
+ return
12
+ figures.append({"path": rel_path, "caption": caption})
13
+
14
+
15
+ def _find_output_dir(work_dir: Path) -> Path:
16
+ for candidate in (
17
+ work_dir / "latest",
18
+ work_dir / "outputs" / "latest",
19
+ work_dir / "outputs",
20
+ work_dir,
21
+ ):
22
+ if candidate.is_dir():
23
+ return candidate
24
+ return work_dir
25
+
26
+
27
+ def _parse_summary_txt(path: Path) -> dict[str, Any]:
28
+ data: dict[str, Any] = {}
29
+ if not path.is_file():
30
+ return data
31
+
32
+ for line in path.read_text(encoding="utf-8").splitlines():
33
+ stripped = line.strip()
34
+ if not stripped or "=" not in stripped:
35
+ continue
36
+ key, value = stripped.split("=", 1)
37
+ key = key.strip()
38
+ value = value.strip()
39
+ try:
40
+ data[key] = int(value)
41
+ continue
42
+ except ValueError:
43
+ pass
44
+ try:
45
+ data[key] = float(value)
46
+ continue
47
+ except ValueError:
48
+ pass
49
+ data[key] = value
50
+ return data
51
+
52
+
53
+ def _read_numeric_column(path: Path, field_name: str) -> list[float]:
54
+ if not path.is_file():
55
+ return []
56
+
57
+ values: list[float] = []
58
+ with open(path, encoding="utf-8", newline="") as f:
59
+ reader = csv.DictReader(f)
60
+ for row in reader:
61
+ if row is None:
62
+ continue
63
+ raw = row.get(field_name, "").strip()
64
+ if not raw:
65
+ continue
66
+ try:
67
+ values.append(float(raw))
68
+ except ValueError:
69
+ continue
70
+ return values
71
+
72
+
73
+ def _save_fig(fig, output_path: Path) -> bool:
74
+ try:
75
+ fig.savefig(output_path, dpi=150, bbox_inches="tight")
76
+ import matplotlib.pyplot as plt
77
+
78
+ plt.close(fig)
79
+ except Exception:
80
+ return False
81
+ return True
82
+
83
+
84
+ def _save_line_plot(
85
+ x_values: list[float],
86
+ y_values: list[float],
87
+ *,
88
+ title: str,
89
+ xlabel: str,
90
+ ylabel: str,
91
+ output_path: Path,
92
+ ) -> bool:
93
+ try:
94
+ import matplotlib
95
+
96
+ matplotlib.use("Agg")
97
+ import matplotlib.pyplot as plt
98
+ except Exception:
99
+ return False
100
+
101
+ fig, ax = plt.subplots(figsize=(6.0, 4.0))
102
+ ax.plot(x_values, y_values, linewidth=1.6)
103
+ ax.set_title(title)
104
+ ax.set_xlabel(xlabel)
105
+ ax.set_ylabel(ylabel)
106
+ ax.grid(True, linestyle=":", alpha=0.35)
107
+ fig.tight_layout()
108
+ return _save_fig(fig, output_path)
109
+
110
+
111
+ def _save_bar_plot(
112
+ x_values: list[float],
113
+ y_values: list[float],
114
+ *,
115
+ title: str,
116
+ xlabel: str,
117
+ ylabel: str,
118
+ output_path: Path,
119
+ ) -> bool:
120
+ try:
121
+ import matplotlib
122
+
123
+ matplotlib.use("Agg")
124
+ import matplotlib.pyplot as plt
125
+ except Exception:
126
+ return False
127
+
128
+ fig, ax = plt.subplots(figsize=(6.4, 4.0))
129
+ ax.bar(x_values, y_values, width=0.9)
130
+ ax.set_title(title)
131
+ ax.set_xlabel(xlabel)
132
+ ax.set_ylabel(ylabel)
133
+ ax.grid(True, axis="y", linestyle=":", alpha=0.35)
134
+ fig.tight_layout()
135
+ return _save_fig(fig, output_path)
136
+
137
+
138
+ def _try_make_beach_plots(
139
+ output_dir: Path,
140
+ fig_dir: Path,
141
+ summary: dict[str, Any],
142
+ ) -> None:
143
+ try:
144
+ from beach import Beach
145
+ except Exception:
146
+ return
147
+
148
+ try:
149
+ run = Beach(output_dir)
150
+ except Exception:
151
+ return
152
+
153
+ try:
154
+ fig, _ = run.plot_bar()
155
+ if _save_fig(fig, fig_dir / "charges_bar.png"):
156
+ _append_figure(summary, "figures/charges_bar.png", "Charge bar chart")
157
+ except Exception:
158
+ pass
159
+
160
+ try:
161
+ result = run.result
162
+ if result.triangles is not None:
163
+ fig, _ = run.plot_mesh()
164
+ if _save_fig(fig, fig_dir / "charge_mesh.png"):
165
+ _append_figure(summary, "figures/charge_mesh.png", "Charge mesh")
166
+ except Exception:
167
+ pass
168
+
169
+ try:
170
+ result = run.result
171
+ if result.triangles is not None:
172
+ fig, _ = run.plot_potential()
173
+ if _save_fig(fig, fig_dir / "potential_mesh.png"):
174
+ _append_figure(
175
+ summary,
176
+ "figures/potential_mesh.png",
177
+ "Reconstructed potential mesh",
178
+ )
179
+ except Exception:
180
+ pass
181
+
182
+
183
+ def summarize(run_dir: Path, base_summary: dict[str, Any]) -> dict[str, Any]:
184
+ """BEACH post-process scaffold."""
185
+
186
+ summary = dict(base_summary)
187
+ analysis_dir = run_dir / "analysis"
188
+ fig_dir = analysis_dir / "figures"
189
+ fig_dir.mkdir(parents=True, exist_ok=True)
190
+
191
+ output_dir = _find_output_dir(run_dir / "work")
192
+
193
+ for key, value in _parse_summary_txt(output_dir / "summary.txt").items():
194
+ summary.setdefault(key, value)
195
+
196
+ charges = _read_numeric_column(output_dir / "charges.csv", "charge_C")
197
+ if charges:
198
+ summary["charge_elements"] = len(charges)
199
+ summary["charge_total_c"] = float(sum(charges))
200
+ summary["charge_abs_sum_c"] = float(sum(abs(value) for value in charges))
201
+ summary["charge_abs_max_c"] = float(max(abs(value) for value in charges))
202
+ if _save_bar_plot(
203
+ [float(idx) for idx in range(len(charges))],
204
+ charges,
205
+ title="Charge by Element",
206
+ xlabel="Element index",
207
+ ylabel="Charge [C]",
208
+ output_path=fig_dir / "charge_distribution.png",
209
+ ):
210
+ _append_figure(
211
+ summary,
212
+ "figures/charge_distribution.png",
213
+ "Element charge distribution",
214
+ )
215
+
216
+ charge_history = _read_numeric_column(output_dir / "charge_history.csv", "charge_C")
217
+ if charge_history:
218
+ summary["charge_history_samples"] = len(charge_history)
219
+ if _save_line_plot(
220
+ [float(idx) for idx in range(len(charge_history))],
221
+ charge_history,
222
+ title="Charge History",
223
+ xlabel="Sample",
224
+ ylabel="Charge [C]",
225
+ output_path=fig_dir / "charge_history.png",
226
+ ):
227
+ _append_figure(
228
+ summary,
229
+ "figures/charge_history.png",
230
+ "Charge history trace",
231
+ )
232
+
233
+ potential_history = _read_numeric_column(
234
+ output_dir / "potential_history.csv",
235
+ "potential_V",
236
+ )
237
+ if potential_history:
238
+ summary["potential_history_samples"] = len(potential_history)
239
+ summary["potential_final_v"] = float(potential_history[-1])
240
+ if _save_line_plot(
241
+ [float(idx) for idx in range(len(potential_history))],
242
+ potential_history,
243
+ title="Potential History",
244
+ xlabel="Sample",
245
+ ylabel="Potential [V]",
246
+ output_path=fig_dir / "potential_history.png",
247
+ ):
248
+ _append_figure(
249
+ summary,
250
+ "figures/potential_history.png",
251
+ "Potential history trace",
252
+ )
253
+
254
+ performance_path = output_dir / "performance_profile.csv"
255
+ if performance_path.is_file():
256
+ try:
257
+ from beach.cli.plot_performance_profile import load_performance_profile
258
+ except Exception:
259
+ pass
260
+ else:
261
+ try:
262
+ _metadata, rows = load_performance_profile(performance_path)
263
+ if rows:
264
+ hottest = max(rows, key=lambda row: float(row["rank_max_s"]))
265
+ summary["profile_region_hottest"] = str(hottest["region"])
266
+ summary["profile_rank_max_s"] = float(hottest["rank_max_s"])
267
+ except Exception:
268
+ pass
269
+
270
+ _try_make_beach_plots(output_dir, fig_dir, summary)
271
+
272
+ return summary
@@ -0,0 +1,39 @@
1
+ ### EMSES (Electromagnetic Particle-in-Cell Simulator)
2
+
3
+ #### 概要
4
+ EMSES は 3D 電磁粒子シミュレーション (PIC) コード。
5
+ MPI 並列で実行し、電磁場と荷電粒子の自己無撞着な時間発展を計算する。
6
+
7
+ #### 入力ファイル
8
+ - **`input/plasma.toml`** — メイン設定ファイル (TOML 形式)
9
+ - `[jobcon]`: `nstep` (総ステップ数) が最重要パラメータ
10
+ - `[tmgrid]`: `nx`, `ny`, `nz` (グリッド数), `dt` (時間刻み)
11
+ - `[[species]]`: 粒子種の定義 (質量比, 温度, 密度 etc.)
12
+ - `[emfield]`: 外部磁場・電場の設定
13
+ - `[[ptcond.objects]]`: 境界条件・導体オブジェクト
14
+
15
+ #### 出力ファイル (`work/latest/` 以下)
16
+ - `*.h5` — HDF5 形式の電磁場データ (ex, ey, ez, bx, by, bz, etc.)
17
+ - `energy` — エネルギー時系列 (ASCII)。最終行のステップ番号で完了判定
18
+ - `SNAPSHOT1/esdat*.h5` — リスタート用スナップショット
19
+ - 各種診断ファイル: `ewave`, `chgacm1`, `influx`, `icur` 等
20
+
21
+ #### 完了判定
22
+ - `work/latest/energy` の最終行のステップ番号 ≥ `nstep` → completed
23
+ - stderr に "error", "segmentation fault", "killed" → failed
24
+
25
+ #### パラメータサーベイでよく変えるパラメータ
26
+ - `tmgrid.dt`, `tmgrid.nx/ny/nz`
27
+ - `species[0].temperature`, `species[0].density`
28
+ - `emfield.ex0`, `emfield.bx0` (外部場)
29
+ - `jobcon.nstep`
30
+
31
+ #### ドキュメント・参考
32
+ - EMSES ソースリポジトリの README / docs/
33
+ - `plasma.toml` のスキーマは `format_version = 2` (structured TOML)
34
+ - パラメータの dot 記法例: `tmgrid.nx=128`, `species.0.temperature=1.0e6`
35
+
36
+ #### 実行コマンド
37
+ ```
38
+ srun mpiemses3D input/plasma.toml -o work/latest
39
+ ```
@@ -0,0 +1,18 @@
1
+ [case]
2
+ name = ""
3
+ simulator = "emses"
4
+ launcher = "default"
5
+ description = ""
6
+
7
+ [params]
8
+ # "tmgrid.nx" = 64
9
+ # "tmgrid.ny" = 64
10
+ # "tmgrid.nz" = 64
11
+ # "tmgrid.dt" = 1.0
12
+ # "jobcon.nstep" = 10000
13
+
14
+ [job]
15
+ partition = ""
16
+ nodes = 1
17
+ ntasks = 1
18
+ walltime = "01:00:00"
@@ -0,0 +1,118 @@
1
+ #:schema https://raw.githubusercontent.com/CS12-Laboratory/mpiemses3d-schema/refs/heads/main/plasma.schema.json
2
+ #
3
+ # Minimal EMSES plasma.toml stub generated by `runops case new -s emses`.
4
+ #
5
+ # This template is a small working starting point — enough to launch a
6
+ # 1D test plasma run without external boundaries. Adjust species, grid,
7
+ # boundary conditions, and ptcond to match your problem. See
8
+ # `refs/MPIEMSES3D/docs/Parameters.md` (or the project's CLAUDE.md) for
9
+ # the full parameter reference, or run `runops case new ... ` without
10
+ # `--minimal` to drop in the full reference template via refs/.
11
+
12
+ [meta]
13
+ format_version = 2
14
+
15
+ [meta.unit_conversion]
16
+ dx = 0.5
17
+ to_c = 10000.0
18
+
19
+ # ===========================================================================
20
+ # Species — one electron + one ion (proton). Add more [[species]] blocks as
21
+ # needed. Run `emu generate -u plasma.toml` to populate [meta.physical] with
22
+ # SI values derived from the simulation units below.
23
+ # ===========================================================================
24
+
25
+ [[species]]
26
+ wp = 2.10390362104881
27
+ qm = -1.0
28
+ path = 44.237443494336965
29
+ peth = 44.237443494336965
30
+ vdri = 0.0
31
+ vdthz = 0.0
32
+ vdthxy = 0.0
33
+ npin = 5242880
34
+ np = 10485760
35
+ npbnd = [0, 0, 2]
36
+ inpf = 1
37
+ inpb = 1
38
+ injct = 1
39
+ npr = 524288
40
+ nflag_emit = 0
41
+ nepl = 0
42
+ ipahdf = 0
43
+ ipadig = 1024
44
+ ipaxyz = [0, 0, 0, 0, 0, 0, 0]
45
+
46
+ [[species]]
47
+ wp = 0.04909886429062906
48
+ qm = 0.000544617021778308
49
+ path = 1.032370595764297
50
+ peth = 1.032370595764297
51
+ vdri = 0.0
52
+ vdthz = 0.0
53
+ vdthxy = 0.0
54
+ npin = 5242880
55
+ np = 10485760
56
+ npbnd = [0, 0, 2]
57
+ inpf = 1
58
+ inpb = 1
59
+ injct = 1
60
+ npr = 524288
61
+ nflag_emit = 0
62
+ nepl = 0
63
+ ipahdf = 0
64
+ ipadig = 1024
65
+
66
+ # ===========================================================================
67
+ [esorem]
68
+ emflag = 0 # 0 = electrostatic, 1 = electromagnetic
69
+
70
+ [jobcon]
71
+ jobnum = [0, 1]
72
+ nstep = 10000
73
+
74
+ [digcon]
75
+ hdfdigstart = 0
76
+ intfoc = 100
77
+ ifdiag = 1000
78
+ ijdiag = 1000
79
+ daverg = 1
80
+ ifxyz = [1, 1, 1, 1, 1, 1, 1]
81
+ ijxyz = [1, 1, 1]
82
+
83
+ [plasma]
84
+ wc = 0.0
85
+ cv = 10000.0
86
+ phiz = 0.0
87
+ phixy = 0.0
88
+
89
+ [tmgrid]
90
+ dt = 0.002
91
+ nx = 32
92
+ ny = 32
93
+ nz = 256
94
+
95
+ [system]
96
+ ionchg = 0
97
+ nfbnd = [0, 0, 1]
98
+ mtd_vbnd = [0, 0, 1]
99
+ nflag_ecrct = 1
100
+
101
+ [ptcond]
102
+ modeww = -2
103
+ zssurf = 60.0
104
+ npc = 0
105
+ npcg = 0
106
+ pcgs = [0]
107
+ ccgs = [0]
108
+ mtd_vchg = [0]
109
+
110
+ [[ptcond.boundaries]]
111
+ type = "flat-surface"
112
+
113
+ [mpi]
114
+ nodes = [2, 4, 32]
115
+
116
+ [gradema]
117
+ grad_coef = 1.0
118
+ smooth_coef = 1.0