Skip to content

Add standalone physics settle check over pooled layouts#769

Open
xyao-nv wants to merge 16 commits into
mainfrom
xyao/dev/add_physics_validation
Open

Add standalone physics settle check over pooled layouts#769
xyao-nv wants to merge 16 commits into
mainfrom
xyao/dev/add_physics_validation

Conversation

@xyao-nv

@xyao-nv xyao-nv commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

When a solver returns a pooled layouts, there are 2 types of validations. One is analytical, fast, does not require SimApp, e.g. check no overlapping & on relation (existing). The other involves stepping physics, slow but more closely reflects how end user consumes, e.g. physics stability.
In an ideal world, placement_enev shall only use valid layout everytime thru env.reset()

Detailed description

Why

Agentic Env Gen requires a post-validation step to rule out envs with unstable physics, and re-trigger generation if nothing valid. It has to be automatic.

How

python isaaclab_arena/scripts/run_placement_pool_validation.py (--viz kit --render) --num_envs 10 pick_and_place_maple_table
The standalone script checks if physics settled by advancing physics only, over the pool of layouts returned by the solver, and report results.

What

  1. Refactored existing validation:
  • added a PlacementValidationChecklist data class to contain each individual validation check results;
  • included a required/optional validation failures to let user decide which optimized layouts shall be selected as the best-ranked.
  1. Added physics_settle.py to execute physics stepping without rendering, and check linear_vel, and ang_vel if below thresholds.
  2. Added placement_pool_validation.py orchestras how many episodes over how many envs needed for the fixed pool of layouts
  3. Added user-interface run_placement_pool_validation.py to execute the check

Next

Add integration with Pool filling and lookup table.
Ideal pipeline:
Each time pool is filled (after solver, before placement event) -> [Optional] run physics check for all env's pool unvalidated layouts & log it down -> Select (valid) one to load in placement events

@isaaclab-review-bot isaaclab-review-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: Add physics settle validation check to PooledObjectSolver

Good feature direction — gating layouts on physical settling is the right post-validation strategy for agentic env gen. The PlacementValidationChecklist refactor cleanly generalizes validation, and the failure cache with layout-signature invalidation is well-thought-out. A few issues need attention before merging:


🔴 Critical: CLI argument --enable_physics_settle_check is always True

isaaclab_arena/cli/isaaclab_arena_cli.py

arena_group.add_argument(
    "--enable_physics_settle_check",
    action="store_true",
    default=True,
    ...
)

action="store_true" combined with default=True means the flag is always True — there's no way for the user to disable it. store_true only sets the value to True when the flag is present; since the default is already True, the flag is a no-op.

Fix: Use BooleanOptionalAction (Python 3.9+) to get both --enable_physics_settle_check and --no-enable_physics_settle_check:

arena_group.add_argument(
    "--enable_physics_settle_check",
    action=argparse.BooleanOptionalAction,
    default=True,
    help=...,
)

🔴 Critical: Wrong TYPE_CHECKING import in placement_events.py

isaaclab_arena/relations/placement_events.py:19

if TYPE_CHECKING:
    from isaaclab_arena.assets.object_base import ObjectBase
    from isaaclab_arena.relations.placement_validation import PlacementResult

PlacementResult lives in isaaclab_arena.relations.placement_result, not placement_validation (which only defines PlacementValidationChecklist). This won't crash at runtime (guarded by TYPE_CHECKING), but type checkers and IDE navigation will break on the result: PlacementResult annotation in write_layout_to_sim.

Fix:

from isaaclab_arena.relations.placement_result import PlacementResult

🟡 Warning: PhysicsSettleFailureCache._load can crash on corrupted JSON

isaaclab_arena/relations/physics_settle_failure_cache.py:99-108

def _load(self) -> set[int]:
    path = self._path()
    if path is None or not os.path.exists(path):
        return set()
    with open(path) as cache_file:
        data = json.load(cache_file)
    ...

