simrun 0.1.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.
simrun/workflow.py ADDED
@@ -0,0 +1,188 @@
1
+ """The workflow model: tasks, the references between them and their order."""
2
+
3
+ from enum import StrEnum
4
+ from pathlib import Path
5
+ from typing import Any, Self
6
+
7
+ import yaml
8
+ from pydantic import BaseModel, Field, ValidationError, model_validator
9
+
10
+
11
+ class SplitMode(StrEnum):
12
+ """How the pieces of a task are placed on the nodes."""
13
+
14
+ FASTEST = "fastest"
15
+ SINGLE_SERVER = "single-server"
16
+
17
+
18
+ class Priority(StrEnum):
19
+ """The priority of a run in the queue."""
20
+
21
+ OWNER = "owner"
22
+ STANDARD = "standard"
23
+ AGENT = "agent"
24
+
25
+
26
+ class WorkflowError(ValueError):
27
+ """A workflow which is not valid."""
28
+
29
+
30
+ class ResourceOverride(BaseModel):
31
+ """Resources of a task which replace the learned or default ones."""
32
+
33
+ workers: int | None = None
34
+ memory_per_worker_gb: float | None = None
35
+ scratch_gb: float | None = None
36
+ gpus: int | None = None
37
+
38
+
39
+ class TaskSpec(BaseModel):
40
+ """A task of a workflow.
41
+
42
+ Attributes:
43
+ id: Identifier of the task within the workflow.
44
+ type: Name of the task plugin.
45
+ settings: Settings handed to the plugin.
46
+ inputs: Input port to the output of another task, `<task>.<port>`.
47
+ split: Split mode of the task, the one of the workflow by default.
48
+ resources: Resources replacing the learned or default ones.
49
+ stage: Input ports copied to local scratch.
50
+ """
51
+
52
+ id: str = Field(pattern=r"^[a-z][a-z0-9-]*$")
53
+ type: str
54
+ settings: dict[str, Any] = Field(default_factory=dict)
55
+ inputs: dict[str, str] = Field(default_factory=dict)
56
+ split: SplitMode | None = None
57
+ resources: ResourceOverride | None = None
58
+ stage: list[str] = Field(default_factory=list)
59
+
60
+ def dependencies(self) -> list[str]:
61
+ """Ids of the tasks this task reads from, in input order, without repeats."""
62
+ return list(dict.fromkeys(ref.split(".", 1)[0] for ref in self.inputs.values()))
63
+
64
+
65
+ class Workflow(BaseModel):
66
+ """A workflow: tasks which read the outputs of each other."""
67
+
68
+ name: str
69
+ tasks: list[TaskSpec]
70
+ split: SplitMode = SplitMode.FASTEST
71
+ reserve: str | None = None
72
+
73
+ def __init__(self, **data: Any) -> None:
74
+ """Create a workflow, a reference error is raised as a `WorkflowError`."""
75
+ try:
76
+ super().__init__(**data)
77
+ except ValidationError as error:
78
+ for detail in error.errors():
79
+ cause = detail.get("ctx", {}).get("error")
80
+ if isinstance(cause, WorkflowError):
81
+ raise cause from error
82
+ raise
83
+
84
+ @model_validator(mode="after")
85
+ def _validate(self) -> Self:
86
+ ids = [t.id for t in self.tasks]
87
+ duplicates = sorted({i for i in ids if ids.count(i) > 1})
88
+ if duplicates:
89
+ raise WorkflowError(f"duplicate task ids: {', '.join(duplicates)}")
90
+ for task in self.tasks:
91
+ for port, ref in task.inputs.items():
92
+ source, dot, output = ref.partition(".")
93
+ if not dot or not output:
94
+ raise WorkflowError(
95
+ f"input {port!r} of task {task.id!r} must be "
96
+ f"'<task>.<port>', got {ref!r}"
97
+ )
98
+ if source not in ids:
99
+ raise WorkflowError(
100
+ f"input {port!r} of task {task.id!r} refers to "
101
+ f"unknown task {source!r}"
102
+ )
103
+ self.order()
104
+ return self
105
+
106
+ def task(self, task_id: str) -> TaskSpec:
107
+ """Return the task with the given id.
108
+
109
+ Raises:
110
+ WorkflowError: If there is no such task.
111
+ """
112
+ for task in self.tasks:
113
+ if task.id == task_id:
114
+ return task
115
+ raise WorkflowError(f"unknown task {task_id!r}")
116
+
117
+ def order(self) -> list[TaskSpec]:
118
+ """Return the tasks in topological order, stable in file order.
119
+
120
+ Raises:
121
+ WorkflowError: If the references form a cycle.
122
+ """
123
+ remaining = {t.id: set(t.dependencies()) for t in self.tasks}
124
+ done: set[str] = set()
125
+ ordered: list[TaskSpec] = []
126
+ while remaining:
127
+ ready = next(
128
+ (
129
+ t
130
+ for t in self.tasks
131
+ if t.id in remaining and remaining[t.id] <= done
132
+ ),
133
+ None,
134
+ )
135
+ if ready is None:
136
+ raise WorkflowError(
137
+ f"cycle among tasks: {', '.join(sorted(remaining))}"
138
+ )
139
+ ordered.append(ready)
140
+ done.add(ready.id)
141
+ del remaining[ready.id]
142
+ return ordered
143
+
144
+
145
+ def load_workflow(path: Path) -> Workflow:
146
+ """Read a workflow from a YAML file.
147
+
148
+ Raises:
149
+ WorkflowError: If the file is not a valid workflow.
150
+ """
151
+ try:
152
+ data = yaml.safe_load(path.read_text())
153
+ if not isinstance(data, dict):
154
+ raise WorkflowError(f"{path}: not a mapping")
155
+ return Workflow(**data)
156
+ except (ValidationError, yaml.YAMLError, UnicodeDecodeError, TypeError) as error:
157
+ raise WorkflowError(f"{path}: {error}") from error
158
+
159
+
160
+ def dump_workflow(workflow: Workflow, path: Path) -> None:
161
+ """Write a workflow as a YAML file."""
162
+ data = workflow.model_dump(mode="json")
163
+ path.write_text(yaml.safe_dump(data, sort_keys=False))
164
+
165
+
166
+ class _IndentedDumper(yaml.SafeDumper):
167
+ """The safe dumper which indents a sequence below its key, as `- id:` below `tasks:`."""
168
+
169
+ def increase_indent(self, flow: bool = False, indentless: bool = False) -> None:
170
+ """Indent every block, also a sequence which PyYAML would leave indentless."""
171
+ super().increase_indent(flow, False)
172
+
173
+
174
+ def workflow_yaml(workflow: Workflow) -> str:
175
+ """The compact YAML of a workflow: the fields which differ from the defaults.
176
+
177
+ `name` and `tasks` are always written, also when they are the defaults. A
178
+ sequence is indented below its key, as the workflows of the docs are written.
179
+
180
+ Args:
181
+ workflow: The workflow.
182
+
183
+ Returns:
184
+ The YAML text.
185
+ """
186
+ data = workflow.model_dump(mode="json", exclude_defaults=True)
187
+ data = {"name": workflow.name, "tasks": data.get("tasks", [])} | data
188
+ return yaml.dump(data, Dumper=_IndentedDumper, sort_keys=False)
@@ -0,0 +1,107 @@
1
+ Metadata-Version: 2.5
2
+ Name: simrun
3
+ Version: 0.1.0
4
+ Summary: Runner of simulation workflows on a k3s cluster with Argo Workflows and Kueue.
5
+ Project-URL: Homepage, https://matthiaskoenig.github.io/simrun
6
+ Project-URL: Documentation, https://matthiaskoenig.github.io/simrun
7
+ Project-URL: Source, https://github.com/matthiaskoenig/simrun
8
+ Project-URL: Issues, https://github.com/matthiaskoenig/simrun/issues
9
+ Project-URL: Changelog, https://github.com/matthiaskoenig/simrun/tree/develop/release-notes
10
+ Project-URL: Download, https://pypi.org/project/simrun
11
+ Author-email: Matthias König <konigmatt@googlemail.com>
12
+ Maintainer-email: Matthias König <konigmatt@googlemail.com>
13
+ License-Expression: MIT
14
+ License-File: LICENSE
15
+ Keywords: argo workflows,computational modeling,k3s,kubernetes,kueue,mcp,simulation,workflow
16
+ Classifier: Development Status :: 3 - Alpha
17
+ Classifier: Environment :: Console
18
+ Classifier: Framework :: FastAPI
19
+ Classifier: Intended Audience :: Science/Research
20
+ Classifier: Intended Audience :: System Administrators
21
+ Classifier: Operating System :: POSIX :: Linux
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Programming Language :: Python :: 3.15
24
+ Classifier: Programming Language :: Python :: Implementation :: CPython
25
+ Classifier: Topic :: Scientific/Engineering
26
+ Classifier: Topic :: System :: Clustering
27
+ Classifier: Topic :: System :: Distributed Computing
28
+ Requires-Python: >=3.14
29
+ Requires-Dist: fastapi>=0.162
30
+ Requires-Dist: fastmcp>=4.1
31
+ Requires-Dist: hera>=7.2
32
+ Requires-Dist: httpx>=0.28.1
33
+ Requires-Dist: kubernetes>=37
34
+ Requires-Dist: pydantic>=2.14
35
+ Requires-Dist: pyyaml>=6.0.3
36
+ Requires-Dist: rich>=14
37
+ Requires-Dist: tomlkit>=0.15.2
38
+ Requires-Dist: typer>=0.27
39
+ Requires-Dist: uvicorn>=0.54.0
40
+ Provides-Extra: cwl
41
+ Requires-Dist: cwl-utils>=0.46; extra == 'cwl'
42
+ Requires-Dist: cwltool>=3.3.20260925135507; extra == 'cwl'
43
+ Provides-Extra: dev
44
+ Requires-Dist: bump-my-version>=1.6.7; extra == 'dev'
45
+ Requires-Dist: cwl-utils>=0.46; extra == 'dev'
46
+ Requires-Dist: cwltool>=3.3.20260925135507; extra == 'dev'
47
+ Requires-Dist: mkdocstrings-python>=2.0.10; extra == 'dev'
48
+ Requires-Dist: pre-commit>=4.7.0; extra == 'dev'
49
+ Requires-Dist: pytest-xdist>=3.8.0; extra == 'dev'
50
+ Requires-Dist: pytest>=9.2.0; extra == 'dev'
51
+ Requires-Dist: ruff>=0.17.4; extra == 'dev'
52
+ Requires-Dist: tox!=4.67.5,>=4.64.9; extra == 'dev'
53
+ Requires-Dist: tox-uv>=1.36.1; extra == 'dev'
54
+ Requires-Dist: ty>=0.0.98; extra == 'dev'
55
+ Requires-Dist: zensical>=0.0.73; extra == 'dev'
56
+ Description-Content-Type: text/markdown
57
+
58
+ # simrun: simulation workflows on a k3s cluster
59
+ [![CI-CD](https://github.com/matthiaskoenig/simrun/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/matthiaskoenig/simrun/actions/workflows/ci-cd.yml)
60
+ [![Documentation](https://img.shields.io/badge/docs-simrun-3f51b5.svg)](https://matthiaskoenig.github.io/simrun)
61
+ [![Version](https://img.shields.io/pypi/v/simrun.svg)](https://pypi.org/project/simrun/)
62
+ [![Python Versions](https://img.shields.io/pypi/pyversions/simrun.svg)](https://pypi.org/project/simrun/)
63
+ [![MIT License](https://img.shields.io/pypi/l/simrun.svg)](https://opensource.org/licenses/MIT)
64
+ [![DOI](https://img.shields.io/badge/DOI-after%20the%20first%20release-lightgrey.svg)](https://matthiaskoenig.github.io/simrun/development/#pypi-and-zenodo)
65
+
66
+ `simrun` runs simulation workflows on a small cluster of servers with [k3s](https://k3s.io), [Argo Workflows](https://argoproj.github.io/workflows/) and [Kueue](https://kueue.sigs.k8s.io). People and agents submit a workflow of tasks together with the working trees of their model and libraries, uncommitted changes included, and get a run id at once; the run is queued, placed, executed and recorded without the submitter staying connected.
67
+
68
+ Features include
69
+
70
+ - **workflows** - YAML files of tasks of task types from plugins, which read the output ports of other tasks
71
+ - **split tasks** - the units of a task (the repeats of a fit, the points of a scan) spread over the idle servers and are taken back by runs of a higher priority, with results which do not depend on the split
72
+ - **priorities** - the owner of the cluster and the owner's agents first, then the other people, then their agents, with preemption by Kueue and reservations of nodes
73
+ - **learned resources** - the memory of a pod is learned from earlier runs, a pod killed for memory is retried with more
74
+ - **snapshots and environments** - the working trees are snapshotted into a shared run store, the third party dependencies are locked and built into an image only when they change
75
+ - **for people and agents** - a command line, an HTTP API and an MCP server to submit, follow, compare and cancel runs
76
+
77
+ ```bash
78
+ simrun submit fit.yaml --source model=~/git/pkdb_models --source sbmlsim=~/git/sbmlsim --group hctz
79
+ simrun wait <run>
80
+ simrun compare <run> <run>
81
+ ```
82
+
83
+ The documentation is available at [https://matthiaskoenig.github.io/simrun](https://matthiaskoenig.github.io/simrun): [installation](https://matthiaskoenig.github.io/simrun/installation/), [concepts](https://matthiaskoenig.github.io/simrun/concepts/), [workflows](https://matthiaskoenig.github.io/simrun/workflows/), [command line](https://matthiaskoenig.github.io/simrun/command-line/), [agents and MCP](https://matthiaskoenig.github.io/simrun/mcp/), [plugins](https://matthiaskoenig.github.io/simrun/plugins/), [service](https://matthiaskoenig.github.io/simrun/service/), [cluster deployment](https://matthiaskoenig.github.io/simrun/deployment/) and [development](https://matthiaskoenig.github.io/simrun/development/).
84
+
85
+ If you have any questions or issues please [open an issue](https://github.com/matthiaskoenig/simrun/issues).
86
+
87
+ ## Installation
88
+
89
+ ```bash
90
+ pip install simrun
91
+ ```
92
+
93
+ `simrun` needs python 3.14 or newer and runs on Linux, see [Installation](https://matthiaskoenig.github.io/simrun/installation/).
94
+
95
+ ## How to cite
96
+
97
+ If you use `simrun` please cite the archived software on [Zenodo](https://zenodo.org); the DOI follows with the first release, see [CITATION.cff](CITATION.cff).
98
+
99
+ ## License
100
+
101
+ `simrun` is licensed under the [MIT License](LICENSE).
102
+
103
+ ## Funding
104
+
105
+ Matthias König is supported by the Federal Ministry of Education and Research (BMBF, Germany) within the research network Systems Medicine of the Liver (**LiSyM**, grant number 031L0054) and by the German Research Foundation (DFG) within the Research Unit Programme FOR 5151 [QuaLiPerF](https://qualiperf.de) (Quantifying Liver Perfusion-Function Relationship in Complex Resection - A Systems Medicine Approach) by grant number 436883643 and by grant number 465194077 (Priority Programme SPP 2311, Subproject SimLivA). Matthias König has received funds from the EOSCsecretariat.eu which has received funding from the European Union's Horizon Programme call H2020-INFRAEOSC-05-2018-2019, grant Agreement number 831644.
106
+
107
+ © 2026 Matthias König
@@ -0,0 +1,39 @@
1
+ simrun/__init__.py,sha256=2WkMl29mm1mI4GONAAt1hwehkyf5Mk_eGtZBeo19Sfs,88
2
+ simrun/argo.py,sha256=AdOoY3cHBQFAZEcP9oHIcnYPuf-dtlT-arSQeUn3E8M,17169
3
+ simrun/build_constraints.txt,sha256=vUuUCjavINiqy0H0W4Qa4lx_1OQ29RswduUukMHd2QA,722
4
+ simrun/cgroup.py,sha256=pkLqXMRssLstML-2BkUdNZkpjuXfMdlLXuELTnoGpCI,2240
5
+ simrun/cli.py,sha256=TTmBtEP4k8xgGxM9xICJ3j9uG6oT9jPWN9eWO-eJGas,10996
6
+ simrun/client.py,sha256=Z6r2beJj2M1RXB420inAIW7p7Ow4rNiRU9p_p72czDQ,15077
7
+ simrun/config.py,sha256=vYvO0CIDThMJBm76fcT-vOZcjBDzWtypmcWCUG7P9mY,2882
8
+ simrun/cwl.py,sha256=iMNWRPdqNKmXlROSmnrb_GGN1xyoJIx2vL9Uh4V1mUs,10294
9
+ simrun/demo.py,sha256=I0Gy1VpKdGA95crR_4UNxrttpgEJK9AVjBrB2cX6Dfo,3039
10
+ simrun/edit.py,sha256=nYNhLJXWJrRFUL0W2jEC7weuHexRTkihasR47AW6LMo,14618
11
+ simrun/environment.py,sha256=f92LEuwPcLz5Vu3JTUbzl2cRKhJQ9wsyLlNg0sad_mA,16413
12
+ simrun/ledger.py,sha256=RD6tuEoUSJ2p8qfdWI6s12jgxqwYx3-r-wdQjaVTxik,10799
13
+ simrun/mcp.py,sha256=xIUd1QXTIGGh8gPzT0xKXHejNAky4atW0vi3R2VsJNY,5779
14
+ simrun/planner.py,sha256=vdaCrFwFDd_vI639vhkTEbS3xkIj2cx6tkMwqKACSag,9907
15
+ simrun/plugins.py,sha256=nG7qm_d4sF4oc0qhPPnUBhueg4TDM2a9gZh4bM4RMvw,5436
16
+ simrun/plugins_builtin.py,sha256=k8D_28hA9hF5v1y-t0sFxTiTRgJWYi-AXgc9WhmkErk,735
17
+ simrun/podspec.py,sha256=SlRZcECVPXtx8RzWMn6AqRYg6k9Yxctga8SZ1De5zFY,5976
18
+ simrun/resources.py,sha256=8E-wfz96dqE5b7gmPjjq4v8uMyxy-7COrSqD6weYsI4,9709
19
+ simrun/snapshot.py,sha256=iqyhO3C1ONddOXIPHKlPK0VkHCDQht1ixTUHxvz0_FM,7020
20
+ simrun/store.py,sha256=BefcMp_7psI5JcBnZBhW3hyGqxfoIASge0pLvXUOJrw,5749
21
+ simrun/testing.py,sha256=NDYf5iG5yP3fDnrJPodVCJzmMnl-J9MuE17eHJ7PENY,9986
22
+ simrun/worker.py,sha256=NcI4jjLiw_l-Z5xuY3UiUMIet3KQON8eP2snbqDgQZ8,40011
23
+ simrun/workflow.py,sha256=Z-XEsS2VKkutO--As8L6Lxsq5MaOglkudBI0diPzE4U,6294
24
+ simrun/service/__init__.py,sha256=uB44sSOqMuPgxogrzpSbqTSLlOUAd1giqDFmRocqJFY,83
25
+ simrun/service/app.py,sha256=pfAub9oPGOa7ynzJoJATn3FfC53uInK3ze7ih4-24v0,64999
26
+ simrun/service/argo_backend.py,sha256=8pPRVl3Jpgz5IzqMyYR8ywEUaoMMqW9BdQi-MYie8pk,33873
27
+ simrun/service/auth.py,sha256=MlVBPMy6X_NACq66YO9B1CLvkSazTwk10FaUUkxz9nA,2944
28
+ simrun/service/backend.py,sha256=E-jsRzcYPCQCg0Xejfh_MfM3ddAJoPM4uaOf21bp-P8,5685
29
+ simrun/service/compare.py,sha256=o-P-Rwghmo4pZ4a0XTTFqVBF_EPvd6KRa7FJXF_aCjc,2404
30
+ simrun/service/controller.py,sha256=K0NpDTgr0_acfPCu3Xbk10neJKCbL45Ah1k70EDuXws,32918
31
+ simrun/service/edits.py,sha256=kBuQr7AyPViWm-EYDIxeuT-l0T-0ryBFBpjB0Nv68Y8,12974
32
+ simrun/service/index.py,sha256=fPQCImiVHXfCRCib1scWCxG1cjx31qwBhUuOdXDBYwY,10914
33
+ simrun/service/logs.py,sha256=fmLfVDyvbbqQeZaHDjNR9RZ2Usq4P1UO45u405hCVPY,3324
34
+ simrun/service/submission.py,sha256=yTYGxLXoKE6PrTWfjhPFxYgnVmt7yMF71xltxCb_u_c,13852
35
+ simrun-0.1.0.dist-info/METADATA,sha256=LxhHtHnHuoATTKNEkEN_tn_T9iwYcZ1m2_LuFfwRd48,6813
36
+ simrun-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
37
+ simrun-0.1.0.dist-info/entry_points.txt,sha256=mYlANnOVF5jAfb1G-5RFtiNEbCnoPs2dIe34ZALW7lQ,281
38
+ simrun-0.1.0.dist-info/licenses/LICENSE,sha256=zPHyGxsERxx2deQExcGSq3mg5NgDaCZ5-4pfkbgsEXo,1059
39
+ simrun-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,10 @@
1
+ [console_scripts]
2
+ simrun = simrun.cli:app
3
+ simrun-exec = simrun.worker:exec_main
4
+ simrun-mcp = simrun.mcp:main
5
+ simrun-merge = simrun.worker:merge_main
6
+ simrun-worker = simrun.worker:main
7
+
8
+ [simrun.task_types]
9
+ command = simrun.plugins_builtin:COMMAND
10
+ demo.squares = simrun.demo:SQUARES
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2026 Matthias König
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.