Skip to content

Commit 52c035c

Browse files
committed
code clean-up
1 parent b7f818d commit 52c035c

4 files changed

Lines changed: 73 additions & 8 deletions

File tree

isaaclab_arena/relations/layout_pool_serialization.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ def write_pool_document(path: Path, document: PoolDocument) -> None:
114114
path.parent.mkdir(parents=True, exist_ok=True)
115115
tmp_path = path.with_name(f"{path.name}.tmp")
116116
try:
117-
tmp_path.write_text(payload)
117+
tmp_path.write_text(payload, encoding="utf-8")
118118
os.replace(tmp_path, path)
119119
except OSError:
120120
tmp_path.unlink(missing_ok=True)
@@ -129,7 +129,7 @@ def read_pool_document(path: Path) -> PoolDocument:
129129
"""
130130
assert path.is_file(), f"Layout pool file not found: {path}"
131131
try:
132-
data = json.loads(path.read_text())
132+
data = json.loads(path.read_text(encoding="utf-8"))
133133
except json.JSONDecodeError as exc:
134134
raise ValueError(f"Layout pool file is not valid JSON: {path}") from exc
135135
return PoolDocument.from_dict(data, path)

isaaclab_arena/relations/pooled_object_placer.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,7 @@ def sample_with_replacement(self, count: int) -> list[PlacementResult]:
483483
# Reusable layouts are interchangeable: draw each slot from the flattened pool, with the
484484
# per-env RNG only selecting which stream the slot draws from.
485485
all_layouts = [layout for pool in self._env_pools for layout in pool.layouts]
486+
assert all_layouts, "No accepted layouts to sample from."
486487
return [self._draw(self._env_rngs[i % self._num_envs], all_layouts) for i in range(count)]
487488

488489
@staticmethod
@@ -525,6 +526,22 @@ def total_remaining(self) -> int:
525526
"""Total unread layouts across all env pools."""
526527
return self._total_available()
527528

529+
@property
530+
def stored_layouts(self) -> tuple[tuple[PlacementResult, ...], ...]:
531+
"""Read-only view of every stored layout, grouped by absolute env id.
532+
533+
Returns one inner tuple per env (outer length num_envs), each holding that env's stored
534+
PlacementResults in pool order, including already-consumed ones. The grouping is meaningful
535+
for env-specific layouts (see requires_env_indexed_layouts) and an arbitrary partition for
536+
reusable ones, which can be flattened.
537+
538+
The tuples are a snapshot (later refills do not appear), but the PlacementResult objects are
539+
live, so a post-pool check (e.g. a simulation collision test) records its outcome on a layout
540+
in place via result.validation.with_check(...) and accepts() then reflects it. Resampling is
541+
left to the caller via the existing sample_* methods.
542+
"""
543+
return tuple(tuple(pooled.result for pooled in pool.layouts) for pool in self._env_pools)
544+
528545
# ------------------------------------------------------------------
529546
# Persistence: save/load solved layouts to reuse poses without re-solving
530547
# ------------------------------------------------------------------
@@ -565,8 +582,9 @@ def load(
565582
env-count/heterogeneity/object-name mismatches fail loudly (see layout_pool_serialization
566583
for the structural checks). The saved placement_seed is restored (placer_params.placement_seed is
567584
ignored for seeding) so sampling matches the saved run; refill offset is not persisted, so
568-
a refill restarts from the first solve batch. pool_size becomes the loaded layout count,
569-
governing refill size.
585+
a refill restarts from the first solve batch. pool_size becomes the total loaded layout count
586+
across all envs (not per-env), so a multi-env refill batches larger than the original
587+
per-batch pool_size; harmless because refills on a loaded pool are rare.
570588
"""
571589
path = Path(path)
572590
document = read_pool_document(path)

isaaclab_arena/tests/test_layout_pool_serialization.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
"""Unit tests for the pure layout-pool JSON serialization helpers (no simulation)."""
77

8+
from pathlib import Path
9+
810
import pytest
911

