POMDPPlanners.tests.test_environments.test_push_pomdp package

Tests for Push POMDP environments.

This package contains test modules for the Push POMDP environments: - test_push_pomdp.py: Discrete Push POMDP environment tests - test_continuous_push_pomdp.py: Continuous Push POMDP environment tests - test_continuous_push_geometry.py: Continuous Push geometry utility tests - test_push_pomdp_beliefs/: Belief factory and vectorized updater tests

Subpackages

Submodules

POMDPPlanners.tests.test_environments.test_push_pomdp.test_continuous_push_geometry module

Tests for Continuous Push POMDP geometry utilities.

This module tests circle-AABB overlap, point-inside-AABB, collision resolution, and grid clamping functions, including their batch variants.

class POMDPPlanners.tests.test_environments.test_push_pomdp.test_continuous_push_geometry.TestContinuousPushGeometry[source]

Bases: object

Test circle-AABB and point-AABB geometry utilities.

setup_method()[source]

Set up shared test fixtures.

test_batch_clamp_circle_to_grid_shapes()[source]

Test batch circle grid clamping output shape.

Purpose: Validates output shape of batch circle clamping.

Given: 10 random positions. When: batch_clamp_circle_to_grid is called. Then: Output shape matches (10, 2) and values are in range.

Test type: unit

test_batch_clamp_point_to_grid()[source]

Test batch point grid clamping.

Purpose: Validates batch point clamping bounds.

Given: Points at (-1, 20) and (5, 5). When: batch_clamp_point_to_grid is called with grid_size=10. Then: First is clamped to (0, 9), second unchanged.

Test type: unit

test_batch_point_inside_aabb()[source]

Test batch point-inside-AABB detection.

Purpose: Validates batch point-inside-AABB returns correct booleans.

Given: Points at (5, 5) inside and (0, 0) outside the AABB. When: batch_point_inside_aabb is called. Then: First is True, second is False.

Test type: unit

test_batch_resolve_circle_wall_collision_shapes()[source]

Test batch circle-wall collision resolution output shape.

Purpose: Validates output shape of batch collision resolution.

Given: 10 random positions with radius 0.5 and one wall. When: batch_resolve_circle_wall_collision is called. Then: Output shape matches input shape (10, 2).

Test type: unit

test_circle_aabb_overlap_false()[source]

Test that non-overlapping circle-AABB returns False.

Purpose: Validates that distant circles are not detected as overlapping.

Given: A circle centered at (1.0, 1.0) with radius 0.3 and an AABB

centered at (5, 5) with half-extent 1.

When: circle_aabb_overlap is called. Then: Returns False.

Test type: unit

test_circle_aabb_overlap_true()[source]

Test that overlapping circle-AABB returns True.

Purpose: Validates circle-AABB overlap detection.

Given: A circle centered at (3.5, 5.0) with radius 1.0 and

an AABB centered at (5, 5) with half-extent 1.

When: circle_aabb_overlap is called. Then: Returns True because the circle reaches into the AABB.

Test type: unit

test_clamp_circle_to_grid()[source]

Test that circle clamping keeps circle within grid.

Purpose: Validates grid clamping for circles.

Given: A circle centered at (-1, 12) with radius 0.3 on a grid of size 10. When: clamp_circle_to_grid is called. Then: Center is clamped to [radius, grid_size - 1 - radius].

Test type: unit

test_clamp_point_to_grid()[source]

Test that point clamping keeps point within [0, grid_size-1].

Purpose: Validates grid clamping for points.

Given: A point at (-2, 15) on a grid of size 10. When: clamp_point_to_grid is called. Then: Point is clamped to [0, 9].

Test type: unit

test_point_inside_aabb_false()[source]

Test that a point outside an AABB is not detected.

Purpose: Validates point-outside-AABB.

Given: A point at (1.0, 1.0) outside the AABB [4, 6] x [4, 6]. When: point_inside_aabb is called. Then: Returns False.

Test type: unit

test_point_inside_aabb_true()[source]

Test that a point inside an AABB is detected.

Purpose: Validates point-inside-AABB test.

Given: A point at (5.0, 5.0) inside the AABB [4, 6] x [4, 6]. When: point_inside_aabb is called. Then: Returns True.

Test type: unit

test_resolve_circle_wall_collision_no_walls()[source]

Test collision resolution with no walls returns copy.

Purpose: Validates no-op when there are no walls.

Given: Empty walls array. When: resolve_circle_wall_collision is called. Then: Returns a copy of the original position.

Test type: unit

test_resolve_circle_wall_collision_pushes_out()[source]

