simhub 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.
simhub/__init__.py ADDED
@@ -0,0 +1,239 @@
1
+ """simhub: define an environment, a policy and a task in Python; run them anywhere.
2
+
3
+ Two things ship from this package and they are built from one source tree.
4
+
5
+ **The library** -- what you import. Three small protocols (an environment, a
6
+ policy, a task), one episode loop, one recorder, and a client that launches
7
+ batches of the same thing on hosted GPUs::
8
+
9
+ import simhub
10
+
11
+ @simhub.environment("warehouse-pick")
12
+ def build(cell_seed: int = 7) -> simhub.Env:
13
+ from .mjcf import build_mjcf # heavy imports inside
14
+ return PickEnv(build_mjcf(cell_seed))
15
+
16
+ @simhub.policy("scripted")
17
+ def scripted(env):
18
+ from .policies import ScriptedPick
19
+ return ScriptedPick(env)
20
+
21
+ simhub.run("warehouse-pick", "scripted", out="out/one") # here, now
22
+
23
+ sh = simhub.connect()
24
+ batch = sh.project("./warehouse_pick").launch(
25
+ env="warehouse-pick",
26
+ policies=["scripted", "groot-ft"],
27
+ seeds=range(32),
28
+ task=simhub.Task(placed="eq:1", collisions="eq:0"),
29
+ record=simhub.Record(cameras=["hero", "wrist"], video="hero"),
30
+ )
31
+ batch.wait(); batch.report().table()
32
+
33
+ **The server** -- the artifact store, job queue and workers under
34
+ ``simhub.api``, ``simhub.worker`` and ``simhub.adapters``. Never published;
35
+ installed from source in this repository.
36
+
37
+ Both live here so that ``protocol.py`` -- the thing most likely to drift --
38
+ exists exactly once. The two distributions must never be installed into the
39
+ same virtual environment; they provide the same import path.
40
+
41
+ Everything below is imported lazily. Importing ``simhub`` in an API container
42
+ must not cost numpy, and importing it in a notebook must not cost FastAPI.
43
+ """
44
+
45
+ from __future__ import annotations
46
+
47
+ from typing import TYPE_CHECKING, Any
48
+
49
+ __version__ = "0.1.0"
50
+
51
+ if TYPE_CHECKING: # pragma: no cover - names for a type checker, bound lazily below
52
+ from simhub.batch import Batch as Batch
53
+ from simhub.checks import CheckReport as CheckReport
54
+ from simhub.checks import check as check
55
+ from simhub.client import Client as Client
56
+ from simhub.client import SimhubError as SimhubError
57
+ from simhub.client import connect as connect
58
+ from simhub.env import Env as Env
59
+ from simhub.env import Obs as Obs
60
+ from simhub.env import StepInfo as StepInfo
61
+ from simhub.env import environment as environment
62
+ from simhub.env import make_env as make_env
63
+ from simhub.named_assets import NamedAsset as NamedAsset
64
+ from simhub.named_assets import asset as asset
65
+ from simhub.policies import Policy as Policy
66
+ from simhub.policies import make_policy as make_policy
67
+ from simhub.policies import policy as policy
68
+ from simhub.project import Project as Project
69
+ from simhub.recording import Recorder as Recorder
70
+ from simhub.recording import VideoWriter as VideoWriter
71
+ from simhub.report import Report as Report
72
+ from simhub.rollout import Record as Record
73
+ from simhub.rollout import RolloutResult as RolloutResult
74
+ from simhub.scorer import BestOfK as BestOfK
75
+ from simhub.scorer import MeanMetric as MeanMetric
76
+ from simhub.scorer import StageProgress as StageProgress
77
+ from simhub.scorer import SuccessRate as SuccessRate
78
+ from simhub.session import Session as Session
79
+ from simhub.spec import ArraySpec as ArraySpec
80
+ from simhub.spec import CameraSpec as CameraSpec
81
+ from simhub.spec import EnvSpec as EnvSpec
82
+ from simhub.spec import MetricSpec as MetricSpec
83
+ from simhub.spec import PolicySpec as PolicySpec
84
+ from simhub.suite import Episode as Episode
85
+ from simhub.suite import Eval as Eval
86
+ from simhub.suite import Suite as Suite
87
+ from simhub.task import Conditions as Conditions
88
+ from simhub.task import Score as Score
89
+ from simhub.task import Stages as Stages
90
+ from simhub.task import Task as Task
91
+
92
+ #: name -> the module it lives in. A table rather than a chain of try/imports
93
+ #: so that ``dir(simhub)`` is honest and a typo names the attribute rather
94
+ #: than failing somewhere inside an import.
95
+ #:
96
+ #: **No name here may also be a submodule of this package**, and that is a hard
97
+ #: rule rather than a style: importing ``simhub.policies`` makes the import
98
+ #: system set ``simhub.policies`` on this module, which would shadow a lazy
99
+ #: export of the same name -- so ``simhub.policy`` would be the decorator until
100
+ #: something imported the module and the module afterwards. That is why the
101
+ #: decorators live in ``policies.py`` rather than ``policy.py``, the scene check
102
+ #: in ``checks.py``, and the object resolver in ``objects.py``.
103
+ #:
104
+ #: ``rollout`` is the one that kept its module name and therefore is *not*
105
+ #: exported: ``simhub.rollout`` is the module, and :func:`simhub.run` is the
106
+ #: one-episode call.
107
+ _EXPORTS = {
108
+ "asset": "simhub.named_assets",
109
+ "NamedAsset": "simhub.named_assets",
110
+ "ArraySpec": "simhub.spec",
111
+ "CameraSpec": "simhub.spec",
112
+ "EnvSpec": "simhub.spec",
113
+ "MetricSpec": "simhub.spec",
114
+ "PolicySpec": "simhub.spec",
115
+ "Env": "simhub.env",
116
+ "Obs": "simhub.env",
117
+ "StepInfo": "simhub.env",
118
+ "environment": "simhub.env",
119
+ "make_env": "simhub.env",
120
+ "Policy": "simhub.policies",
121
+ "policy": "simhub.policies",
122
+ "make_policy": "simhub.policies",
123
+ "Task": "simhub.task",
124
+ "Conditions": "simhub.task",
125
+ "Stages": "simhub.task",
126
+ "Score": "simhub.task",
127
+ "Verdict": "simhub.task",
128
+ "Record": "simhub.rollout",
129
+ "RolloutResult": "simhub.rollout",
130
+ "Recorder": "simhub.recording",
131
+ "VideoWriter": "simhub.recording",
132
+ "annotate_frame": "simhub.recording",
133
+ "read_trajectory": "simhub.recording",
134
+ "Emitter": "simhub.emit",
135
+ "Environment": "simhub.base",
136
+ "PolicyBase": "simhub.base",
137
+ "MujocoEnvironment": "simhub.base",
138
+ "ScriptedPolicy": "simhub.base",
139
+ "manifest": "simhub.single",
140
+ "loaded": "simhub.registry",
141
+ "Project": "simhub.project",
142
+ "Client": "simhub.client",
143
+ "SimhubError": "simhub.client",
144
+ "connect": "simhub.client",
145
+ "Session": "simhub.session",
146
+ "Batch": "simhub.batch",
147
+ "Suite": "simhub.suite",
148
+ "Episode": "simhub.suite",
149
+ "Eval": "simhub.suite",
150
+ "Report": "simhub.report",
151
+ "SuccessRate": "simhub.scorer",
152
+ "MeanMetric": "simhub.scorer",
153
+ "StageProgress": "simhub.scorer",
154
+ "BestOfK": "simhub.scorer",
155
+ "AirTransport": "simhub.particulate",
156
+ "CoatingParameters": "simhub.particulate",
157
+ "ExtractionContact": "simhub.particulate",
158
+ "ParticleClass": "simhub.particulate",
159
+ "WetCoating": "simhub.particulate",
160
+ "CleaningTool": "simhub.cleaning",
161
+ "SurfaceContamination": "simhub.cleaning",
162
+ "render_cycles": "simhub.replay",
163
+ "check": "simhub.checks",
164
+ "CheckReport": "simhub.checks",
165
+ "assets": "simhub.objects",
166
+ "weights": "simhub.objects",
167
+ "check_access": "simhub.objects",
168
+ }
169
+
170
+ __all__ = [*sorted(_EXPORTS), "run", "__version__"]
171
+
172
+
173
+ def __getattr__(name: str) -> Any:
174
+ module = _EXPORTS.get(name)
175
+ if module is None:
176
+ raise AttributeError(f"module 'simhub' has no attribute {name!r}")
177
+ import importlib
178
+
179
+ return getattr(importlib.import_module(module), name)
180
+
181
+
182
+ def __dir__():
183
+ return sorted(__all__)
184
+
185
+
186
+ def run(
187
+ env: str,
188
+ policy: str = "simhub:hold",
189
+ *,
190
+ seed: int = 0,
191
+ out: Any = None,
192
+ task: Any = None,
193
+ record: Any = None,
194
+ max_steps: Any = None,
195
+ env_kwargs: Any = None,
196
+ policy_kwargs: Any = None,
197
+ prompt: Any = None,
198
+ ):
199
+ """One episode, in this interpreter, writing the hosted output layout.
200
+
201
+ A development affordance, and saying so plainly matters: anybody can step
202
+ MuJoCo in a Python loop for free, so a library whose pitch is "we wrap your
203
+ loop" has no reason to exist. What this is for is the debug cycle -- a
204
+ ``NameError`` should cost a second, not a round trip -- and for one
205
+ property that is worth more than it looks.
206
+
207
+ **The laptop predicts the cluster.** This is the same
208
+ :func:`~simhub.rollout.rollout` the container calls, writing the same
209
+ files, so a local result and a hosted result are the same artifacts read by
210
+ the same report code. A test asserts they stay identical for a fixed seed;
211
+ the moment they diverge, iterating locally stops being worth anything.
212
+
213
+ What it is *not* is the product. A 3-billion-parameter policy on a real
214
+ GPU, sixty episodes at once, photorealistic rendering, a fine-tune on what
215
+ you recorded, and a number with a content digest behind it are all the
216
+ hosted path, and none of them fits here.
217
+ """
218
+ from pathlib import Path
219
+
220
+ from simhub.env import make_env
221
+ from simhub.policies import make_policy
222
+ from simhub.rollout import Record, rollout
223
+
224
+ instance = make_env(env, **(env_kwargs or {}))
225
+ if prompt:
226
+ import dataclasses
227
+
228
+ instance.spec = dataclasses.replace(instance.spec, prompt=prompt)
229
+ controller = make_policy(policy, instance, **(policy_kwargs or {}))
230
+ return rollout(
231
+ instance,
232
+ controller,
233
+ seed=seed,
234
+ out=Path(out) if out is not None else None,
235
+ task=task,
236
+ record=record or Record(),
237
+ max_steps=max_steps,
238
+ policy_name=policy,
239
+ )
simhub/asset_cli.py ADDED
@@ -0,0 +1,58 @@
1
+ """Authenticated CLI for named environment assets, shared by both installs."""
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import json
6
+ import sys
7
+ from typing import Optional, Sequence
8
+
9
+ from simhub.client import Client, SimhubError
10
+ from simhub.named_assets import AssetError, fetch
11
+
12
+
13
+ def main(argv: Optional[Sequence[str]] = None) -> int:
14
+ parser = argparse.ArgumentParser(prog="simhub asset", description=__doc__)
15
+ parser.add_argument("--url", help="API URL (default: SIMHUB_API_URL)")
16
+ commands = parser.add_subparsers(dest="command", required=True)
17
+ upload = commands.add_parser("upload", help="Upload a named URDF and its meshes, or a mesh")
18
+ upload.add_argument("source")
19
+ upload.add_argument("--env", required=True, help="hosted environment name; created if absent")
20
+ upload.add_argument("--name", required=True, help="asset name within the environment")
21
+ upload.add_argument("--root", help="allowed dependency root (default: source directory)")
22
+ upload.add_argument("--package", action="append", default=[], metavar="NAME=PATH",
23
+ help="resolve a ROS package:// prefix; repeat for multiple packages")
24
+ upload.add_argument("--message", default="")
25
+ listing = commands.add_parser("list", help="List the latest revision of each named asset")
26
+ listing.add_argument("--env", required=True)
27
+ listing.add_argument("--version", default="latest")
28
+ get = commands.add_parser("get", help="Download and verify an asset and its dependencies")
29
+ get.add_argument("reference", help="environment/name[@version]")
30
+ get.add_argument("--out", required=True)
31
+ args = parser.parse_args(argv)
32
+ try:
33
+ with Client(url=args.url) as client:
34
+ if args.command == "upload":
35
+ packages = {}
36
+ for item in args.package:
37
+ key, separator, path = item.partition("=")
38
+ if not separator or not key or not path or key in packages:
39
+ raise AssetError("each --package must be a unique NAME=PATH")
40
+ packages[key] = path
41
+ result = client.upload_asset(
42
+ args.source, environment=args.env, name=args.name, root=args.root,
43
+ packages=packages, message=args.message,
44
+ )
45
+ elif args.command == "list":
46
+ result = client.named_assets(args.env, args.version)
47
+ else:
48
+ resolved = fetch(client, args.reference, into=args.out)
49
+ result = {"path": str(resolved.path), "digest": resolved.digest}
50
+ print(json.dumps(result, indent=2))
51
+ return 0
52
+ except (AssetError, SimhubError, OSError, ValueError) as exc:
53
+ print(str(exc), file=sys.stderr)
54
+ return 2
55
+
56
+
57
+ if __name__ == "__main__":
58
+ raise SystemExit(main())