A corrupted or truncated cache file (e.g. from a crash mid-write, disk full, or manual editing) will raise json.JSONDecodeError and crash pool construction with a confusing traceback. Since this is an optimization cache (not critical state), the safe behavior is to log a warning and return an empty set.

Suggested fix:

def _load(self) -> set[int]:
    path = self._path()
    if path is None or not os.path.exists(path):
        return set()
    try:
        with open(path) as cache_file:
            data = json.load(cache_file)
    except (json.JSONDecodeError, OSError):
        return set()
    ...

🟡 Warning: verify_in_sim_and_reselect may append duplicate entries to newly_failed

isaaclab_arena/relations/pooled_object_placer.py:600-620

In the retry loop, when an env fails to settle on attempt 0, the layout is appended to newly_failed. On attempt 1, a replacement is drawn and written, but if the same original layout object (already marked with physics_settled: False via add_checklist_item) is somehow still referenced, the "physics_settled" in checklist.checklist_items guard will use the cached False verdict and append it again. More practically: the current[env_id] is updated to the replacement after the first failure, but the original layout is already in newly_failed — so the replacement (if it also fails) gets appended too, which is correct. However, the if "physics_settled" in checklist.checklist_items branch re-reads the old verdict without re-stepping, which means a replacement layout that was previously cached as failed will short-circuit correctly. This logic is sound but fragile — a brief comment clarifying that current[env_id] is always the replacement at this point would help future readers.


🟡 Warning: _last_applied is never cleared between resets

isaaclab_arena/relations/pooled_object_placer.py:563-565

note_applied accumulates entries in _last_applied keyed by env_id. On subsequent resets, solve_and_place_objects calls note_applied again which overwrites the entry — so it's fine for the settle pass. But if some envs are not reset (partial reset), _last_applied still holds stale entries from prior resets. When verify_in_sim_and_reselect is called, it processes all entries in _last_applied, potentially re-stepping and re-checking envs that weren't just reset.

Suggestion: Clear _last_applied at the start of verify_in_sim_and_reselect (after copying current = dict(self._last_applied)) or only record applied layouts for the current reset batch.


🔵 Improvement: EnvLayoutPool.available is O(n) on every call

isaaclab_arena/relations/pooled_object_placer.py:37-40

@property
def available(self) -> int:
    return sum(
        1 for layout in self.layouts[self.cursor :] if not layout.validation_checklist.physics_settled_failed
    )

This scans all remaining layouts on every access. It's called from _available_per_env() which is called frequently during solve loops. For typical pool sizes (~100 layouts) this is fine, but if pools grow large or the check is called in tight loops, consider maintaining a counter decremented when a layout is marked as a settle failure.


🧪 Test Coverage

The PR includes solid test coverage:

  • test_physics_settle.py: End-to-end sim tests for the settle primitives (at-rest, colliding, parallel per-env) — good.
  • test_physics_settle_failure_cache.py: Persistence round-trip, invalidation (seed mismatch, signature mismatch), and signature determinism/sensitivity — thorough.
  • Existing tests updated to use the new PlacementValidationChecklist API — no regressions.

Gap: No unit test for verify_in_sim_and_reselect with the retry loop (e.g. mock that demonstrates re-selection when first layout fails and next one settles). The sim tests exercise the primitives, but the orchestration logic is only tested end-to-end via the sim. Consider a focused mock test for the retry/re-selection flow.


CI Status

Pre-commit is currently failing — likely a lint/formatting issue that needs a fix-up commit before merge.


Verdict: Minor fixes needed. The critical CLI bug and the wrong import should be fixed before merge. The cache robustness and _last_applied clearing are recommended improvements.


Update (e6d8178): Significant refactor addressing several issues from the initial review and restructuring the physics settle check architecture:

