humalab 0.0.5__py3-none-any.whl → 0.0.7__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 (42) hide show
  1. humalab/__init__.py +25 -0
  2. humalab/assets/__init__.py +8 -2
  3. humalab/assets/files/resource_file.py +96 -6
  4. humalab/assets/files/urdf_file.py +49 -11
  5. humalab/assets/resource_operator.py +139 -0
  6. humalab/constants.py +48 -5
  7. humalab/dists/__init__.py +7 -0
  8. humalab/dists/bernoulli.py +26 -1
  9. humalab/dists/categorical.py +25 -0
  10. humalab/dists/discrete.py +27 -2
  11. humalab/dists/distribution.py +11 -0
  12. humalab/dists/gaussian.py +27 -2
  13. humalab/dists/log_uniform.py +29 -3
  14. humalab/dists/truncated_gaussian.py +33 -4
  15. humalab/dists/uniform.py +24 -0
  16. humalab/episode.py +291 -11
  17. humalab/humalab.py +93 -38
  18. humalab/humalab_api_client.py +297 -95
  19. humalab/humalab_config.py +49 -0
  20. humalab/humalab_test.py +46 -17
  21. humalab/metrics/__init__.py +11 -5
  22. humalab/metrics/code.py +59 -0
  23. humalab/metrics/metric.py +69 -102
  24. humalab/metrics/scenario_stats.py +163 -0
  25. humalab/metrics/summary.py +45 -24
  26. humalab/run.py +224 -101
  27. humalab/scenarios/__init__.py +11 -0
  28. humalab/{scenario.py → scenarios/scenario.py} +130 -136
  29. humalab/scenarios/scenario_operator.py +114 -0
  30. humalab/{scenario_test.py → scenarios/scenario_test.py} +150 -269
  31. humalab/utils.py +37 -0
  32. {humalab-0.0.5.dist-info → humalab-0.0.7.dist-info}/METADATA +1 -1
  33. humalab-0.0.7.dist-info/RECORD +39 -0
  34. humalab/assets/resource_manager.py +0 -58
  35. humalab/evaluators/__init__.py +0 -16
  36. humalab/humalab_main.py +0 -119
  37. humalab/metrics/dist_metric.py +0 -22
  38. humalab-0.0.5.dist-info/RECORD +0 -37
  39. {humalab-0.0.5.dist-info → humalab-0.0.7.dist-info}/WHEEL +0 -0
  40. {humalab-0.0.5.dist-info → humalab-0.0.7.dist-info}/entry_points.txt +0 -0
  41. {humalab-0.0.5.dist-info → humalab-0.0.7.dist-info}/licenses/LICENSE +0 -0
  42. {humalab-0.0.5.dist-info → humalab-0.0.7.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,79 +124,107 @@ 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,
185
164
  }
186
165
 
187
166
  class Scenario:
167
+ """Manages scenario configurations with probabilistic distributions.
168
+
169
+ A Scenario encapsulates a configuration template that can contain distribution
170
+ resolvers (e.g., ${uniform:0,1}). When resolved, these distributions are sampled
171
+ to produce concrete scenario instances. Each resolution creates a new episode
172
+ with different sampled values.
173
+
174
+ Supported distributions include uniform, gaussian, bernoulli, categorical,
175
+ discrete, log_uniform, and truncated_gaussian, with support for 0D-3D variants.
176
+
177
+ Attributes:
178
+ template (DictConfig | ListConfig): The template scenario configuration.
179
+ yaml (str): The current scenario configuration as a YAML string.
180
+ """
188
181
  dist_cache = {}
189
182
  def __init__(self) -> None:
190
183
  self._generator = np.random.default_rng()
191
184
  self._scenario_template = OmegaConf.create()
192
185
  self._cur_scenario = OmegaConf.create()
193
186
  self._scenario_id = None
187
+ self._seed = None
188
+
189
+ self._episode_vals = {}
190
+ self._lock = RLock()
194
191
 
195
192
  def init(self,
196
- run_id: str,
197
- episode_id: str,
198
- scenario: str | list | dict | None = None,
199
- seed: int | None=None,
193
+ scenario: str | list | dict | None = None,
194
+ seed: int | None=None,
200
195
  scenario_id: str | None=None,
201
196
  # num_env: int | None = None
202
197
  ) -> None:
