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,335 @@
1
+ """Abstract base class for simulator adapters.
2
+
3
+ Each simulator (e.g. lunar_pic) must implement this interface to handle
4
+ its specific input format, execution command, output detection, and
5
+ provenance collection.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import shlex
11
+ from abc import ABC, abstractmethod
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ from runops.core.validation import ValidationIssue
16
+
17
+
18
+ class SimulatorAdapter(ABC):
19
+ """Abstract base class for simulator-specific adapters.
20
+
21
+ Adapters handle everything that varies between simulators:
22
+ input file rendering, runtime resolution, command construction,
23
+ output detection, status inference, summarization, and provenance.
24
+ """
25
+
26
+ @classmethod
27
+ def default_config(cls) -> dict[str, Any]:
28
+ """Return the default simulators.toml entry for this adapter.
29
+
30
+ Override in subclasses to provide simulator-specific defaults.
31
+
32
+ Returns:
33
+ Dictionary suitable for writing as a ``[simulators.<name>]``
34
+ section in ``simulators.toml``.
35
+ """
36
+ return {
37
+ "adapter": getattr(cls, "adapter_name", ""),
38
+ "resolver_mode": "local_executable",
39
+ "executable": "",
40
+ }
41
+
42
+ @classmethod
43
+ def interactive_config(cls) -> dict[str, Any]:
44
+ """Interactively prompt the user to build a simulator config.
45
+
46
+ Uses typer.prompt() for each configurable field.
47
+ Override in subclasses for simulator-specific prompts.
48
+
49
+ Returns:
50
+ Configuration dictionary for simulators.toml.
51
+ """
52
+ import typer
53
+
54
+ defaults = cls.default_config()
55
+ adapter_name = defaults.get("adapter", "")
56
+
57
+ typer.echo(f"\n Configuring '{adapter_name}' simulator:")
58
+
59
+ resolver_mode = typer.prompt(
60
+ " Resolver mode (local_executable / local_source / package)",
61
+ default=defaults.get("resolver_mode", "local_executable"),
62
+ )
63
+ executable = typer.prompt(
64
+ " Executable path or name",
65
+ default=defaults.get("executable", ""),
66
+ )
67
+
68
+ config = {
69
+ "adapter": adapter_name,
70
+ "resolver_mode": resolver_mode,
71
+ "executable": executable,
72
+ }
73
+
74
+ if resolver_mode == "local_source":
75
+ source_repo = typer.prompt(" Source repository path", default="")
76
+ build_command = typer.prompt(" Build command", default="make -j")
77
+ config["source_repo"] = source_repo
78
+ config["build_command"] = build_command
79
+
80
+ # Carry over non-prompted defaults (e.g. modules)
81
+ for key, value in defaults.items():
82
+ if key not in config:
83
+ config[key] = value
84
+
85
+ return config
86
+
87
+ @classmethod
88
+ def case_template(cls) -> dict[str, str]:
89
+ """Return template files for a new case.
90
+
91
+ Override in subclasses to provide simulator-specific templates.
92
+
93
+ Returns:
94
+ Dict mapping filename to content string.
95
+ Must include ``"case.toml"``.
96
+ """
97
+ from runops.templates import render
98
+
99
+ name = getattr(cls, "adapter_name", "generic")
100
+ return {
101
+ "case.toml": render("adapters/generic/case.toml.j2", name=name),
102
+ "summarize.py": render("adapters/generic/summarize.py"),
103
+ }
104
+
105
+ @classmethod
106
+ def pip_packages(cls) -> list[str]:
107
+ """Return pip packages to install for this simulator.
108
+
109
+ Override in subclasses to list Python packages needed for
110
+ analysis, post-processing, or utilities.
111
+
112
+ Returns:
113
+ List of pip package specifiers (e.g. ``["emout", "h5py"]``).
114
+ """
115
+ return []
116
+
117
+ @classmethod
118
+ def doc_repos(cls) -> list[tuple[str, str]]:
119
+ """Return documentation/reference repositories to clone.
120
+
121
+ Override in subclasses to list Git repositories that contain
122
+ parameter references, usage examples, or documentation that
123
+ AI agents and users can consult.
124
+
125
+ Returns:
126
+ List of ``(clone_url, dest_dir_name)`` tuples.
127
+ ``dest_dir_name`` is the directory name under the project's
128
+ ``refs/`` directory.
129
+ """
130
+ return []
131
+
132
+ @classmethod
133
+ def knowledge_sources(cls) -> dict[str, list[str]]:
134
+ """Return glob patterns for knowledge-relevant files per doc repo.
135
+
136
+ Override in subclasses to specify which files in each reference
137
+ repository (under ``refs/``) should be indexed into the
138
+ ``.runops/knowledge/`` directory.
139
+
140
+ Returns:
141
+ Dictionary mapping ``dest_dir_name`` (from :meth:`doc_repos`)
142
+ to a list of glob patterns relative to the repo root.
143
+ """
144
+ return {}
145
+
146
+ @classmethod
147
+ def parameter_schema(cls) -> dict[str, dict[str, Any]]:
148
+ """Return machine-readable parameter metadata.
149
+
150
+ Override in subclasses to provide parameter definitions including
151
+ type, unit, valid range, constraints, derivation formulas, and
152
+ interdependencies.
153
+
154
+ Returns:
155
+ Dict mapping dot-notation parameter paths to metadata dicts.
156
+ Each metadata dict may contain keys: ``type``, ``unit``,
157
+ ``description``, ``range`` (``[min, max]``, ``None`` =
158
+ unbounded), ``default``, ``constraints`` (list of
159
+ constraint names), ``derived_from`` (formula string),
160
+ ``interdependencies`` (list of related parameter paths).
161
+ """
162
+ return {}
163
+
164
+ @classmethod
165
+ def default_plot_recipes(cls) -> dict[str, dict[str, Any]]:
166
+ """Return adapter-aware survey plot recipes.
167
+
168
+ Each recipe may define ``description``, ``x``, ``y``, ``kind``,
169
+ ``group_by``, and ``title``. ``x`` / ``y`` / ``group_by`` may be
170
+ either a string or a list of fallback column names.
171
+ """
172
+ return {}
173
+
174
+ def validate_params(
175
+ self,
176
+ case_data: dict[str, Any],
177
+ ) -> list[ValidationIssue]:
178
+ """Validate parameters against physics constraints.
179
+
180
+ Called before run creation to catch configuration errors early.
181
+ Override in subclasses to implement simulator-specific checks.
182
+
183
+ Args:
184
+ case_data: Case data dict with ``"case"`` and ``"params"``
185
+ sections (same structure as :meth:`render_inputs`).
186
+
187
+ Returns:
188
+ List of :class:`ValidationIssue` instances.
189
+ Empty list means no issues found.
190
+ """
191
+ return []
192
+
193
+ @classmethod
194
+ def agent_guide(cls) -> str:
195
+ """Return AI agent guide for this simulator as markdown.
196
+
197
+ Override in subclasses to provide simulator-specific knowledge
198
+ including input/output formats, key parameters, typical
199
+ workflows, and documentation references.
200
+
201
+ Returns:
202
+ Markdown string for inclusion in CLAUDE.md / AGENTS.md.
203
+ """
204
+ name = getattr(cls, "adapter_name", cls.__name__)
205
+ return f"### {name}\n\nNo detailed guide available.\n"
206
+
207
+ @property
208
+ @abstractmethod
209
+ def name(self) -> str:
210
+ """Return the canonical name of this adapter."""
211
+ ...
212
+
213
+ @abstractmethod
214
+ def render_inputs(self, case_data: dict[str, Any], run_dir: Path) -> list[str]:
215
+ """Generate simulator input files in the run directory.
216
+
217
+ Args:
218
+ case_data: Merged case/survey parameters.
219
+ run_dir: Target run directory.
220
+
221
+ Returns:
222
+ List of relative paths to generated input files.
223
+ """
224
+ ...
225
+
226
+ @abstractmethod
227
+ def resolve_runtime(
228
+ self, simulator_config: dict[str, Any], resolver_mode: str
229
+ ) -> dict[str, Any]:
230
+ """Resolve the simulator runtime (executable, build, etc.).
231
+
232
+ Args:
233
+ simulator_config: Simulator section from simulators.toml.
234
+ resolver_mode: One of "package", "local_source", "local_executable".
235
+
236
+ Returns:
237
+ Runtime information dictionary including executable path.
238
+ """
239
+ ...
240
+
241
+ @abstractmethod
242
+ def build_program_command(
243
+ self, runtime_info: dict[str, Any], run_dir: Path
244
+ ) -> list[str]:
245
+ """Build the simulator execution command (without MPI launcher prefix).
246
+
247
+ Args:
248
+ runtime_info: Output from resolve_runtime.
249
+ run_dir: The run directory.
250
+
251
+ Returns:
252
+ Command as a list of strings.
253
+ """
254
+ ...
255
+
256
+ def build_version_capture_commands(
257
+ self,
258
+ runtime_info: dict[str, Any],
259
+ program_cmd: list[str],
260
+ run_dir: Path,
261
+ ) -> list[str]:
262
+ """Return shell commands that record simulator version metadata.
263
+
264
+ The default implementation probes ``--version`` and ``-V`` on the
265
+ resolved executable and writes the output to ``SIMULATOR_VERSION.txt``
266
+ in the run's ``work/`` directory. Failures are swallowed so the main
267
+ simulation is not blocked by missing version flags.
268
+ """
269
+ executable = str(runtime_info.get("executable", "")).strip()
270
+ if not executable and program_cmd:
271
+ executable = str(program_cmd[0]).strip()
272
+ if not executable:
273
+ return []
274
+
275
+ quoted = shlex.quote(executable)
276
+ output_path = shlex.quote(str(run_dir / "work" / "SIMULATOR_VERSION.txt"))
277
+ display = shlex.quote(f"executable: {executable}")
278
+ return [
279
+ (
280
+ "( "
281
+ f"printf '%s\\n' {display}; "
282
+ "date -Iseconds; "
283
+ f"{quoted} --version || {quoted} -V || "
284
+ "printf '%s\\n' 'version probe unsupported'"
285
+ f" ) > {output_path} 2>&1 || true"
286
+ )
287
+ ]
288
+
289
+ @abstractmethod
290
+ def detect_outputs(self, run_dir: Path) -> dict[str, Any]:
291
+ """Detect output files produced by the simulation.
292
+
293
+ Args:
294
+ run_dir: The run directory.
295
+
296
+ Returns:
297
+ Dictionary describing detected outputs.
298
+ """
299
+ ...
300
+
301
+ @abstractmethod
302
+ def detect_status(self, run_dir: Path) -> str:
303
+ """Infer simulation completion status from output files.
304
+
305
+ Args:
306
+ run_dir: The run directory.
307
+
308
+ Returns:
309
+ A status string (e.g. "completed", "failed").
310
+ """
311
+ ...
312
+
313
+ @abstractmethod
314
+ def summarize(self, run_dir: Path) -> dict[str, Any]:
315
+ """Extract key metrics from simulation outputs.
316
+
317
+ Args:
318
+ run_dir: The run directory.
319
+
320
+ Returns:
321
+ Summary dictionary for analysis/summary.json.
322
+ """
323
+ ...
324
+
325
+ @abstractmethod
326
+ def collect_provenance(self, runtime_info: dict[str, Any]) -> dict[str, Any]:
327
+ """Collect code provenance for manifest recording.
328
+
329
+ Args:
330
+ runtime_info: Output from resolve_runtime.
331
+
332
+ Returns:
333
+ Provenance dictionary (git commit, exe hash, etc.).
334
+ """
335
+ ...
@@ -0,0 +1,5 @@
1
+ """Simulator-specific adapter implementations.
2
+
3
+ Add new simulators here by creating a module with a class
4
+ that inherits from ``SimulatorAdapter``.
5
+ """