✅ Issues Addressed

  1. CLI default fixed--enable_physics_settle_check now defaults to False with action="store_true", which is the correct pattern (flag absent → disabled, flag present → enabled). This resolves the "always True" bug from the initial review.

  2. PhysicsSettleFailureCache removed entirely — The on-disk persistence cache (physics_settle_failure_cache.py) and its test file have been deleted. The approach was replaced by a simpler design: failed layouts are marked in-memory only via the PlacementValidationChecklist. This eliminates the corrupted-JSON crash risk and the layout-signature complexity flagged in the initial review.

  3. _last_applied is now a public propertynote_applied() was removed; callers write directly to pool.last_applied[env_id] = result. The stale-partial-reset concern remains (the dict is never cleared), but the logic is now in placement_events.py where the caller has full control.

  4. verify_in_sim_and_reselect moved out of PooledObjectPlacer — It's now a free function in placement_events.py, receiving the pool, env, and a new PhysicsSettleParams dataclass. This is a cleaner separation of concerns: the pool owns layout storage/sampling; the event module owns sim-stepping logic.

  5. newly_failed tracking removed — Since the cache is gone, there's no need to accumulate and persist failures. The logic now simply marks the checklist and moves on, removing the duplicate-append fragility.

🆕 New Architecture

  • PhysicsSettleParams (new dataclass): Holds num_steps, lin_vel_thresh, ang_vel_thresh, max_retries. Decoupled from ObjectPlacerParams which previously held these fields.
  • PlacementCheck enum (new StrEnum): Replaces raw string keys ("no_overlap", "on_relation", "physics_settled") with typed constants. Good for IDE support and typo prevention.
  • run_placement_physics_settle_check returns VecEnvObs | None — After the settle pass steps physics and potentially swaps layouts, the reset-time observation is stale. The function now recomputes and returns a fresh observation (including RTX sensor re-renders). The caller in policy_runner.py adopts it. This is a correctness fix for camera-based policies.
  • draw_replacement is now public — renamed from _draw_replacement since it's called from outside the class.