1012
from isaaclab_arena.relations.layout_pool_serialization import (
@@ -165,6 +167,21 @@ def test_write_pool_document_rejects_non_finite_leaving_no_files(tmp_path):
165167
assert not path.with_name(f"{path.name}.tmp").exists()
166168

167169

170+
def test_write_pool_document_cleans_up_tmp_on_os_error(tmp_path, monkeypatch):
171+
path = tmp_path / "pool.json"
172+
document = PoolDocument.from_dict(_pool_dict(num_envs=1), path=path)
173+
174+
def fail_mid_write(self, *args, **kwargs):
175+
self.write_bytes(b"partial") # leave an orphan .tmp, then fail like a full disk
176+
raise OSError("No space left on device")
177+
178+
monkeypatch.setattr(Path, "write_text", fail_mid_write)
179+
with pytest.raises(OSError, match="No space left on device"):
180+
write_pool_document(path, document)
181+
assert not path.exists()
182+
assert not path.with_name(f"{path.name}.tmp").exists()
183+
184+
168185
def test_read_pool_document_missing_file(tmp_path):
169186
with pytest.raises(AssertionError, match="not found"):
170187
read_pool_document(tmp_path / "missing.json")

isaaclab_arena/tests/test_object_placer_reproducibility.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,39 @@ def test_pooled_placer_homogeneous_stored_layouts_have_distinct_positions_dicts(
477477
), f"Layouts {i} and {j} share the same positions dict reference"
478478

479479

480+
def test_pooled_placer_stored_layouts_groups_live_results_by_env():
481+
"""stored_layouts must return a per-env snapshot of the live stored PlacementResults."""
482+
num_envs = 3
483+
solver_params = RelationSolverParams(max_iters=50)
484+
placer_params = ObjectPlacerParams(placement_seed=42, solver_params=solver_params, apply_positions_to_objects=False)
485+
486+
pool = PooledObjectPlacer(
487+
objects=list(_create_test_objects()), placer_params=placer_params, pool_size=12, num_envs=num_envs
488+
)
489+
490+
grouped = pool.stored_layouts
491+
assert len(grouped) == num_envs
492+
assert all(isinstance(env_layouts, tuple) for env_layouts in grouped)
493+
# Each returned result is the same live object the pool stores (no copy), grouped per env.
494+
for cur_env, env_layouts in enumerate(grouped):
495+
assert list(env_layouts) == [pooled.result for pooled in pool._env_pools[cur_env].layouts]
496+
497+
498+
def test_pooled_placer_stored_layouts_post_validation_flows_into_accepts():
499+
"""Enriching a stored layout's report in place (the post-validation use case) must change accepts()."""
500+
solver_params = RelationSolverParams(max_iters=50)
501+
placer_params = ObjectPlacerParams(placement_seed=42, solver_params=solver_params, apply_positions_to_objects=False)
502+
503+
pool = PooledObjectPlacer(objects=list(_create_test_objects()), placer_params=placer_params, pool_size=4)
504+
result = pool.stored_layouts[0][0]
505+
assert pool.accepts(result)
506+
507+
# Record a failing post-pool check (e.g. a simulation collision test) on the live layout.
508+
result.validation = result.validation.with_check("sim_collision_free", False)
509+
assert "sim_collision_free" in result.validation.failed_checks
510+
assert not pool.accepts(result)
511+
512+
480513
def test_pooled_placer_homogeneous_sample_without_replacement_count_exceeds_pool_size():
481514
"""sample_without_replacement(count) where count > pool_size must solve a larger batch in one shot."""
482515
solver_params = RelationSolverParams(max_iters=50)
@@ -714,10 +747,7 @@ def test_pooled_placer_save_load_round_trip_preserves_layouts(tmp_path):
714747

715748

716749
def test_pooled_placer_save_load_round_trip_multi_env_homogeneous(tmp_path):
717-
"""A multi-env homogeneous pool's per-env layouts survive the round trip intact.
718-
719-
Env-specific (variant) correctness is covered by the heterogeneous round-trip test.
720-
"""
750+
"""A multi-env homogeneous pool's per-env layouts survive the round trip (variants covered separately)."""
721751
num_envs = 3
722752
placer_params = _make_seeded_params()
723753
pool = PooledObjectPlacer(

0 commit comments

Comments
 (0)