203
198
  """
204
199
  Initialize the scenario with the given parameters.
205
-
200
+
206
201
  Args:
207
- run_id: The ID of the current run.
208
- episode_id: The ID of the current episode.
209
- scenario: The scenario configuration (YAML string, list, or dict).
210
- seed: Optional seed for random number generation.
211
- scenario_id: Optional scenario ID. If None, a new UUID is generated.
212
- # num_env: Optional number of parallel environments.
202
+ scenario (str | list | dict | None): The scenario configuration. Can be a YAML
203
+ string, list, or dict. If None, an empty configuration is used.
204
+ seed (int | None): Optional seed for random number generation. If None, uses
205
+ a non-deterministic seed.
206
+ scenario_id (str | None): Optional scenario ID in the format 'id' or 'id:version'.
207
+ If None, a new UUID is generated.
213
208
  """
214
- self._run_id = run_id
215
- self._episode_id = episode_id
216
- self._metrics = {}
217
-
218
209
  self._num_env = None # num_env
210
+ self._seed = seed
211
+
212
+ # Parse scenario id
213
+ scenario_version = 1
214
+ if scenario_id is not None:
215
+ scenario_arr = scenario_id.split(":")
216
+ if len(scenario_arr) < 1:
217
+ raise ValueError("Invalid scenario_id format. Expected 'scenario_id' or 'scenario_name:version'.")
218
+ scenario_id = scenario_arr[0]
219
+ scenario_version = int(scenario_arr[1]) if len(scenario_arr) > 1 else None
219
220
  self._scenario_id = scenario_id or str(uuid.uuid4())
221
+ self._scenario_version = scenario_version
222
+
220
223
  self._generator = np.random.default_rng(seed)
221
224
  self._configure()
222
225
  scenario = scenario or {}
226
+
223
227
  self._scenario_template = OmegaConf.create(scenario)
224
- self.reset(episode_id=episode_id)
225
228
 
226
229
  def _validate_distribution_params(self, dist_name: str, *args: tuple) -> None:
227
230
  dimensions = DISTRIBUTION_DIMENSION_MAP[dist_name]
@@ -291,13 +294,6 @@ class Scenario:
291
294
  root_yaml = yaml.safe_load(OmegaConf.to_yaml(_root_))
292
295
  key_path = self._get_node_path(root_yaml, str(_node_))
293
296
 
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
297
  shape = None
302
298
 
303
299
  if DISTRIBUTION_DIMENSION_MAP[dist_name] == -1:
@@ -315,7 +311,11 @@ class Scenario:
315
311
 
316
312
  if isinstance(ret_val, list):
317
313
  ret_val = ListConfig(ret_val)
318
- self._metrics[key_path].log(ret_val)
314
+
315
+ self._episode_vals[key_path] = {
316
+ "value": ret_val,
317
+ "distribution": dist_name,
318
+ }
319
319
  return ret_val
320
320
 
321
321
  for dist_name in DISTRIBUTION_MAP.keys():
@@ -325,33 +325,36 @@ class Scenario:
325
325
  self.dist_cache.clear()
326
326
  OmegaConf.clear_resolvers()
327
327
 
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.
328
+ def resolve(self) -> tuple[DictConfig | ListConfig, dict]:
329
+ """Resolve the scenario configuration, sampling all distributions.
330
+
331
+ Returns:
332
+ tuple[DictConfig | ListConfig, dict]: The resolved scenario and episode values.
344
333
  """
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.
334
+ with self._lock:
335
+ cur_scenario = copy.deepcopy(self._scenario_template)
336
+ self._episode_vals = {}
337
+ OmegaConf.resolve(cur_scenario)
338
+ episode_vals = copy.deepcopy(self._episode_vals)
339
+ return cur_scenario, episode_vals
340
+
341
+ @property
342
+ def scenario_id(self) -> str | None:
343
+ """The scenario ID.
344
+
345
+ Returns:
346
+ str | None: The scenario ID, or None if not set.
347
+ """
348
+ return self._scenario_id
349
+
350
+ @property
351
+ def seed(self) -> int | None:
352
+ """The random seed for the scenario.
353
+
354
+ Returns:
355
+ int | None: The random seed, or None if not set.
352
356
  """
353
- for metric in self._metrics.values():
354
- metric.finish()
357
+ return self._seed
355
358
 
356
359
  @property
357
360
  def template(self) -> Any:
@@ -362,15 +365,6 @@ class Scenario:
362
365
  """
363
366
  return self._scenario_template
364
367
 
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
368
  @property
375
369
  def yaml(self) -> str:
376
370
  """The current scenario configuration as a YAML string.
@@ -378,4 +372,4 @@ class Scenario:
378
372
  Returns:
379
373
  str: The current scenario as a YAML string.