🟡 Remaining Concerns

  1. _last_applied stale-entry issue persists — The dict is still never cleared between resets. In partial-reset scenarios, verify_in_sim_and_reselect processes all entries including envs not just reset. The caller (policy_runner.py) invokes it once after env.reset(), so for full resets this is fine, but partial resets could re-verify stale layouts unnecessarily.

  2. PlacementResult import — The TYPE_CHECKING import in placement_events.py still imports PlacementResult from placement_validation (line in the diff shows from isaaclab_arena.relations.placement_validation import PlacementResult). This was flagged in the initial review and appears unchanged. (Needs verification — the diff shows the import block was restructured but the target module may still be wrong.)

  3. objects_settled logic flip — The condition was changed from not (lin > thresh or ang > thresh) to lin <= thresh and ang <= thresh. These are logically equivalent (De Morgan's law), so this is a readability improvement, not a bug fix. 👍

  4. New test test_settle_check_reselects_unstable_layout — Excellent addition! This directly addresses the test coverage gap identified in the initial review. It builds a real sim env, seeds an unstable layout (cracker box dropped from height), and verifies the settle pass re-selects a stable resting layout. This is the orchestration-level test that was missing.

🧪 Test Coverage Update

  • test_physics_settle_failure_cache.py removed (no longer needed since cache is deleted)
  • test_settle_check_reselects_unstable_layout added — covers the full retry/re-selection flow with live physics
  • test_placement_events.py updated — mock pools now expose last_applied: dict = {} instead of note_applied() method

Updated Verdict: Good progress. The refactor simplifies the architecture significantly (removing the cache, separating concerns, fixing the CLI default). The observation-refresh after settle is a meaningful correctness improvement. The TYPE_CHECKING import issue from the initial review should still be verified/fixed. Overall, closer to merge-ready.


Update (9078efe): Minor cleanup commit — no new issues.

Changes

  1. policy_runner.py — Added from __future__ import annotations and removed string-quoted type annotations ("PolicyBase"PolicyBase). This is a standard Python 3.10+ forward-reference pattern backported via the future import. Clean.

  2. test_relation_solver_interface.py — Introduced _fallback_layout() test helper that constructs PlacementResult with the new PlacementValidationChecklist API (using PlacementCheck.NO_OVERLAP: False to indicate failure). Replaced inline PlacementResult(success=False, ...) calls throughout the test file. This aligns the tests with the API changes from the previous commit — good housekeeping.

Assessment

Both changes are mechanical/cleanup in nature. No new logic, no new issues. Tests properly updated to match the new validation checklist interface. ✅

@cvolkcvolk

cvolkcvolk commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Thanks for putting this together! I read through it and wanted to share some thoughts, mostly around keeping the placement/solver layer sim-agnostic.

Main design point to keep the solver/placement code sim-free

We deliberately kept the solver and placement code (ObjectPlacer, PooledObjectPlacer, ...) free of any SimulationApp dependency. Both to keep its dependency surface lean and because that path is fast, whereas anything touching sim is comparatively expensive. I'd love to preserve that boundary here.

Conceptually PooledObjectPlacer, a pure pool/solver object, now owns verify_in_sim_and_reselect(env), which takes a live env and steps physics. The lazy imports hide that from the dependency graph, but the class has effectively grown sim-coupled.

Idea: move the settle/reselection loop into placement_events.py (which already imports isaaclab and owns the sim writes), and have it reach the pool only through a small public API. That keeps PooledObjectPlacer as sim-agnostic as it is today.

On the cross-run failure cache

PhysicsSettleFailureCache + compute_layout_signature are a lot of machinery for skipping a known-bad layout within a run...
Would physics_settled_failed be enough to cover our current blocking use cases? Consider keeping just the in-run skip and dropping the disk cache + signature for now. It might be cleaner to reintroduce it later behind an explicit, simpler check (once cross-run skipping turns out to matter for the env-gen loop)

Comment thread isaaclab_arena/cli/isaaclab_arena_cli.py Outdated
Comment thread isaaclab_arena/evaluation/policy_runner.py Outdated
placement_seed: int | None = None,
resolve_on_reset: bool | None = None,
random_yaw_init: bool = False,
physics_settle_cache_key: str | None = None,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may want to add more validation checks at the end, which would likely introduce more arguments to the function. I’m wondering whether we can design a clean and extensible interface so that users can easily understand the options, or at least define a well-structured argument list.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's scope out a design tgt and imple it in future MRs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good — scoping a design target first makes sense here. Happy to review the follow-up when it lands. 👍

Comment thread isaaclab_arena/utils/physics_settle.py Outdated
…_cache)

Strip the persistent disk cache from this branch: PhysicsSettleFailureCache,
compute_layout_signature, the physics_settle_cache_key plumbing, and the
_enqueue wrappers that pre-marked cached failures. The in-sim settle check
and live re-selection (verify_in_sim_and_reselect) remain.
Full cache implementation is preserved on branch xyao/dev/physcis_failure_cache.

Signed-off-by: Xinjie Yao <xyao@nvidia.com>
Comment thread isaaclab_arena/relations/physics_settle_failure_cache.py Outdated
Comment thread isaaclab_arena/relations/physics_settle_failure_cache.py Outdated
Comment thread isaaclab_arena/relations/placement_events.py Outdated
Comment thread isaaclab_arena/relations/placement_validation.py
@xyao-nv xyao-nv marked this pull request as ready for review June 9, 2026 23:47
@xyao-nv xyao-nv changed the title Add physics settle validation check to PooledObjectSolver Add physics settle validation check over pooled layouts into Placement Event Jun 9, 2026
@xyao-nv

xyao-nv commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator Author

@isaaclab-review-bot

@greptile-apps

