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
|
@@ -0,0 +1,792 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
import numpy as np
|
|
3
|
+
from humalab.scenarios.scenario import Scenario
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ScenarioTest(unittest.TestCase):
|
|
7
|
+
"""Unit tests for Scenario class."""
|
|
8
|
+
|
|
9
|
+
def setUp(self):
|
|
10
|
+
"""Set up test fixtures before each test method."""
|
|
11
|
+
self.scenario = Scenario()
|
|
12
|
+
|
|
13
|
+
def tearDown(self):
|
|
14
|
+
"""Clean up after each test method."""
|
|
15
|
+
self.scenario._clear_resolvers()
|
|
16
|
+
|
|
17
|
+
def test_init_should_initialize_with_empty_scenario(self):
|
|
18
|
+
"""Test that init() initializes with empty scenario when none provided."""
|
|
19
|
+
# Pre-condition
|
|
20
|
+
self.assertIsNone(self.scenario._scenario_id)
|
|
21
|
+
|
|
22
|
+
# In-test
|
|
23
|
+
self.scenario.init(
|
|
24
|
+
scenario=None,
|
|
25
|
+
seed=42
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Post-condition
|
|
29
|
+
self.assertIsNotNone(self.scenario._scenario_id)
|
|
30
|
+
self.assertEqual(len(self.scenario._scenario_template), 0)
|
|
31
|
+
|
|
32
|
+
def test_init_should_initialize_with_dict_scenario(self):
|
|
33
|
+
"""Test that init() correctly processes dict-based scenario."""
|
|
34
|
+
# Pre-condition
|
|
35
|
+
scenario_dict = {
|
|
36
|
+
"test_key": "test_value",
|
|
37
|
+
"nested": {"inner_key": "inner_value"}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# In-test
|
|
41
|
+
self.scenario.init(
|
|
42
|
+
scenario=scenario_dict,
|
|
43
|
+
seed=42
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Post-condition
|
|
47
|
+
resolved, _ = self.scenario.resolve()
|
|
48
|
+
self.assertEqual(resolved.test_key, "test_value")
|
|
49
|
+
self.assertEqual(resolved.nested.inner_key, "inner_value")
|
|
50
|
+
|
|
51
|
+
def test_init_should_use_provided_scenario_id(self):
|
|
52
|
+
"""Test that init() uses provided scenario_id."""
|
|
53
|
+
# Pre-condition
|
|
54
|
+
custom_id = "custom_scenario_id"
|
|
55
|
+
|
|
56
|
+
# In-test
|
|
57
|
+
self.scenario.init(
|
|
58
|
+
scenario={},
|
|
59
|
+
scenario_id=custom_id
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Post-condition
|
|
63
|
+
self.assertEqual(self.scenario._scenario_id, custom_id)
|
|
64
|
+
|
|
65
|
+
def test_init_should_set_seed_for_reproducibility(self):
|
|
66
|
+
"""Test that init() with same seed produces reproducible results."""
|
|
67
|
+
# Pre-condition
|
|
68
|
+
scenario_config = {"value": "${uniform: 0.0, 1.0}"}
|
|
69
|
+
seed = 42
|
|
70
|
+
|
|
71
|
+
# In-test
|
|
72
|
+
scenario1 = Scenario()
|
|
73
|
+
scenario1.init(
|
|
74
|
+
scenario=scenario_config,
|
|
75
|
+
seed=seed
|
|
76
|
+
)
|
|
77
|
+
resolved1, _ = scenario1.resolve()
|
|
78
|
+
value1 = resolved1.value
|
|
79
|
+
|
|
80
|
+
scenario2 = Scenario()
|
|
81
|
+
scenario2.init(
|
|
82
|
+
scenario=scenario_config,
|
|
83
|
+
seed=seed
|
|
84
|
+
)
|
|
85
|
+
resolved2, _ = scenario2.resolve()
|
|
86
|
+
value2 = resolved2.value
|
|
87
|
+
|
|
88
|
+
# Post-condition
|
|
89
|
+
self.assertEqual(value1, value2)
|
|
90
|
+
|
|
91
|
+
# Cleanup
|
|
92
|
+
scenario1._clear_resolvers()
|
|
93
|
+
scenario2._clear_resolvers()
|
|
94
|
+
|
|
95
|
+
def test_uniform_distribution_should_resolve_correctly(self):
|
|
96
|
+
"""Test that uniform distribution resolver works correctly."""
|
|
97
|
+
# Pre-condition
|
|
98
|
+
scenario_config = {
|
|
99
|
+
"uniform_value": "${uniform: 0.0, 1.0}"
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# In-test
|
|
103
|
+
self.scenario.init(
|
|
104
|
+
scenario=scenario_config,
|
|
105
|
+
seed=42
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Post-condition
|
|
109
|
+
resolved, _ = self.scenario.resolve()
|
|
110
|
+
value = resolved.uniform_value
|
|
111
|
+
self.assertIsInstance(value, (int, float))
|
|
112
|
+
self.assertGreaterEqual(value, 0.0)
|
|
113
|
+
self.assertLessEqual(value, 1.0)
|
|
114
|
+
|
|
115
|
+
def test_uniform_distribution_should_handle_size_parameter(self):
|
|
116
|
+
"""Test that uniform_1d distribution returns list."""
|
|
117
|
+
# Pre-condition
|
|
118
|
+
scenario_config = {
|
|
119
|
+
"uniform_array": "${uniform_1d: 0.0, 1.0}"
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
# In-test
|
|
123
|
+
self.scenario.init(
|
|
124
|
+
scenario=scenario_config,
|
|
125
|
+
seed=42
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Post-condition
|
|
129
|
+
resolved, _ = self.scenario.resolve()
|
|
130
|
+
value = resolved.uniform_array
|
|
131
|
+
# Convert to list if it's a ListConfig
|
|
132
|
+
value_list = list(value) if hasattr(value, '__iter__') and not isinstance(value, str) else [value]
|
|
133
|
+
self.assertGreaterEqual(len(value_list), 1)
|
|
134
|
+
for v in value_list:
|
|
135
|
+
self.assertGreaterEqual(v, 0.0)
|
|
136
|
+
self.assertLessEqual(v, 1.0)
|
|
137
|
+
|
|
138
|
+
def test_gaussian_distribution_should_resolve_correctly(self):
|
|
139
|
+
"""Test that gaussian distribution resolver works correctly."""
|
|
140
|
+
# Pre-condition
|
|
141
|
+
scenario_config = {
|
|
142
|
+
"gaussian_value": "${gaussian: 0.0, 1.0}"
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
# In-test
|
|
146
|
+
self.scenario.init(
|
|
147
|
+
scenario=scenario_config,
|
|
148
|
+
seed=42
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Post-condition
|
|
152
|
+
resolved, _ = self.scenario.resolve()
|
|
153
|
+
value = resolved.gaussian_value
|
|
154
|
+
self.assertIsInstance(value, (int, float))
|
|
155
|
+
|
|
156
|
+
def test_gaussian_distribution_should_handle_size_parameter(self):
|
|
157
|
+
"""Test that gaussian_1d distribution returns list."""
|
|
158
|
+
# Pre-condition
|
|
159
|
+
scenario_config = {
|
|
160
|
+
"gaussian_array": "${gaussian_1d: 0.0, 1.0}"
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
# In-test
|
|
164
|
+
self.scenario.init(
|
|
165
|
+
scenario=scenario_config,
|
|
166
|
+
seed=42
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Post-condition
|
|
170
|
+
resolved, _ = self.scenario.resolve()
|
|
171
|
+
value = resolved.gaussian_array
|
|
172
|
+
# Convert to list if it's a ListConfig
|
|
173
|
+
value_list = list(value) if hasattr(value, '__iter__') and not isinstance(value, str) else [value]
|
|
174
|
+
self.assertGreaterEqual(len(value_list), 1)
|
|
175
|
+
|
|
176
|
+
def test_bernoulli_distribution_should_resolve_correctly(self):
|
|
177
|
+
"""Test that bernoulli distribution resolver works correctly."""
|
|
178
|
+
# Pre-condition
|
|
179
|
+
scenario_config = {
|
|
180
|
+
"bernoulli_value": "${bernoulli: 0.5}"
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
# In-test
|
|
184
|
+
self.scenario.init(
|
|
185
|
+
scenario=scenario_config,
|
|
186
|
+
seed=42
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Post-condition
|
|
190
|
+
resolved, _ = self.scenario.resolve()
|
|
191
|
+
value = resolved.bernoulli_value
|
|
192
|
+
self.assertIn(value, [0, 1, True, False])
|
|
193
|
+
|
|
194
|
+
def test_resolve_should_regenerate_distribution_values(self):
|
|
195
|
+
"""Test that resolve() regenerates new values from distributions."""
|
|
196
|
+
# Pre-condition
|
|
197
|
+
scenario_config = {
|
|
198
|
+
"random_value": "${uniform: 0.0, 100.0}"
|
|
199
|
+
}
|
|
200
|
+
self.scenario.init(
|
|
201
|
+
scenario=scenario_config,
|
|
202
|
+
seed=None # No seed for randomness
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# In-test - First resolve
|
|
206
|
+
resolved1, _ = self.scenario.resolve()
|
|
207
|
+
value1 = resolved1.random_value
|
|
208
|
+
|
|
209
|
+
# Second resolve
|
|
210
|
+
resolved2, _ = self.scenario.resolve()
|
|
211
|
+
value2 = resolved2.random_value
|
|
212
|
+
|
|
213
|
+
# Post-condition
|
|
214
|
+
# Values should be different (statistically very unlikely to be same)
|
|
215
|
+
# Note: There's a tiny chance they could be equal, but extremely unlikely
|
|
216
|
+
self.assertIsInstance(value1, (int, float))
|
|
217
|
+
self.assertIsInstance(value2, (int, float))
|
|
218
|
+
|
|
219
|
+
def test_template_property_should_access_scenario_template(self):
|
|
220
|
+
"""Test that template property allows access to scenario values."""
|
|
221
|
+
# Pre-condition
|
|
222
|
+
scenario_config = {
|
|
223
|
+
"test_attribute": "test_value"
|
|
224
|
+
}
|
|
225
|
+
self.scenario.init(
|
|
226
|
+
scenario=scenario_config
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# In-test
|
|
230
|
+
value = self.scenario.template.test_attribute
|
|
231
|
+
|
|
232
|
+
# Post-condition
|
|
233
|
+
self.assertEqual(value, "test_value")
|
|
234
|
+
|
|
235
|
+
def test_template_should_contain_unresolved_distributions(self):
|
|
236
|
+
"""Test that template contains unresolved distribution strings."""
|
|
237
|
+
# Pre-condition
|
|
238
|
+
scenario_config = {
|
|
239
|
+
"test_key": "${uniform: 0.0, 1.0}"
|
|
240
|
+
}
|
|
241
|
+
self.scenario.init(
|
|
242
|
+
scenario=scenario_config
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# In-test
|
|
246
|
+
yaml_str = self.scenario.yaml
|
|
247
|
+
|
|
248
|
+
# Post-condition
|
|
249
|
+
self.assertIn("uniform", yaml_str)
|
|
250
|
+
|
|
251
|
+
def test_resolve_should_return_resolved_values(self):
|
|
252
|
+
"""Test that resolve() returns dict-style access to resolved values."""
|
|
253
|
+
# Pre-condition
|
|
254
|
+
scenario_config = {
|
|
255
|
+
"test_key": "test_value"
|
|
256
|
+
}
|
|
257
|
+
self.scenario.init(
|
|
258
|
+
scenario=scenario_config
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# In-test
|
|
262
|
+
resolved, _ = self.scenario.resolve()
|
|
263
|
+
value = resolved["test_key"]
|
|
264
|
+
|
|
265
|
+
# Post-condition
|
|
266
|
+
self.assertEqual(value, "test_value")
|
|
267
|
+
|
|
268
|
+
def test_resolve_should_raise_error_for_missing_key(self):
|
|
269
|
+
"""Test that resolve() result raises KeyError for missing keys."""
|
|
270
|
+
# Pre-condition
|
|
271
|
+
self.scenario.init(
|
|
272
|
+
scenario={}
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
# In-test & Post-condition
|
|
276
|
+
resolved, _ = self.scenario.resolve()
|
|
277
|
+
with self.assertRaises(KeyError):
|
|
278
|
+
_ = resolved["nonexistent_key"]
|
|
279
|
+
|
|
280
|
+
def test_get_final_size_should_handle_none_size_without_num_env(self):
|
|
281
|
+
"""Test _get_final_size with None size and no num_env."""
|
|
282
|
+
# Pre-condition
|
|
283
|
+
self.scenario.init(
|
|
284
|
+
scenario={}
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
# In-test
|
|
288
|
+
result = self.scenario._get_final_size(None)
|
|
289
|
+
|
|
290
|
+
# Post-condition
|
|
291
|
+
self.assertIsNone(result)
|
|
292
|
+
|
|
293
|
+
def test_get_final_size_should_handle_int_size_without_num_env(self):
|
|
294
|
+
"""Test _get_final_size with int size and no num_env."""
|
|
295
|
+
# Pre-condition
|
|
296
|
+
self.scenario.init(
|
|
297
|
+
scenario={}
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
# In-test
|
|
301
|
+
result = self.scenario._get_final_size(3)
|
|
302
|
+
|
|
303
|
+
# Post-condition
|
|
304
|
+
self.assertEqual(result, 3)
|
|
305
|
+
|
|
306
|
+
def test_get_final_size_should_handle_tuple_size_without_num_env(self):
|
|
307
|
+
"""Test _get_final_size with tuple size and no num_env."""
|
|
308
|
+
# Pre-condition
|
|
309
|
+
self.scenario.init(
|
|
310
|
+
scenario={}
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
# In-test
|
|
314
|
+
result = self.scenario._get_final_size((2, 3))
|
|
315
|
+
|
|
316
|
+
# Post-condition
|
|
317
|
+
self.assertEqual(result, (2, 3))
|
|
318
|
+
|
|
319
|
+
def test_get_final_size_should_handle_size_without_num_env(self):
|
|
320
|
+
"""Test _get_final_size with size but no num_env."""
|
|
321
|
+
# Pre-condition
|
|
322
|
+
self.scenario.init(
|
|
323
|
+
scenario={}
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
# In-test
|
|
327
|
+
result = self.scenario._get_final_size(5)
|
|
328
|
+
|
|
329
|
+
# Post-condition
|
|
330
|
+
self.assertEqual(result, 5)
|
|
331
|
+
|
|
332
|
+
def test_convert_to_python_should_handle_numpy_scalar(self):
|
|
333
|
+
"""Test _convert_to_python with numpy scalar."""
|
|
334
|
+
# Pre-condition
|
|
335
|
+
np_scalar = np.float64(3.14)
|
|
336
|
+
|
|
337
|
+
# In-test
|
|
338
|
+
result = Scenario._convert_to_python(np_scalar)
|
|
339
|
+
|
|
340
|
+
# Post-condition
|
|
341
|
+
self.assertIsInstance(result, float)
|
|
342
|
+
self.assertEqual(result, 3.14)
|
|
343
|
+
|
|
344
|
+
def test_convert_to_python_should_handle_numpy_array(self):
|
|
345
|
+
"""Test _convert_to_python with numpy array."""
|
|
346
|
+
# Pre-condition
|
|
347
|
+
np_array = np.array([1, 2, 3])
|
|
348
|
+
|
|
349
|
+
# In-test
|
|
350
|
+
result = Scenario._convert_to_python(np_array)
|
|
351
|
+
|
|
352
|
+
# Post-condition
|
|
353
|
+
self.assertIsInstance(result, list)
|
|
354
|
+
self.assertEqual(result, [1, 2, 3])
|
|
355
|
+
|
|
356
|
+
def test_convert_to_python_should_handle_zero_dim_array(self):
|
|
357
|
+
"""Test _convert_to_python with 0-dimensional numpy array."""
|
|
358
|
+
# Pre-condition
|
|
359
|
+
np_zero_dim = np.array(42)
|
|
360
|
+
|
|
361
|
+
# In-test
|
|
362
|
+
result = Scenario._convert_to_python(np_zero_dim)
|
|
363
|
+
|
|
364
|
+
# Post-condition
|
|
365
|
+
self.assertIsInstance(result, int)
|
|
366
|
+
self.assertEqual(result, 42)
|
|
367
|
+
|
|
368
|
+
def test_convert_to_python_should_handle_regular_python_types(self):
|
|
369
|
+
"""Test _convert_to_python with regular Python types."""
|
|
370
|
+
# Pre-condition
|
|
371
|
+
regular_values = [42, 3.14, "string", [1, 2, 3], {"key": "value"}]
|
|
372
|
+
|
|
373
|
+
# In-test & Post-condition
|
|
374
|
+
for value in regular_values:
|
|
375
|
+
result = Scenario._convert_to_python(value)
|
|
376
|
+
self.assertEqual(result, value)
|
|
377
|
+
|
|
378
|
+
def test_get_node_path_should_find_simple_key(self):
|
|
379
|
+
"""Test _get_node_path with simple dictionary key."""
|
|
380
|
+
# Pre-condition
|
|
381
|
+
root = {"key1": "target_node", "key2": "other"}
|
|
382
|
+
self.scenario.init(
|
|
383
|
+
scenario={}
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
# In-test
|
|
387
|
+
path = self.scenario._get_node_path(root, "target_node")
|
|
388
|
+
|
|
389
|
+
# Post-condition
|
|
390
|
+
self.assertEqual(path, "key1")
|
|
391
|
+
|
|
392
|
+
def test_get_node_path_should_find_nested_key(self):
|
|
393
|
+
"""Test _get_node_path with nested dictionary."""
|
|
394
|
+
# Pre-condition
|
|
395
|
+
root = {"level1": {"level2": "target_node"}}
|
|
396
|
+
self.scenario.init(
|
|
397
|
+
scenario={}
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
# In-test
|
|
401
|
+
path = self.scenario._get_node_path(root, "target_node")
|
|
402
|
+
|
|
403
|
+
# Post-condition
|
|
404
|
+
self.assertEqual(path, "level1.level2")
|
|
405
|
+
|
|
406
|
+
def test_get_node_path_should_find_in_list(self):
|
|
407
|
+
"""Test _get_node_path with list containing target."""
|
|
408
|
+
# Pre-condition
|
|
409
|
+
root = {"key": ["item1", "target_node", "item3"]}
|
|
410
|
+
self.scenario.init(
|
|
411
|
+
scenario={}
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
# In-test
|
|
415
|
+
path = self.scenario._get_node_path(root, "target_node")
|
|
416
|
+
|
|
417
|
+
# Post-condition
|
|
418
|
+
self.assertEqual(path, "key[1]")
|
|
419
|
+
|
|
420
|
+
def test_get_node_path_should_return_empty_for_missing_node(self):
|
|
421
|
+
"""Test _get_node_path returns empty string when node not found."""
|
|
422
|
+
# Pre-condition
|
|
423
|
+
root = {"key": "value"}
|
|
424
|
+
self.scenario.init(
|
|
425
|
+
scenario={}
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
# In-test
|
|
429
|
+
path = self.scenario._get_node_path(root, "nonexistent")
|
|
430
|
+
|
|
431
|
+
# Post-condition
|
|
432
|
+
self.assertEqual(path, "")
|
|
433
|
+
|
|
434
|
+
def test_template_property_should_return_scenario_template(self):
|
|
435
|
+
"""Test that template property returns the scenario template."""
|
|
436
|
+
# Pre-condition
|
|
437
|
+
scenario_config = {"key": "value"}
|
|
438
|
+
self.scenario.init(
|
|
439
|
+
scenario=scenario_config
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
# In-test
|
|
443
|
+
template = self.scenario.template
|
|
444
|
+
|
|
445
|
+
# Post-condition
|
|
446
|
+
self.assertIsNotNone(template)
|
|
447
|
+
self.assertEqual(template.key, "value")
|
|
448
|
+
|
|
449
|
+
def test_resolve_should_return_resolved_scenario(self):
|
|
450
|
+
"""Test that resolve() returns the resolved scenario."""
|
|
451
|
+
# Pre-condition
|
|
452
|
+
scenario_config = {"key": "value"}
|
|
453
|
+
self.scenario.init(
|
|
454
|
+
scenario=scenario_config
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
# In-test
|
|
458
|
+
resolved, _ = self.scenario.resolve()
|
|
459
|
+
|
|
460
|
+
# Post-condition
|
|
461
|
+
self.assertIsNotNone(resolved)
|
|
462
|
+
self.assertEqual(resolved.key, "value")
|
|
463
|
+
|
|
464
|
+
def test_yaml_property_should_return_yaml_representation(self):
|
|
465
|
+
"""Test that yaml property returns YAML string."""
|
|
466
|
+
# Pre-condition
|
|
467
|
+
scenario_config = {"key": "value"}
|
|
468
|
+
self.scenario.init(
|
|
469
|
+
scenario=scenario_config
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
# In-test
|
|
473
|
+
yaml_str = self.scenario.yaml
|
|
474
|
+
|
|
475
|
+
# Post-condition
|
|
476
|
+
self.assertIsInstance(yaml_str, str)
|
|
477
|
+
self.assertIn("key:", yaml_str)
|
|
478
|
+
self.assertIn("value", yaml_str)
|
|
479
|
+
|
|
480
|
+
def test_resolve_returns_episode_vals(self):
|
|
481
|
+
"""Test that resolve() returns episode values for distributions."""
|
|
482
|
+
# Pre-condition
|
|
483
|
+
scenario_config = {
|
|
484
|
+
"dist_value": "${uniform: 0.0, 1.0}"
|
|
485
|
+
}
|
|
486
|
+
self.scenario.init(
|
|
487
|
+
scenario=scenario_config,
|
|
488
|
+
seed=42
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
# In-test
|
|
492
|
+
resolved, episode_vals = self.scenario.resolve()
|
|
493
|
+
|
|
494
|
+
# Post-condition
|
|
495
|
+
# Verify resolved scenario has the value
|
|
496
|
+
self.assertIsNotNone(resolved.dist_value)
|
|
497
|
+
# Verify episode_vals dict contains the distribution samples
|
|
498
|
+
self.assertGreater(len(episode_vals), 0)
|
|
499
|
+
|
|
500
|
+
def test_nested_scenario_access_should_work(self):
|
|
501
|
+
"""Test accessing deeply nested scenario values."""
|
|
502
|
+
# Pre-condition
|
|
503
|
+
scenario_config = {
|
|
504
|
+
"level1": {
|
|
505
|
+
"level2": {
|
|
506
|
+
"level3": "deep_value"
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
self.scenario.init(
|
|
511
|
+
scenario=scenario_config
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
# In-test
|
|
515
|
+
resolved, _ = self.scenario.resolve()
|
|
516
|
+
value = resolved.level1.level2.level3
|
|
517
|
+
|
|
518
|
+
# Post-condition
|
|
519
|
+
self.assertEqual(value, "deep_value")
|
|
520
|
+
|
|
521
|
+
def test_multiple_distributions_should_work_together(self):
|
|
522
|
+
"""Test scenario with multiple different distributions."""
|
|
523
|
+
# Pre-condition
|
|
524
|
+
scenario_config = {
|
|
525
|
+
"uniform_val": "${uniform: 0.0, 1.0}",
|
|
526
|
+
"gaussian_val": "${gaussian: 0.0, 1.0}",
|
|
527
|
+
"bernoulli_val": "${bernoulli: 0.5}"
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
# In-test
|
|
531
|
+
self.scenario.init(
|
|
532
|
+
scenario=scenario_config,
|
|
533
|
+
seed=42
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
# Post-condition
|
|
537
|
+
resolved, _ = self.scenario.resolve()
|
|
538
|
+
self.assertIsInstance(resolved.uniform_val, (int, float))
|
|
539
|
+
self.assertIsInstance(resolved.gaussian_val, (int, float))
|
|
540
|
+
self.assertIn(resolved.bernoulli_val, [0, 1, True, False])
|
|
541
|
+
|
|
542
|
+
def test_clear_resolvers_should_clear_dist_cache(self):
|
|
543
|
+
"""Test that _clear_resolvers clears the distribution cache."""
|
|
544
|
+
# Pre-condition
|
|
545
|
+
scenario_config = {"value": "${uniform: 0.0, 1.0}"}
|
|
546
|
+
self.scenario.init(
|
|
547
|
+
scenario=scenario_config,
|
|
548
|
+
seed=42
|
|
549
|
+
)
|
|
550
|
+
_ = self.scenario.resolve() # Trigger cache population
|
|
551
|
+
|
|
552
|
+
# In-test
|
|
553
|
+
self.scenario._clear_resolvers()
|
|
554
|
+
|
|
555
|
+
# Post-condition
|
|
556
|
+
self.assertEqual(len(Scenario.dist_cache), 0)
|
|
557
|
+
|
|
558
|
+
def test_main_script_scenario_should_initialize_with_nested_structure(self):
|
|
559
|
+
"""Test scenario initialization matching the __main__ script example."""
|
|
560
|
+
# Pre-condition
|
|
561
|
+
scenario_config = {
|
|
562
|
+
"scenario": {
|
|
563
|
+
"scenario_id": "scenario_1",
|
|
564
|
+
"cup_x": "${uniform: 0.7, 1.5}",
|
|
565
|
+
"cup_y": "${uniform: 0.3, 0.7}",
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
# In-test
|
|
570
|
+
self.scenario.init(
|
|
571
|
+
scenario=scenario_config,
|
|
572
|
+
seed=42
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
# Post-condition
|
|
576
|
+
# Verify scenario structure exists
|
|
577
|
+
resolved, _ = self.scenario.resolve()
|
|
578
|
+
self.assertIsNotNone(resolved.scenario)
|
|
579
|
+
self.assertEqual(resolved.scenario.scenario_id, "scenario_1")
|
|
580
|
+
|
|
581
|
+
# Verify cup_x and cup_y are resolved
|
|
582
|
+
cup_x = resolved.scenario.cup_x
|
|
583
|
+
cup_y = resolved.scenario.cup_y
|
|
584
|
+
|
|
585
|
+
# Verify values are in expected ranges
|
|
586
|
+
self.assertGreaterEqual(cup_x, 0.7)
|
|
587
|
+
self.assertLessEqual(cup_x, 1.5)
|
|
588
|
+
self.assertGreaterEqual(cup_y, 0.3)
|
|
589
|
+
self.assertLessEqual(cup_y, 0.7)
|
|
590
|
+
|
|
591
|
+
def test_main_script_scenario_should_allow_both_access_methods(self):
|
|
592
|
+
"""Test that both attribute and dict access work as shown in __main__ script."""
|
|
593
|
+
# Pre-condition
|
|
594
|
+
scenario_config = {
|
|
595
|
+
"scenario": {
|
|
596
|
+
"scenario_id": "scenario_1",
|
|
597
|
+
"cup_x": "${uniform: 0.7, 1.5}",
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
# In-test
|
|
602
|
+
self.scenario.init(
|
|
603
|
+
scenario=scenario_config,
|
|
604
|
+
seed=42
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
# Post-condition
|
|
608
|
+
# Both access methods should return the same value
|
|
609
|
+
resolved, _ = self.scenario.resolve()
|
|
610
|
+
cup_x_attr = resolved.scenario.cup_x
|
|
611
|
+
cup_x_dict = resolved["scenario"].cup_x
|
|
612
|
+
|
|
613
|
+
self.assertEqual(cup_x_attr, cup_x_dict)
|
|
614
|
+
|
|
615
|
+
def test_main_script_scenario_should_regenerate_on_resolve(self):
|
|
616
|
+
"""Test that resolve regenerates values as shown in __main__ script."""
|
|
617
|
+
# Pre-condition
|
|
618
|
+
scenario_config = {
|
|
619
|
+
"scenario": {
|
|
620
|
+
"cup_x": "${uniform: 0.7, 1.5}",
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
self.scenario.init(
|
|
624
|
+
scenario=scenario_config,
|
|
625
|
+
seed=None # No seed for random values
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
resolved1, _ = self.scenario.resolve()
|
|
629
|
+
first_cup_x = resolved1.scenario.cup_x
|
|
630
|
+
|
|
631
|
+
# In-test
|
|
632
|
+
resolved2, _ = self.scenario.resolve()
|
|
633
|
+
second_cup_x = resolved2.scenario.cup_x
|
|
634
|
+
|
|
635
|
+
# Post-condition
|
|
636
|
+
# Values should be in valid range
|
|
637
|
+
self.assertGreaterEqual(first_cup_x, 0.7)
|
|
638
|
+
self.assertLessEqual(first_cup_x, 1.5)
|
|
639
|
+
self.assertGreaterEqual(second_cup_x, 0.7)
|
|
640
|
+
self.assertLessEqual(second_cup_x, 1.5)
|
|
641
|
+
|
|
642
|
+
def test_main_script_scenario_should_convert_to_numpy_array(self):
|
|
643
|
+
"""Test that scenario values can be converted to numpy arrays."""
|
|
644
|
+
# Pre-condition
|
|
645
|
+
scenario_config = {
|
|
646
|
+
"scenario": {
|
|
647
|
+
"cup_x": "${uniform_1d: 0.7, 1.5}",
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
self.scenario.init(
|
|
651
|
+
scenario=scenario_config,
|
|
652
|
+
seed=42
|
|
653
|
+
)
|
|
654
|
+
|
|
655
|
+
# In-test
|
|
656
|
+
resolved, _ = self.scenario.resolve()
|
|
657
|
+
cup_x = resolved.scenario.cup_x
|
|
658
|
+
np_array = np.array(cup_x)
|
|
659
|
+
|
|
660
|
+
# Post-condition
|
|
661
|
+
self.assertIsInstance(np_array, np.ndarray)
|
|
662
|
+
|
|
663
|
+
# Verify values are in expected range
|
|
664
|
+
for val in np.atleast_1d(np_array):
|
|
665
|
+
self.assertGreaterEqual(val, 0.7)
|
|
666
|
+
self.assertLessEqual(val, 1.5)
|
|
667
|
+
|
|
668
|
+
def test_main_script_scenario_should_produce_valid_yaml(self):
|
|
669
|
+
"""Test that scenario.yaml returns valid YAML string."""
|
|
670
|
+
# Pre-condition
|
|
671
|
+
scenario_config = {
|
|
672
|
+
"scenario": {
|
|
673
|
+
"scenario_id": "scenario_1",
|
|
674
|
+
"cup_x": "${uniform: 0.7, 1.5}",
|
|
675
|
+
"cup_y": "${uniform: 0.3, 0.7}",
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
self.scenario.init(
|
|
679
|
+
scenario=scenario_config,
|
|
680
|
+
seed=42
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
# In-test
|
|
684
|
+
yaml_str = self.scenario.yaml
|
|
685
|
+
|
|
686
|
+
# Post-condition
|
|
687
|
+
self.assertIsInstance(yaml_str, str)
|
|
688
|
+
self.assertIn("scenario:", yaml_str)
|
|
689
|
+
self.assertIn("scenario_id:", yaml_str)
|
|
690
|
+
self.assertIn("scenario_1", yaml_str)
|
|
691
|
+
self.assertIn("cup_x:", yaml_str)
|
|
692
|
+
self.assertIn("cup_y:", yaml_str)
|
|
693
|
+
|
|
694
|
+
def test_main_script_scenario_should_handle_multiple_resolves(self):
|
|
695
|
+
"""Test multiple resolve calls as shown in __main__ script."""
|
|
696
|
+
# Pre-condition
|
|
697
|
+
scenario_config = {
|
|
698
|
+
"scenario": {
|
|
699
|
+
"cup_x": "${uniform: 0.7, 1.5}",
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
self.scenario.init(
|
|
703
|
+
scenario=scenario_config,
|
|
704
|
+
seed=42
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
resolved1, _ = self.scenario.resolve()
|
|
708
|
+
first_value = resolved1.scenario.cup_x
|
|
709
|
+
|
|
710
|
+
# In-test - First resolve
|
|
711
|
+
resolved2, _ = self.scenario.resolve()
|
|
712
|
+
second_value = resolved2.scenario.cup_x
|
|
713
|
+
|
|
714
|
+
# In-test - Second resolve
|
|
715
|
+
resolved3, _ = self.scenario.resolve()
|
|
716
|
+
third_value = resolved3.scenario.cup_x
|
|
717
|
+
|
|
718
|
+
# Post-condition
|
|
719
|
+
# All values should be in range
|
|
720
|
+
for val in [first_value, second_value, third_value]:
|
|
721
|
+
self.assertGreaterEqual(val, 0.7)
|
|
722
|
+
self.assertLessEqual(val, 1.5)
|
|
723
|
+
|
|
724
|
+
def test_main_script_scenario_should_reinitialize_with_none(self):
|
|
725
|
+
"""Test reinitializing scenario with None as shown in __main__ script."""
|
|
726
|
+
# Pre-condition
|
|
727
|
+
scenario_config = {
|
|
728
|
+
"scenario": {
|
|
729
|
+
"cup_x": "${uniform: 0.7, 1.5}",
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
self.scenario.init(
|
|
733
|
+
scenario=scenario_config,
|
|
734
|
+
seed=42
|
|
735
|
+
)
|
|
736
|
+
|
|
737
|
+
# Verify initial scenario has content
|
|
738
|
+
first_yaml = self.scenario.yaml
|
|
739
|
+
self.assertIn("cup_x:", first_yaml)
|
|
740
|
+
|
|
741
|
+
# In-test - Reinitialize with None
|
|
742
|
+
self.scenario.init(
|
|
743
|
+
scenario=None,
|
|
744
|
+
seed=42
|
|
745
|
+
)
|
|
746
|
+
|
|
747
|
+
# Post-condition
|
|
748
|
+
# Should have an empty scenario
|
|
749
|
+
second_yaml = self.scenario.yaml
|
|
750
|
+
self.assertEqual(second_yaml.strip(), "{}")
|
|
751
|
+
|
|
752
|
+
def test_main_script_scenario_should_handle_seed_consistency(self):
|
|
753
|
+
"""Test that same seed produces consistent results across resolves."""
|
|
754
|
+
# Pre-condition
|
|
755
|
+
scenario_config = {
|
|
756
|
+
"scenario": {
|
|
757
|
+
"cup_x": "${uniform: 0.7, 1.5}",
|
|
758
|
+
"cup_y": "${uniform: 0.3, 0.7}",
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
# Create first scenario with seed
|
|
763
|
+
scenario1 = Scenario()
|
|
764
|
+
scenario1.init(
|
|
765
|
+
scenario=scenario_config,
|
|
766
|
+
seed=42
|
|
767
|
+
)
|
|
768
|
+
resolved1, _ = scenario1.resolve()
|
|
769
|
+
values1_x = resolved1.scenario.cup_x
|
|
770
|
+
values1_y = resolved1.scenario.cup_y
|
|
771
|
+
|
|
772
|
+
# Create second scenario with same seed
|
|
773
|
+
scenario2 = Scenario()
|
|
774
|
+
scenario2.init(
|
|
775
|
+
scenario=scenario_config,
|
|
776
|
+
seed=42
|
|
777
|
+
)
|
|
778
|
+
resolved2, _ = scenario2.resolve()
|
|
779
|
+
values2_x = resolved2.scenario.cup_x
|
|
780
|
+
values2_y = resolved2.scenario.cup_y
|
|
781
|
+
|
|
782
|
+
# Post-condition
|
|
783
|
+
self.assertEqual(values1_x, values2_x)
|
|
784
|
+
self.assertEqual(values1_y, values2_y)
|
|
785
|
+
|
|
786
|
+
# Cleanup
|
|
787
|
+
scenario1._clear_resolvers()
|
|
788
|
+
scenario2._clear_resolvers()
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
if __name__ == "__main__":
|
|
792
|
+
unittest.main()
|