Test that collision resolution pushes circle away from AABB.

Purpose: Validates that an overlapping circle is pushed out.

Given: A circle centered at (4.5, 5.0) with radius 0.3 overlapping

the AABB [4, 6] x [4, 6].

When: resolve_circle_wall_collision is called. Then: The returned position moves the circle center away from

the original overlap.

Test type: unit

POMDPPlanners.tests.test_environments.test_push_pomdp.test_continuous_push_hazard_terminal module

POMDPPlanners.tests.test_environments.test_push_pomdp.test_continuous_push_native_equivalence module

POMDPPlanners.tests.test_environments.test_push_pomdp.test_continuous_push_pomdp module

POMDPPlanners.tests.test_environments.test_push_pomdp.test_continuous_push_pomdp_features module

POMDPPlanners.tests.test_environments.test_push_pomdp.test_push_discrete_native_transition module

POMDPPlanners.tests.test_environments.test_push_pomdp.test_push_pomdp module

POMDPPlanners.tests.test_environments.test_push_pomdp.test_push_pomdp_feature_driven module

Feature-driven bug-hunting tests for PushPOMDP (discrete).

This module complements test_push_pomdp.py with feature-driven tests that target asymmetries and edge cases between scalar and batch APIs, boundary semantics for terminal/obstacle-collision predicates, and the sample/PDF pair on the observation model.

The test file was created after a sibling skill run on the light-dark POMDP exposed a real asymmetry between observation_log_probability (scalar, floored) and observation_log_probability_per_state (batch, un-floored). The tests below specifically check that no analogous asymmetry exists in the Push POMDP and exercise other features that lack dedicated coverage.

POMDPPlanners.tests.test_environments.test_push_pomdp.test_push_pomdp_feature_driven.test_hash_observation_consistent_with_equality()[source]

hash_observation returns equal hashes for equal observations.

Return type:

None

Purpose: Sanity-check the hash-equality contract used by belief

clustering. Equal observations (per is_equal_observation) must hash to equal values; unequal observations should hash to different values for at least one numerically-different pair.

Given: A PushPOMDP and three observations: two that are

np.array_equal and one that differs in the object slice.

When: Their hash values (as bytes) are compared. Then: The two equal observations have the same hash; the third

differs from them.

Test type: unit

POMDPPlanners.tests.test_environments.test_push_pomdp.test_push_pomdp_feature_driven.test_is_terminal_strictly_less_than_half_unit()[source]

is_terminal uses strict < 0.25 on squared distance, NOT <=.

Return type:

None

Purpose: Pins down the boundary semantics of is_terminal so a

future change from < to <= (or vice versa) is caught.

Given: A state where the object-target squared distance equals

exactly 0.25 (object 0.5 units away from target).

When: env.is_terminal(state) is queried. Then: Returns False (because 0.25 < 0.25 is False).

And for distance epsilon under 0.5, returns True.

Test type: unit

POMDPPlanners.tests.test_environments.test_push_pomdp.test_push_pomdp_feature_driven.test_metric_names_match_compute_metrics_output()[source]

get_metric_names matches the names emitted by compute_metrics.

Return type:

None

Purpose: Validates the data-integrity contract: the

get_metric_names declaration must exactly match the names produced by compute_metrics so downstream simulation consumers can index by name reliably.

Given: A PushPOMDP and a one-step history. When: get_metric_names() and compute_metrics(...) are both

invoked.

Then: The set of names from each is identical.

Test type: unit

POMDPPlanners.tests.test_environments.test_push_pomdp.test_push_pomdp_feature_driven.test_native_simulate_rollout_with_error_prob_runs_and_is_finite()[source]

Native rollout with non-zero transition_error_prob returns a finite scalar.

Return type:

None

Purpose: Smoke-test the C++ error-action path in

simulate_rollout_discrete (which uses the kErrorActions table and an extra RNG draw per step). Without this test, the error branch in C++ would be untested by the existing deterministic-only parity tests.

Given: A PushPOMDP with transition_error_prob=0.4 and a fixed

initial state. Native RNG seeded and called via env.simulate_random_rollout (which delegates to the C++ kernel).

When: A 30-step rollout is executed. Then: The discounted return is finite, lies in

[reward_range[0]*30, reward_range[1]*30], and a second call with a different native seed yields a (likely) different return, confirming the error-action RNG is actually used.

Test type: integration

POMDPPlanners.tests.test_environments.test_push_pomdp.test_push_pomdp_feature_driven.test_observation_log_probability_extreme_far_observation_no_floor_asymmetry()[source]