greptile-apps Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a standalone physics-settle validation sweep over pooled placement layouts, refactors the placement validation model from a single bool flag to a structured PlacementValidationChecklist, and wires the full pipeline through a new CLI script.

  • PlacementValidationChecklist introduces required vs optional checks; geometric checks (NO_OVERLAP, ON_RELATION) gate layout success while the new PHYSICS_SETTLED check is stamped as optional so unstable layouts degrade gracefully without blocking selection.
  • validate_pool_layouts iterates every episode in every env's queue in parallel, writes poses with zero velocity, steps physics N substeps, then reads back object velocities to stamp PHYSICS_SETTLED — the mutation is in-place on the pool's stored PlacementResult objects.
  • All test suites and test helpers are updated to use validation_checklist= in place of the removed success= field; four new sim tests cover rest, collision, parallel-layout, and full pool-sweep scenarios.

Confidence Score: 5/5

The change is safe to merge; the new pool validation sweep is a pure addition and all existing placement paths are correctly updated to the new checklist model.

The core data-model refactor (bool → PlacementValidationChecklist) is applied consistently across all callers, tests, and helpers. The new physics-settle infrastructure is well-isolated and only mutates pool layouts in-place by design. Two minor latent issues were found: a bare dict subscript in pass_validation_checklist that would raise KeyError if required_items ever contains a key absent from checklist_items, and a silent truncation in validate_pool_layouts when the pool holds more env queues than the sim has envs. Neither is triggered by any current code path.

placement_validation.py (latent KeyError in pass_validation_checklist) and placement_pool_validation.py (silent pool truncation when pool size > sim env count).

Important Files Changed

Filename Overview
isaaclab_arena/relations/placement_validation.py New file introducing PlacementCheck enum and PlacementValidationChecklist dataclass; pass_validation_checklist has a latent KeyError when required_itemschecklist_items.keys() (no invariant enforced at construction).
isaaclab_arena/relations/placement_pool_validation.py New orchestration module for pool-level physics validation; silently truncates results when pool size exceeds sim env count; private helpers have incomplete type annotations.
isaaclab_arena/utils/physics_settle.py New sim-side primitives for stepping physics and reading per-env object velocities; uses wp.to_torch for Isaac Lab asset data, consistent with Isaac Lab's warp-backed data model.
isaaclab_arena/relations/placement_events.py Refactored to extract write_layout_to_sim, get_base_rotations, get_movable_object_names, and the new get_placement_pool accessor; loop body cleanly delegates to the extracted helpers.
isaaclab_arena/relations/placement_result.py Replaced success: bool field with validation_checklist: PlacementValidationChecklist; success is now a computed property delegating to pass_validation_checklist().
isaaclab_arena/relations/object_placer.py Replaced boolean is_valid with PlacementValidationChecklist; ranking now uses rank_required_and_optional_failures tuple for multi-tier sorting; verbose debug logging added for fallback layouts.
isaaclab_arena/relations/physics_settle_params.py New dataclass holding settle-check tuning knobs (num_steps, lin_vel_thresh, ang_vel_thresh); straightforward, no issues found.
isaaclab_arena/relations/pooled_object_placer.py Added objects property and layouts_per_env() introspection method to expose pool internals to the offline validator; changes are minimal and additive.
isaaclab_arena/scripts/run_placement_pool_validation.py New standalone script wiring CLI flags to validate_pool_layouts; CLI defaults match PhysicsSettleParams defaults; correct assertion on None return for non-pooled envs.
isaaclab_arena/tests/test_physics_settle.py Comprehensive sim tests covering rest/colliding/parallel/pool sweep scenarios; each test runs inside a subprocess to avoid SimApp singleton issues.
isaaclab_arena/environments/arena_env_builder.py Replaced hardcoded string "placement_reset" with the newly exported PLACEMENT_RESET_EVENT_NAME constant for consistency.
isaaclab_arena/evaluation/policy_runner.py Added from __future__ import annotations and cleaned up TYPE_CHECKING string-quoted annotations; call site reformatted; no functional change to rollout logic.
isaaclab_arena/tests/test_heterogeneous_placement.py Updated all PlacementResult constructions from success=bool to validation_checklist=_checklist(bool); consistent with the pattern in other updated test files.
isaaclab_arena/tests/test_placement_events.py Updated PlacementResult constructions to use validation_checklist; added a local _checklist helper mirroring the pattern in other test files.
isaaclab_arena/tests/test_relation_solver_interface.py Replaced inline PlacementResult(success=False, ...) calls with a shared _fallback_layout factory; reduces duplication and keeps test helpers consistent.
isaaclab_arena/tests/test_validate_placement.py Updated all _validate_placement assertions to call .pass_validation_checklist() on the returned PlacementValidationChecklist; no logic changes.

