humalab 0.0.4__py3-none-any.whl → 0.0.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of humalab might be problematic. Click here for more details.
- humalab/__init__.py +11 -0
- humalab/assets/__init__.py +2 -2
- humalab/assets/files/resource_file.py +29 -3
- humalab/assets/files/urdf_file.py +14 -10
- humalab/assets/resource_operator.py +91 -0
- humalab/constants.py +39 -5
- humalab/dists/bernoulli.py +16 -0
- humalab/dists/categorical.py +4 -0
- humalab/dists/discrete.py +22 -0
- humalab/dists/gaussian.py +22 -0
- humalab/dists/log_uniform.py +22 -0
- humalab/dists/truncated_gaussian.py +36 -0
- humalab/dists/uniform.py +22 -0
- humalab/episode.py +196 -0
- humalab/humalab.py +116 -153
- humalab/humalab_api_client.py +760 -62
- humalab/humalab_config.py +0 -13
- humalab/humalab_test.py +46 -29
- humalab/metrics/__init__.py +5 -5
- humalab/metrics/code.py +28 -0
- humalab/metrics/metric.py +41 -108
- humalab/metrics/scenario_stats.py +95 -0
- humalab/metrics/summary.py +24 -18
- humalab/run.py +180 -115
- humalab/scenarios/__init__.py +4 -0
- humalab/scenarios/scenario.py +372 -0
- humalab/scenarios/scenario_operator.py +82 -0
- humalab/{scenario_test.py → scenarios/scenario_test.py} +150 -269
- humalab/utils.py +37 -0
- {humalab-0.0.4.dist-info → humalab-0.0.6.dist-info}/METADATA +1 -1
- humalab-0.0.6.dist-info/RECORD +39 -0
- humalab/assets/resource_manager.py +0 -57
- humalab/metrics/dist_metric.py +0 -22
- humalab/scenario.py +0 -225
- humalab-0.0.4.dist-info/RECORD +0 -34
- {humalab-0.0.4.dist-info → humalab-0.0.6.dist-info}/WHEEL +0 -0
- {humalab-0.0.4.dist-info → humalab-0.0.6.dist-info}/entry_points.txt +0 -0
- {humalab-0.0.4.dist-info → humalab-0.0.6.dist-info}/licenses/LICENSE +0 -0
- {humalab-0.0.4.dist-info → humalab-0.0.6.dist-info}/top_level.txt +0 -0
humalab/__init__.py
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
from humalab.humalab import init, finish, login
|
|
2
|
+
from humalab import assets
|
|
3
|
+
from humalab import metrics
|
|
4
|
+
from humalab import scenarios
|
|
2
5
|
from humalab.run import Run
|
|
6
|
+
from humalab.constants import MetricDimType, GraphType
|
|
7
|
+
# from humalab import evaluators
|
|
3
8
|
|
|
4
9
|
__all__ = [
|
|
5
10
|
"init",
|
|
6
11
|
"finish",
|
|
7
12
|
"login",
|
|
13
|
+
"assets",
|
|
14
|
+
"metrics",
|
|
15
|
+
"scenarios",
|
|
8
16
|
"Run",
|
|
17
|
+
"MetricDimType",
|
|
18
|
+
"GraphType",
|
|
19
|
+
# "evaluators",
|
|
9
20
|
]
|
humalab/assets/__init__.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from .
|
|
1
|
+
from .resource_operator import download, list_resources
|
|
2
2
|
from .files import ResourceFile, URDFFile
|
|
3
3
|
|
|
4
|
-
__all__ = ["
|
|
4
|
+
__all__ = ["download", "list_resources", "ResourceFile", "URDFFile"]
|
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
from humalab.constants import DEFAULT_PROJECT
|
|
5
|
+
|
|
6
|
+
class ResourceType(Enum):
|
|
7
|
+
URDF = "urdf"
|
|
8
|
+
MJCF = "mjcf"
|
|
9
|
+
USD = "usd"
|
|
10
|
+
MESH = "mesh"
|
|
11
|
+
VIDEO = "video"
|
|
12
|
+
IMAGE = "image"
|
|
13
|
+
DATA = "data"
|
|
14
|
+
|
|
2
15
|
|
|
3
16
|
|
|
4
17
|
class ResourceFile:
|
|
@@ -6,16 +19,22 @@ class ResourceFile:
|
|
|
6
19
|
name: str,
|
|
7
20
|
version: int,
|
|
8
21
|
filename: str,
|
|
9
|
-
resource_type: str,
|
|
22
|
+
resource_type: str | ResourceType,
|
|
23
|
+
project: str = DEFAULT_PROJECT,
|
|
10
24
|
description: str | None = None,
|
|
11
25
|
created_at: datetime | None = None):
|
|
26
|
+
self._project = project
|
|
12
27
|
self._name = name
|
|
13
28
|
self._version = version
|
|
14
29
|
self._filename = filename
|
|
15
|
-
self._resource_type = resource_type
|
|
30
|
+
self._resource_type = ResourceType(resource_type)
|
|
16
31
|
self._description = description
|
|
17
32
|
self._created_at = created_at
|
|
18
33
|
|
|
34
|
+
@property
|
|
35
|
+
def project(self) -> str:
|
|
36
|
+
return self._project
|
|
37
|
+
|
|
19
38
|
@property
|
|
20
39
|
def name(self) -> str:
|
|
21
40
|
return self._name
|
|
@@ -29,7 +48,7 @@ class ResourceFile:
|
|
|
29
48
|
return self._filename
|
|
30
49
|
|
|
31
50
|
@property
|
|
32
|
-
def resource_type(self) ->
|
|
51
|
+
def resource_type(self) -> ResourceType:
|
|
33
52
|
return self._resource_type
|
|
34
53
|
|
|
35
54
|
@property
|
|
@@ -39,3 +58,10 @@ class ResourceFile:
|
|
|
39
58
|
@property
|
|
40
59
|
def description(self) -> str | None:
|
|
41
60
|
return self._description
|
|
61
|
+
|
|
62
|
+
def __repr__(self) -> str:
|
|
63
|
+
return f"ResourceFile(project={self._project}, name={self._name}, version={self._version}, filename={self._filename}, resource_type={self._resource_type}, description={self._description}, created_at={self._created_at})"
|
|
64
|
+
|
|
65
|
+
def __str__(self) -> str:
|
|
66
|
+
return self.__repr__()
|
|
67
|
+
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
1
|
import os
|
|
3
2
|
import glob
|
|
4
|
-
from
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from humalab.assets.files.resource_file import ResourceFile, ResourceType
|
|
5
6
|
from humalab.assets.archive import extract_archive
|
|
7
|
+
from humalab.constants import DEFAULT_PROJECT
|
|
6
8
|
|
|
7
9
|
|
|
8
10
|
class URDFFile(ResourceFile):
|
|
@@ -10,30 +12,32 @@ class URDFFile(ResourceFile):
|
|
|
10
12
|
name: str,
|
|
11
13
|
version: int,
|
|
12
14
|
filename: str,
|
|
15
|
+
project: str = DEFAULT_PROJECT,
|
|
13
16
|
urdf_filename: str | None = None,
|
|
14
17
|
description: str | None = None,
|
|
15
18
|
created_at: datetime | None = None,):
|
|
16
|
-
super().__init__(
|
|
19
|
+
super().__init__(project=project,
|
|
20
|
+
name=name,
|
|
17
21
|
version=version,
|
|
18
22
|
description=description,
|
|
19
23
|
filename=filename,
|
|
20
|
-
resource_type=
|
|
24
|
+
resource_type=ResourceType.URDF,
|
|
21
25
|
created_at=created_at)
|
|
22
26
|
self._urdf_base_filename = urdf_filename
|
|
23
27
|
self._urdf_filename, self._root_path = self._extract()
|
|
24
28
|
self._urdf_filename = os.path.join(self._urdf_filename, self._urdf_filename)
|
|
25
29
|
|
|
26
30
|
def _extract(self):
|
|
27
|
-
working_path = os.path.dirname(self.
|
|
28
|
-
if
|
|
29
|
-
_, ext = os.path.splitext(self.
|
|
31
|
+
working_path = os.path.dirname(self.filename)
|
|
32
|
+
if os.path.exists(self.filename):
|
|
33
|
+
_, ext = os.path.splitext(self.filename)
|
|
30
34
|
ext = ext.lstrip('.') # Remove leading dot
|
|
31
35
|
if ext.lower() != "urdf":
|
|
32
|
-
extract_archive(self.
|
|
36
|
+
extract_archive(self.filename, working_path)
|
|
33
37
|
try:
|
|
34
|
-
os.remove(self.
|
|
38
|
+
os.remove(self.filename)
|
|
35
39
|
except Exception as e:
|
|
36
|
-
print(f"Error removing saved file {self.
|
|
40
|
+
print(f"Error removing saved file {self.filename}: {e}")
|
|
37
41
|
local_filename = self.search_resource_file(self._urdf_base_filename, working_path)
|
|
38
42
|
if local_filename is None:
|
|
39
43
|
raise ValueError(f"Resource filename {self._urdf_base_filename} not found in {working_path}")
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from humalab.constants import DEFAULT_PROJECT
|
|
2
|
+
from humalab.assets.files.resource_file import ResourceFile, ResourceType
|
|
3
|
+
from humalab.humalab_config import HumalabConfig
|
|
4
|
+
from humalab.humalab_api_client import HumaLabApiClient
|
|
5
|
+
from humalab.assets.files.urdf_file import URDFFile
|
|
6
|
+
import os
|
|
7
|
+
from typing import Any, Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _asset_dir(humalab_config: HumalabConfig, name: str, version: int) -> str:
|
|
11
|
+
return os.path.join(humalab_config.workspace_path, "assets", name, f"{version}")
|
|
12
|
+
|
|
13
|
+
def _create_asset_dir(humalab_config: HumalabConfig, name: str, version: int) -> bool:
|
|
14
|
+
asset_dir = _asset_dir(humalab_config, name, version)
|
|
15
|
+
if not os.path.exists(asset_dir):
|
|
16
|
+
os.makedirs(asset_dir, exist_ok=True)
|
|
17
|
+
return True
|
|
18
|
+
return False
|
|
19
|
+
|
|
20
|
+
def download(name: str,
|
|
21
|
+
version: int | None=None,
|
|
22
|
+
project: str = DEFAULT_PROJECT,
|
|
23
|
+
|
|
24
|
+
host: str | None = None,
|
|
25
|
+
api_key: str | None = None,
|
|
26
|
+
timeout: float | None = None,
|
|
27
|
+
) -> Any:
|
|
28
|
+
humalab_config = HumalabConfig()
|
|
29
|
+
|
|
30
|
+
api_client = HumaLabApiClient(base_url=host,
|
|
31
|
+
api_key=api_key,
|
|
32
|
+
timeout=timeout)
|
|
33
|
+
|
|
34
|
+
resource = api_client.get_resource(project_name=project, name=name, version=version)
|
|
35
|
+
filename = os.path.basename(resource['resource_url'])
|
|
36
|
+
filename = os.path.join(_asset_dir(humalab_config, name, resource["version"]), filename)
|
|
37
|
+
if _create_asset_dir(humalab_config, name, resource["version"]):
|
|
38
|
+
file_content = api_client.download_resource(project_name=project, name="lerobot")
|
|
39
|
+
with open(filename, "wb") as f:
|
|
40
|
+
f.write(file_content)
|
|
41
|
+
|
|
42
|
+
if resource["resource_type"].lower() == "urdf":
|
|
43
|
+
return URDFFile(project=project,
|
|
44
|
+
name=name,
|
|
45
|
+
version=resource["version"],
|
|
46
|
+
description=resource.get("description"),
|
|
47
|
+
filename=filename,
|
|
48
|
+
urdf_filename=resource.get("filename"),
|
|
49
|
+
created_at=resource.get("created_at"))
|
|
50
|
+
|
|
51
|
+
return ResourceFile(project=project,
|
|
52
|
+
name=name,
|
|
53
|
+
version=resource["version"],
|
|
54
|
+
filename=filename,
|
|
55
|
+
resource_type=resource["resource_type"],
|
|
56
|
+
description=resource.get("description"),
|
|
57
|
+
created_at=resource.get("created_at"))
|
|
58
|
+
|
|
59
|
+
def list_resources(project: str = DEFAULT_PROJECT,
|
|
60
|
+
resource_types: Optional[list[str | ResourceType]] = None,
|
|
61
|
+
limit: int = 20,
|
|
62
|
+
offset: int = 0,
|
|
63
|
+
latest_only: bool = True,
|
|
64
|
+
|
|
65
|
+
host: str | None = None,
|
|
66
|
+
api_key: str | None = None,
|
|
67
|
+
timeout: float | None = None,) -> list[ResourceFile]:
|
|
68
|
+
api_client = HumaLabApiClient(base_url=host,
|
|
69
|
+
api_key=api_key,
|
|
70
|
+
timeout=timeout)
|
|
71
|
+
|
|
72
|
+
resource_type_string = None
|
|
73
|
+
if resource_types:
|
|
74
|
+
resource_type_strings = {rt.value if isinstance(rt, ResourceType) else rt for rt in resource_types}
|
|
75
|
+
resource_type_string = ",".join(resource_type_strings)
|
|
76
|
+
resp = api_client.get_resources(project_name=project,
|
|
77
|
+
resource_types=resource_type_string,
|
|
78
|
+
limit=limit,
|
|
79
|
+
offset=offset,
|
|
80
|
+
latest_only=latest_only)
|
|
81
|
+
resources = resp.get("resources", [])
|
|
82
|
+
ret_list = []
|
|
83
|
+
for resource in resources:
|
|
84
|
+
ret_list.append(ResourceFile(name=resource["name"],
|
|
85
|
+
version=resource.get("version"),
|
|
86
|
+
project=project,
|
|
87
|
+
filename=resource.get("filename"),
|
|
88
|
+
resource_type=resource.get("resource_type"),
|
|
89
|
+
description=resource.get("description"),
|
|
90
|
+
created_at=resource.get("created_at")))
|
|
91
|
+
return ret_list
|
humalab/constants.py
CHANGED
|
@@ -1,7 +1,41 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
|
|
4
|
+
RESERVED_NAMES = {
|
|
5
|
+
"sceanario"
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
DEFAULT_PROJECT = "default"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ArtifactType(Enum):
|
|
12
|
+
"""Types of artifacts that can be stored"""
|
|
13
|
+
METRICS = "metrics" # Run & Episode
|
|
14
|
+
SCENARIO_STATS = "scenario_stats" # Run only
|
|
15
|
+
PYTHON = "python" # Run & Episode
|
|
16
|
+
CODE = "code" # Run & Episode (YAML)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MetricType(Enum):
|
|
20
|
+
METRICS = ArtifactType.METRICS.value
|
|
21
|
+
SCENARIO_STATS = ArtifactType.SCENARIO_STATS.value
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class GraphType(Enum):
|
|
25
|
+
"""Types of graphs supported by Humalab."""
|
|
26
|
+
NUMERIC = "numeric"
|
|
27
|
+
LINE = "line"
|
|
28
|
+
BAR = "bar"
|
|
29
|
+
SCATTER = "scatter"
|
|
30
|
+
HISTOGRAM = "histogram"
|
|
31
|
+
GAUSSIAN = "gaussian"
|
|
32
|
+
HEATMAP = "heatmap"
|
|
33
|
+
THREE_D_MAP = "3d_map"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class MetricDimType(Enum):
|
|
37
|
+
"""Types of metric dimensions"""
|
|
38
|
+
ZERO_D = "0d"
|
|
39
|
+
ONE_D = "1d"
|
|
40
|
+
TWO_D = "2d"
|
|
41
|
+
THREE_D = "3d"
|
humalab/dists/bernoulli.py
CHANGED
|
@@ -20,6 +20,22 @@ class Bernoulli(Distribution):
|
|
|
20
20
|
self._p = p
|
|
21
21
|
self._size = size
|
|
22
22
|
|
|
23
|
+
@staticmethod
|
|
24
|
+
def validate(dimensions: int, *args) -> bool:
|
|
25
|
+
arg1 = args[0]
|
|
26
|
+
if dimensions == 0:
|
|
27
|
+
if not isinstance(arg1, (int, float)):
|
|
28
|
+
return False
|
|
29
|
+
return True
|
|
30
|
+
if dimensions == -1:
|
|
31
|
+
return True
|
|
32
|
+
if not isinstance(arg1, (int, float)):
|
|
33
|
+
if isinstance(arg1, (list, np.ndarray)):
|
|
34
|
+
if len(arg1) != dimensions:
|
|
35
|
+
return False
|
|
36
|
+
|
|
37
|
+
return True
|
|
38
|
+
|
|
23
39
|
def _sample(self) -> int | float | np.ndarray:
|
|
24
40
|
return self._generator.binomial(n=1, p=self._p, size=self._size)
|
|
25
41
|
|
humalab/dists/categorical.py
CHANGED
|
@@ -25,6 +25,10 @@ class Categorical(Distribution):
|
|
|
25
25
|
weights = [w / weight_sum for w in weights]
|
|
26
26
|
self._weights = weights
|
|
27
27
|
|
|
28
|
+
@staticmethod
|
|
29
|
+
def validate(dimensions: int, *args) -> bool:
|
|
30
|
+
return True
|
|
31
|
+
|
|
28
32
|
def _sample(self) -> int | float | np.ndarray:
|
|
29
33
|
return self._generator.choice(self._choices, size=self._size, p=self._weights)
|
|
30
34
|
|
humalab/dists/discrete.py
CHANGED
|
@@ -26,6 +26,28 @@ class Discrete(Distribution):
|
|
|
26
26
|
self._high = np.array(high)
|
|
27
27
|
self._size = size
|
|
28
28
|
self._endpoint = endpoint if endpoint is not None else True
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def validate(dimensions: int, *args) -> bool:
|
|
32
|
+
arg1 = args[0]
|
|
33
|
+
arg2 = args[1]
|
|
34
|
+
if dimensions == 0:
|
|
35
|
+
if not isinstance(arg1, int):
|
|
36
|
+
return False
|
|
37
|
+
if not isinstance(arg2, int):
|
|
38
|
+
return False
|
|
39
|
+
return True
|
|
40
|
+
if dimensions == -1:
|
|
41
|
+
return True
|
|
42
|
+
if not isinstance(arg1, int):
|
|
43
|
+
if isinstance(arg1, (list, np.ndarray)):
|
|
44
|
+
if len(arg1) != dimensions:
|
|
45
|
+
return False
|
|
46
|
+
if not isinstance(arg2, int):
|
|
47
|
+
if isinstance(arg2, (list, np.ndarray)):
|
|
48
|
+
if len(arg2) != dimensions:
|
|
49
|
+
return False
|
|
50
|
+
return True
|
|
29
51
|
|
|
30
52
|
def _sample(self) -> int | float | np.ndarray:
|
|
31
53
|
return self._generator.integers(self._low, self._high, size=self._size, endpoint=self._endpoint)
|
humalab/dists/gaussian.py
CHANGED
|
@@ -23,6 +23,28 @@ class Gaussian(Distribution):
|
|
|
23
23
|
self._scale = scale
|
|
24
24
|
self._size = size
|
|
25
25
|
|
|
26
|
+
@staticmethod
|
|
27
|
+
def validate(dimensions: int, *args) -> bool:
|
|
28
|
+
arg1 = args[0]
|
|
29
|
+
arg2 = args[1]
|
|
30
|
+
if dimensions == 0:
|
|
31
|
+
if not isinstance(arg1, (int, float)):
|
|
32
|
+
return False
|
|
33
|
+
if not isinstance(arg2, (int, float)):
|
|
34
|
+
return False
|
|
35
|
+
return True
|
|
36
|
+
if dimensions == -1:
|
|
37
|
+
return True
|
|
38
|
+
if not isinstance(arg1, (int, float)):
|
|
39
|
+
if isinstance(arg1, (list, np.ndarray)):
|
|
40
|
+
if len(arg1) != dimensions:
|
|
41
|
+
return False
|
|
42
|
+
if not isinstance(arg2, (int, float)):
|
|
43
|
+
if isinstance(arg2, (list, np.ndarray)):
|
|
44
|
+
if len(arg2) != dimensions:
|
|
45
|
+
return False
|
|
46
|
+
return True
|
|
47
|
+
|
|
26
48
|
def _sample(self) -> int | float | np.ndarray:
|
|
27
49
|
return self._generator.normal(loc=self._loc, scale=self._scale, size=self._size)
|
|
28
50
|
|
humalab/dists/log_uniform.py
CHANGED
|
@@ -22,6 +22,28 @@ class LogUniform(Distribution):
|
|
|
22
22
|
self._log_low = np.log(np.array(low))
|
|
23
23
|
self._log_high = np.log(np.array(high))
|
|
24
24
|
self._size = size
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def validate(dimensions: int, *args) -> bool:
|
|
28
|
+
arg1 = args[0]
|
|
29
|
+
arg2 = args[1]
|
|
30
|
+
if dimensions == 0:
|
|
31
|
+
if not isinstance(arg1, (int, float)):
|
|
32
|
+
return False
|
|
33
|
+
if not isinstance(arg2, (int, float)):
|
|
34
|
+
return False
|
|
35
|
+
return True
|
|
36
|
+
if dimensions == -1:
|
|
37
|
+
return True
|
|
38
|
+
if not isinstance(arg1, (int, float)):
|
|
39
|
+
if isinstance(arg1, (list, np.ndarray)):
|
|
40
|
+
if len(arg1) != dimensions:
|
|
41
|
+
return False
|
|
42
|
+
if not isinstance(arg2, (int, float)):
|
|
43
|
+
if isinstance(arg2, (list, np.ndarray)):
|
|
44
|
+
if len(arg2) != dimensions:
|
|
45
|
+
return False
|
|
46
|
+
return True
|
|
25
47
|
|
|
26
48
|
def _sample(self) -> int | float | np.ndarray:
|
|
27
49
|
return np.exp(self._generator.uniform(self._log_low, self._log_high, size=self._size))
|
|
@@ -29,6 +29,42 @@ class TruncatedGaussian(Distribution):
|
|
|
29
29
|
self._high = high
|
|
30
30
|
self._size = size
|
|
31
31
|
|
|
32
|
+
@staticmethod
|
|
33
|
+
def validate(dimensions: int, *args) -> bool:
|
|
34
|
+
arg1 = args[0]
|
|
35
|
+
arg2 = args[1]
|
|
36
|
+
arg3 = args[2]
|
|
37
|
+
arg4 = args[3]
|
|
38
|
+
if dimensions == 0:
|
|
39
|
+
if not isinstance(arg1, (int, float)):
|
|
40
|
+
return False
|
|
41
|
+
if not isinstance(arg2, (int, float)):
|
|
42
|
+
return False
|
|
43
|
+
if not isinstance(arg3, (int, float)):
|
|
44
|
+
return False
|
|
45
|
+
if not isinstance(arg4, (int, float)):
|
|
46
|
+
return False
|
|
47
|
+
return True
|
|
48
|
+
if dimensions == -1:
|
|
49
|
+
return True
|
|
50
|
+
if not isinstance(arg1, (int, float)):
|
|
51
|
+
if isinstance(arg1, (list, np.ndarray)):
|
|
52
|
+
if len(arg1) != dimensions:
|
|
53
|
+
return False
|
|
54
|
+
if not isinstance(arg2, (int, float)):
|
|
55
|
+
if isinstance(arg2, (list, np.ndarray)):
|
|
56
|
+
if len(arg2) != dimensions:
|
|
57
|
+
return False
|
|
58
|
+
if not isinstance(arg3, (int, float)):
|
|
59
|
+
if isinstance(arg3, (list, np.ndarray)):
|
|
60
|
+
if len(arg3) != dimensions:
|
|
61
|
+
return False
|
|
62
|
+
if not isinstance(arg4, (int, float)):
|
|
63
|
+
if isinstance(arg4, (list, np.ndarray)):
|
|
64
|
+
if len(arg4) != dimensions:
|
|
65
|
+
return False
|
|
66
|
+
return True
|
|
67
|
+
|
|
32
68
|
def _sample(self) -> int | float | np.ndarray:
|
|
33
69
|
samples = self._generator.normal(loc=self._loc, scale=self._scale, size=self._size)
|
|
34
70
|
mask = (samples < self._low) | (samples > self._high)
|
humalab/dists/uniform.py
CHANGED
|
@@ -23,6 +23,28 @@ class Uniform(Distribution):
|
|
|
23
23
|
self._high = np.array(high)
|
|
24
24
|
self._size = size
|
|
25
25
|
|
|
26
|
+
@staticmethod
|
|
27
|
+
def validate(dimensions: int, *args) -> bool:
|
|
28
|
+
arg1 = args[0]
|
|
29
|
+
arg2 = args[1]
|
|
30
|
+
if dimensions == 0:
|
|
31
|
+
if not isinstance(arg1, (int, float)):
|
|
32
|
+
return False
|
|
33
|
+
if not isinstance(arg2, (int, float)):
|
|
34
|
+
return False
|
|
35
|
+
return True
|
|
36
|
+
if dimensions == -1:
|
|
37
|
+
return True
|
|
38
|
+
if not isinstance(arg1, (int, float)):
|
|
39
|
+
if isinstance(arg1, (list, np.ndarray)):
|
|
40
|
+
if len(arg1) > dimensions:
|
|
41
|
+
return False
|
|
42
|
+
if not isinstance(arg2, (int, float)):
|
|
43
|
+
if isinstance(arg2, (list, np.ndarray)):
|
|
44
|
+
if len(arg2) > dimensions:
|
|
45
|
+
return False
|
|
46
|
+
return True
|
|
47
|
+
|
|
26
48
|
def _sample(self) -> int | float | np.ndarray:
|
|
27
49
|
return self._generator.uniform(self._low, self._high, size=self._size)
|
|
28
50
|
|
humalab/episode.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
from humalab.constants import RESERVED_NAMES, ArtifactType
|
|
2
|
+
from humalab.humalab_api_client import HumaLabApiClient, EpisodeStatus
|
|
3
|
+
from humalab.metrics.code import Code
|
|
4
|
+
from humalab.metrics.summary import Summary
|
|
5
|
+
from humalab.metrics.metric import Metrics
|
|
6
|
+
from omegaconf import DictConfig, ListConfig, OmegaConf
|
|
7
|
+
from typing import Any
|
|
8
|
+
import pickle
|
|
9
|
+
import traceback
|
|
10
|
+
|
|
11
|
+
from humalab.utils import is_standard_type
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Episode:
|
|
15
|
+
def __init__(self,
|
|
16
|
+
run_id: str,
|
|
17
|
+
episode_id: str,
|
|
18
|
+
scenario_conf: DictConfig | ListConfig,
|
|
19
|
+
episode_vals: dict | None = None,
|
|
20
|
+
|
|
21
|
+
base_url: str | None = None,
|
|
22
|
+
api_key: str | None = None,
|
|
23
|
+
timeout: float | None = None,):
|
|
24
|
+
self._run_id = run_id
|
|
25
|
+
self._episode_id = episode_id
|
|
26
|
+
self._episode_status = EpisodeStatus.RUNNING
|
|
27
|
+
self._scenario_conf = scenario_conf
|
|
28
|
+
self._logs = {}
|
|
29
|
+
self._episode_vals = episode_vals or {}
|
|
30
|
+
self._is_finished = False
|
|
31
|
+
|
|
32
|
+
self._api_client = HumaLabApiClient(base_url=base_url,
|
|
33
|
+
api_key=api_key,
|
|
34
|
+
timeout=timeout)
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def run_id(self) -> str:
|
|
38
|
+
return self._run_id
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def episode_id(self) -> str:
|
|
42
|
+
return self._episode_id
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def scenario(self) -> DictConfig | ListConfig:
|
|
46
|
+
return self._scenario_conf
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def status(self) -> EpisodeStatus:
|
|
50
|
+
return self._episode_status
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def episode_vals(self) -> dict:
|
|
54
|
+
return self._episode_vals
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def is_finished(self) -> bool:
|
|
58
|
+
return self._is_finished
|
|
59
|
+
|
|
60
|
+
def __enter__(self):
|
|
61
|
+
return self
|
|
62
|
+
|
|
63
|
+
def __exit__(self, exception_type, exception_value, exception_traceback):
|
|
64
|
+
if self._is_finished:
|
|
65
|
+
return
|
|
66
|
+
if exception_type is not None:
|
|
67
|
+
err_msg = "".join(traceback.format_exception(exception_type, exception_value, exception_traceback))
|
|
68
|
+
self.finish(status=EpisodeStatus.ERRORED, err_msg=err_msg)
|
|
69
|
+
else:
|
|
70
|
+
self.finish(status=EpisodeStatus.SUCCESS)
|
|
71
|
+
|
|
72
|
+
def __getattr__(self, name: Any) -> Any:
|
|
73
|
+
if name in self._scenario_conf:
|
|
74
|
+
return self._scenario_conf[name]
|
|
75
|
+
raise AttributeError(f"'Scenario' object has no attribute '{name}'")
|
|
76
|
+
|
|
77
|
+
def __getitem__(self, key: Any) -> Any:
|
|
78
|
+
if key in self._scenario_conf:
|
|
79
|
+
return self._scenario_conf[key]
|
|
80
|
+
raise KeyError(f"'Scenario' object has no key '{key}'")
|
|
81
|
+
|
|
82
|
+
def add_metric(self, name: str, metric: Metrics) -> None:
|
|
83
|
+
if name in self._logs:
|
|
84
|
+
raise ValueError(f"{name} is a reserved name and is not allowed.")
|
|
85
|
+
self._logs[name] = metric
|
|
86
|
+
|
|
87
|
+
def log_code(self, key: str, code_content: str) -> None:
|
|
88
|
+
"""Log code content as an artifact.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
key (str): The key for the code artifact.
|
|
92
|
+
code_content (str): The code content to log.
|
|
93
|
+
"""
|
|
94
|
+
if key in RESERVED_NAMES:
|
|
95
|
+
raise ValueError(f"{key} is a reserved name and is not allowed.")
|
|
96
|
+
self._logs[key] = Code(
|
|
97
|
+
run_id=self._run_id,
|
|
98
|
+
key=key,
|
|
99
|
+
code_content=code_content,
|
|
100
|
+
episode_id=self._episode_id
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def log(self, data: dict, x: dict | None = None, replace: bool = False) -> None:
|
|
104
|
+
for key, value in data.items():
|
|
105
|
+
if key in RESERVED_NAMES:
|
|
106
|
+
raise ValueError(f"{key} is a reserved name and is not allowed.")
|
|
107
|
+
if key not in self._logs:
|
|
108
|
+
self._logs[key] = value
|
|
109
|
+
else:
|
|
110
|
+
cur_val = self._logs[key]
|
|
111
|
+
if isinstance(cur_val, Metrics):
|
|
112
|
+
cur_x = x.get(key) if x is not None else None
|
|
113
|
+
cur_val.log(value, x=cur_x, replace=replace)
|
|
114
|
+
else:
|
|
115
|
+
if replace:
|
|
116
|
+
self._logs[key] = value
|
|
117
|
+
else:
|
|
118
|
+
raise ValueError(f"Cannot log value for key '{key}' as there is already a value logged.")
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def yaml(self) -> str:
|
|
122
|
+
"""The current scenario configuration as a YAML string.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
str: The current scenario as a YAML string.
|
|
126
|
+
"""
|
|
127
|
+
return OmegaConf.to_yaml(self._scenario_conf)
|
|
128
|
+
|
|
129
|
+
def discard(self) -> None:
|
|
130
|
+
self._finish(EpisodeStatus.CANCELED)
|
|
131
|
+
|
|
132
|
+
def success(self) -> None:
|
|
133
|
+
self._finish(EpisodeStatus.SUCCESS)
|
|
134
|
+
|
|
135
|
+
def fail(self) -> None:
|
|
136
|
+
self._finish(EpisodeStatus.FAILED)
|
|
137
|
+
|
|
138
|
+
def finish(self, status: EpisodeStatus, err_msg: str | None = None) -> None:
|
|
139
|
+
if self._is_finished:
|
|
140
|
+
return
|
|
141
|
+
self._is_finished = True
|
|
142
|
+
self._episode_status = status
|
|
143
|
+
|
|
144
|
+
self._api_client.upload_code(
|
|
145
|
+
artifact_key="scenario",
|
|
146
|
+
run_id=self._run_id,
|
|
147
|
+
episode_id=self._episode_id,
|
|
148
|
+
code_content=self.yaml
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# TODO: submit final metrics
|
|
152
|
+
for key, value in self._logs.items():
|
|
153
|
+
if isinstance(value, Summary):
|
|
154
|
+
metric_val = value.finalize()
|
|
155
|
+
pickled = pickle.dumps(metric_val["value"])
|
|
156
|
+
self._api_client.upload_python(
|
|
157
|
+
artifact_key=key,
|
|
158
|
+
run_id=self._id,
|
|
159
|
+
episode_id=self._episode_id,
|
|
160
|
+
pickled_bytes=pickled
|
|
161
|
+
)
|
|
162
|
+
elif isinstance(value, Metrics):
|
|
163
|
+
metric_val = value.finalize()
|
|
164
|
+
pickled = pickle.dumps(metric_val)
|
|
165
|
+
self._api_client.upload_metrics(
|
|
166
|
+
artifact_key=key,
|
|
167
|
+
run_id=self._id,
|
|
168
|
+
episode_id=self._episode_id,
|
|
169
|
+
pickled_bytes=pickled,
|
|
170
|
+
graph_type=value.graph_type.value,
|
|
171
|
+
metric_dim_type=value.metric_dim_type.value
|
|
172
|
+
)
|
|
173
|
+
elif isinstance(value, Code):
|
|
174
|
+
self._api_client.upload_code(
|
|
175
|
+
artifact_key=value.key,
|
|
176
|
+
run_id=value.run_id,
|
|
177
|
+
episode_id=value.episode_id,
|
|
178
|
+
code_content=value.code_content
|
|
179
|
+
)
|
|
180
|
+
else:
|
|
181
|
+
if not is_standard_type(value):
|
|
182
|
+
raise ValueError(f"Value for key '{key}' is not a standard type.")
|
|
183
|
+
pickled = pickle.dumps(value)
|
|
184
|
+
self._api_client.upload_python(
|
|
185
|
+
artifact_key=key,
|
|
186
|
+
run_id=self._run_id,
|
|
187
|
+
episode_id=self._episode_id,
|
|
188
|
+
pickled_bytes=pickled
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
self._api_client.update_episode(
|
|
192
|
+
run_id=self._run_id,
|
|
193
|
+
episode_id=self._episode_id,
|
|
194
|
+
status=status,
|
|
195
|
+
err_msg=err_msg
|
|
196
|
+
)
|