At extreme distance both scalar and batch return the same un-floored value.

Return type:

None

Purpose: Specifically targets the light-dark-style asymmetry where

the scalar path could clip log(p) at log(1e-300) while the batch path would return the raw kernel output. An extreme observation (object position 1e6 away from next_state’s object position) drives the kernel into log-prob territory far below log(1e-300) -690.

Given: A PushPOMDP and a next_state with object at (5, 5); an

observation with object at (1e6, 1e6). At that distance the true Gaussian log-pdf is on the order of -1e13.

When: Both scalar and batch log-prob APIs evaluate this pair. Then: Both return identical, finite (non-NaN) values that are far

below -700 (i.e. neither path floors). If the scalar path is floored, this test fails.

Test type: unit

POMDPPlanners.tests.test_environments.test_push_pomdp.test_push_pomdp_feature_driven.test_observation_log_probability_scalar_matches_batch_kernel()[source]

Scalar observation_log_probability equals batch per_state form.

Return type:

None

Purpose: Mirror of the asymmetry found in the light-dark POMDP:

scalar floored at log(1e-300) while the batch path returned the un-floored kernel. This test asserts no such asymmetry exists in the Push POMDP for a fixed observation evaluated against many next-states (or vice versa).

Given: A PushPOMDP with observation_noise=0.1 and no obstacles. A

fixed observation and a fixed next-state.

When: ``env.observation_log_probability(next_state, action,

[observation])`` (scalar path) and env.observation_log_probability_per_state([next_state], action, observation) (batch path) are both evaluated.

Then: Both return the same value (single Gaussian log-pdf at the

same point). Tested across a sweep of (next_state, observation) pairs that include points far from the mean (where the light-dark bug manifests via under/un-flooring).

Test type: unit

POMDPPlanners.tests.test_environments.test_push_pomdp.test_push_pomdp_feature_driven.test_reward_at_target_includes_terminal_bonus()[source]

Reward includes +100 bonus when next-state object is within 0.5 of target.

Return type:

None

Purpose: Validates that _reward_from_next_state adds the +100

terminal bonus exactly when the object distance to target is below 0.5, and that the bonus is omitted just outside that band.

Given: A PushPOMDP without obstacles. Two next-states that share the

same robot position but differ only in the object-target distance: one with object exactly at the target (distance 0), another with object 0.5 units away (distance == 0.5).

When: _reward_from_next_state is called for both. Then: The first returns +100.0 (bonus) - 0.0 (distance) = 100.0.

The second returns -0.5 (no bonus, because 0.5 is NOT < 0.5).

Test type: unit

POMDPPlanners.tests.test_environments.test_push_pomdp.test_push_pomdp_feature_driven.test_reward_batch_obstacle_radius_boundary_matches_scalar()[source]

Batch and scalar obstacle-penalty agree at the exact radius boundary.

Return type:

None

Purpose: Both _obstacle_penalty_batch and the scalar

_is_colliding_with_obstacle_scalar use <= against the squared radius, which is the only consistent choice. This test confirms they agree when the intended position lies exactly on the obstacle radius (squared distance == r^2).

Given: A PushPOMDP with obstacle at (3, 3), radius 0.5,

friction_coefficient=0.0 and transition_error_prob=0 (so the transition is fully deterministic). Robot at (2.5, 3.0): action “right” intends (3.5, 3.0) which is at squared distance 0.25 from the obstacle centre — exactly on the boundary.

When: reward_batch is called on a batch of 5 copies and

reward on one scalar.

Then: Both yield the same reward (same obstacle-penalty decision).

Test type: unit

POMDPPlanners.tests.test_environments.test_push_pomdp.test_push_pomdp_feature_driven.test_reward_obstacle_penalty_uses_realised_position_not_intended()[source]

Obstacle penalty triggers off the REALISED robot position, not the intended move.

Return type:

None

Purpose: Pins the corrected design — _reward_from_next_state applies

obstacle_penalty based on the realised next_state[:2], not on state[:2] + action_dxy. When a move is blocked by an obstacle the robot stays put, the realised position is clear, and no penalty fires.

Given: A PushPOMDP with one obstacle at (3, 3), radius 0.5. Robot at

(2, 3); action “right” intends (3, 3) which is inside the obstacle, but the transition blocks the move so the robot stays at (2, 3).

When: env.reward(state, "right") is called. Then: The reward equals the bare -distance term — no obstacle

penalty (because the realised position is clear).

Test type: unit

POMDPPlanners.tests.test_environments.test_push_pomdp.test_push_pomdp_feature_driven.test_reward_range_bounds_actual_rewards_across_random_states()[source]