Sequence Diagram

sequenceDiagram
    participant Script as run_placement_pool_validation.py
    participant VPL as validate_pool_layouts()
    participant WLE as _write_layouts_per_episode()
    participant PS as physics_settle
    participant GB as _grade_settled_batch()
    participant CL as PlacementValidationChecklist

    Script->>VPL: validate_pool_layouts(env, settle_params)
    VPL->>VPL: get_placement_pool(env) → pool
    VPL->>VPL: pool.layouts_per_env() → layouts_per_env[env_id]

    loop episode_index in range(max_episodes)
        VPL->>WLE: _write_layouts_per_episode(env, layouts_per_env, episode_index)
        loop env_id with layout at episode_index
            WLE->>WLE: write_layout_to_sim(env, env_id, layout)
            Note over WLE: sets pose + zero velocity
        end
        WLE-->>VPL: batch [(env_id, PlacementResult)]

        VPL->>PS: step_physics(env, num_steps) [ALL envs]
        PS->>PS: sim.step() + scene.update() × num_steps

        VPL->>GB: _grade_settled_batch(env, batch)
        GB->>PS: objects_settled_per_episode(env, env_ids)
        PS-->>GB: [bool per env_id]
        loop (env_id, layout), settled
            GB->>CL: add_checklist_item(PHYSICS_SETTLED, settled)
        end
        GB-->>VPL: [(env_id, checklist)]
    end

    VPL-->>Script: [(env_id, episode_idx, checklist)]
    Script->>Script: log_validation_results(results)
Loading

Reviews (4): Last reviewed commit: "remove old" | Re-trigger Greptile

@isaaclab-review-bot

Copy link
Copy Markdown
Contributor

👋 @xyao-nv — I see 5 new commits since my last review (5d94431e6d8178). Here's a quick incremental pass:

Summary of Changes

Major refactor that lifts the physics-settle logic out of PooledObjectPlacer and into a standalone function in placement_events.py, drops the failure cache entirely, introduces a dedicated PhysicsSettleParams dataclass, and adopts a PlacementCheck enum for checklist keys.

What Looks Good ✅

  1. Separation of concernsPooledObjectPlacer is now purely a geometric pool/draw engine; the physics-based verification lives in placement_events.verify_in_sim_and_reselect. Much cleaner ownership boundaries.
  2. PhysicsSettleParams dataclass — well-documented, sensible defaults, and cleanly threaded from the CLI all the way down.
  3. PlacementCheck enum — eliminates stringly-typed checklist keys; compile-time typo protection.
  4. Observation refresh after settlerun_placement_physics_settle_check now returns the updated VecEnvObs so the caller doesn't start a rollout on stale obs. Good catch.
  5. Simplified velocity checklin_velocity <= thresh and ang_velocity <= thresh is clearer than the double-negation it replaced.
  6. Removing the failure cache — significantly reduces complexity; the bounded retry budget in max_retries handles the same concern more simply.