380
374
  """
381
- return OmegaConf.to_yaml(self._cur_scenario)
375
+ return OmegaConf.to_yaml(self._scenario_template)
@@ -0,0 +1,114 @@
1
+ """Operations for managing and retrieving scenarios."""
2
+
3
+ from typing import Optional
4
+ from dataclasses import dataclass
5
+
6
+ from humalab.humalab_api_client import HumaLabApiClient
7
+ from humalab.scenarios.scenario import Scenario
8
+ from humalab.constants import DEFAULT_PROJECT
9
+
10
+ @dataclass
11
+ class ScenarioMetadata:
12
+ """Metadata for a scenario stored in HumaLab.
13
+
14
+ Attributes:
15
+ id (str): Unique identifier for the scenario.
16
+ version (int): Version number of the scenario.
17
+ project (str): Project name the scenario belongs to.
18
+ name (str): Human-readable scenario name.
19
+ description (str | None): Optional scenario description.
20
+ created_at (str): ISO timestamp when scenario was created.
21
+ updated_at (str): ISO timestamp when scenario was last updated.
22
+ """
23
+ id: str
24
+ version: int
25
+ project: str
26
+ name: str
27
+ description: str | None
28
+ created_at: str
29
+ updated_at: str
30
+
31
+
32
+ def list_scenarios(project: str = DEFAULT_PROJECT,
33
+ limit: int = 20,
34
+ offset: int = 0,
35
+ include_inactive: bool = False,
36
+ search: Optional[str] = None,
37
+ status_filter: Optional[str] = None,
38
+
39
+ base_url: str | None = None,
40
+ api_key: str | None = None,
41
+ timeout: float | None = None,
42
+ ) -> list[ScenarioMetadata]:
43
+ """
44
+ List all scenarios for a given project.
45
+
46
+ Args:
47
+ project (str): The project name to list scenarios from. Defaults to DEFAULT_PROJECT.
48
+ limit (int): Maximum number of scenarios to return. Defaults to 20.
49
+ offset (int): Number of scenarios to skip for pagination. Defaults to 0.
50
+ include_inactive (bool): Whether to include inactive scenarios. Defaults to False.
51
+ search (Optional[str]): Search query to filter scenarios by name or description. Defaults to None.
52
+ status_filter (Optional[str]): Filter scenarios by status. Defaults to None.
53
+ base_url (str | None): The base URL of the HumaLab API. If None, uses configured value.
54
+ api_key (str | None): The API key for authentication. If None, uses configured value.
55
+ timeout (float | None): The timeout for API requests in seconds. If None, uses configured value.
56
+
57
+ Returns:
58
+ list[ScenarioMetadata]: A list of scenario metadata objects.
59
+ """
60
+ api_client = HumaLabApiClient(base_url=base_url,
61
+ api_key=api_key,
62
+ timeout=timeout)
63
+ resp = api_client.get_scenarios(project_name=project,
64
+ limit=limit,
65
+ offset=offset,
66
+ include_inactive=include_inactive,
67
+ search=search,
68
+ status_filter=status_filter)
69
+ ret_list = []
70
+ for scenario in resp.get("scenarios", []):
71
+ scenario["project"] = project
72
+ ret_list.append(ScenarioMetadata(id=scenario["uuid"],
73
+ version=scenario["version"],
74
+ project=project,
75
+ name=scenario["name"],
76
+ description=scenario.get("description"),
77
+ created_at=scenario.get("created_at"),
78
+ updated_at=scenario.get("updated_at")))
79
+ return ret_list
80
+
81
+ def get_scenario(scenario_id: str,
82
+ version: int | None = None,
83
+ project: str = DEFAULT_PROJECT,
84
+ seed: int | None=None,
85
+
86
+ base_url: str | None = None,
87
+ api_key: str | None = None,
88
+ timeout: float | None = None,) -> Scenario:
89
+ """Retrieve and initialize a scenario from HumaLab.
90
+
91
+ Args:
92
+ scenario_id (str): The unique identifier of the scenario.
93
+ version (int | None): Optional specific version to retrieve.
94
+ project (str): The project name. Defaults to DEFAULT_PROJECT.
95
+ seed (int | None): Optional seed for scenario randomization.
96
+ base_url (str | None): Optional API host override.
97
+ api_key (str | None): Optional API key override.
98
+ timeout (float | None): Optional timeout override.
99
+
100
+ Returns:
101
+ Scenario: The initialized scenario instance.
102
+ """
103
+ api_client = HumaLabApiClient(base_url=base_url,
104
+ api_key=api_key,
105
+ timeout=timeout)
106
+ scenario_resp = api_client.get_scenario(
107
+ project_name=project,
108
+ uuid=scenario_id, version=version)
109
+ scenario = Scenario()
110
+
111
+ scenario.init(scenario=scenario_resp["yaml_content"],
112
+ seed=seed,
113
+ scenario_id=f"{scenario_id}:{version}" if version is not None else scenario_id)
114
+ return scenario