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.
- runops/__init__.py +5 -0
- runops/_data/README.md +476 -0
- runops/adapters/__init__.py +29 -0
- runops/adapters/_utils/__init__.py +36 -0
- runops/adapters/_utils/toml_utils.py +81 -0
- runops/adapters/base.py +335 -0
- runops/adapters/contrib/__init__.py +5 -0
- runops/adapters/contrib/beach.py +837 -0
- runops/adapters/contrib/emses.py +1010 -0
- runops/adapters/generic.py +439 -0
- runops/adapters/registry.py +244 -0
- runops/cli/__init__.py +3 -0
- runops/cli/analyze.py +222 -0
- runops/cli/clone.py +104 -0
- runops/cli/config.py +217 -0
- runops/cli/context.py +56 -0
- runops/cli/create.py +263 -0
- runops/cli/dashboard.py +179 -0
- runops/cli/extend.py +204 -0
- runops/cli/history.py +105 -0
- runops/cli/init.py +1432 -0
- runops/cli/jobs.py +145 -0
- runops/cli/knowledge.py +1017 -0
- runops/cli/list.py +102 -0
- runops/cli/log.py +163 -0
- runops/cli/main.py +96 -0
- runops/cli/manage.py +231 -0
- runops/cli/new.py +343 -0
- runops/cli/notes.py +257 -0
- runops/cli/run_lookup.py +148 -0
- runops/cli/setup.py +174 -0
- runops/cli/status.py +187 -0
- runops/cli/submit.py +297 -0
- runops/cli/update.py +113 -0
- runops/cli/update_harness.py +245 -0
- runops/cli/update_refs.py +370 -0
- runops/core/__init__.py +3 -0
- runops/core/actions.py +1186 -0
- runops/core/analysis.py +1090 -0
- runops/core/campaign.py +156 -0
- runops/core/case.py +307 -0
- runops/core/context.py +426 -0
- runops/core/discovery.py +192 -0
- runops/core/environment.py +266 -0
- runops/core/exceptions.py +93 -0
- runops/core/knowledge.py +595 -0
- runops/core/knowledge_source.py +1204 -0
- runops/core/manifest.py +219 -0
- runops/core/project.py +171 -0
- runops/core/provenance.py +147 -0
- runops/core/retry.py +193 -0
- runops/core/run.py +170 -0
- runops/core/run_creation.py +456 -0
- runops/core/site.py +337 -0
- runops/core/state.py +197 -0
- runops/core/survey.py +380 -0
- runops/core/validation.py +40 -0
- runops/harness/__init__.py +27 -0
- runops/harness/builder.py +327 -0
- runops/harness/claude.py +189 -0
- runops/jobgen/__init__.py +3 -0
- runops/jobgen/generator.py +295 -0
- runops/launchers/__init__.py +17 -0
- runops/launchers/base.py +313 -0
- runops/launchers/mpiexec.py +131 -0
- runops/launchers/mpirun.py +132 -0
- runops/launchers/srun.py +126 -0
- runops/sites/__init__.py +0 -0
- runops/sites/camphor.md +98 -0
- runops/sites/camphor.toml +27 -0
- runops/slurm/__init__.py +3 -0
- runops/slurm/query.py +384 -0
- runops/slurm/submit.py +203 -0
- runops/templates/__init__.py +29 -0
- runops/templates/adapters/beach/agent_guide.md +50 -0
- runops/templates/adapters/beach/beach.toml +19 -0
- runops/templates/adapters/beach/case.toml +16 -0
- runops/templates/adapters/beach/summarize.py +272 -0
- runops/templates/adapters/emses/agent_guide.md +39 -0
- runops/templates/adapters/emses/case.toml +18 -0
- runops/templates/adapters/emses/plasma.toml +118 -0
- runops/templates/adapters/emses/summarize.py +413 -0
- runops/templates/adapters/generic/case.toml.j2 +13 -0
- runops/templates/adapters/generic/summarize.py +21 -0
- runops/templates/agent.md +156 -0
- runops/templates/rules/cookbook.md +22 -0
- runops/templates/scaffold/campaign.toml.j2 +10 -0
- runops/templates/scaffold/cases_claude.md +22 -0
- runops/templates/scaffold/facts.toml +2 -0
- runops/templates/scaffold/gitignore.txt +30 -0
- runops/templates/scaffold/notes/README.md +69 -0
- runops/templates/scaffold/rules/plan-before-act.md +17 -0
- runops/templates/scaffold/rules/runops-workflow.md +84 -0
- runops/templates/scaffold/rules/upstream-feedback.md +85 -0
- runops/templates/scaffold/runs_claude.md +24 -0
- runops/templates/scaffold/vscode_settings.json +9 -0
- runops/templates/skills/analyze/SKILL.md +40 -0
- runops/templates/skills/check-status/SKILL.md +29 -0
- runops/templates/skills/cleanup/SKILL.md +43 -0
- runops/templates/skills/create-run/SKILL.md +135 -0
- runops/templates/skills/debug-failed/SKILL.md +38 -0
- runops/templates/skills/learn/SKILL.md +54 -0
- runops/templates/skills/new-case/SKILL.md +108 -0
- runops/templates/skills/note/SKILL.md +107 -0
- runops/templates/skills/run-all/SKILL.md +47 -0
- runops/templates/skills/runops-reference/SKILL.md +203 -0
- runops/templates/skills/setup-campaign/SKILL.md +111 -0
- runops/templates/skills/setup-env/SKILL.md +32 -0
- runops/templates/skills/survey-design/SKILL.md +73 -0
- runops/templates/survey.toml.j2 +22 -0
- runops-0.2.0.dist-info/METADATA +491 -0
- runops-0.2.0.dist-info/RECORD +115 -0
- runops-0.2.0.dist-info/WHEEL +4 -0
- runops-0.2.0.dist-info/entry_points.txt +2 -0
- runops-0.2.0.dist-info/licenses/LICENSE +201 -0
runops/adapters/base.py
ADDED
|
@@ -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
|
+
...
|