Minor Observations / Questions ❓

  1. Default enable_physics_settle_check=False in rollout_policy — previously it was True. Was this intentional (opt-in only via CLI flag)? Just want to confirm the default-off is desired so existing callers aren't surprised.
  2. last_applied is now a plain dict exposed as a property (placement_pool.last_applied[env_id] = result). Since external code mutates it directly, consider whether a setter method (or at least documenting the contract) would be safer long-term — though for now the two call-sites are well-understood.
  3. Typo in commit message: "udpate" → "update" (commits 7 & 10). Non-blocking, just cosmetic for history.
  4. Comment in physics_settle.py: "metric recoder" → "metric recorder".

Overall this is a clean refactor. The interface is simpler, responsibilities are better isolated, and the new test (_test_settle_check_reselects_unstable_layout) exercises the live-physics path end-to-end. LGTM on this iteration. 🚀

Comment thread isaaclab_arena/relations/placement_events.py
Comment on lines +43 to +52
def pass_validation_checklist(self, checklist_items: list[str] | None = None) -> bool:
"""Check whether the gating checks pass. Defaults to the required checks only.
Args:
checklist_items: checks that must pass. If None, only required checks gate (optional checks are ignored).
Returns:
True if all the considered checks pass, False otherwise
"""
if checklist_items is None:
checklist_items = self._required()
return all(self.checklist_items[item] for item in checklist_items)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Parameter name shadows instance attribute, risking a silent KeyError

The parameter checklist_items in pass_validation_checklist has the same name as the instance attribute self.checklist_items. Inside the function, the local checklist_items is used as the set of keys to evaluate, while self.checklist_items is the dict of results. The lookup self.checklist_items[item] is correct today, but any future edit that accidentally drops the self. prefix would silently attempt to subscript the list[str] parameter and either produce a wrong result or raise a TypeError. Renaming the parameter (e.g., items_to_check) removes the ambiguity entirely.

Suggested change
def pass_validation_checklist(self, checklist_items: list[str] | None = None) -> bool:
"""Check whether the gating checks pass. Defaults to the required checks only.
Args:
checklist_items: checks that must pass. If None, only required checks gate (optional checks are ignored).
Returns:
True if all the considered checks pass, False otherwise
"""
if checklist_items is None:
checklist_items = self._required()
return all(self.checklist_items[item] for item in checklist_items)
def pass_validation_checklist(self, items_to_check: list[str] | None = None) -> bool:
"""Check whether the gating checks pass. Defaults to the required checks only.
Args:
items_to_check: checks that must pass. If None, only required checks gate (optional checks are ignored).
Returns:
True if all the considered checks pass, False otherwise
"""
if items_to_check is None:
items_to_check = self._required()
return all(self.checklist_items[item] for item in items_to_check)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread isaaclab_arena/relations/placement_events.py Outdated
Comment thread isaaclab_arena/utils/physics_settle.py Outdated
@xyao-nv xyao-nv changed the title Add physics settle validation check over pooled layouts into Placement Event Add standalone physics settle check over pooled layouts Jun 10, 2026

@isaaclab-review-bot isaaclab-review-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: Add standalone physics settle check over pooled layouts

Nice work structuring the physics settle validation as a separate, composable layer on top of the existing analytical checks. The PlacementValidationChecklist abstraction is clean, and separating the sim-side primitives (physics_settle.py) from the orchestration logic (placement_pool_validation.py) is a good design decision. The test coverage for the new settle primitives is thorough.

A few issues I'd like to flag:


🐛 Bug: Incorrect TYPE_CHECKING import in placement_events.py

if TYPE_CHECKING:
    from isaaclab_arena.assets.object_base import ObjectBase
    from isaaclab_arena.relations.placement_validation import PlacementResult  # ← wrong module

PlacementResult lives in isaaclab_arena.relations.placement_result, not placement_validation. This will cause type-checker failures (mypy/pyright) when analyzing this file. Should be:

