sequence-base 0.5.1__tar.gz → 0.6.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.
- {sequence_base-0.5.1 → sequence_base-0.6.0}/PKG-INFO +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/pyproject.toml +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/__init__.py +1 -3
- sequence_base-0.6.0/sequence_base/codec/compressed.py +174 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/constants/limits.py +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/constants/version.py +2 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/construct.py +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/contract/__init__.py +27 -16
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/contract/connect.py +8 -30
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/contract/deployments.py +14 -12
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/contract/observe.py +8 -3
- sequence_base-0.6.0/sequence_base/contract/own_io.py +97 -0
- sequence_base-0.6.0/sequence_base/contract/planner.py +234 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/contract/policy.py +24 -18
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schema_export.py +24 -24
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/ActRequest.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/ActResponse.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/ActionChunk.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/ActionContract.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/ActionStep.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/AdapterPair.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/AddLocal.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/Autoscaler.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/BenchmarkRegisterRequest.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/BenchmarkSpec.json +3 -3
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/BenchmarkStatusResponse.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/BootstrapResponse.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/Camera.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/CodeBundle.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/Concurrency.json +1 -1
- sequence_base-0.6.0/sequence_base/schemas/ConnectRequest.json +28 -0
- sequence_base-0.6.0/sequence_base/schemas/ConnectResponse.json +204 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/DeploymentAccepted.json +1 -1
- sequence_base-0.6.0/sequence_base/schemas/DeploymentPolicy.json +332 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/DeploymentRequest.json +28 -300
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/DeploymentStatusResponse.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/ErrorBody.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/ErrorCodes.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/Estimate.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/EvalDryRunResponse.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/EvalReport.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/EvalRequest.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/EvalResponse.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/ImageFrame.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/ImageSpec.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/ImageStepAddLocal.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/ImageStepApt.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/ImageStepEnv.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/ImageStepProject.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/ImageStepRun.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/ImageStepTemplateFiles.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/ImageStepUvPip.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/InferParam.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/Lease.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/MetricAgg.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/Observation.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/ObservationContract.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/ObserveRequest.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/ObserveResponse.json +1 -1
- sequence_base-0.6.0/sequence_base/schemas/PlanAppend.json +13 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/PlanAppended.json +1 -1
- sequence_base-0.6.0/sequence_base/schemas/PlannerMember.json +88 -0
- sequence_base-0.5.1/sequence_base/schemas/GroupObservation.json → sequence_base-0.6.0/sequence_base/schemas/PlannerObservation.json +4 -4
- sequence_base-0.6.0/sequence_base/schemas/PlannerObserve.json +13 -0
- sequence_base-0.6.0/sequence_base/schemas/PlannerObserved.json +16 -0
- sequence_base-0.6.0/sequence_base/schemas/PlannerPlanIn.json +33 -0
- sequence_base-0.6.0/sequence_base/schemas/PlannerPlans.json +66 -0
- sequence_base-0.5.1/sequence_base/schemas/TeamPlansWritten.json → sequence_base-0.6.0/sequence_base/schemas/PlannerPlansWritten.json +3 -3
- sequence_base-0.5.1/sequence_base/schemas/TeamMember.json → sequence_base-0.6.0/sequence_base/schemas/PlannerRobot.json +4 -9
- sequence_base-0.6.0/sequence_base/schemas/PlannerRound.json +200 -0
- sequence_base-0.5.1/sequence_base/schemas/TeamGroupsRequest.json → sequence_base-0.6.0/sequence_base/schemas/PlannerRoundRequest.json +3 -3
- sequence_base-0.6.0/sequence_base/schemas/PlannerSpec.json +22 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/PolicyRegistration.json +21 -293
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/PolicySpec.json +19 -291
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/Project.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/Proprioception.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/ReplaySpec.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/Resources.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/RolloutResult.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/SecretCreateRequest.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/SecretInfo.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/SecretRef.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/SeqFlags.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/SettingsChange.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/SettingsChanged.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/SettingsView.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/Standby.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/TaskAgg.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/TemplateFiles.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/Tensors.json +2 -2
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/Timeouts.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/UploadTarget.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/VolumeFile.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/VolumeInfo.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/VolumeMount.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/VolumeRef.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/VolumeUploadRequest.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/VolumeUploadResponse.json +1 -1
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/schemas/WarmingError.json +1 -1
- sequence_base-0.5.1/sequence_base/contract/obs_schedule.py +0 -170
- sequence_base-0.5.1/sequence_base/contract/own_io.py +0 -208
- sequence_base-0.5.1/sequence_base/contract/team.py +0 -185
- sequence_base-0.5.1/sequence_base/schemas/ConnectRequest.json +0 -68
- sequence_base-0.5.1/sequence_base/schemas/ConnectResponse.json +0 -476
- sequence_base-0.5.1/sequence_base/schemas/DeploymentPolicy.json +0 -604
- sequence_base-0.5.1/sequence_base/schemas/GroupObserve.json +0 -13
- sequence_base-0.5.1/sequence_base/schemas/GroupObserved.json +0 -16
- sequence_base-0.5.1/sequence_base/schemas/Observations.json +0 -264
- sequence_base-0.5.1/sequence_base/schemas/PlanAppend.json +0 -13
- sequence_base-0.5.1/sequence_base/schemas/Stream.json +0 -193
- sequence_base-0.5.1/sequence_base/schemas/TeamGroup.json +0 -120
- sequence_base-0.5.1/sequence_base/schemas/TeamGroups.json +0 -135
- sequence_base-0.5.1/sequence_base/schemas/TeamPlans.json +0 -51
- sequence_base-0.5.1/sequence_base/schemas/TeamSpec.json +0 -22
- {sequence_base-0.5.1 → sequence_base-0.6.0}/.gitignore +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/README.md +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/auth.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/by_name.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/codec/__init__.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/codec/image.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/codec/marked.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/constants/__init__.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/constants/cards.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/constants/headers.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/contract/accounts.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/contract/act.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/contract/adapters.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/contract/benchmarks.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/contract/errors.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/contract/eval.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/contract/flags.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/contract/io.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/contract/retired.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/contract/settings.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/contract/templates.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/contract/volumes.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/contract/wire.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/episode.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/errors/__init__.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/errors/transport.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/msgpack_io.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/obs/__init__.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/obs/log.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/obs/rid.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/presets/__init__.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/presets/frames.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/presets/robot_arm.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/telemetry/__init__.py +0 -0
- {sequence_base-0.5.1 → sequence_base-0.6.0}/sequence_base/telemetry/events.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sequence-base
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Shared foundation for the Sequences platform — wire contract, correlation, error taxonomy, constants. Used by the robot SDK, gateway, and container harness.
|
|
5
5
|
Project-URL: Changelog, https://generalsequences.com/docs/changelog/
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sequence-base"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.6.0"
|
|
4
4
|
description = "Shared foundation for the Sequences platform — wire contract, correlation, error taxonomy, constants. Used by the robot SDK, gateway, and container harness."
|
|
5
5
|
requires-python = ">=3.10"
|
|
6
6
|
dependencies = ["pydantic>=2.0"]
|
|
@@ -86,8 +86,6 @@ from sequence_base.contract import (
|
|
|
86
86
|
PolicyConstructor,
|
|
87
87
|
PolicyRegistration,
|
|
88
88
|
PolicySpec,
|
|
89
|
-
Observations,
|
|
90
|
-
Stream,
|
|
91
89
|
Tensors,
|
|
92
90
|
Proprioception,
|
|
93
91
|
ReplaySpec,
|
|
@@ -145,7 +143,7 @@ __all__ = [
|
|
|
145
143
|
"FROZEN_PATH", "TemplateFiles", "ImageStepTemplateFiles", "Project", "ImageStepProject", "ImageStep", "ImageSpec",
|
|
146
144
|
"VolumeMount", "VolumeRef", "Concurrency", "ActionContract", "Camera", "ObservationContract",
|
|
147
145
|
"PolicySpec", "PolicyRegistration", "PolicyConstructor",
|
|
148
|
-
"
|
|
146
|
+
"Tensors",
|
|
149
147
|
|
|
150
148
|
"MAX_CODE_BUNDLE_BYTES", "CodeBundle",
|
|
151
149
|
"Resources", "Autoscaler", "Timeouts", "DeploymentPolicy", "InferParam", "DeploymentRequest",
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Compressed observe items: what a robot's SDK puts in place of a large array or bytes of its observe data when sending it
|
|
3
|
+
compressed saves more upload time than compressing it costs, and what a worker turns back - losslessly, so every reader
|
|
4
|
+
after the worker is handed the bytes the robot built.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
In one sentence: the robot's SDK compresses an unmarked numpy array or bytes of 16 KiB or more with zstd level 1 (zlib
|
|
9
|
+
level 1 where it has no zstd) and puts the map below in its place → the worker names contract.observe.ZSTD_FORMAT, so
|
|
10
|
+
only it is sent such maps → as a post comes, before anything keeps it, expand() turns every map back into the array or
|
|
11
|
+
bytes it was (np.frombuffer: read-only, as msgpack_io reads an array) and says how many bytes that added.
|
|
12
|
+
|
|
13
|
+
The map, version 1 (inside the observe body's msgpack; `data` is bin):
|
|
14
|
+
{"__seq__": "compressed", "v": 1, "codec": "zstd" | "zlib", "n": <bytes before>, "data": <compressed>}
|
|
15
|
+
bytes as they were
|
|
16
|
+
... and "type": numpy's string for its dtype, "shape": the array's - an array, its data C-ordered
|
|
17
|
+
`n` is checked against the bytes the data turns back into, and against the most one item may hold (the bytes of an
|
|
18
|
+
episode's observations a worker keeps): a map that says less than it holds is refused rather than decompressed past it.
|
|
19
|
+
|
|
20
|
+
WHY the observation format's reserved key: marked items (codec/marked.py) already take a map with "__seq__", so one key
|
|
21
|
+
is reserved in observe data, not two; a compressed item is never inside a marked one - the SDK compresses after encoding
|
|
22
|
+
its markers and leaves their maps as they are - and expand() does not look inside one.
|
|
23
|
+
WHY decompressed as the post comes, at the parent: the stream, Field data and the standby's saves all read a post's
|
|
24
|
+
frames as it comes, and each must keep the robot's data, not the wire's.
|
|
25
|
+
|
|
26
|
+
wire = compress(frame, "zstd") # a reference encoder (the SDK has its own, with its rule)
|
|
27
|
+
value, grew = expand(post_inputs, limit=64 << 20) # the worker, as a post comes
|
|
28
|
+
"""
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import zlib
|
|
32
|
+
from typing import Any
|
|
33
|
+
|
|
34
|
+
from sequence_base.codec.marked import MARK, is_marked
|
|
35
|
+
|
|
36
|
+
__all__ = ["CODECS", "KIND", "VERSION", "CompressedError", "compress", "expand", "holds", "is_compressed"]
|
|
37
|
+
|
|
38
|
+
KIND = "compressed"
|
|
39
|
+
VERSION = 1
|
|
40
|
+
CODECS = ("zstd", "zlib")
|
|
41
|
+
_LEVEL = 1
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class CompressedError(ValueError):
|
|
45
|
+
"""A compressed item that does not read: an unknown codec, data that does not turn back into the `n` bytes it says,
|
|
46
|
+
an `n` past the limit, or an array's type or shape that does not fit them."""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def is_compressed(value: Any) -> bool:
|
|
50
|
+
return isinstance(value, dict) and value.get(MARK) == KIND
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _zstd():
|
|
54
|
+
"""zstd's (compress, decompress) - zstandard's, else Python 3.14's compression.zstd. The decompress takes the bytes
|
|
55
|
+
the item says it holds and refuses a frame whose header says other: a header is what sizes the buffer."""
|
|
56
|
+
try:
|
|
57
|
+
import zstandard
|
|
58
|
+
|
|
59
|
+
def unzstd(b: bytes, n: int) -> bytes:
|
|
60
|
+
said = zstandard.frame_content_size(b)
|
|
61
|
+
if said not in (-1, n):
|
|
62
|
+
raise CompressedError(f"a zstd item whose frame says {said} bytes and whose map says {n}")
|
|
63
|
+
return zstandard.ZstdDecompressor().decompress(b, max_output_size=n)
|
|
64
|
+
|
|
65
|
+
return (lambda b: zstandard.ZstdCompressor(level=_LEVEL).compress(b), unzstd)
|
|
66
|
+
except ImportError:
|
|
67
|
+
from compression import zstd
|
|
68
|
+
|
|
69
|
+
def unzstd(b: bytes, n: int) -> bytes:
|
|
70
|
+
said = zstd.get_frame_info(b).decompressed_size
|
|
71
|
+
if said not in (None, n):
|
|
72
|
+
raise CompressedError(f"a zstd item whose frame says {said} bytes and whose map says {n}")
|
|
73
|
+
d = zstd.ZstdDecompressor()
|
|
74
|
+
raw = d.decompress(b, max_length=n)
|
|
75
|
+
if not d.eof:
|
|
76
|
+
raise CompressedError(f"a zstd item that holds more than the {n} bytes it says")
|
|
77
|
+
return raw
|
|
78
|
+
|
|
79
|
+
return (lambda b: zstd.compress(b, level=_LEVEL), unzstd)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def compress(value: Any, codec: str = "zstd") -> dict:
|
|
83
|
+
"""`value` - a numpy array or bytes - as its compressed item: a reference encoder, for tests and the platform's own
|
|
84
|
+
senders; the robot's SDK decides by its own rule what to compress."""
|
|
85
|
+
import numpy as np
|
|
86
|
+
|
|
87
|
+
if isinstance(value, np.ndarray):
|
|
88
|
+
raw = np.ascontiguousarray(value).tobytes()
|
|
89
|
+
extra = {"type": value.dtype.str, "shape": list(value.shape)}
|
|
90
|
+
else:
|
|
91
|
+
raw, extra = bytes(value), {}
|
|
92
|
+
if codec == "zstd":
|
|
93
|
+
data = _zstd()[0](raw)
|
|
94
|
+
elif codec == "zlib":
|
|
95
|
+
data = zlib.compress(raw, _LEVEL)
|
|
96
|
+
else:
|
|
97
|
+
raise CompressedError(f"no codec {codec!r}: {', '.join(CODECS)}")
|
|
98
|
+
return dict([(MARK, KIND), ("v", VERSION), ("codec", codec), ("n", len(raw)), *extra.items(), ("data", data)])
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _raw(item: dict, limit: int) -> bytes:
|
|
102
|
+
codec, n, data = item.get("codec"), item.get("n"), item.get("data")
|
|
103
|
+
if codec not in CODECS:
|
|
104
|
+
raise CompressedError(f"a compressed item of codec {codec!r}: {', '.join(CODECS)} are read")
|
|
105
|
+
if isinstance(n, bool) or not isinstance(n, int) or not 0 <= n <= limit:
|
|
106
|
+
raise CompressedError(f"a compressed item of {n!r} bytes: at most {limit} are taken")
|
|
107
|
+
if not isinstance(data, (bytes, bytearray)):
|
|
108
|
+
raise CompressedError("a compressed item whose data is not bytes")
|
|
109
|
+
try:
|
|
110
|
+
if codec == "zstd":
|
|
111
|
+
raw = _zstd()[1](bytes(data), n)
|
|
112
|
+
else:
|
|
113
|
+
d = zlib.decompressobj()
|
|
114
|
+
raw = d.decompress(bytes(data), n)
|
|
115
|
+
if d.unconsumed_tail or not d.eof:
|
|
116
|
+
raise CompressedError(f"a zlib item that holds more than the {n} bytes it says")
|
|
117
|
+
except CompressedError:
|
|
118
|
+
raise
|
|
119
|
+
except Exception as exc:
|
|
120
|
+
raise CompressedError(f"a {codec} item that does not read: {exc}") from None
|
|
121
|
+
if len(raw) != n:
|
|
122
|
+
raise CompressedError(f"a {codec} item of {len(raw)} bytes that says it holds {n}")
|
|
123
|
+
return raw
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _value(item: dict, limit: int) -> Any:
|
|
127
|
+
raw = _raw(item, limit)
|
|
128
|
+
if "type" not in item and "shape" not in item:
|
|
129
|
+
return raw
|
|
130
|
+
import numpy as np
|
|
131
|
+
|
|
132
|
+
from sequence_base.msgpack_io import MsgpackError, _dtype
|
|
133
|
+
|
|
134
|
+
shape = item.get("shape")
|
|
135
|
+
if not isinstance(shape, list) or not all(isinstance(d, int) and not isinstance(d, bool) and d >= 0 for d in shape):
|
|
136
|
+
raise CompressedError("a compressed array whose shape is not a list of non-negative ints")
|
|
137
|
+
try:
|
|
138
|
+
dtype = _dtype(item.get("type"))
|
|
139
|
+
except MsgpackError as exc:
|
|
140
|
+
raise CompressedError(f"a compressed array: {exc}") from None
|
|
141
|
+
if dtype.itemsize * int(np.prod(shape, dtype=np.int64)) != len(raw):
|
|
142
|
+
raise CompressedError(f"a compressed {dtype} array of shape {shape} that holds {len(raw)} bytes")
|
|
143
|
+
return np.frombuffer(raw, dtype=dtype).reshape(shape)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def expand(value: Any, *, limit: int) -> "tuple[Any, int]":
|
|
147
|
+
"""`value` with every compressed item in it - in dicts and lists at any depth, never inside a marked item - turned
|
|
148
|
+
back into the array or bytes it was, the same objects where none is; and how many bytes that added. `limit`: the
|
|
149
|
+
most bytes one item may turn back into. CompressedError for an item that does not read."""
|
|
150
|
+
grew = [0]
|
|
151
|
+
|
|
152
|
+
def walk(v: Any) -> Any:
|
|
153
|
+
if isinstance(v, dict):
|
|
154
|
+
if is_compressed(v):
|
|
155
|
+
out = _value(v, limit)
|
|
156
|
+
grew[0] += max(0, int(v["n"]) - len(v["data"]))
|
|
157
|
+
return out
|
|
158
|
+
if is_marked(v) or not holds(v):
|
|
159
|
+
return v
|
|
160
|
+
return {k: walk(x) for k, x in v.items()}
|
|
161
|
+
if isinstance(v, list):
|
|
162
|
+
return [walk(x) for x in v] if holds(v) else v
|
|
163
|
+
return v
|
|
164
|
+
|
|
165
|
+
return walk(value), grew[0]
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def holds(value: Any) -> bool:
|
|
169
|
+
"""Whether `value` holds a compressed item - in dicts and lists at any depth, not inside a marked item."""
|
|
170
|
+
if isinstance(value, dict):
|
|
171
|
+
return is_compressed(value) or (not is_marked(value) and any(holds(v) for v in value.values()))
|
|
172
|
+
if isinstance(value, list):
|
|
173
|
+
return any(holds(v) for v in value)
|
|
174
|
+
return False
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
How a class built by its constructor is built - a policy's or a
|
|
2
|
+
How a class built by its constructor is built - a policy's or a planner's (PolicyRegistration.constructor), a benchmark's
|
|
3
3
|
(BenchmarkSpec.constructor): its constructor - or the factory it names, a classmethod of it or a module's function -
|
|
4
4
|
called with the arguments its decorator was given (init=), once the forms in them are filled in.
|
|
5
5
|
|
|
@@ -45,7 +45,7 @@ from sequence_base.contract.policy import (
|
|
|
45
45
|
)
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
from sequence_base.contract.own_io import
|
|
48
|
+
from sequence_base.contract.own_io import OBSERVATIONS_GONE, Tensors
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
from sequence_base.contract.deployments import (
|
|
@@ -124,20 +124,30 @@ from sequence_base.contract.settings import (
|
|
|
124
124
|
)
|
|
125
125
|
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
127
|
+
|
|
128
|
+
from sequence_base.contract.planner import (
|
|
129
|
+
KEPT,
|
|
130
|
+
MOST_LAYERS,
|
|
131
|
+
PLANS_MOST,
|
|
132
|
+
STATE_MOST,
|
|
133
|
+
PLAN_ON_A_POLICY,
|
|
134
|
+
PLANNER_ID,
|
|
135
|
+
POLICY_ID,
|
|
136
|
+
ROBOT_ID,
|
|
137
|
+
TEAM_GONE,
|
|
132
138
|
PlanAppend,
|
|
133
139
|
PlanAppended,
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
140
|
+
PlannerMember,
|
|
141
|
+
PlannerObservation,
|
|
142
|
+
PlannerObserve,
|
|
143
|
+
PlannerObserved,
|
|
144
|
+
PlannerPlanIn,
|
|
145
|
+
PlannerPlans,
|
|
146
|
+
PlannerPlansWritten,
|
|
147
|
+
PlannerRobot,
|
|
148
|
+
PlannerRound,
|
|
149
|
+
PlannerRoundRequest,
|
|
150
|
+
PlannerSpec,
|
|
141
151
|
)
|
|
142
152
|
|
|
143
153
|
|
|
@@ -154,7 +164,7 @@ __all__ = [
|
|
|
154
164
|
"TemplateFiles", "ImageStepTemplateFiles", "Project", "ImageStepProject", "FROZEN_PATH", "ImageStep", "ImageSpec",
|
|
155
165
|
"VolumeMount", "VolumeRef", "Concurrency", "ActionContract", "Camera", "ObservationContract",
|
|
156
166
|
"PolicySpec", "PolicyRegistration", "PolicyConstructor",
|
|
157
|
-
"
|
|
167
|
+
"OBSERVATIONS_GONE", "Tensors",
|
|
158
168
|
|
|
159
169
|
"MAX_CODE_BUNDLE_BYTES", "CodeBundle",
|
|
160
170
|
"Resources", "Autoscaler", "Timeouts", "DeploymentPolicy", "InferParam", "DeploymentRequest",
|
|
@@ -174,8 +184,9 @@ __all__ = [
|
|
|
174
184
|
"SETTINGS", "Setting", "SettingsChange", "Applied", "SettingState", "SettingsView", "SettingChanged",
|
|
175
185
|
"SettingsChanged",
|
|
176
186
|
|
|
177
|
-
"
|
|
178
|
-
"
|
|
187
|
+
"KEPT", "MOST_LAYERS", "PLANS_MOST", "STATE_MOST", "PLAN_ON_A_POLICY", "PLANNER_ID", "POLICY_ID", "ROBOT_ID", "TEAM_GONE", "PlannerSpec",
|
|
188
|
+
"PlannerRoundRequest", "PlannerRobot", "PlannerMember", "PlannerObservation", "PlannerPlanIn", "PlannerRound",
|
|
189
|
+
"PlannerPlans", "PlannerPlansWritten", "PlanAppend", "PlanAppended", "PlannerObserve", "PlannerObserved",
|
|
179
190
|
|
|
180
191
|
"TEMPLATE_SCHEME", "parse_template",
|
|
181
192
|
|
|
@@ -16,9 +16,8 @@ from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from sequence_base.contract.errors import WarmingError
|
|
19
|
-
from sequence_base.contract.
|
|
19
|
+
from sequence_base.contract.planner import TEAM_GONE
|
|
20
20
|
from sequence_base.contract.retired import dropped
|
|
21
|
-
from sequence_base.contract.team import check_name
|
|
22
21
|
|
|
23
22
|
__all__ = ["Lease", "ConnectRequest", "ConnectResponse", "Standby", "WarmingError"]
|
|
24
23
|
|
|
@@ -47,34 +46,15 @@ class ConnectRequest(BaseModel):
|
|
|
47
46
|
|
|
48
47
|
|
|
49
48
|
switched: bool = Field(default=False, description="this robot just switched to the standby copy it was handed")
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
team: str | None = Field(default=None, description="the team this robot joins, by name")
|
|
55
|
-
group: str | None = Field(default=None, description="its group in that team; None: the default group")
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
team_key: str | None = Field(default=None, max_length=256,
|
|
60
|
-
description="a team key of the group it joins (seq_team_…); its group is the key's")
|
|
61
|
-
|
|
62
49
|
@model_validator(mode="before")
|
|
63
50
|
@classmethod
|
|
64
51
|
def _retired(cls, data: Any) -> Any:
|
|
65
|
-
return dropped(data, "embodiment")
|
|
66
52
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if self.team is None:
|
|
73
|
-
raise ValueError("a group is a team's: say the team too")
|
|
74
|
-
check_name(self.group, "group")
|
|
75
|
-
if self.team_key is not None and self.team is None:
|
|
76
|
-
raise ValueError("a team key is a team's: say the team too")
|
|
77
|
-
return self
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
if isinstance(data, dict) and any(data.get(k) is not None for k in ("team", "group", "team_key")):
|
|
56
|
+
raise ValueError(TEAM_GONE)
|
|
57
|
+
return dropped(data, "embodiment", "team", "group", "team_key")
|
|
78
58
|
|
|
79
59
|
|
|
80
60
|
class Standby(BaseModel):
|
|
@@ -118,9 +98,6 @@ class ConnectResponse(BaseModel):
|
|
|
118
98
|
io: str | None = None
|
|
119
99
|
|
|
120
100
|
|
|
121
|
-
observations: Observations | None = None
|
|
122
|
-
|
|
123
|
-
|
|
124
101
|
standby: Standby | None = None
|
|
125
102
|
|
|
126
103
|
|
|
@@ -133,4 +110,5 @@ class ConnectResponse(BaseModel):
|
|
|
133
110
|
@classmethod
|
|
134
111
|
def _retired(cls, data: Any) -> Any:
|
|
135
112
|
|
|
136
|
-
|
|
113
|
+
|
|
114
|
+
return dropped(data, "embodiment", "feedback", "action", "observation", "actions", "observations")
|
|
@@ -21,9 +21,9 @@ from typing import Any, Literal
|
|
|
21
21
|
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
|
22
22
|
|
|
23
23
|
from sequence_base.contract.accounts import SecretRef
|
|
24
|
-
from sequence_base.contract.own_io import
|
|
24
|
+
from sequence_base.contract.own_io import Tensors, observations_gone
|
|
25
|
+
from sequence_base.contract.planner import PLAN_ON_A_POLICY, PlannerSpec, team_gone
|
|
25
26
|
from sequence_base.contract.retired import dropped
|
|
26
|
-
from sequence_base.contract.team import TeamSpec
|
|
27
27
|
from sequence_base.contract.policy import (
|
|
28
28
|
KIND_NAME,
|
|
29
29
|
SCALING_RETIRED,
|
|
@@ -185,7 +185,8 @@ class DeploymentPolicy(BaseModel):
|
|
|
185
185
|
@model_validator(mode="before")
|
|
186
186
|
@classmethod
|
|
187
187
|
def _retired(cls, data: Any) -> Any:
|
|
188
|
-
return dropped(data, "embodiments", "feedback", "action", "observation",
|
|
188
|
+
return dropped(team_gone(observations_gone(data)), "embodiments", "feedback", "action", "observation",
|
|
189
|
+
"action_declared", "actions")
|
|
189
190
|
|
|
190
191
|
handler_ref: str
|
|
191
192
|
|
|
@@ -219,26 +220,27 @@ class DeploymentPolicy(BaseModel):
|
|
|
219
220
|
models: list[str] | None = Field(default=None, max_length=16)
|
|
220
221
|
|
|
221
222
|
|
|
222
|
-
|
|
223
|
-
|
|
224
223
|
plan_every_s: float | None = Field(default=None, gt=0)
|
|
225
224
|
|
|
226
225
|
|
|
227
|
-
observations: Observations | None = None
|
|
228
226
|
tensors: Tensors | None = None
|
|
229
227
|
|
|
230
228
|
|
|
231
|
-
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
planner: PlannerSpec | None = None
|
|
232
232
|
|
|
233
233
|
@model_validator(mode="after")
|
|
234
|
-
def
|
|
235
|
-
if self.
|
|
236
|
-
said = [name for name in ("hz", "action_chunk_len", "action_chunk_exec_len", "
|
|
234
|
+
def _planner_answers_no_act(self) -> "DeploymentPolicy":
|
|
235
|
+
if self.planner is not None:
|
|
236
|
+
said = [name for name in ("hz", "action_chunk_len", "action_chunk_exec_len", "tensors")
|
|
237
237
|
if getattr(self, name) is not None]
|
|
238
238
|
if said:
|
|
239
|
-
raise ValueError(f"a
|
|
239
|
+
raise ValueError(f"a planner answers no act, so it declares no {', '.join(said)}")
|
|
240
240
|
if self.plan_every_s is None:
|
|
241
|
-
raise ValueError("a
|
|
241
|
+
raise ValueError("a planner plans: its policy block says plan_every_s")
|
|
242
|
+
elif self.plan_every_s is not None:
|
|
243
|
+
raise ValueError(PLAN_ON_A_POLICY)
|
|
242
244
|
return self
|
|
243
245
|
|
|
244
246
|
@model_validator(mode="after")
|
|
@@ -14,8 +14,7 @@ are on the worker before the act is.
|
|
|
14
14
|
|
|
15
15
|
What a policy declares of what it reads (its cameras, how many a second, when its upload falls behind) is plan 87's
|
|
16
16
|
first layer, which waits for the comparison of each part (the user's 69): the stream here is the transport alone, for
|
|
17
|
-
a policy
|
|
18
|
-
contract 0.5.25).
|
|
17
|
+
a policy handed every observation as its robot sends it (an @seq.observe method's, or the newest kept for its act).
|
|
19
18
|
|
|
20
19
|
"""
|
|
21
20
|
from __future__ import annotations
|
|
@@ -24,7 +23,8 @@ from typing import Any
|
|
|
24
23
|
|
|
25
24
|
from pydantic import BaseModel, ConfigDict, Field
|
|
26
25
|
|
|
27
|
-
__all__ = ["FORMAT", "FRAMES_MISSING", "FRAMES_OVER_LIMIT", "ObserveFrame", "ObserveRequest", "ObserveResponse"
|
|
26
|
+
__all__ = ["FORMAT", "FRAMES_MISSING", "FRAMES_OVER_LIMIT", "ObserveFrame", "ObserveRequest", "ObserveResponse",
|
|
27
|
+
"ZSTD_FORMAT"]
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
|
|
@@ -33,6 +33,11 @@ FORMAT = "x-seq-observe/1"
|
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
|
|
36
|
+
ZSTD_FORMAT = "x-seq-zstd/1"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
36
41
|
FRAMES_MISSING = "frames_missing"
|
|
37
42
|
FRAMES_OVER_LIMIT = "frames_over_limit"
|
|
38
43
|
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""
|
|
2
|
+
What a policy of its own inputs declares (contract 0.5.24): whether its inputs reach it as torch tensors - and what an
|
|
3
|
+
eval plays of its answers (rows_of). A policy that declares nothing is sent and handed what it is today.
|
|
4
|
+
|
|
5
|
+
`observations=seq.Observations(...)` and `seq.Stream(...)`, which said which keys of an observation a policy read at
|
|
6
|
+
which steps, are gone: a policy is handed every observation as its robot sends it, and one that reads more than the
|
|
7
|
+
newest keeps them in an @seq.observe method (on self, or in episode.state). A spec or a deploy that still declares them
|
|
8
|
+
is refused (OBSERVATIONS_GONE); one stored with `observations: null` reads as it did.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import numbers
|
|
15
|
+
from collections.abc import Mapping
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from pydantic import BaseModel, ConfigDict
|
|
19
|
+
|
|
20
|
+
from sequence_base.contract.retired import dropped
|
|
21
|
+
|
|
22
|
+
__all__ = ["OBSERVATIONS_GONE", "Tensors", "observations_gone", "rows_of"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
OBSERVATIONS_GONE = ("observations= is gone: a policy is handed every observation as its robot sends it - keep the ones "
|
|
26
|
+
"an act reads in an @seq.observe method (on self, or in episode.state), and deploy again with the "
|
|
27
|
+
"latest sequence-ai")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def observations_gone(data: Any) -> Any:
|
|
31
|
+
"""A model's before-validator for a body that may name `observations`: refused (OBSERVATIONS_GONE) when it declares
|
|
32
|
+
some, read without it when it is null - as a spec stored before it went holds it."""
|
|
33
|
+
if isinstance(data, dict) and data.get("observations") is not None:
|
|
34
|
+
raise ValueError(OBSERVATIONS_GONE)
|
|
35
|
+
return dropped(data, "observations")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Tensors(BaseModel):
|
|
39
|
+
"""A policy's inputs reach its code as torch tensors on its device - a batch dimension first when `batch` - and the
|
|
40
|
+
tensors it answers with go back as arrays; float64 arrays arrive as float32. Converted in the replica, last before
|
|
41
|
+
the policy's code."""
|
|
42
|
+
|
|
43
|
+
model_config = ConfigDict(extra="forbid")
|
|
44
|
+
|
|
45
|
+
batch: bool = True
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def rows_of(answer: Any, actions: Any = None) -> list:
|
|
49
|
+
"""The rows of a policy's own answer that play, in order, as an eval's adapter reads them. `actions`: what its
|
|
50
|
+
caller says of the rows, a dict or an object - each element of the field its `key` names (the answer itself for
|
|
51
|
+
none) is a row, a dict one too, cut at its `open_loop_horizon`. Without it, the default: a dict, or a list of
|
|
52
|
+
numbers, is one action; a list of lists of one length, or a 2-D array, plays row by row; a ragged or deeper
|
|
53
|
+
nesting, or a list of dicts, is refused, saying to turn it into actions in an adapter - and a number, a text or any
|
|
54
|
+
other object is one action, as before.
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
if actions is not None:
|
|
58
|
+
key = actions.get("key") if isinstance(actions, dict) else actions.key
|
|
59
|
+
horizon = actions.get("open_loop_horizon") if isinstance(actions, dict) else actions.open_loop_horizon
|
|
60
|
+
where = "answer" if key is None else f"answer's {key!r} field"
|
|
61
|
+
if key is not None:
|
|
62
|
+
if not isinstance(answer, Mapping) or key not in answer:
|
|
63
|
+
raise ValueError(f"the policy's answer has no {key!r} field, which holds the rows to play")
|
|
64
|
+
answer = answer[key]
|
|
65
|
+
if isinstance(answer, (Mapping, str, bytes)) or not (isinstance(answer, (list, tuple))
|
|
66
|
+
or (getattr(answer, "ndim", 0) or 0) >= 1):
|
|
67
|
+
raise ValueError(f"the policy's {where} is {type(answer).__name__}, not a list of rows or an array of them")
|
|
68
|
+
rows = list(answer)
|
|
69
|
+
if not rows:
|
|
70
|
+
raise ValueError(f"the policy's {where} holds no rows to play")
|
|
71
|
+
return rows[:horizon] if horizon else rows
|
|
72
|
+
ndim = getattr(answer, "ndim", None)
|
|
73
|
+
if isinstance(ndim, int) and not isinstance(answer, (Mapping, str, bytes)):
|
|
74
|
+
if ndim <= 1:
|
|
75
|
+
return [answer]
|
|
76
|
+
if ndim == 2:
|
|
77
|
+
return list(answer)
|
|
78
|
+
raise ValueError(_unplayable(f"an array of {ndim} dimensions"))
|
|
79
|
+
if isinstance(answer, (list, tuple)):
|
|
80
|
+
if all(isinstance(v, numbers.Number) for v in answer):
|
|
81
|
+
return [answer]
|
|
82
|
+
if all(isinstance(v, (list, tuple)) or getattr(v, "ndim", None) == 1 for v in answer):
|
|
83
|
+
lengths = [len(v) for v in answer]
|
|
84
|
+
if len(set(lengths)) == 1:
|
|
85
|
+
return list(answer)
|
|
86
|
+
raise ValueError(_unplayable(f"a list of lists of lengths {', '.join(map(str, lengths))}"))
|
|
87
|
+
if all(isinstance(v, Mapping) for v in answer):
|
|
88
|
+
raise ValueError(_unplayable(f"a list of {len(answer)} dicts"))
|
|
89
|
+
kinds = dict.fromkeys("numbers" if isinstance(v, numbers.Number) else "lists" if isinstance(v, (list, tuple))
|
|
90
|
+
else type(v).__name__ for v in answer)
|
|
91
|
+
raise ValueError(_unplayable(f"a list of {' and '.join(kinds)}"))
|
|
92
|
+
return [answer]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _unplayable(what: str) -> str:
|
|
96
|
+
return (f"the policy's answer is {what}, which plays neither as one action (a dict, or a list of numbers) nor row by "
|
|
97
|
+
"row (a list of lists of one length, or a 2-D array): turn it into actions in an adapter")
|