humalab 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- humalab/__init__.py +34 -0
- humalab/assets/__init__.py +10 -0
- humalab/assets/archive.py +101 -0
- humalab/assets/files/__init__.py +4 -0
- humalab/assets/files/resource_file.py +131 -0
- humalab/assets/files/urdf_file.py +103 -0
- humalab/assets/resource_operator.py +139 -0
- humalab/constants.py +50 -0
- humalab/dists/__init__.py +24 -0
- humalab/dists/bernoulli.py +84 -0
- humalab/dists/categorical.py +81 -0
- humalab/dists/discrete.py +103 -0
- humalab/dists/distribution.py +49 -0
- humalab/dists/gaussian.py +96 -0
- humalab/dists/log_uniform.py +97 -0
- humalab/dists/truncated_gaussian.py +129 -0
- humalab/dists/uniform.py +95 -0
- humalab/episode.py +306 -0
- humalab/humalab.py +219 -0
- humalab/humalab_api_client.py +966 -0
- humalab/humalab_config.py +122 -0
- humalab/humalab_test.py +527 -0
- humalab/metrics/__init__.py +17 -0
- humalab/metrics/code.py +59 -0
- humalab/metrics/metric.py +96 -0
- humalab/metrics/scenario_stats.py +163 -0
- humalab/metrics/summary.py +75 -0
- humalab/run.py +325 -0
- humalab/scenarios/__init__.py +11 -0
- humalab/scenarios/scenario.py +375 -0
- humalab/scenarios/scenario_operator.py +114 -0
- humalab/scenarios/scenario_test.py +792 -0
- humalab/utils.py +37 -0
- humalab-0.1.0.dist-info/METADATA +43 -0
- humalab-0.1.0.dist-info/RECORD +39 -0
- humalab-0.1.0.dist-info/WHEEL +5 -0
- humalab-0.1.0.dist-info/entry_points.txt +2 -0
- humalab-0.1.0.dist-info/licenses/LICENSE +21 -0
- humalab-0.1.0.dist-info/top_level.txt +1 -0
humalab/metrics/code.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
class Code:
|
|
2
|
+
"""Class for logging code artifacts.
|
|
3
|
+
|
|
4
|
+
Code artifacts capture source code or configuration files associated with
|
|
5
|
+
runs or episodes. They are stored as text content and can be retrieved later
|
|
6
|
+
for reproducibility and debugging purposes.
|
|
7
|
+
|
|
8
|
+
Attributes:
|
|
9
|
+
run_id (str): The unique identifier of the associated run.
|
|
10
|
+
key (str): The artifact key/name for this code.
|
|
11
|
+
code_content (str): The actual code or text content.
|
|
12
|
+
episode_id (str | None): Optional episode identifier if scoped to an episode.
|
|
13
|
+
"""
|
|
14
|
+
def __init__(self,
|
|
15
|
+
run_id: str,
|
|
16
|
+
key: str,
|
|
17
|
+
code_content: str,
|
|
18
|
+
episode_id: str | None = None) -> None:
|
|
19
|
+
super().__init__()
|
|
20
|
+
self._run_id = run_id
|
|
21
|
+
self._key = key
|
|
22
|
+
self._code_content = code_content
|
|
23
|
+
self._episode_id = episode_id
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def run_id(self) -> str:
|
|
27
|
+
"""The unique identifier of the associated run.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
str: The run ID.
|
|
31
|
+
"""
|
|
32
|
+
return self._run_id
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def key(self) -> str:
|
|
36
|
+
"""The artifact key/name for this code.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
str: The artifact key.
|
|
40
|
+
"""
|
|
41
|
+
return self._key
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def code_content(self) -> str:
|
|
45
|
+
"""The actual code or text content.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
str: The code content.
|
|
49
|
+
"""
|
|
50
|
+
return self._code_content
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def episode_id(self) -> str | None:
|
|
54
|
+
"""Optional episode identifier if scoped to an episode.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
str | None: The episode ID, or None if run-scoped.
|
|
58
|
+
"""
|
|
59
|
+
return self._episode_id
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from humalab.constants import MetricDimType, GraphType
|
|
3
|
+
|
|
4
|
+
GRAPH_TO_DIM_TYPE = {
|
|
5
|
+
GraphType.NUMERIC: MetricDimType.ZERO_D,
|
|
6
|
+
GraphType.LINE: MetricDimType.ONE_D,
|
|
7
|
+
GraphType.HISTOGRAM: MetricDimType.ONE_D,
|
|
8
|
+
GraphType.BAR: MetricDimType.ONE_D,
|
|
9
|
+
GraphType.GAUSSIAN: MetricDimType.ONE_D,
|
|
10
|
+
GraphType.SCATTER: MetricDimType.TWO_D,
|
|
11
|
+
GraphType.HEATMAP: MetricDimType.TWO_D,
|
|
12
|
+
GraphType.THREE_D_MAP: MetricDimType.THREE_D,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Metrics:
|
|
17
|
+
"""Base class for tracking and logging metrics during runs and episodes.
|
|
18
|
+
|
|
19
|
+
Metrics provide a flexible way to log time-series data or aggregated values
|
|
20
|
+
during experiments. Data points are collected with optional x-axis values
|
|
21
|
+
and can be visualized using different graph types.
|
|
22
|
+
|
|
23
|
+
Subclasses should override _finalize() to implement custom processing logic.
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
graph_type (GraphType): The type of graph used for visualization.
|
|
27
|
+
"""
|
|
28
|
+
def __init__(self,
|
|
29
|
+
graph_type: GraphType=GraphType.LINE) -> None:
|
|
30
|
+
"""Initialize a new Metrics instance.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
graph_type (GraphType): The type of graph to use for visualization
|
|
34
|
+
(e.g., LINE, BAR, HISTOGRAM, SCATTER). Defaults to LINE.
|
|
35
|
+
"""
|
|
36
|
+
self._values = []
|
|
37
|
+
self._x_values = []
|
|
38
|
+
self._step = -1
|
|
39
|
+
self._metric_dim_type = GRAPH_TO_DIM_TYPE.get(graph_type, MetricDimType.ONE_D)
|
|
40
|
+
self._graph_type = graph_type
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def metric_dim_type(self) -> MetricDimType:
|
|
44
|
+
"""The dimensionality of the metric data.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
MetricDimType: The metric dimension type.
|
|
48
|
+
"""
|
|
49
|
+
return self._metric_dim_type
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def graph_type(self) -> GraphType:
|
|
53
|
+
"""The type of graph used for visualization.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
GraphType: The graph type.
|
|
57
|
+
"""
|
|
58
|
+
return self._graph_type
|
|
59
|
+
|
|
60
|
+
def log(self, data: Any, x: Any = None, replace: bool = False) -> None:
|
|
61
|
+
"""Log a new data point for the metric.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
data (Any): The data point to log.
|
|
65
|
+
x (Any | None): The x-axis value associated with the data point.
|
|
66
|
+
If None, uses an auto-incrementing step counter.
|
|
67
|
+
replace (bool): Whether to replace the last logged value. Defaults to False.
|
|
68
|
+
"""
|
|
69
|
+
if replace:
|
|
70
|
+
self._values[-1] = data
|
|
71
|
+
if x is not None:
|
|
72
|
+
self._x_values[-1] = x
|
|
73
|
+
else:
|
|
74
|
+
self._values.append(data)
|
|
75
|
+
if x is not None:
|
|
76
|
+
self._x_values.append(x)
|
|
77
|
+
else:
|
|
78
|
+
self._x_values.append(self._step)
|
|
79
|
+
self._step += 1
|
|
80
|
+
|
|
81
|
+
def finalize(self) -> dict:
|
|
82
|
+
"""Finalize the logged data for processing."""
|
|
83
|
+
ret_result = self._finalize()
|
|
84
|
+
|
|
85
|
+
return ret_result
|
|
86
|
+
|
|
87
|
+
def _finalize(self) -> dict:
|
|
88
|
+
"""Process the logged data before submission. To be implemented by subclasses."""
|
|
89
|
+
ret_val = {
|
|
90
|
+
"values": self._values,
|
|
91
|
+
"x_values": self._x_values
|
|
92
|
+
}
|
|
93
|
+
self._values = []
|
|
94
|
+
self._x_values = []
|
|
95
|
+
self._step = -1
|
|
96
|
+
return ret_val
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
from humalab.metrics.metric import Metrics
|
|
2
|
+
from humalab.constants import ArtifactType, GraphType, MetricDimType
|
|
3
|
+
from humalab.humalab_api_client import EpisodeStatus
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
SCENARIO_STATS_NEED_FLATTEN = {
|
|
8
|
+
"uniform_1d",
|
|
9
|
+
"bernoulli_1d",
|
|
10
|
+
"categorical_1d",
|
|
11
|
+
"discrete_1d",
|
|
12
|
+
"log_uniform_1d",
|
|
13
|
+
"gaussian_1d",
|
|
14
|
+
"truncated_gaussian_1d"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
DISTRIBUTION_GRAPH_TYPE = {
|
|
19
|
+
# 0D distributions
|
|
20
|
+
"uniform": GraphType.HISTOGRAM,
|
|
21
|
+
"bernoulli": GraphType.HISTOGRAM,
|
|
22
|
+
"categorical": GraphType.BAR,
|
|
23
|
+
"discrete": GraphType.BAR,
|
|
24
|
+
"log_uniform": GraphType.HISTOGRAM,
|
|
25
|
+
"gaussian": GraphType.GAUSSIAN,
|
|
26
|
+
"truncated_gaussian": GraphType.GAUSSIAN,
|
|
27
|
+
|
|
28
|
+
# 1D distributions
|
|
29
|
+
"uniform_1d": GraphType.HISTOGRAM,
|
|
30
|
+
"bernoulli_1d": GraphType.HISTOGRAM,
|
|
31
|
+
"categorical_1d": GraphType.BAR,
|
|
32
|
+
"discrete_1d": GraphType.BAR,
|
|
33
|
+
"log_uniform_1d": GraphType.HISTOGRAM,
|
|
34
|
+
"gaussian_1d": GraphType.GAUSSIAN,
|
|
35
|
+
"truncated_gaussian_1d": GraphType.GAUSSIAN,
|
|
36
|
+
|
|
37
|
+
# 2D distributions
|
|
38
|
+
"uniform_2d": GraphType.SCATTER,
|
|
39
|
+
"gaussian_2d": GraphType.HEATMAP,
|
|
40
|
+
"truncated_gaussian_2d": GraphType.HEATMAP,
|
|
41
|
+
|
|
42
|
+
# 3D distributions
|
|
43
|
+
"uniform_3d": GraphType.THREE_D_MAP,
|
|
44
|
+
"gaussian_3d": GraphType.THREE_D_MAP,
|
|
45
|
+
"truncated_gaussian_3d": GraphType.THREE_D_MAP,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
class ScenarioStats(Metrics):
|
|
49
|
+
"""Metric to track scenario statistics across episodes.
|
|
50
|
+
|
|
51
|
+
This class logs sampled values from scenario distributions and tracks episode
|
|
52
|
+
statuses. It supports various distribution types and automatically handles
|
|
53
|
+
flattening for 1D distributions.
|
|
54
|
+
|
|
55
|
+
Attributes:
|
|
56
|
+
name (str): The name of the scenario statistic.
|
|
57
|
+
distribution_type (str): The type of distribution (e.g., 'uniform', 'gaussian').
|
|
58
|
+
artifact_type (ArtifactType): The artifact type, always SCENARIO_STATS.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(self,
|
|
62
|
+
name: str,
|
|
63
|
+
distribution_type: str,
|
|
64
|
+
) -> None:
|
|
65
|
+
super().__init__(
|
|
66
|
+
graph_type=DISTRIBUTION_GRAPH_TYPE[distribution_type]
|
|
67
|
+
)
|
|
68
|
+
self._name = name
|
|
69
|
+
self._distribution_type = distribution_type
|
|
70
|
+
self._artifact_type = ArtifactType.SCENARIO_STATS
|
|
71
|
+
self._values = {}
|
|
72
|
+
self._results = {}
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def name(self) -> str:
|
|
76
|
+
"""The name of the scenario statistic.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
str: The statistic name.
|
|
80
|
+
"""
|
|
81
|
+
return self._name
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def distribution_type(self) -> str:
|
|
85
|
+
"""The type of distribution used for this statistic.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
str: The distribution type (e.g., 'uniform', 'gaussian').
|
|
89
|
+
"""
|
|
90
|
+
return self._distribution_type
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def artifact_type(self) -> ArtifactType:
|
|
94
|
+
"""The artifact type, always SCENARIO_STATS.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
ArtifactType: The artifact type.
|
|
98
|
+
"""
|
|
99
|
+
return self._artifact_type
|
|
100
|
+
|
|
101
|
+
def log(self, data: Any, x: Any = None, replace: bool = False) -> None:
|
|
102
|
+
"""Log a sampled value from the scenario distribution.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
data (Any): The sampled value to log.
|
|
106
|
+
x (Any | None): The key/identifier for this sample (typically episode_id).
|
|
107
|
+
If None, auto-incrementing step is used.
|
|
108
|
+
replace (bool): Whether to replace an existing value. Defaults to False.
|
|
109
|
+
|
|
110
|
+
Raises:
|
|
111
|
+
ValueError: If data for the given x already exists and replace is False.
|
|
112
|
+
"""
|
|
113
|
+
if x in self._values:
|
|
114
|
+
if replace:
|
|
115
|
+
if self._distribution_type in SCENARIO_STATS_NEED_FLATTEN:
|
|
116
|
+
data = data[0]
|
|
117
|
+
self._values[x] = data
|
|
118
|
+
else:
|
|
119
|
+
raise ValueError(f"Data for episode_id {x} already exists. Use replace=True to overwrite.")
|
|
120
|
+
else:
|
|
121
|
+
if self._distribution_type in SCENARIO_STATS_NEED_FLATTEN:
|
|
122
|
+
data = data[0]
|
|
123
|
+
self._values[x] = data
|
|
124
|
+
|
|
125
|
+
def log_status(self,
|
|
126
|
+
episode_id: str,
|
|
127
|
+
episode_status: EpisodeStatus,
|
|
128
|
+
replace: bool = False) -> None:
|
|
129
|
+
"""Log the status of an episode.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
episode_id (str): The unique identifier of the episode.
|
|
133
|
+
episode_status (EpisodeStatus): The status of the episode.
|
|
134
|
+
replace (bool): Whether to replace an existing status for this episode.
|
|
135
|
+
Defaults to False.
|
|
136
|
+
|
|
137
|
+
Raises:
|
|
138
|
+
ValueError: If status for the episode_id already exists and replace is False.
|
|
139
|
+
"""
|
|
140
|
+
if episode_id in self._results:
|
|
141
|
+
if replace:
|
|
142
|
+
self._results[episode_id] = episode_status.value
|
|
143
|
+
else:
|
|
144
|
+
raise ValueError(f"Data for episode_id {episode_id} already exists. Use replace=True to overwrite.")
|
|
145
|
+
else:
|
|
146
|
+
self._results[episode_id] = episode_status.value
|
|
147
|
+
|
|
148
|
+
def _finalize(self) -> dict:
|
|
149
|
+
"""Finalize and return all collected scenario statistics.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
dict: Dictionary containing values, results, and distribution type.
|
|
153
|
+
"""
|
|
154
|
+
ret_val = {
|
|
155
|
+
"values": self._values,
|
|
156
|
+
"results": self._results,
|
|
157
|
+
"distribution_type": self._distribution_type,
|
|
158
|
+
}
|
|
159
|
+
self._values = {}
|
|
160
|
+
self._results = {}
|
|
161
|
+
return ret_val
|
|
162
|
+
|
|
163
|
+
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
|
|
2
|
+
from humalab.metrics.metric import Metrics
|
|
3
|
+
from humalab.constants import MetricDimType, GraphType
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Summary(Metrics):
|
|
7
|
+
"""A metric that aggregates logged values into a single summary statistic.
|
|
8
|
+
|
|
9
|
+
Summary metrics collect values throughout a run or episode and compute a single
|
|
10
|
+
aggregated result. Supported aggregation methods include min, max, mean, first,
|
|
11
|
+
last, and none (no aggregation).
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
summary (str): The aggregation method used.
|
|
15
|
+
"""
|
|
16
|
+
def __init__(self,
|
|
17
|
+
summary: str,
|
|
18
|
+
) -> None:
|
|
19
|
+
"""
|
|
20
|
+
A summary metric that captures a single value per episode or run.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
summary (str): Specify the aggregation method for the summary.
|
|
24
|
+
Supported aggregations include "min", "max", "mean", "last",
|
|
25
|
+
"first", and "none". "none" prevents a summary from being generated.
|
|
26
|
+
"""
|
|
27
|
+
if summary not in {"min", "max", "mean", "last", "first", "none"}:
|
|
28
|
+
raise ValueError(f"Unsupported summary type: {summary}. Supported types are 'min', 'max', 'mean', 'last', 'first', and 'none'.")
|
|
29
|
+
super().__init__(graph_type=GraphType.NUMERIC)
|
|
30
|
+
self._summary = summary
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def summary(self) -> str:
|
|
34
|
+
"""The aggregation method for this summary metric.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
str: The summary type (e.g., 'min', 'max', 'mean').
|
|
38
|
+
"""
|
|
39
|
+
return self._summary
|
|
40
|
+
|
|
41
|
+
def _finalize(self) -> dict:
|
|
42
|
+
"""Compute the final aggregated value.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
dict: Dictionary containing the aggregated value and summary type.
|
|
46
|
+
"""
|
|
47
|
+
if not self._values:
|
|
48
|
+
return {
|
|
49
|
+
"value": None,
|
|
50
|
+
"summary": self.summary
|
|
51
|
+
}
|
|
52
|
+
final_val = None
|
|
53
|
+
# For summary metrics, we only keep the latest value
|
|
54
|
+
if self.summary == "last":
|
|
55
|
+
final_val = self._values[-1]
|
|
56
|
+
elif self.summary == "first":
|
|
57
|
+
final_val = self._values[0]
|
|
58
|
+
elif self.summary == "none":
|
|
59
|
+
final_val = None
|
|
60
|
+
elif self.summary in {"min", "max", "mean"}:
|
|
61
|
+
if not self._values:
|
|
62
|
+
final_val = None
|
|
63
|
+
else:
|
|
64
|
+
if self.summary == "min":
|
|
65
|
+
agg_value = min(self._values)
|
|
66
|
+
elif self.summary == "max":
|
|
67
|
+
agg_value = max(self._values)
|
|
68
|
+
elif self.summary == "mean":
|
|
69
|
+
agg_value = sum(self._values) / len(self._values)
|
|
70
|
+
final_val = agg_value
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
"value": final_val,
|
|
74
|
+
"summary": self.summary
|
|
75
|
+
}
|