from isaaclab_arena.relations.placement_result import PlacementResult

⚠️ enable_physics_settle_check is accepted but unused

The rollout_policy() function in policy_runner.py accepts enable_physics_settle_check: bool = False (line 68), and main() passes the CLI arg through (line 255), but the parameter is never referenced in the function body. The CLI arg and plumbing exist, but no settle check is actually invoked during rollout.

Is this intentional scaffolding for a follow-up PR? If so, consider adding a # TODO or raising NotImplementedError when the flag is True, so users don't assume it's functional.


📝 Misleading docstring on draw_replacement

The docstring states:

"Draw the next pooled layout to retry in one env, skipping layouts known to fail to settle."

But the implementation simply draws the next layout from the queue (sample_for_envs or _sample_reusable_without_replacement) without any filtering of previously-failed layouts. The description implies a skip-list mechanism that isn't present. Consider updating the docstring to match what it actually does (draws the next available layout), or noting that the skip behavior is planned for the follow-up cache PR.


🛡️ Potential KeyError in pass_validation_checklist

In PlacementValidationChecklist.pass_validation_checklist():

def pass_validation_checklist(self, checklist_items: list[str] | None = None) -> bool:
    if checklist_items is None:
        checklist_items = self._required()
    return all(self.checklist_items[item] for item in checklist_items)

If required_items contains a check name that hasn't yet been added to checklist_items (e.g., a check that's registered as required but the validation hasn't run yet), this raises a KeyError. Consider using .get(item, False) with a clear semantic ("missing checks are treated as failing"), or validating upfront that all required items are present and raising a more informative error.


💡 Minor: _grade_settled_batch passes gym-wrapped env to objects_settled_per_episode

In placement_pool_validation.py, _grade_settled_batch passes the gym-wrapped env to physics_settle.objects_settled_per_episode, which then calls env.unwrapped.scene internally. This works fine, but it's inconsistent with _write_layouts_per_episode which passes env.unwrapped directly to write_layout_to_sim. Consider making the unwrap point consistent across helpers.


Overall this is solid foundational work. The split between the offline validator script (run_placement_pool_validation.py) and the rollout-time check is well-motivated. Looking forward to seeing the runtime settle-and-reselect loop land in a follow-up!


Update (2cfa1b8): New commits address most previous concerns. Summary:

enable_physics_settle_check unused parameter — Resolved. The CLI arg and rollout_policy parameter have been removed entirely. Clean approach.

Misleading draw_replacement docstring — Resolved. The method (and last_applied property) have been removed entirely, simplifying the PooledObjectPlacer API.

Typo: "recoder" → "recorder" — Fixed in physics_settle.py.

Removed unused last_applied tracking — The last_applied dict and draw_replacement method have been completely removed from PooledObjectPlacer, along with the tracking in placement_events.py. This is a cleaner approach than trying to maintain state for a runtime settle-and-reselect feature that isn't implemented yet.

Tests updated — Mock pools in test_placement_events.py no longer expose last_applied, and test_physics_settle.py now reads from layouts_per_env() instead of last_applied.

Wrong TYPE_CHECKING import (PlacementResult from placement_validation instead of placement_result) — Still present at line 20 of placement_events.py. This bug was flagged in the initial review and remains unaddressed.

pass_validation_checklist potential KeyError — Not addressed in this diff (not blocking).


Verdict: Good cleanup. The new commit removes scaffolding for the runtime settle-and-reselect feature that wasn't being used, keeping only the offline pool validator path. One TYPE_CHECKING import bug remains from the initial review (wrong module for PlacementResult). Fix that and this is merge-ready.

Comment thread isaaclab_arena/evaluation/policy_runner.py Outdated
or ``None`` when ``placement_pool`` is omitted and the env has no pooled layouts.
"""
# No-ops when no layouts are stored in the pool
if placement_pool is None:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we simplify the if condition here? We have two if placement_pool is None

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants