simulo-interfaces 0.22.0__tar.gz → 0.23.0__tar.gz
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.
- {simulo_interfaces-0.22.0/src/simulo_interfaces.egg-info → simulo_interfaces-0.23.0}/PKG-INFO +1 -1
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/pyproject.toml +1 -1
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/record.py +30 -2
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/robot.py +81 -25
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/terrain.py +2 -4
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/__init__.py +91 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/enums.py +2 -2
- simulo_interfaces-0.23.0/src/simulo/interfaces/platform/evaluation.py +451 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/manifest.py +7 -6
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/runs.py +11 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/submit.py +10 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/runtime/components.py +3 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/runtime/env.py +12 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/runtime/task.py +35 -6
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/runtime/trainer.py +4 -12
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0/src/simulo_interfaces.egg-info}/PKG-INFO +1 -1
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo_interfaces.egg-info/SOURCES.txt +1 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/MANIFEST.in +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/PYPI.md +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/setup.cfg +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/__init__.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/__init__.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/actuator.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/asset.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/checkpoint.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/entity.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/light.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/materials.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/physics.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/pose.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/primitives.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/prop.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/scene.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/sensor.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/visual.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/world.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/exceptions.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/ids.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/_payload.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/app.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/asset.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/asset_catalog.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/callbacks.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/debug.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/domain.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/job_events.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/onnx_bundle.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/outputs.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/preview.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/runtime.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/systems.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/volume.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/py.typed +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/runtime/__init__.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/runtime/anomaly.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/runtime/player.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/runtime/policy.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/runtime/tensors.py +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo_interfaces.egg-info/dependency_links.txt +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo_interfaces.egg-info/requires.txt +0 -0
- {simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo_interfaces.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "simulo-interfaces"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.23.0"
|
|
8
8
|
description = "Typed Python contracts for the Simulo cloud robotics SDK and managed simulation runtime."
|
|
9
9
|
readme = "PYPI.md"
|
|
10
10
|
requires-python = ">=3.11"
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/record.py
RENAMED
|
@@ -44,10 +44,13 @@ fingerprint that overrides the auto-collected one when present).
|
|
|
44
44
|
|
|
45
45
|
from __future__ import annotations
|
|
46
46
|
|
|
47
|
+
import math
|
|
47
48
|
import os
|
|
49
|
+
import sys
|
|
48
50
|
from dataclasses import dataclass, field
|
|
51
|
+
from numbers import Real
|
|
49
52
|
from pathlib import Path
|
|
50
|
-
from typing import Any, Dict, Literal, Mapping, Optional, Sequence, Tuple, Union
|
|
53
|
+
from typing import Any, Dict, Literal, Mapping, Optional, Sequence, SupportsFloat, Tuple, Union
|
|
51
54
|
|
|
52
55
|
# All include_* field names this config exposes. Centralizing avoids drift
|
|
53
56
|
# between the dataclass declaration, _PROFILE_DEFAULTS, and resolve_includes.
|
|
@@ -253,7 +256,14 @@ class RecordConfig:
|
|
|
253
256
|
video_fallback: Literal["software", "error"] = "software"
|
|
254
257
|
video_codec: Literal["h264", "hevc", "av1"] = "h264"
|
|
255
258
|
video_bitrate: Optional[str] = "8M"
|
|
256
|
-
|
|
259
|
+
# ``None`` leaves cadence to the frame producer. The encoder then uses its
|
|
260
|
+
# documented 30 fps default, but the recorder omits ``video_fps`` from
|
|
261
|
+
# /run/start because packet timestamps, not an encoder setting, describe
|
|
262
|
+
# that producer-owned cadence. Callers such as evaluation filming set the
|
|
263
|
+
# measured cadence explicitly.
|
|
264
|
+
# Values must also fit a finite IEEE-754 double because both supported
|
|
265
|
+
# encoders and the MCAP JSON record use that representation.
|
|
266
|
+
video_fps: Optional[SupportsFloat] = None
|
|
257
267
|
video_gop: Optional[int] = 30
|
|
258
268
|
video_storage: Literal["embedded", "sidecar"] = "embedded"
|
|
259
269
|
# When True (default), the recorder writes a parallel
|
|
@@ -300,6 +310,24 @@ class RecordConfig:
|
|
|
300
310
|
raise ValueError(f"RecordConfig.profile must be one of {VALID_PROFILES}; got {self.profile!r}")
|
|
301
311
|
if self.record_every_n_steps < 1:
|
|
302
312
|
raise ValueError(f"RecordConfig.record_every_n_steps must be >= 1; got {self.record_every_n_steps}")
|
|
313
|
+
try:
|
|
314
|
+
video_fps = None if self.video_fps is None else float(self.video_fps)
|
|
315
|
+
except (OverflowError, TypeError, ValueError):
|
|
316
|
+
video_fps = None
|
|
317
|
+
if self.video_fps is not None and (
|
|
318
|
+
isinstance(self.video_fps, bool)
|
|
319
|
+
or not isinstance(self.video_fps, Real)
|
|
320
|
+
or video_fps is None
|
|
321
|
+
or not math.isfinite(video_fps)
|
|
322
|
+
# 0.1 fps is a policy floor that keeps rates sensible; ffmpeg's
|
|
323
|
+
# rational conversion only rounds to zero far below it.
|
|
324
|
+
or video_fps < 0.1
|
|
325
|
+
or video_fps > sys.float_info.max
|
|
326
|
+
):
|
|
327
|
+
raise ValueError(
|
|
328
|
+
"RecordConfig.video_fps must be a finite real number representable by the encoder and at least 0.1 "
|
|
329
|
+
f"when set; got {self.video_fps!r}"
|
|
330
|
+
)
|
|
303
331
|
# env_indices: accept any sequence of ints, including a torch tensor —
|
|
304
332
|
# but normalize to a tuple of ints for hashability + reproducibility.
|
|
305
333
|
try:
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/robot.py
RENAMED
|
@@ -7,7 +7,7 @@ and reset paths all reach through a live engine handle), so its
|
|
|
7
7
|
*implementation* cannot live in a zero-dependency package. What CAN live here
|
|
8
8
|
is its declared public surface: this Protocol names exactly what authoring
|
|
9
9
|
code may rely on when it holds a robot — construction-time configuration,
|
|
10
|
-
joint/body queries, command targets, state reads, and reset.
|
|
10
|
+
joint/body queries, command targets, default state, live state reads, and reset.
|
|
11
11
|
|
|
12
12
|
The backend's concrete ``Robot`` class explicitly inherits this Protocol
|
|
13
13
|
(nominal conformance). Enforcement is the standard four layers for a mirror —
|
|
@@ -23,15 +23,16 @@ The Protocol stays structural for any other implementer.
|
|
|
23
23
|
|
|
24
24
|
``state`` is DECLARED, as ``RobotStateProtocol`` — the grouped live-state
|
|
25
25
|
contract. It is the supported, typed way to read a robot's simulation state
|
|
26
|
-
(``robot.state.joint_positions``, ``robot.state.pose``, …):
|
|
26
|
+
(``robot.state.joint_positions``, ``robot.state.pose``, …): eight read-only,
|
|
27
27
|
batched members whose names are plain English rather than engine vocabulary.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
28
|
+
Six members are zero-copy live views of the simulation's buffers. The two
|
|
29
|
+
base-frame velocity members are different: the engine rotates the current
|
|
30
|
+
world-frame velocities into the base frame on every read, returning a new
|
|
31
|
+
tensor. That small computation and allocation are the cost of those two
|
|
32
|
+
accesses, and writing into either result does not reach the simulation.
|
|
33
|
+
Unlike ``internals``, all eight names ARE a compatibility promise: they keep
|
|
34
|
+
working across engine upgrades, because the backend maps each one to whatever
|
|
35
|
+
the engine calls it.
|
|
35
36
|
|
|
36
37
|
Deliberate scoping notes, so they are not mistaken for drift (each is an
|
|
37
38
|
entry in the backend conformance test's documented-omissions allowlist):
|
|
@@ -68,27 +69,31 @@ from simulo.interfaces.runtime.tensors import TensorLike
|
|
|
68
69
|
|
|
69
70
|
@runtime_checkable
|
|
70
71
|
class RobotStateProtocol(Protocol):
|
|
71
|
-
"""The live robot-state contract — batched
|
|
72
|
+
"""The live robot-state contract — batched and read-only.
|
|
72
73
|
|
|
73
74
|
Obtained from a robot as ``robot.state``; never constructed directly.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
Six members (``pose``, the two world-frame velocities, and the three
|
|
76
|
+
``joint_*`` members) are zero-copy live views of the simulation's own
|
|
77
|
+
buffers. ``linear_velocity_in_base_frame`` and
|
|
78
|
+
``angular_velocity_in_base_frame`` are computed from the current base
|
|
79
|
+
orientation on every read. Each of those reads returns a new tensor,
|
|
80
|
+
costs a small computation, and cannot modify the simulation when written
|
|
81
|
+
into. All members carry the environment batch as their leading dimension
|
|
82
|
+
(the scene's ``num_envs``).
|
|
78
83
|
|
|
79
84
|
**Read a member each time you need it — do not hold the returned tensor
|
|
80
|
-
across steps, and do not write into it.** The
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
(``robot.state`` itself) may be held freely; the tensors its
|
|
85
|
-
return should be re-read at each use.
|
|
85
|
+
across steps, and do not write into it.** The six live-view tensors share
|
|
86
|
+
simulation memory, and writing into one writes into the simulation. The
|
|
87
|
+
engine can also rebind those buffers, while the base-frame members are
|
|
88
|
+
recomputed, so a tensor held across a step can silently serve stale values.
|
|
89
|
+
The accessor (``robot.state`` itself) may be held freely; the tensors its
|
|
90
|
+
members return should be re-read at each use.
|
|
86
91
|
|
|
87
92
|
Members raise ``RuntimeError`` until the simulation runtime has attached
|
|
88
93
|
the robot — read them from ``on_start`` onward. The ``state`` accessor
|
|
89
94
|
itself is always available and is the same object on every access.
|
|
90
95
|
|
|
91
|
-
These
|
|
96
|
+
These eight names are a compatibility promise: they keep working across
|
|
92
97
|
engine upgrades. For anything not named here, the ``internals`` escape
|
|
93
98
|
hatch remains available — explicitly unstable, at your own risk.
|
|
94
99
|
"""
|
|
@@ -111,6 +116,24 @@ class RobotStateProtocol(Protocol):
|
|
|
111
116
|
``[wx, wy, wz]``."""
|
|
112
117
|
...
|
|
113
118
|
|
|
119
|
+
@property
|
|
120
|
+
def linear_velocity_in_base_frame(self) -> TensorLike:
|
|
121
|
+
"""Base linear velocity expressed in the base frame, shape
|
|
122
|
+
``(num_envs, 3)`` in m/s with columns ``[vx, vy, vz]``. Available
|
|
123
|
+
from ``on_start`` onward. Computed from the current base orientation
|
|
124
|
+
on every read; each read returns a new tensor, costs a small
|
|
125
|
+
computation, and writing into it does not reach the simulation."""
|
|
126
|
+
...
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def angular_velocity_in_base_frame(self) -> TensorLike:
|
|
130
|
+
"""Base angular velocity expressed in the base frame, shape
|
|
131
|
+
``(num_envs, 3)`` in rad/s with columns ``[wx, wy, wz]``. Available
|
|
132
|
+
from ``on_start`` onward. Computed from the current base orientation
|
|
133
|
+
on every read; each read returns a new tensor, costs a small
|
|
134
|
+
computation, and writing into it does not reach the simulation."""
|
|
135
|
+
...
|
|
136
|
+
|
|
114
137
|
@property
|
|
115
138
|
def joint_positions(self) -> TensorLike:
|
|
116
139
|
"""Joint positions (rad or m), shape ``(num_envs, num_joints)``."""
|
|
@@ -146,7 +169,7 @@ class RobotProtocol(Protocol):
|
|
|
146
169
|
"""Model source (USD or URDF) defining the robot."""
|
|
147
170
|
|
|
148
171
|
initial_pose: Pose
|
|
149
|
-
"""Pose the robot spawns at,
|
|
172
|
+
"""Pose the robot spawns at (identity when not given). For a robot copied into each environment, the position is measured from that environment's origin, and default_pose reports it with the origin added. For some catalog robots, such as the cartpole, the identity pose keeps the catalog model's own spawn height; any other pose replaces it. A robot added under a Transform spawns at the Transform's position."""
|
|
150
173
|
|
|
151
174
|
collision_enabled: bool
|
|
152
175
|
"""Whether collision detection is enabled."""
|
|
@@ -203,12 +226,45 @@ class RobotProtocol(Protocol):
|
|
|
203
226
|
"""The grouped live robot state — the supported, typed state surface.
|
|
204
227
|
|
|
205
228
|
Always available (the accessor is the same object on every access);
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
229
|
+
six members read straight through to the simulation's state buffers,
|
|
230
|
+
while the two base-frame velocity members are computed into new
|
|
231
|
+
tensors on every read. Members raise ``RuntimeError`` until the
|
|
232
|
+
simulation runtime has attached the robot — read them from
|
|
233
|
+
``on_start`` onward.
|
|
209
234
|
"""
|
|
210
235
|
...
|
|
211
236
|
|
|
237
|
+
@property
|
|
238
|
+
def default_joint_positions(self) -> TensorLike:
|
|
239
|
+
"""Joint positions that :meth:`reset` writes, shape
|
|
240
|
+
``(num_envs, num_joints)`` in rad for hinge joints and m for slider
|
|
241
|
+
joints. Columns use the same order as ``state.joint_positions`` and
|
|
242
|
+
the indices returned by :meth:`find_joints`. Available from
|
|
243
|
+
``on_start`` onward. Each read returns a new tensor, so writing into
|
|
244
|
+
it cannot change a later reset."""
|
|
245
|
+
...
|
|
246
|
+
|
|
247
|
+
@property
|
|
248
|
+
def default_joint_velocities(self) -> TensorLike:
|
|
249
|
+
"""Joint velocities that :meth:`reset` writes, shape
|
|
250
|
+
``(num_envs, num_joints)`` in rad/s for hinge joints and m/s for
|
|
251
|
+
slider joints. Columns use the same order as
|
|
252
|
+
``state.joint_positions`` and the indices returned by
|
|
253
|
+
:meth:`find_joints`. Available from ``on_start`` onward. Each read
|
|
254
|
+
returns a new tensor, so writing into it cannot change a later
|
|
255
|
+
reset."""
|
|
256
|
+
...
|
|
257
|
+
|
|
258
|
+
@property
|
|
259
|
+
def default_pose(self) -> TensorLike:
|
|
260
|
+
"""Base pose that :meth:`reset` writes in the world frame, shape
|
|
261
|
+
``(num_envs, 7)`` with columns ``[x, y, z, qw, qx, qy, qz]``:
|
|
262
|
+
position in m followed by a w-first unit quaternion. Each
|
|
263
|
+
environment's origin is included. Available from ``on_start``
|
|
264
|
+
onward. Each read returns a new tensor, so writing into it cannot
|
|
265
|
+
change a later reset."""
|
|
266
|
+
...
|
|
267
|
+
|
|
212
268
|
@property
|
|
213
269
|
def sensors(self) -> List[Any]:
|
|
214
270
|
"""Sensors attached to this robot via :meth:`add_sensor`."""
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/terrain.py
RENAMED
|
@@ -59,10 +59,8 @@ class SubTerrainConfig:
|
|
|
59
59
|
- "hf_pyramid_slope": Height-field pyramid slope
|
|
60
60
|
- "hf_pyramid_slope_inv": Inverted height-field pyramid slope
|
|
61
61
|
proportion: Proportion of terrain grid to use this type (0.0-1.0).
|
|
62
|
-
Defaults to 1.0
|
|
63
|
-
sub-terrain
|
|
64
|
-
explicitly (Wave 0 audit verified), so the default-value
|
|
65
|
-
change from the pre-Wave-B local default of 0.2 is safe.
|
|
62
|
+
Defaults to 1.0, which uses the whole grid for this single
|
|
63
|
+
sub-terrain.
|
|
66
64
|
params: Type-specific parameters (e.g., height, step_height, etc.)
|
|
67
65
|
|
|
68
66
|
Example:
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/__init__.py
RENAMED
|
@@ -79,6 +79,50 @@ from simulo.interfaces.platform.enums import (
|
|
|
79
79
|
OutputKind,
|
|
80
80
|
SystemType,
|
|
81
81
|
)
|
|
82
|
+
from simulo.interfaces.platform.evaluation import (
|
|
83
|
+
ENDED_BY_TASK_TRUNCATION,
|
|
84
|
+
ENDED_BY_TIME_LIMIT,
|
|
85
|
+
EPISODES_CSV_CHECK_PREFIX,
|
|
86
|
+
EPISODES_CSV_COLUMNS_AFTER_CHECKS,
|
|
87
|
+
EPISODES_CSV_COLUMNS_BEFORE_CHECKS,
|
|
88
|
+
EVALUATION_ACTIONS,
|
|
89
|
+
EVALUATION_REPORT_SCHEMA_VERSION,
|
|
90
|
+
MAX_CHECK_NAME_CHARS,
|
|
91
|
+
MAX_CHECKS_PER_RULE,
|
|
92
|
+
MAX_END_CONDITION_NAME_CHARS,
|
|
93
|
+
MAX_END_CONDITIONS,
|
|
94
|
+
MAX_EVALUATION_HEADLINE_SUMMARY_CHARS,
|
|
95
|
+
MAX_FAILURE_GROUPS,
|
|
96
|
+
MAX_LISTED_EPISODES,
|
|
97
|
+
MAX_LISTED_SIMULATION_PROBLEMS,
|
|
98
|
+
MAX_NEXT_LINES,
|
|
99
|
+
MAX_RECORDED_EPISODES,
|
|
100
|
+
MAX_RECORDING_PROBLEM_CHARS,
|
|
101
|
+
CheckCount,
|
|
102
|
+
ComparisonVerdict,
|
|
103
|
+
EndingCount,
|
|
104
|
+
EvaluationActions,
|
|
105
|
+
EvaluationCheckpoint,
|
|
106
|
+
EvaluationComparison,
|
|
107
|
+
EvaluationHeadline,
|
|
108
|
+
EvaluationReport,
|
|
109
|
+
EvaluationResult,
|
|
110
|
+
EvaluationTask,
|
|
111
|
+
EvaluationTested,
|
|
112
|
+
FailureGroup,
|
|
113
|
+
LikelyRange,
|
|
114
|
+
ListedEpisode,
|
|
115
|
+
NextLine,
|
|
116
|
+
NextRule,
|
|
117
|
+
ReplayFrame,
|
|
118
|
+
ReplayPose,
|
|
119
|
+
ReplayTrace,
|
|
120
|
+
SimulationProblem,
|
|
121
|
+
SimulationProblemEpisode,
|
|
122
|
+
end_condition_names_problem,
|
|
123
|
+
episodes_csv_columns,
|
|
124
|
+
success_checks_problem,
|
|
125
|
+
)
|
|
82
126
|
from simulo.interfaces.platform.job_events import (
|
|
83
127
|
MAX_PUBLIC_ASSET_FINDING_COUNT,
|
|
84
128
|
MAX_PUBLIC_JOB_EVENT_ATTEMPT,
|
|
@@ -241,6 +285,7 @@ from simulo.interfaces.platform.submit import (
|
|
|
241
285
|
CHECKPOINT_UNAVAILABLE_CODE,
|
|
242
286
|
CHECKPOINT_UPLOAD_MISSING_CODE,
|
|
243
287
|
EXPORTS_ROUTE,
|
|
288
|
+
INPUT_CHECKPOINT_LATEST_AVAILABLE_META_KEY,
|
|
244
289
|
INPUT_CHECKPOINT_NOT_ALLOWED_CODE,
|
|
245
290
|
JOB_RECORDING_DOWNLOAD_ROUTE_TEMPLATE,
|
|
246
291
|
JOB_RECORDINGS_ROUTE_TEMPLATE,
|
|
@@ -419,6 +464,7 @@ __all__ = [
|
|
|
419
464
|
"WORKER_JOB_CHECKPOINTS_PRESIGN_ROUTE_TEMPLATE",
|
|
420
465
|
"WORKER_JOB_CHECKPOINTS_ROUTE_TEMPLATE",
|
|
421
466
|
"WORKER_CLAIM_INPUT_CHECKPOINT_FIELDS",
|
|
467
|
+
"INPUT_CHECKPOINT_LATEST_AVAILABLE_META_KEY",
|
|
422
468
|
"WORKER_LEASE_HEADER",
|
|
423
469
|
"MAX_PACKAGE_BYTES",
|
|
424
470
|
"MAX_LOG_CHUNK_BYTES",
|
|
@@ -579,6 +625,51 @@ __all__ = [
|
|
|
579
625
|
"PreviewReportRow",
|
|
580
626
|
"PreviewReportStage",
|
|
581
627
|
"PreviewReport",
|
|
628
|
+
# evaluation report wire contract — schema version, how an episode ended,
|
|
629
|
+
# the bounds that keep the report under the result cap, the report shapes,
|
|
630
|
+
# the replay trace filming reads, and episodes.csv
|
|
631
|
+
"EVALUATION_REPORT_SCHEMA_VERSION",
|
|
632
|
+
"MAX_EVALUATION_HEADLINE_SUMMARY_CHARS",
|
|
633
|
+
"ENDED_BY_TIME_LIMIT",
|
|
634
|
+
"ENDED_BY_TASK_TRUNCATION",
|
|
635
|
+
"MAX_END_CONDITION_NAME_CHARS",
|
|
636
|
+
"MAX_END_CONDITIONS",
|
|
637
|
+
"MAX_CHECKS_PER_RULE",
|
|
638
|
+
"MAX_CHECK_NAME_CHARS",
|
|
639
|
+
"MAX_FAILURE_GROUPS",
|
|
640
|
+
"MAX_RECORDED_EPISODES",
|
|
641
|
+
"MAX_RECORDING_PROBLEM_CHARS",
|
|
642
|
+
"MAX_LISTED_EPISODES",
|
|
643
|
+
"MAX_LISTED_SIMULATION_PROBLEMS",
|
|
644
|
+
"MAX_NEXT_LINES",
|
|
645
|
+
"end_condition_names_problem",
|
|
646
|
+
"success_checks_problem",
|
|
647
|
+
"EVALUATION_ACTIONS",
|
|
648
|
+
"EvaluationActions",
|
|
649
|
+
"ComparisonVerdict",
|
|
650
|
+
"NextRule",
|
|
651
|
+
"SimulationProblem",
|
|
652
|
+
"EvaluationCheckpoint",
|
|
653
|
+
"EvaluationTask",
|
|
654
|
+
"EvaluationTested",
|
|
655
|
+
"LikelyRange",
|
|
656
|
+
"EvaluationHeadline",
|
|
657
|
+
"CheckCount",
|
|
658
|
+
"EndingCount",
|
|
659
|
+
"FailureGroup",
|
|
660
|
+
"EvaluationResult",
|
|
661
|
+
"EvaluationComparison",
|
|
662
|
+
"ListedEpisode",
|
|
663
|
+
"SimulationProblemEpisode",
|
|
664
|
+
"NextLine",
|
|
665
|
+
"EvaluationReport",
|
|
666
|
+
"ReplayPose",
|
|
667
|
+
"ReplayFrame",
|
|
668
|
+
"ReplayTrace",
|
|
669
|
+
"EPISODES_CSV_COLUMNS_BEFORE_CHECKS",
|
|
670
|
+
"EPISODES_CSV_CHECK_PREFIX",
|
|
671
|
+
"EPISODES_CSV_COLUMNS_AFTER_CHECKS",
|
|
672
|
+
"episodes_csv_columns",
|
|
582
673
|
# protocols
|
|
583
674
|
"RuntimeProtocol",
|
|
584
675
|
"AssetProtocol",
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/enums.py
RENAMED
|
@@ -19,10 +19,10 @@ class JobType(StrEnum):
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
#: Types a user can submit today. Platform-owned jobs use the remaining types.
|
|
22
|
-
USER_SUBMITTABLE_JOB_TYPES = frozenset({JobType.PREVIEW, JobType.TRAIN, JobType.PLAY})
|
|
22
|
+
USER_SUBMITTABLE_JOB_TYPES = frozenset({JobType.PREVIEW, JobType.TRAIN, JobType.EVAL, JobType.PLAY})
|
|
23
23
|
|
|
24
24
|
#: Declared job types whose user-submission workflows are not available yet.
|
|
25
|
-
UNAVAILABLE_JOB_TYPES = frozenset(
|
|
25
|
+
UNAVAILABLE_JOB_TYPES: frozenset[JobType] = frozenset()
|
|
26
26
|
|
|
27
27
|
#: Job types whose submission may carry an input policy checkpoint.
|
|
28
28
|
INPUT_CHECKPOINT_JOB_TYPES = frozenset({JobType.TRAIN, JobType.PLAY, JobType.EVAL})
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
"""Evaluation report: what an eval job returns, writes and replays.
|
|
2
|
+
|
|
3
|
+
An eval job runs a trained checkpoint on numbered episodes and judges each
|
|
4
|
+
one with the eval file's ``@app.success`` function. The runtime returns the
|
|
5
|
+
report as a plain ``dict`` of :class:`EvaluationReport` shape and registers it
|
|
6
|
+
as a ``report`` output; the client renders it, the Console reads it from the
|
|
7
|
+
job result, and the control plane checks its schema version. Every episode's
|
|
8
|
+
detail goes in ``episodes.csv`` (:func:`episodes_csv_columns`), so the report
|
|
9
|
+
stays small whatever the episode count.
|
|
10
|
+
|
|
11
|
+
The comparison block (``--compare``) is part of v1 from the start, so comparing
|
|
12
|
+
two checkpoints never needs a new schema version. Additive-only: a breaking
|
|
13
|
+
reshape mints ``simulo.evaluation_report.v2``.
|
|
14
|
+
|
|
15
|
+
Percentages are whole numbers. Times are seconds from the episode's start.
|
|
16
|
+
Checkpoints are written ``policy_x:best`` or ``policy_x:latest``.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from typing import Final, Literal, NotRequired, Sequence, TypedDict
|
|
20
|
+
|
|
21
|
+
EVALUATION_REPORT_SCHEMA_VERSION: Final = "simulo.evaluation_report.v1"
|
|
22
|
+
|
|
23
|
+
# --- How an episode ended ----------------------------------------------------
|
|
24
|
+
#
|
|
25
|
+
# The report's "Why" column and the ``ended_by`` column of ``episodes.csv``
|
|
26
|
+
# hold exactly one of: the name of the task's end condition that ended the
|
|
27
|
+
# episode (a key of the dict ``get_dones()`` returns for ``terminated``),
|
|
28
|
+
# :data:`ENDED_BY_TIME_LIMIT`, or :data:`ENDED_BY_TASK_TRUNCATION`.
|
|
29
|
+
|
|
30
|
+
ENDED_BY_TIME_LIMIT: Final = "time limit"
|
|
31
|
+
"""The episode reached the time limit: Simulo's step counter reached
|
|
32
|
+
``max_episode_length - 1`` and no end condition was true. Reserved for that
|
|
33
|
+
boundary; it counts as surviving."""
|
|
34
|
+
|
|
35
|
+
ENDED_BY_TASK_TRUNCATION: Final = "cut short by the task"
|
|
36
|
+
"""The task's ``truncated`` ended the episode before the time limit and no end
|
|
37
|
+
condition was true. Never counts as surviving."""
|
|
38
|
+
|
|
39
|
+
MAX_END_CONDITION_NAME_CHARS: Final = 64
|
|
40
|
+
"""Longest name ``get_dones()`` may give an end condition."""
|
|
41
|
+
|
|
42
|
+
# --- Bounds ------------------------------------------------------------------
|
|
43
|
+
#
|
|
44
|
+
# The report is a job result, so it must fit the worker's result cap
|
|
45
|
+
# (``simulo.interfaces.platform.submit.MAX_RESULT_BYTES``, 1 MiB) whatever the
|
|
46
|
+
# episode count. Every list in it is bounded by one of these, and every name in
|
|
47
|
+
# it by a character limit, so its size does not grow with the episodes run.
|
|
48
|
+
# The ``get_dones()`` check enforces the end-condition bounds
|
|
49
|
+
# (:func:`end_condition_names_problem`); the evaluator enforces the check bounds
|
|
50
|
+
# (:func:`success_checks_problem`) and builds every list within its bound.
|
|
51
|
+
|
|
52
|
+
MAX_END_CONDITIONS: Final = 32
|
|
53
|
+
"""Most end conditions ``get_dones()`` may name. A RESULT's ``endings`` holds
|
|
54
|
+
at most this many plus :data:`ENDED_BY_TIME_LIMIT` and
|
|
55
|
+
:data:`ENDED_BY_TASK_TRUNCATION`."""
|
|
56
|
+
|
|
57
|
+
MAX_CHECKS_PER_RULE: Final = 16
|
|
58
|
+
"""Most checks one ``@app.success`` function may name with ``task.check()``."""
|
|
59
|
+
|
|
60
|
+
MAX_CHECK_NAME_CHARS: Final = 64
|
|
61
|
+
"""Longest name ``task.check()`` may give a check."""
|
|
62
|
+
|
|
63
|
+
MAX_FAILURE_GROUPS: Final = 3
|
|
64
|
+
"""Failure groups a RESULT lists; ``other_failure_combinations`` counts the rest."""
|
|
65
|
+
|
|
66
|
+
MAX_RECORDED_EPISODES: Final = 10
|
|
67
|
+
"""Episodes an evaluation records."""
|
|
68
|
+
|
|
69
|
+
MAX_LISTED_EPISODES: Final = 13
|
|
70
|
+
"""Episodes WENT WRONG lists: the recorded ones, then up to three more."""
|
|
71
|
+
|
|
72
|
+
MAX_LISTED_SIMULATION_PROBLEMS: Final = 10
|
|
73
|
+
"""Episodes SIMULATION PROBLEMS lists; ``simulation_problem_episodes`` counts them all."""
|
|
74
|
+
|
|
75
|
+
MAX_NEXT_LINES: Final = 4
|
|
76
|
+
"""NEXT lines: up to three rules that match, then the ``--compare`` offer."""
|
|
77
|
+
|
|
78
|
+
MAX_RECORDING_PROBLEM_CHARS: Final = 512
|
|
79
|
+
"""Longest reason an evaluation can report when requested filming did not complete."""
|
|
80
|
+
|
|
81
|
+
MAX_EVALUATION_HEADLINE_SUMMARY_CHARS: Final = 512
|
|
82
|
+
"""Longest summary the jobs list may carry for one evaluation, omitted when longer than this.
|
|
83
|
+
|
|
84
|
+
The full report is available from the evaluation job result. Policy job lists
|
|
85
|
+
only need a compact rendering input, so this cap prevents one worker result
|
|
86
|
+
from making an otherwise paginated response unbounded.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
_RESERVED_ENDINGS: Final = {
|
|
90
|
+
ENDED_BY_TIME_LIMIT: "an episode that ran to the time limit",
|
|
91
|
+
ENDED_BY_TASK_TRUNCATION: "an episode your truncated ended before the time limit",
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _name_problem(source: str, noun: str, example: str, limit: int, name: object) -> str | None:
|
|
96
|
+
if not isinstance(name, str):
|
|
97
|
+
return f'{source} {noun} named {name!r}; name each one with a string, such as "{example}".'
|
|
98
|
+
if not name:
|
|
99
|
+
return f'{source} {noun} with an empty name; name each one, such as "{example}".'
|
|
100
|
+
if name != name.strip():
|
|
101
|
+
return f'{source} "{name}", which starts or ends with a space; name it "{name.strip()}".'
|
|
102
|
+
if len(name) > limit:
|
|
103
|
+
return f'{source} "{name}", a name of {len(name)} characters; names can be at most {limit} characters.'
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def end_condition_names_problem(names: Sequence[object]) -> str | None:
|
|
108
|
+
"""What is wrong with the end-condition names ``get_dones()`` returned, or ``None``.
|
|
109
|
+
|
|
110
|
+
The message names the condition and says how to fix the task. Each name is
|
|
111
|
+
a non-empty string of at most :data:`MAX_END_CONDITION_NAME_CHARS`
|
|
112
|
+
characters with no leading or trailing space, and not one of the two words
|
|
113
|
+
the report uses for an episode no condition ended (in any case); there are
|
|
114
|
+
at most :data:`MAX_END_CONDITIONS` of them.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
if len(names) > MAX_END_CONDITIONS:
|
|
118
|
+
return (
|
|
119
|
+
f"get_dones() returned {len(names)} end conditions; a task can name at most "
|
|
120
|
+
f"{MAX_END_CONDITIONS}, so combine some of them."
|
|
121
|
+
)
|
|
122
|
+
for name in names:
|
|
123
|
+
source = "get_dones() returned"
|
|
124
|
+
problem = _name_problem(source, "an end condition", "pole fell", MAX_END_CONDITION_NAME_CHARS, name)
|
|
125
|
+
if problem is None and isinstance(name, str) and name.casefold() in _RESERVED_ENDINGS:
|
|
126
|
+
meaning = _RESERVED_ENDINGS[name.casefold()]
|
|
127
|
+
problem = f'{source} "{name}", which the report uses for {meaning}; give the condition another name.'
|
|
128
|
+
if problem is not None:
|
|
129
|
+
return problem
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def success_checks_problem(function: str, names: Sequence[object]) -> str | None:
|
|
134
|
+
"""What is wrong with the checks the ``@app.success`` function ``function`` named, or ``None``.
|
|
135
|
+
|
|
136
|
+
``names`` are its ``task.check()`` names in call order. Each is a non-empty
|
|
137
|
+
string of at most :data:`MAX_CHECK_NAME_CHARS` characters with no leading or
|
|
138
|
+
trailing space; there are at most :data:`MAX_CHECKS_PER_RULE` of them.
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
if len(names) > MAX_CHECKS_PER_RULE:
|
|
142
|
+
return (
|
|
143
|
+
f"{function}() named {len(names)} checks with task.check(); a success rule can name at most "
|
|
144
|
+
f"{MAX_CHECKS_PER_RULE}, so combine some of them."
|
|
145
|
+
)
|
|
146
|
+
for name in names:
|
|
147
|
+
problem = _name_problem("task.check() got", "a check", "pole upright", MAX_CHECK_NAME_CHARS, name)
|
|
148
|
+
if problem is not None:
|
|
149
|
+
return problem
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
EvaluationActions = Literal["best", "sampled"]
|
|
154
|
+
"""``best`` is the policy's best-guess action, what you would deploy;
|
|
155
|
+
``sampled`` keeps training's random variation on, as a diagnostic."""
|
|
156
|
+
|
|
157
|
+
EVALUATION_ACTIONS: Final = ("best", "sampled")
|
|
158
|
+
|
|
159
|
+
ComparisonVerdict = Literal["did_better", "did_worse", "no_clear_difference"]
|
|
160
|
+
"""How the ``--policy`` checkpoint did against the ``--compare`` checkpoint,
|
|
161
|
+
judged on the episodes whose outcome changed (McNemar's exact test, p < 0.05)."""
|
|
162
|
+
|
|
163
|
+
NextRule = Literal[
|
|
164
|
+
"simulation_problems",
|
|
165
|
+
"never_learned",
|
|
166
|
+
"reward_pays_for_failure",
|
|
167
|
+
"one_check_missed",
|
|
168
|
+
"more_episodes",
|
|
169
|
+
"watch_worst_failure",
|
|
170
|
+
"all_succeeded",
|
|
171
|
+
"compare_latest",
|
|
172
|
+
]
|
|
173
|
+
"""Which rule produced a NEXT line. The first seven are checked in this order and
|
|
174
|
+
NEXT prints up to three that match: simulation problems; a likely range whose
|
|
175
|
+
top is under 10%; the failures' median total reward at least the successes';
|
|
176
|
+
the largest failure group missing exactly one check; a likely range wider than
|
|
177
|
+
15 points or crossing ``--require-success``; some failures, when the second
|
|
178
|
+
rule did not match; every episode succeeded. ``compare_latest`` is the ``--compare policy_x:latest`` offer after them."""
|
|
179
|
+
|
|
180
|
+
SimulationProblem = Literal["reward_not_finite", "observation_not_finite", "observation_out_of_bounds"]
|
|
181
|
+
"""What made a step unusable; an episode with one counts as a failure."""
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class EvaluationCheckpoint(TypedDict):
|
|
185
|
+
"""One checkpoint that was evaluated, from its sidecar."""
|
|
186
|
+
|
|
187
|
+
reference: str
|
|
188
|
+
"""``policy_x:best`` or ``policy_x:latest``."""
|
|
189
|
+
policy_id: str
|
|
190
|
+
kind: Literal["best", "latest"]
|
|
191
|
+
iteration: int
|
|
192
|
+
training_reward: float | None
|
|
193
|
+
digest_sha256: str
|
|
194
|
+
"""Digest of the checkpoint file, so a report names the exact weights."""
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class EvaluationTask(TypedDict):
|
|
198
|
+
"""The task the episodes ran, against the one the checkpoint was trained on."""
|
|
199
|
+
|
|
200
|
+
name: str
|
|
201
|
+
"""The task class name."""
|
|
202
|
+
file: str
|
|
203
|
+
digest: str
|
|
204
|
+
"""Hash of today's ``task.py``."""
|
|
205
|
+
trained_digest: str | None
|
|
206
|
+
"""Hash of ``task.py`` when the checkpoint was trained; ``None`` when unknown."""
|
|
207
|
+
changed: bool
|
|
208
|
+
"""The task-changed flag: the class name or the digest differs from training's."""
|
|
209
|
+
end_conditions: list[str]
|
|
210
|
+
"""The names ``get_dones()`` gives its end conditions, in the task's order;
|
|
211
|
+
empty for a task that never ends an episode early. At most
|
|
212
|
+
:data:`MAX_END_CONDITIONS`."""
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class EvaluationTested(TypedDict):
|
|
216
|
+
"""WHAT WAS TESTED."""
|
|
217
|
+
|
|
218
|
+
checkpoint: EvaluationCheckpoint
|
|
219
|
+
task: EvaluationTask
|
|
220
|
+
first_episode: int
|
|
221
|
+
last_episode: int
|
|
222
|
+
"""Episode numbers run, inclusive. Each number fixes its starting state."""
|
|
223
|
+
num_envs: int
|
|
224
|
+
actions: EvaluationActions
|
|
225
|
+
success_function: str
|
|
226
|
+
"""The ``@app.success`` function's name, such as ``balanced``."""
|
|
227
|
+
success_file: str
|
|
228
|
+
checks: list[str]
|
|
229
|
+
"""Every ``task.check()`` name, in the order the rule declares them; at most
|
|
230
|
+
:data:`MAX_CHECKS_PER_RULE`."""
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class LikelyRange(TypedDict):
|
|
234
|
+
"""95% Wilson score interval, low rounded down and high rounded up."""
|
|
235
|
+
|
|
236
|
+
low_percent: int
|
|
237
|
+
high_percent: int
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
class EvaluationHeadline(TypedDict):
|
|
241
|
+
"""Compact result projection served on an evaluation job record."""
|
|
242
|
+
|
|
243
|
+
summary: str
|
|
244
|
+
"""The report's first RESULT line, omitted when longer than :data:`MAX_EVALUATION_HEADLINE_SUMMARY_CHARS`."""
|
|
245
|
+
episodes: int
|
|
246
|
+
succeeded: int
|
|
247
|
+
likely_range: LikelyRange
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class CheckCount(TypedDict):
|
|
251
|
+
"""How many episodes passed one check on its own."""
|
|
252
|
+
|
|
253
|
+
name: str
|
|
254
|
+
passed: int
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class EndingCount(TypedDict):
|
|
258
|
+
"""How many episodes ended one way."""
|
|
259
|
+
|
|
260
|
+
ended_by: str
|
|
261
|
+
"""A condition name, :data:`ENDED_BY_TIME_LIMIT` or :data:`ENDED_BY_TASK_TRUNCATION`."""
|
|
262
|
+
episodes: int
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class FailureGroup(TypedDict):
|
|
266
|
+
"""Failures grouped by exactly which checks were "no"."""
|
|
267
|
+
|
|
268
|
+
missed: list[str]
|
|
269
|
+
"""Check names, in rule order; empty when the rule failed with every check passing."""
|
|
270
|
+
episodes: int
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class EvaluationResult(TypedDict):
|
|
274
|
+
"""RESULT for one checkpoint."""
|
|
275
|
+
|
|
276
|
+
checkpoint: str
|
|
277
|
+
"""The checkpoint's ``reference``."""
|
|
278
|
+
episodes: int
|
|
279
|
+
succeeded: int
|
|
280
|
+
likely_range: LikelyRange
|
|
281
|
+
checks: list[CheckCount]
|
|
282
|
+
"""One entry per check, in rule order."""
|
|
283
|
+
failure_groups: list[FailureGroup]
|
|
284
|
+
"""Most common first, at most :data:`MAX_FAILURE_GROUPS`."""
|
|
285
|
+
other_failure_combinations: int
|
|
286
|
+
"""How many further groups exist beyond ``failure_groups``. Their episodes
|
|
287
|
+
are the failures ``failure_groups`` doesn't hold, so the listed groups
|
|
288
|
+
account for every failure exactly when this is 0."""
|
|
289
|
+
endings: list[EndingCount]
|
|
290
|
+
"""Every episode counted by how it ended, most common first."""
|
|
291
|
+
simulation_problem_episodes: int
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class EvaluationComparison(TypedDict):
|
|
295
|
+
"""The ``--compare`` verdict: the same episode numbers, run by both checkpoints."""
|
|
296
|
+
|
|
297
|
+
verdict: ComparisonVerdict
|
|
298
|
+
p_value: float
|
|
299
|
+
compared_with: EvaluationCheckpoint
|
|
300
|
+
"""The ``--compare`` checkpoint."""
|
|
301
|
+
compared_result: EvaluationResult
|
|
302
|
+
"""The ``--compare`` checkpoint's own RESULT."""
|
|
303
|
+
improved: int
|
|
304
|
+
"""Episodes that failed with ``compared_with`` and succeeded with the ``--policy`` checkpoint."""
|
|
305
|
+
regressed: int
|
|
306
|
+
"""Episodes that succeeded with ``compared_with`` and failed with the ``--policy`` checkpoint."""
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
class ListedEpisode(TypedDict):
|
|
310
|
+
"""One episode in WENT WRONG: a failure, or a recorded success when all succeeded."""
|
|
311
|
+
|
|
312
|
+
episode: int
|
|
313
|
+
success: bool
|
|
314
|
+
ended_by: str
|
|
315
|
+
"""A condition name, :data:`ENDED_BY_TIME_LIMIT` or :data:`ENDED_BY_TASK_TRUNCATION`."""
|
|
316
|
+
ended_at_s: float
|
|
317
|
+
total_reward: float
|
|
318
|
+
checks: dict[str, bool]
|
|
319
|
+
"""Each check's value on the ending step, keyed by name in rule order."""
|
|
320
|
+
simulation_problem: SimulationProblem | None
|
|
321
|
+
recording: str | None
|
|
322
|
+
"""The recording name for ``simulo recordings JOB <name>``, such as ``episode-64``;
|
|
323
|
+
``None`` when the episode was not recorded."""
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class SimulationProblemEpisode(TypedDict):
|
|
327
|
+
"""One episode in SIMULATION PROBLEMS, at the first step that went wrong."""
|
|
328
|
+
|
|
329
|
+
episode: int
|
|
330
|
+
problem: SimulationProblem
|
|
331
|
+
at_s: float
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
class NextLine(TypedDict):
|
|
335
|
+
"""One NEXT line: what to do, and the command that does it."""
|
|
336
|
+
|
|
337
|
+
rule: NextRule
|
|
338
|
+
text: str
|
|
339
|
+
command: str | None
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
class EvaluationReport(TypedDict):
|
|
343
|
+
"""The whole report, as returned by the eval job body."""
|
|
344
|
+
|
|
345
|
+
report_schema_version: Literal["simulo.evaluation_report.v1"]
|
|
346
|
+
summary: str
|
|
347
|
+
"""RESULT's first line, such as ``87 of 100 episodes succeeded. Likely range: 79% to 93%.``"""
|
|
348
|
+
task_changed: bool
|
|
349
|
+
"""Same as ``tested.task.changed``; when true, ``notice`` leads the report."""
|
|
350
|
+
notice: str | None
|
|
351
|
+
"""The first line when the task changed since training, such as
|
|
352
|
+
``task.py has changed since policy_x was trained``; otherwise ``None``."""
|
|
353
|
+
runtime_version: str
|
|
354
|
+
"""The ``simulo-backend`` version the evaluation ran on."""
|
|
355
|
+
tested: EvaluationTested
|
|
356
|
+
result: EvaluationResult
|
|
357
|
+
"""The ``--policy`` checkpoint's RESULT."""
|
|
358
|
+
comparison: EvaluationComparison | None
|
|
359
|
+
"""Set only for a ``--compare`` evaluation."""
|
|
360
|
+
failures: int
|
|
361
|
+
"""How many episodes failed; ``listed_episodes`` holds a selection of them."""
|
|
362
|
+
recorded: int
|
|
363
|
+
"""How many episodes have a recording, at most :data:`MAX_RECORDED_EPISODES`."""
|
|
364
|
+
listed_episodes: list[ListedEpisode]
|
|
365
|
+
"""The recorded episodes in the order they were chosen, then up to three more
|
|
366
|
+
failures by lowest total reward (ties by episode number); at most
|
|
367
|
+
:data:`MAX_LISTED_EPISODES`."""
|
|
368
|
+
simulation_problems: list[SimulationProblemEpisode]
|
|
369
|
+
"""The first :data:`MAX_LISTED_SIMULATION_PROBLEMS` episodes with a
|
|
370
|
+
simulation problem, in episode order."""
|
|
371
|
+
next: list[NextLine]
|
|
372
|
+
"""At most :data:`MAX_NEXT_LINES`."""
|
|
373
|
+
recording_problem: NotRequired[str]
|
|
374
|
+
"""Why requested filming did not complete, at most
|
|
375
|
+
:data:`MAX_RECORDING_PROBLEM_CHARS` characters. Omitted when filming succeeds
|
|
376
|
+
and by reports written before this additive v1 field existed."""
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
# --- Replay traces (filming) -------------------------------------------------
|
|
380
|
+
#
|
|
381
|
+
# While an evaluation runs, each counted episode buffers the state needed to
|
|
382
|
+
# replay it. The filming pass replays a kept trace frame by frame by setting
|
|
383
|
+
# that state, with no physics, and films it.
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
class ReplayPose(TypedDict):
|
|
387
|
+
"""A world-frame pose: position in metres, orientation as a (w, x, y, z) quaternion."""
|
|
388
|
+
|
|
389
|
+
position: list[float]
|
|
390
|
+
orientation_wxyz: list[float]
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
class ReplayFrame(TypedDict):
|
|
394
|
+
"""The robot's and the scene's state at one moment: the start, then after each policy step."""
|
|
395
|
+
|
|
396
|
+
time_s: float
|
|
397
|
+
joint_positions: list[float]
|
|
398
|
+
"""One value per joint, in ``ReplayTrace.joint_names`` order: radians for a hinge, metres for a slider."""
|
|
399
|
+
joint_velocities: list[float]
|
|
400
|
+
root_pose: ReplayPose
|
|
401
|
+
object_poses: dict[str, ReplayPose]
|
|
402
|
+
"""The scene's moving objects other than the robot, keyed by their scene path."""
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
class ReplayTrace(TypedDict):
|
|
406
|
+
"""One episode, replayable without physics."""
|
|
407
|
+
|
|
408
|
+
episode: int
|
|
409
|
+
checkpoint: str
|
|
410
|
+
joint_names: list[str]
|
|
411
|
+
step_dt: float
|
|
412
|
+
"""Seconds between frames."""
|
|
413
|
+
source_env_origin: list[float]
|
|
414
|
+
"""The source environment's world-frame xyz origin as three finite metres.
|
|
415
|
+
|
|
416
|
+
Filming translates world-frame poses from this origin into its destination
|
|
417
|
+
environment.
|
|
418
|
+
"""
|
|
419
|
+
frames: list[ReplayFrame]
|
|
420
|
+
"""The first frame is the episode's starting state, ``time_s == 0``."""
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
# --- episodes.csv ------------------------------------------------------------
|
|
424
|
+
#
|
|
425
|
+
# One row per episode. Booleans are ``true``/``false``. ``sim_problem`` holds a
|
|
426
|
+
# ``SimulationProblem`` value and ``recording`` a recording name, each empty when
|
|
427
|
+
# there is none. ``first_observation`` is the policy's observation on the
|
|
428
|
+
# episode's first step, as a JSON list.
|
|
429
|
+
|
|
430
|
+
EPISODES_CSV_COLUMNS_BEFORE_CHECKS: Final = (
|
|
431
|
+
"episode",
|
|
432
|
+
"seed",
|
|
433
|
+
"checkpoint",
|
|
434
|
+
"success",
|
|
435
|
+
"ended_by",
|
|
436
|
+
"ended_at_s",
|
|
437
|
+
"total_reward",
|
|
438
|
+
"sim_problem",
|
|
439
|
+
)
|
|
440
|
+
EPISODES_CSV_CHECK_PREFIX: Final = "check:"
|
|
441
|
+
EPISODES_CSV_COLUMNS_AFTER_CHECKS: Final = ("first_observation", "recording")
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def episodes_csv_columns(check_names: Sequence[str]) -> tuple[str, ...]:
|
|
445
|
+
"""The ``episodes.csv`` header for a rule with these checks, in rule order."""
|
|
446
|
+
|
|
447
|
+
return (
|
|
448
|
+
*EPISODES_CSV_COLUMNS_BEFORE_CHECKS,
|
|
449
|
+
*(EPISODES_CSV_CHECK_PREFIX + name for name in check_names),
|
|
450
|
+
*EPISODES_CSV_COLUMNS_AFTER_CHECKS,
|
|
451
|
+
)
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/manifest.py
RENAMED
|
@@ -96,11 +96,11 @@ class Manifest:
|
|
|
96
96
|
source regardless of which job or args were submitted; used for
|
|
97
97
|
reproducibility / source caching.
|
|
98
98
|
* ``package_id`` — the *submission* identity: a ``sha256`` over
|
|
99
|
-
``source_digest + job_name + canonical_json(args)``.
|
|
100
|
-
package directory name. Two submits of the same source
|
|
101
|
-
different job or args get a different ``package_id``
|
|
102
|
-
package dir), so one submission never clobbers another's
|
|
103
|
-
``args``.
|
|
99
|
+
``source_digest + entrypoint.file + job_name + canonical_json(args)``.
|
|
100
|
+
It is also the package directory name. Two submits of the same source
|
|
101
|
+
with a different entry file, job, or args get a different ``package_id``
|
|
102
|
+
(a distinct package dir), so one submission never clobbers another's
|
|
103
|
+
recorded entrypoint or ``args``.
|
|
104
104
|
|
|
105
105
|
``manifest_version`` defaults to **2**. Every entry in ``jobs`` carries a
|
|
106
106
|
required ``"type"`` key holding a :class:`~simulo.interfaces.platform.enums.JobType`
|
|
@@ -109,7 +109,8 @@ class Manifest:
|
|
|
109
109
|
|
|
110
110
|
⚠️ **Wire stability:** :meth:`to_json` bytes feed the package archive,
|
|
111
111
|
whose sha256 the control plane pins per ``package_id`` — while
|
|
112
|
-
``package_id`` itself hashes only
|
|
112
|
+
``package_id`` itself hashes only
|
|
113
|
+
``(source_digest, entrypoint.file, job_name, args)``.
|
|
113
114
|
Any change to this dataclass's fields or serialisation therefore shifts
|
|
114
115
|
``archive_sha256`` under an UNCHANGED ``package_id``, which is exactly
|
|
115
116
|
the server's ``package_conflict`` 409 for every user resubmitting an
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/runs.py
RENAMED
|
@@ -193,6 +193,7 @@ from typing import Any, Optional
|
|
|
193
193
|
from simulo.interfaces.ids import JobId, JobPublicId, PackageId, parse_job_public_id
|
|
194
194
|
from simulo.interfaces.platform._payload import known_payload_fields
|
|
195
195
|
from simulo.interfaces.platform.enums import JobStatus, JobType
|
|
196
|
+
from simulo.interfaces.platform.evaluation import EvaluationHeadline
|
|
196
197
|
from simulo.interfaces.platform.outputs import JobOutputsSummary, OutputFlag
|
|
197
198
|
|
|
198
199
|
#: Default TCP port for the jobs API. Locally this is where ``simulo-backend
|
|
@@ -413,6 +414,16 @@ class JobRecord:
|
|
|
413
414
|
input_checkpoint: Optional[str] = None
|
|
414
415
|
"""The policy checkpoint this job started from, or ``None`` for a fresh job."""
|
|
415
416
|
|
|
417
|
+
compare_checkpoint: Optional[str] = None
|
|
418
|
+
"""The checkpoint an evaluation compares against, or ``None`` when it evaluates one policy."""
|
|
419
|
+
|
|
420
|
+
evaluation: Optional[EvaluationHeadline] = None
|
|
421
|
+
"""Compact result for an evaluation job, or ``None`` for another job or an
|
|
422
|
+
evaluation whose report is not available. Only the policy job list fills
|
|
423
|
+
this field. The full evaluation report stays on the job result endpoint;
|
|
424
|
+
this list-safe projection carries only its summary, episode count,
|
|
425
|
+
successes, and likely success-rate range."""
|
|
426
|
+
|
|
416
427
|
tags: tuple[str, ...] = ()
|
|
417
428
|
"""Immutable, normalized job tags in first-seen order."""
|
|
418
429
|
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/submit.py
RENAMED
|
@@ -104,6 +104,11 @@ platform-standard shape: ``{"error": {"code": ..., "message": ..., "request_id":
|
|
|
104
104
|
checkpoint belongs to one execution. Consumers therefore keep
|
|
105
105
|
``input_checkpoint`` outside ``args`` and outside the package manifest.
|
|
106
106
|
|
|
107
|
+
An evaluation submission may additionally carry ``compare_checkpoint`` at
|
|
108
|
+
the same top level. It uses the same policy checkpoint grammar and resolves
|
|
109
|
+
inside the selected organization. The two fields identify their roles: the
|
|
110
|
+
``input_checkpoint`` is evaluated and ``compare_checkpoint`` is the baseline.
|
|
111
|
+
|
|
107
112
|
**App name (live-view wave, additive):** the request body
|
|
108
113
|
optionally carries ``app_name`` (``str``) — the submitting ``App``'s
|
|
109
114
|
declared name, display context the platform's live viewer shows next to
|
|
@@ -502,6 +507,11 @@ WORKER_CLAIM_INPUT_CHECKPOINT_FIELDS = (
|
|
|
502
507
|
"checkpoint_meta",
|
|
503
508
|
)
|
|
504
509
|
|
|
510
|
+
#: Claim-time metadata fact stating whether the source policy currently has a
|
|
511
|
+
#: ``latest`` checkpoint. The control plane owns this availability snapshot;
|
|
512
|
+
#: workers carry it through the installed sidecar to ``PolicyCheckpoint.meta``.
|
|
513
|
+
INPUT_CHECKPOINT_LATEST_AVAILABLE_META_KEY = "input_checkpoint_latest_available"
|
|
514
|
+
|
|
505
515
|
#: Request header carrying the claim's ``lease_id`` ownership token on every
|
|
506
516
|
#: job-scoped worker call (heartbeat / logs / events / complete). A header — not a body
|
|
507
517
|
#: field — because the logs route's body is raw bytes and heartbeat has no
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/runtime/components.py
RENAMED
|
@@ -87,6 +87,9 @@ class TerminationConditionProtocol(Protocol):
|
|
|
87
87
|
mirror of ``simulo.core.terminations.TerminationCondition``.
|
|
88
88
|
"""
|
|
89
89
|
|
|
90
|
+
name: Optional[str]
|
|
91
|
+
"""Optional plain-language name used to report this end condition."""
|
|
92
|
+
|
|
90
93
|
def initialize(self, robot: RobotProtocol, device: str, num_envs: int) -> None:
|
|
91
94
|
"""One-time setup: cache the ``robot`` handle and size buffers for ``num_envs`` on ``device``."""
|
|
92
95
|
...
|
|
@@ -90,12 +90,24 @@ class LearningEnvProtocol(Protocol):
|
|
|
90
90
|
def step(self, actions: TensorLike) -> Tuple[TensorLike, TensorLike, TensorLike, TensorLike, Dict[str, Any]]:
|
|
91
91
|
"""Advance one step with ``actions`` ``(num_envs, action_dim)``.
|
|
92
92
|
|
|
93
|
+
The environment passes a copy of ``actions`` to the task before
|
|
94
|
+
physics advances. Tasks can modify that copy in place; the original
|
|
95
|
+
batch remains available to recording as the policy's choice.
|
|
96
|
+
|
|
93
97
|
Returns ``(observations, rewards, terminated, truncated, info)`` — the
|
|
94
98
|
gym-style 5-tuple, each tensor batched over ``num_envs``. Environments
|
|
95
99
|
that finish are auto-reset by the implementation.
|
|
96
100
|
"""
|
|
97
101
|
...
|
|
98
102
|
|
|
103
|
+
def seeded_reset(self, env_id: int, episode_number: int) -> None:
|
|
104
|
+
"""Reset one environment after seeding global generators for an episode."""
|
|
105
|
+
...
|
|
106
|
+
|
|
107
|
+
def add_pre_reset_hook(self, hook: Any) -> None:
|
|
108
|
+
"""Register code to run after dones are collected and before reset."""
|
|
109
|
+
...
|
|
110
|
+
|
|
99
111
|
def close(self) -> None:
|
|
100
112
|
"""Shut the simulation down and release its resources."""
|
|
101
113
|
...
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""``TaskProtocol`` — structural mirror of ``simulo.core.task.Task`` (Part A)."""
|
|
2
2
|
|
|
3
|
-
from typing import Any, Protocol, Tuple, runtime_checkable
|
|
3
|
+
from typing import Any, Dict, Protocol, Tuple, runtime_checkable
|
|
4
4
|
|
|
5
5
|
# A REAL import, deliberately not TYPE_CHECKING-guarded: the guarded form left
|
|
6
6
|
# ``typing.get_type_hints(TaskProtocol.build)`` raising ``NameError`` at
|
|
@@ -24,6 +24,11 @@ class TaskProtocol(Protocol):
|
|
|
24
24
|
|
|
25
25
|
Structural mirror of ``simulo.core.task.Task``, the abstract base your task
|
|
26
26
|
subclasses on the worker.
|
|
27
|
+
|
|
28
|
+
Each environment step calls ``on_pre_physics_step(actions)``, advances
|
|
29
|
+
physics, then calls ``on_post_physics_step()`` before collecting dones,
|
|
30
|
+
rewards, observations, and a pre-reset recording snapshot. It resets
|
|
31
|
+
environments whose episodes ended after that snapshot.
|
|
27
32
|
"""
|
|
28
33
|
|
|
29
34
|
observation_dim: int
|
|
@@ -54,6 +59,25 @@ class TaskProtocol(Protocol):
|
|
|
54
59
|
"""
|
|
55
60
|
...
|
|
56
61
|
|
|
62
|
+
def on_pre_physics_step(self, actions: TensorLike) -> None:
|
|
63
|
+
"""Process one policy action batch before physics advances.
|
|
64
|
+
|
|
65
|
+
``LearningEnv.step`` gives this hook a copy of the policy actions, so
|
|
66
|
+
tasks may safely scale or otherwise modify ``actions`` in place. The
|
|
67
|
+
environment keeps the original batch for recording what the policy
|
|
68
|
+
chose.
|
|
69
|
+
"""
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
def on_post_physics_step(self) -> None:
|
|
73
|
+
"""Run once after physics advances and before dones, rewards, and observations.
|
|
74
|
+
|
|
75
|
+
Use this hook for work that must run exactly once per environment step,
|
|
76
|
+
or for an expensive value shared by several later task methods. Values
|
|
77
|
+
cached here must also be set for reset environments in ``reset_idx``.
|
|
78
|
+
"""
|
|
79
|
+
return None
|
|
80
|
+
|
|
57
81
|
def get_observations(self) -> TensorLike:
|
|
58
82
|
"""Return the observation batch, shape ``(num_envs, observation_dim)``."""
|
|
59
83
|
...
|
|
@@ -62,16 +86,21 @@ class TaskProtocol(Protocol):
|
|
|
62
86
|
"""Return the per-step reward for every environment, shape ``(num_envs,)``."""
|
|
63
87
|
...
|
|
64
88
|
|
|
65
|
-
def get_dones(self) -> Tuple[TensorLike, TensorLike]:
|
|
66
|
-
"""Return
|
|
89
|
+
def get_dones(self) -> Tuple[Dict[str, TensorLike], TensorLike]:
|
|
90
|
+
"""Return named termination batches and one truncated batch.
|
|
67
91
|
|
|
68
|
-
``terminated`` marks
|
|
69
|
-
``truncated`` marks episodes cut off by a time
|
|
92
|
+
Each ``terminated`` value marks where the task ended the episode,
|
|
93
|
+
whatever the reason. ``truncated`` marks episodes cut off by a time
|
|
94
|
+
limit.
|
|
70
95
|
"""
|
|
71
96
|
...
|
|
72
97
|
|
|
73
98
|
def apply_actions(self, actions: TensorLike) -> None:
|
|
74
|
-
"""Apply the policy's action batch
|
|
99
|
+
"""Apply a copy of the policy's action batch to the sim.
|
|
100
|
+
|
|
101
|
+
``actions`` has shape ``(num_envs, action_dim)``. It may be modified in
|
|
102
|
+
place without changing the policy action batch the environment records.
|
|
103
|
+
"""
|
|
75
104
|
...
|
|
76
105
|
|
|
77
106
|
def reset_idx(self, env_ids: TensorLike) -> None:
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/runtime/trainer.py
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""``TrainerProtocol`` — structural mirror of ``simulo.core.trainer.Trainer`` (Part A).
|
|
2
2
|
|
|
3
|
-
The contract is deliberately behavioral — ``train`` / ``
|
|
4
|
-
|
|
3
|
+
The contract is deliberately behavioral — ``train`` / ``save`` / ``load`` and
|
|
4
|
+
nothing else (re-affirmed at the Wave C4 mirror completion; the
|
|
5
5
|
executable half of this record is the documented-omissions allowlist in the
|
|
6
6
|
backend suite's conformance engine): ``env`` / ``device`` / ``seed`` /
|
|
7
7
|
``log_dir`` are conveniences the base ``simulo.core.trainer.Trainer.__init__``
|
|
@@ -20,12 +20,12 @@ user-extensible ABC) does not guarantee. Implementations that CAN export (today:
|
|
|
20
20
|
consumers feature-detect with ``isinstance(trainer, PolicyExporterProtocol)``.
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
|
-
from typing import Any, Dict,
|
|
23
|
+
from typing import Any, Dict, Protocol, runtime_checkable
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
@runtime_checkable
|
|
27
27
|
class TrainerProtocol(Protocol):
|
|
28
|
-
"""The training-orchestration contract — run
|
|
28
|
+
"""The training-orchestration contract — run and persist a training job.
|
|
29
29
|
|
|
30
30
|
A trainer owns the RL loop: it drives an environment, updates the policy, and
|
|
31
31
|
reads/writes checkpoints. Structural mirror of ``simulo.core.trainer.Trainer``.
|
|
@@ -39,14 +39,6 @@ class TrainerProtocol(Protocol):
|
|
|
39
39
|
"""
|
|
40
40
|
...
|
|
41
41
|
|
|
42
|
-
def evaluate(self, checkpoint: Optional[str] = ..., num_episodes: int = ...) -> Dict[str, Any]:
|
|
43
|
-
"""Roll out the current (or ``checkpoint``-loaded) policy without learning.
|
|
44
|
-
|
|
45
|
-
Returns evaluation metrics such as mean episode return over
|
|
46
|
-
``num_episodes`` episodes.
|
|
47
|
-
"""
|
|
48
|
-
...
|
|
49
|
-
|
|
50
42
|
def save(self, path: str) -> None:
|
|
51
43
|
"""Write the trainer's full state (policy + optimizer) to ``path``."""
|
|
52
44
|
...
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo_interfaces.egg-info/SOURCES.txt
RENAMED
|
@@ -32,6 +32,7 @@ src/simulo/interfaces/platform/callbacks.py
|
|
|
32
32
|
src/simulo/interfaces/platform/debug.py
|
|
33
33
|
src/simulo/interfaces/platform/domain.py
|
|
34
34
|
src/simulo/interfaces/platform/enums.py
|
|
35
|
+
src/simulo/interfaces/platform/evaluation.py
|
|
35
36
|
src/simulo/interfaces/platform/job_events.py
|
|
36
37
|
src/simulo/interfaces/platform/manifest.py
|
|
37
38
|
src/simulo/interfaces/platform/onnx_bundle.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/__init__.py
RENAMED
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/actuator.py
RENAMED
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/asset.py
RENAMED
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/checkpoint.py
RENAMED
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/entity.py
RENAMED
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/light.py
RENAMED
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/materials.py
RENAMED
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/physics.py
RENAMED
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/pose.py
RENAMED
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/primitives.py
RENAMED
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/prop.py
RENAMED
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/scene.py
RENAMED
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/sensor.py
RENAMED
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/visual.py
RENAMED
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/authoring/world.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/_payload.py
RENAMED
|
File without changes
|
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/asset.py
RENAMED
|
File without changes
|
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/callbacks.py
RENAMED
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/debug.py
RENAMED
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/domain.py
RENAMED
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/job_events.py
RENAMED
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/onnx_bundle.py
RENAMED
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/outputs.py
RENAMED
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/preview.py
RENAMED
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/runtime.py
RENAMED
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/systems.py
RENAMED
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/platform/volume.py
RENAMED
|
File without changes
|
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/runtime/__init__.py
RENAMED
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/runtime/anomaly.py
RENAMED
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/runtime/player.py
RENAMED
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/runtime/policy.py
RENAMED
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo/interfaces/runtime/tensors.py
RENAMED
|
File without changes
|
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo_interfaces.egg-info/requires.txt
RENAMED
|
File without changes
|
{simulo_interfaces-0.22.0 → simulo_interfaces-0.23.0}/src/simulo_interfaces.egg-info/top_level.txt
RENAMED
|
File without changes
|