humalab 0.0.5__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.

Files changed (37) hide show
  1. humalab/__init__.py +11 -0
  2. humalab/assets/__init__.py +2 -2
  3. humalab/assets/files/resource_file.py +29 -3
  4. humalab/assets/files/urdf_file.py +14 -10
  5. humalab/assets/resource_operator.py +91 -0
  6. humalab/constants.py +39 -5
  7. humalab/dists/bernoulli.py +2 -1
  8. humalab/dists/discrete.py +2 -2
  9. humalab/dists/gaussian.py +2 -2
  10. humalab/dists/log_uniform.py +2 -2
  11. humalab/dists/truncated_gaussian.py +4 -4
  12. humalab/episode.py +181 -11
  13. humalab/humalab.py +44 -28
  14. humalab/humalab_api_client.py +301 -94
  15. humalab/humalab_test.py +46 -17
  16. humalab/metrics/__init__.py +5 -5
  17. humalab/metrics/code.py +28 -0
  18. humalab/metrics/metric.py +41 -108
  19. humalab/metrics/scenario_stats.py +95 -0
  20. humalab/metrics/summary.py +24 -18
  21. humalab/run.py +180 -103
  22. humalab/scenarios/__init__.py +4 -0
  23. humalab/{scenario.py → scenarios/scenario.py} +120 -129
  24. humalab/scenarios/scenario_operator.py +82 -0
  25. humalab/{scenario_test.py → scenarios/scenario_test.py} +150 -269
  26. humalab/utils.py +37 -0
  27. {humalab-0.0.5.dist-info → humalab-0.0.6.dist-info}/METADATA +1 -1
  28. humalab-0.0.6.dist-info/RECORD +39 -0
  29. humalab/assets/resource_manager.py +0 -58
  30. humalab/evaluators/__init__.py +0 -16
  31. humalab/humalab_main.py +0 -119
  32. humalab/metrics/dist_metric.py +0 -22
  33. humalab-0.0.5.dist-info/RECORD +0 -37
  34. {humalab-0.0.5.dist-info → humalab-0.0.6.dist-info}/WHEEL +0 -0
  35. {humalab-0.0.5.dist-info → humalab-0.0.6.dist-info}/entry_points.txt +0 -0
  36. {humalab-0.0.5.dist-info → humalab-0.0.6.dist-info}/licenses/LICENSE +0 -0
  37. {humalab-0.0.5.dist-info → humalab-0.0.6.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,8 @@
1
1
  from typing import Any
2
+ from threading import RLock
3
+
2
4
  import numpy as np
3
- from omegaconf import OmegaConf, ListConfig, AnyNode, DictConfig
5
+ from omegaconf import OmegaConf, DictConfig, ListConfig
4
6
  import yaml
5
7
  from humalab.dists.bernoulli import Bernoulli
6
8
  from humalab.dists.categorical import Categorical
@@ -10,9 +12,7 @@ from humalab.dists.log_uniform import LogUniform
10
12
  from humalab.dists.gaussian import Gaussian
11
13
  from humalab.dists.truncated_gaussian import TruncatedGaussian
12
14
  from functools import partial
13
- from humalab.constants import EpisodeStatus
14
- from humalab.metrics.dist_metric import DistributionMetric
15
- from humalab.metrics.metric import MetricGranularity
15
+ from humalab.constants import GraphType, MetricDimType
16
16
  import copy
17
17
  import uuid
18
18
 
@@ -37,39 +37,40 @@ DISTRIBUTION_MAP = {
37
37
 
38
38
  # 2D distributions
39
39
  "uniform_2d": Uniform,
40
- "bernoulli_2d": Bernoulli,
41
- "categorical_2d": Categorical,
42
- "discrete_2d": Discrete,
43
- "log_uniform_2d": LogUniform,
40
+ # "bernoulli_2d": Bernoulli,
41
+ # "categorical_2d": Categorical,
42
+ # "discrete_2d": Discrete,
43
+ # "log_uniform_2d": LogUniform,
44
44
  "gaussian_2d": Gaussian,
45
45
  "truncated_gaussian_2d": TruncatedGaussian,
46
46
 
47
47
  # 3D distributions
48
48
  "uniform_3d": Uniform,
49
- "bernoulli_3d": Bernoulli,
50
- "categorical_3d": Categorical,
51
- "discrete_3d": Discrete,
52
- "log_uniform_3d": LogUniform,
49
+ # "bernoulli_3d": Bernoulli,
50
+ # "categorical_3d": Categorical,
51
+ # "discrete_3d": Discrete,
52
+ # "log_uniform_3d": LogUniform,
53
53
  "gaussian_3d": Gaussian,
54
54
  "truncated_gaussian_3d": TruncatedGaussian,
55
55
 
56
56
  # 4D distributions
57
- "uniform_4d": Uniform,
58
- "bernoulli_4d": Bernoulli,
59
- "categorical_4d": Categorical,
60
- "discrete_4d": Discrete,
61
- "log_uniform_4d": LogUniform,
62
- "gaussian_4d": Gaussian,
63
- "truncated_gaussian_4d": TruncatedGaussian,
64
-
57
+ # "uniform_4d": Uniform,
58
+ # "bernoulli_4d": Bernoulli,
59
+ # "categorical_4d": Categorical,
60
+ # "discrete_4d": Discrete,
61
+ # "log_uniform_4d": LogUniform,
62
+ # "gaussian_4d": Gaussian,
63
+ # "truncated_gaussian_4d": TruncatedGaussian,
64
+
65
65
  # nD distributions
66
- "uniform_nd": Uniform,
67
- "bernoulli_nd": Bernoulli,
68
- "categorical_nd": Categorical,
69
- "discrete_nd": Discrete,
70
- "log_uniform_nd": LogUniform,
71
- "gaussian_nd": Gaussian,
72
- "truncated_gaussian_nd": TruncatedGaussian,
66
+ # "uniform_nd": Uniform,
67
+ # "bernoulli_nd": Bernoulli,
68
+ # "categorical_nd": Categorical,
69
+ # "discrete_nd": Discrete,
70
+ # "log_uniform_nd": LogUniform,
71
+ # "gaussian_nd": Gaussian,
72
+ # "truncated_gaussian_nd": TruncatedGaussian,
73
+
73
74
  }
74
75
 
75
76
  DISTRIBUTION_DIMENSION_MAP = {
@@ -93,39 +94,13 @@ DISTRIBUTION_DIMENSION_MAP = {
93
94
 
94
95
  # 2D distributions
95
96
  "uniform_2d": 2,
96
- "bernoulli_2d": 2,
97
- "categorical_2d": 2,
98
- "discrete_2d": 2,
99
- "log_uniform_2d": 2,
100
97
  "gaussian_2d": 2,
101
98
  "truncated_gaussian_2d": 2,
102
99
 
103
100
  # 3D distributions
104
101
  "uniform_3d": 3,
105
- "bernoulli_3d": 3,
106
- "categorical_3d": 3,
107
- "discrete_3d": 3,
108
- "log_uniform_3d": 3,
109
102
  "gaussian_3d": 3,
110
103
  "truncated_gaussian_3d": 3,
111
-
112
- # 4D distributions
113
- "uniform_4d": 4,
114
- "bernoulli_4d": 4,
115
- "categorical_4d": 4,
116
- "discrete_4d": 4,
117
- "log_uniform_4d": 4,
118
- "gaussian_4d": 4,
119
- "truncated_gaussian_4d": 4,
120
-
121
- # nD distributions
122
- "uniform_nd": -1,
123
- "bernoulli_nd": -1,
124
- "categorical_nd": -1,
125
- "discrete_nd": -1,
126
- "log_uniform_nd": -1,
127
- "gaussian_nd": -1,
128
- "truncated_gaussian_nd": -1,
129
104
  }
130
105
 
131
106
  DISTRIBUTION_PARAM_NUM_MAP = {
@@ -149,39 +124,73 @@ DISTRIBUTION_PARAM_NUM_MAP = {
149
124
 
150
125
  # 2D distributions
151
126
  "uniform_2d": 2,
152
- "bernoulli_2d": 1,
153
- "categorical_2d": 2,
154
- "discrete_2d": 3,
155
- "log_uniform_2d": 2,
156
127
  "gaussian_2d": 2,
157
128
  "truncated_gaussian_2d": 4,
158
129
 
159
130
  # 3D distributions
160
131
  "uniform_3d": 2,
161
- "bernoulli_3d": 1,
162
- "categorical_3d": 2,
163
- "discrete_3d": 3,
164
- "log_uniform_3d": 2,
165
132
  "gaussian_3d": 2,
166
133
  "truncated_gaussian_3d": 4,
134
+ }
167
135
 
168
- # 4D distributions
169
- "uniform_4d": 2,
170
- "bernoulli_4d": 1,
171
- "categorical_4d": 2,
172
- "discrete_4d": 3,
173
- "log_uniform_4d": 2,
174
- "gaussian_4d": 2,
175
- "truncated_gaussian_4d": 4,
136
+ SCENARIO_STATS_DIM_TYPE_MAP = {
137
+ # 0D distributions
138
+ "uniform": MetricDimType.ONE_D,
139
+ "bernoulli": MetricDimType.ONE_D,
140
+ "categorical": MetricDimType.ONE_D,
141
+ "discrete": MetricDimType.ONE_D,
142
+ "log_uniform": MetricDimType.ONE_D,
143
+ "gaussian": MetricDimType.ONE_D,
144
+ "truncated_gaussian": MetricDimType.ONE_D,
176
145
 
177
- # nD distributions
178
- "uniform_nd": 3,
179
- "bernoulli_nd": 2,
180
- "categorical_nd": 3,
181
- "discrete_nd": 4,
182
- "log_uniform_nd": 3,
183
- "gaussian_nd": 3,
184
- "truncated_gaussian_nd": 5,
146
+ # 1D distributions
147
+ "uniform_1d": MetricDimType.ONE_D,
148
+ "bernoulli_1d": MetricDimType.ONE_D,
149
+ "categorical_1d": MetricDimType.ONE_D,
150
+ "discrete_1d": MetricDimType.ONE_D,
151
+ "log_uniform_1d": MetricDimType.ONE_D,
152
+ "gaussian_1d": MetricDimType.ONE_D,
153
+ "truncated_gaussian_1d": MetricDimType.ONE_D,
154
+
155
+ # 2D distributions
156
+ "uniform_2d": MetricDimType.TWO_D,
157
+ "gaussian_2d": MetricDimType.TWO_D,
158
+ "truncated_gaussian_2d": MetricDimType.TWO_D,
159
+
160
+ # 3D distributions
161
+ "uniform_3d": MetricDimType.THREE_D,
162
+ "gaussian_3d": MetricDimType.THREE_D,
163
+ "truncated_gaussian_3d": MetricDimType.THREE_D,
164
+ }
165
+
166
+ DISTRIBUTION_GRAPH_TYPE = {
167
+ # 0D distributions
168
+ "uniform": GraphType.HISTOGRAM,
169
+ "bernoulli": GraphType.BAR,
170
+ "categorical": GraphType.BAR,
171
+ "discrete": GraphType.BAR,
172
+ "log_uniform": GraphType.HISTOGRAM,
173
+ "gaussian": GraphType.GAUSSIAN,
174
+ "truncated_gaussian": GraphType.GAUSSIAN,
175
+
176
+ # 1D distributions
177
+ "uniform_1d": GraphType.HISTOGRAM,
178
+ "bernoulli_1d": GraphType.BAR,
179
+ "categorical_1d": GraphType.BAR,
180
+ "discrete_1d": GraphType.BAR,
181
+ "log_uniform_1d": GraphType.HISTOGRAM,
182
+ "gaussian_1d": GraphType.GAUSSIAN,
183
+ "truncated_gaussian_1d": GraphType.GAUSSIAN,
184
+
185
+ # 2D distributions
186
+ "uniform_2d": GraphType.SCATTER,
187
+ "gaussian_2d": GraphType.HEATMAP,
188
+ "truncated_gaussian_2d": GraphType.HEATMAP,
189
+
190
+ # 3D distributions
191
+ "uniform_3d": GraphType.THREE_D_MAP,
192
+ "gaussian_3d": GraphType.THREE_D_MAP,
193
+ "truncated_gaussian_3d": GraphType.THREE_D_MAP,
185
194
  }
186
195
 
187
196
  class Scenario:
@@ -192,9 +201,10 @@ class Scenario:
192
201
  self._cur_scenario = OmegaConf.create()
193
202
  self._scenario_id = None
194
203
 
204
+ self._episode_vals = {}
205
+ self._lock = RLock()
206
+
195
207
  def init(self,
196
- run_id: str,
197
- episode_id: str,
198
208
  scenario: str | list | dict | None = None,
199
209
  seed: int | None=None,
200
210
  scenario_id: str | None=None,
@@ -204,24 +214,30 @@ class Scenario:
204
214
  Initialize the scenario with the given parameters.
205
215
 
206
216
  Args:
207
- run_id: The ID of the current run.
208
217
  episode_id: The ID of the current episode.
209
218
  scenario: The scenario configuration (YAML string, list, or dict).
210
219
  seed: Optional seed for random number generation.
211
220
  scenario_id: Optional scenario ID. If None, a new UUID is generated.
212
221
  # num_env: Optional number of parallel environments.
213
222
  """
214
- self._run_id = run_id
215
- self._episode_id = episode_id
216
- self._metrics = {}
217
-
218
223
  self._num_env = None # num_env
224
+
225
+ # Parse scenario id
226
+ scenario_version = 1
227
+ if scenario_id is not None:
228
+ scenario_arr = scenario_id.split(":")
229
+ if len(scenario_arr) < 1:
230
+ raise ValueError("Invalid scenario_id format. Expected 'scenario_id' or 'scenario_name:version'.")
231
+ scenario_id = scenario_arr[0]
232
+ scenario_version = int(scenario_arr[1]) if len(scenario_arr) > 1 else None
219
233
  self._scenario_id = scenario_id or str(uuid.uuid4())
234
+ self._scenario_version = scenario_version
235
+
220
236
  self._generator = np.random.default_rng(seed)
221
237
  self._configure()
222
238
  scenario = scenario or {}
239
+
223
240
  self._scenario_template = OmegaConf.create(scenario)
224
- self.reset(episode_id=episode_id)
225
241
 
226
242
  def _validate_distribution_params(self, dist_name: str, *args: tuple) -> None:
227
243
  dimensions = DISTRIBUTION_DIMENSION_MAP[dist_name]
@@ -291,13 +307,6 @@ class Scenario:
291
307
  root_yaml = yaml.safe_load(OmegaConf.to_yaml(_root_))
292
308
  key_path = self._get_node_path(root_yaml, str(_node_))
293
309
 
294
- if key_path not in self._metrics:
295
- self._metrics[key_path] = DistributionMetric(name=key_path,
296
- distribution_type=dist_name,
297
- run_id=self._run_id,
298
- episode_id=self._episode_id,
299
- granularity=MetricGranularity.EPISODE)
300
-
301
310
  shape = None
302
311
 
303
312
  if DISTRIBUTION_DIMENSION_MAP[dist_name] == -1:
@@ -315,7 +324,13 @@ class Scenario:
315
324
 
316
325
  if isinstance(ret_val, list):
317
326
  ret_val = ListConfig(ret_val)
318
- self._metrics[key_path].log(ret_val)
327
+
328
+ self._episode_vals[key_path] = {
329
+ "value": ret_val,
330
+ "distribution": dist_name,
331
+ "graph_type": DISTRIBUTION_GRAPH_TYPE[dist_name],
332
+ "metric_dim_type": SCENARIO_STATS_DIM_TYPE_MAP[dist_name],
333
+ }
319
334
  return ret_val
320
335
 
321
336
  for dist_name in DISTRIBUTION_MAP.keys():
@@ -325,33 +340,18 @@ class Scenario:
325
340
  self.dist_cache.clear()
326
341
  OmegaConf.clear_resolvers()
327
342
 
328
- def __getattr__(self, name: Any) -> Any:
329
- if name in self._cur_scenario:
330
- return self._cur_scenario[name]
331
- raise AttributeError(f"'Scenario' object has no attribute '{name}'")
332
-
333
- def __getitem__(self, key: Any) -> Any:
334
- if key in self._cur_scenario:
335
- return self._cur_scenario[key]
336
- raise KeyError(f"'Scenario' object has no key '{key}'")
337
-
338
- def reset(self,
339
- episode_id: str | None = None) -> None:
340
- """Reset the scenario for a new episode.
341
-
342
- Args:
343
- episode_id: Optional new episode ID. If None, keeps the current episode ID.
344
- """
345
- for metric in self._metrics.values():
346
- metric.reset(episode_id=episode_id)
347
- self._cur_scenario = copy.deepcopy(self._scenario_template)
348
- OmegaConf.resolve(self._cur_scenario)
349
-
350
- def finish(self) -> None:
351
- """Finish the scenario and submit final metrics.
343
+ def resolve(self) -> tuple[DictConfig | ListConfig, dict]:
344
+ """Resolve the scenario configuration, sampling all distributions.
345
+
346
+ Returns:
347
+ tuple[DictConfig | ListConfig, dict]: The resolved scenario and episode values.
352
348
  """
353
- for metric in self._metrics.values():
354
- metric.finish()
349
+ with self._lock:
350
+ cur_scenario = copy.deepcopy(self._scenario_template)
351
+ self._episode_vals = {}
352
+ OmegaConf.resolve(cur_scenario)
353
+ episode_vals = copy.deepcopy(self._episode_vals)
354
+ return cur_scenario, episode_vals
355
355
 
356
356
  @property
357
357
  def template(self) -> Any:
@@ -362,15 +362,6 @@ class Scenario:
362
362
  """
363
363
  return self._scenario_template
364
364
 
365
- @property
366
- def cur_scenario(self) -> Any:
367
- """The current scenario configuration.
368
-
369
- Returns:
370
- Any: The current scenario as an OmegaConf object.
371
- """
372
- return self._cur_scenario
373
-
374
365
  @property
375
366
  def yaml(self) -> str:
376
367
  """The current scenario configuration as a YAML string.
@@ -378,4 +369,4 @@ class Scenario:
378
369
  Returns:
379
370
  str: The current scenario as a YAML string.
380
371
  """
381
- return OmegaConf.to_yaml(self._cur_scenario)
372
+ return OmegaConf.to_yaml(self._scenario_template)
@@ -0,0 +1,82 @@
1
+ from typing import Optional
2
+ from dataclasses import dataclass
3
+
4
+ from humalab.humalab_api_client import HumaLabApiClient
5
+ from humalab.scenarios.scenario import Scenario
6
+ from humalab.constants import DEFAULT_PROJECT
7
+
8
+ @dataclass
9
+ class ScenarioMetadata:
10
+ id: str
11
+ version: int
12
+ project: str
13
+ name: str
14
+ description: str | None
15
+ created_at: str
16
+ updated_at: str
17
+
18
+
19
+ def list_scenarios(project: str = DEFAULT_PROJECT,
20
+ limit: int = 20,
21
+ offset: int = 0,
22
+ include_inactive: bool = False,
23
+ search: Optional[str] = None,
24
+ status_filter: Optional[str] = None,
25
+
26
+ base_url: str | None = None,
27
+ api_key: str | None = None,
28
+ timeout: float | None = None,
29
+ ) -> list[dict]:
30
+ """
31
+ List all scenarios for a given project.
32
+
33
+ Args:
34
+ project: The project name to list scenarios from.
35
+ base_url: The base URL of the HumaLab API.
36
+ api_key: The API key for authentication.
37
+ timeout: The timeout for API requests.
38
+
39
+ Returns:
40
+ A list of scenario metadata dictionaries.
41
+ """
42
+ api_client = HumaLabApiClient(base_url=base_url,
43
+ api_key=api_key,
44
+ timeout=timeout)
45
+ resp = api_client.get_scenarios(project_name=project,
46
+ limit=limit,
47
+ offset=offset,
48
+ include_inactive=include_inactive,
49
+ search=search,
50
+ status_filter=status_filter)
51
+ ret_list = []
52
+ for scenario in resp.get("scenarios", []):
53
+ scenario["project"] = project
54
+ ret_list.append(ScenarioMetadata(id=scenario["uuid"],
55
+ version=scenario["version"],
56
+ project=project,
57
+ name=scenario["name"],
58
+ description=scenario.get("description"),
59
+ created_at=scenario.get("created_at"),
60
+ updated_at=scenario.get("updated_at")))
61
+ return ret_list
62
+
63
+ def get_scenario(scenario_id: str,
64
+ version: int | None = None,
65
+ project: str = DEFAULT_PROJECT,
66
+ seed: int | None=None,
67
+
68
+ base_url: str | None = None,
69
+ api_key: str | None = None,
70
+ timeout: float | None = None,) -> Scenario:
71
+ api_client = HumaLabApiClient(base_url=base_url,
72
+ api_key=api_key,
73
+ timeout=timeout)
74
+ scenario_resp = api_client.get_scenario(
75
+ project_name=project,
76
+ uuid=scenario_id, version=version)
77
+ scenario = Scenario()
78
+
79
+ scenario.init(scenario=scenario_resp["yaml_content"],
80
+ seed=seed,
81
+ scenario_id=f"{scenario_id}:{version}" if version is not None else scenario_id)
82
+ return scenario