All sampled rewards lie inside env.reward_range for many random states.

Return type:

None

Purpose: Cross-checks that the reward_range advertised by the

environment actually bounds the rewards returned by env.reward(...) for a representative random sample of states. A mismatch (e.g. obstacle penalty pushing the reward below the lower bound) would expose a stale or wrong range.

Given: A PushPOMDP with obstacles and obstacle_penalty=-10.0. When: For each of 50 random states and each action, we compute

env.reward(state, action).

Then: Every reward lies within the closed interval

[reward_range[0], reward_range[1]].

Test type: unit

POMDPPlanners.tests.test_environments.test_push_pomdp.test_push_pomdp_feature_driven.test_sample_next_state_batch_matches_scalar_deterministic()[source]

Batch sample_next_state_batch agrees byte-exactly with scalar path.

Return type:

None

Purpose: Validates that PushVectorizedUpdater.batch_transition

(the engine behind sample_next_state_batch) produces results identical to _sample_one_next_state when transition_error_prob=0 (so no RNG is involved). Any divergence in collision-radius semantics (< vs <=), clipping, or push-threshold comparison would surface here.

Given: A PushPOMDP with obstacles and transition_error_prob=0.

A diverse batch of 200 random states and one fixed action.

When: sample_next_state_batch(states, action) and a scalar loop

[sample_next_state(s, action) for s in states] are both evaluated.

Then: Every row of the batch result equals the corresponding scalar

result exactly (atol=1e-12).

Test type: unit

POMDPPlanners.tests.test_environments.test_push_pomdp.test_push_pomdp_feature_driven.test_sample_next_state_batch_push_threshold_boundary()[source]

Batch path uses < (strict) on push-threshold, matching scalar path.

Return type:

None

Purpose: Pins down the push-threshold comparison. Scalar path uses

dist_sq < push_threshold_sq (strict). Batch path uses dist_to_obj < self.push_threshold (strict). This test verifies both yield the same answer at the exact boundary.

Given: A PushPOMDP with push_threshold=1.0 and friction=0.3. Robot

positioned exactly 1.0 unit away from the object. Action “right” attempts to move the robot directly toward the object.

When: Batch and scalar transitions are computed. Then: Both produce identical next-states. The object’s position

determines whether a strict-less-than at exactly 1.0 means “no push” (the documented behaviour). This test asserts the two APIs agree on whatever the boundary semantics are.

Test type: unit

POMDPPlanners.tests.test_environments.test_push_pomdp.test_push_pomdp_feature_driven.test_sample_observation_matches_log_probability_density_2d_gaussian()[source]

Empirical density from samples matches closed-form log-prob (2-D Gaussian).

Return type:

None

Purpose: Validates the sample/PDF pair for sample_observation and

observation_log_probability under the 2-D Gaussian noise on the object slice (cols 2:4). An asymmetry between sampler and PDF (e.g. wrong variance, missing factor of 2*pi) would surface as a Wilson-CI violation.

Given: A PushPOMDP with grid_size large enough that clipping is

negligible (grid_size=100) and observation_noise=0.5 (small relative to grid). next_state object centred at (50, 50). N=5000 observations sampled.

When: We bin the sampled object positions (col 2 only — the

marginal in x) into a histogram and compare the empirical bin probability to the integral of the predicted PDF over each bin (computed from observation_log_probability evaluated at bin centres, multiplied by bin width).

Then: Each bin’s empirical probability is within 3/sqrt(N)

(Wilson-style) of the predicted probability.

Test type: unit

POMDPPlanners.tests.test_environments.test_push_pomdp.test_push_pomdp_feature_driven.test_transition_log_probability_distribution_sums_to_one_with_error()[source]

transition_log_probability gives probabilities that sum to 1 across distinct outcomes.

Return type:

None

Purpose: Validates the closed-form mixture formula in

transition_log_probability when transition_error_prob>0: the total mass over the at-most-4 distinct intended outcomes (for the 4 actions) sums exactly to 1.0. A mistake in the num_error_actions denominator or in the error_match_count accumulator would break this invariant.

Given: A PushPOMDP with no obstacles, transition_error_prob=0.5,

and a state where the 4 actions yield 4 distinct next-states (robot in the interior of the grid; no friction; no pushing).

When: We enumerate the 4 distinct intended next-states and sum the

probabilities np.exp(transition_log_probability(...)).

Then: The sum equals 1.0 exactly (within 1e-12).

Test type: unit

POMDPPlanners.tests.test_environments.test_push_pomdp.test_push_vectorized_model module