POMDPPlanners.environments.laser_tag_pomdp package

LaserTag POMDP Environment Package.

This package implements the LaserTag pursuit-evasion POMDP environment in both discrete-grid and continuous-space variants.

Note

LaserTagState is now represented as numpy arrays with shape (5,). See laser_tag_pomdp.py for state vector structure documentation.

class POMDPPlanners.environments.laser_tag_pomdp.ContinuousLaserTagPOMDP(discount_factor, name='ContinuousLaserTagPOMDP', grid_size=(11.0, 7.0), walls=None, robot_radius=0.3, opponent_radius=0.3, tag_radius=0.5, tag_reward=10.0, tag_penalty=10.0, step_cost=1.0, measurement_noise=1.0, robot_transition_cov_matrix=array([[0.1, 0.], [0., 0.1]]), opponent_transition_cov_matrix=array([[0.05, 0.], [0., 0.05]]), evasion_speed=0.6, dangerous_areas=None, dangerous_area_radius=1.0, dangerous_area_penalty=5.0, dangerous_area_hit_probability=1.0, output_dir=None, debug=False, use_queue_logger=False, initial_state=None, opponent_policy=OpponentPolicy.EVADE)[source]

Bases: Environment

Continuous LaserTag POMDP with continuous [dx, dy, tag_flag] actions.

A pursuit-evasion problem in continuous 2-D space where a robot must navigate to tag an opponent. The robot receives noisy 8-direction laser range observations.

Stochasticity:

The dangerous-area penalty can be applied either deterministically (the default) or stochastically. When dangerous_area_hit_probability == 1.0 (default), the kernel’s deterministic deduction is preserved verbatim, matching legacy behavior. When dangerous_area_hit_probability < 1.0, the accumulated dangerous-area deduction is applied to the reward only with that probability per reward() call, producing a heavy-tailed return distribution suitable for benchmarking risk-sensitive planners (e.g. ICVaR-aware MCTS) against expected-value MCTS on the same env. Note that this makes reward(state, action) non-deterministic given a state-action pair, so any external caching that assumes deterministic rewards must be aware of this. transition_log_probability is unaffected.

Example

>>> import numpy as np
>>> np.random.seed(42)
>>>
>>> # Initialize environment
>>> env = ContinuousLaserTagPOMDP(discount_factor=0.95)
>>>
>>> # Get initial state
>>> initial_state = env.initial_state_dist().sample()[0]
>>>
>>> # Sample complete step
>>> action = np.array([1.0, 0.0, 0.0])
>>> next_state, observation, reward = env.sample_next_step(initial_state, action)
>>>
>>> # Check terminal condition
>>> env.is_terminal(initial_state)
False

Example

Risk-sensitive evaluation – a 10%-tail-risk environment suitable for benchmarking ICVaR-aware planners against expected-value MCTS:

>>> env = ContinuousLaserTagPOMDP(
...     discount_factor=0.95,
...     dangerous_area_penalty=150.0,
...     dangerous_area_hit_probability=0.1,
... )
Parameters:
  • discount_factor (float)

  • name (str)

  • grid_size (Tuple[float, float])

  • walls (Optional[List[Tuple[float, float, float, float]]])

  • robot_radius (float)

  • opponent_radius (float)

  • tag_radius (float)

  • tag_reward (float)

  • tag_penalty (float)

  • step_cost (float)

  • measurement_noise (float)

  • robot_transition_cov_matrix (np.ndarray)

  • opponent_transition_cov_matrix (np.ndarray)

  • evasion_speed (float)

  • dangerous_areas (Optional[List[Tuple[float, float]]])

  • dangerous_area_radius (float)

  • dangerous_area_penalty (float)

  • dangerous_area_hit_probability (float)

  • output_dir (Optional[Path])

  • debug (bool)

  • use_queue_logger (bool)

  • initial_state (Optional[np.ndarray])

  • opponent_policy (OpponentPolicy)

cache_visualization(history, cache_path)[source]

Cache visualization data for an episode history.

This method can be overridden by subclasses to provide environment-specific visualization caching capabilities.

Parameters:
  • history (List[StepData]) – List of step data from an episode

  • cache_path (Path) – Path where visualization data should be cached

Return type:

None

compute_metrics(histories)[source]

Compute environment-specific metrics from episode histories.

This method can be overridden by subclasses to provide custom metric calculations beyond standard return and episode length.

Parameters:

histories (List[History]) – List of episode histories to analyze

Return type:

List[MetricValue]

Returns:

List of computed metrics with confidence intervals

get_metric_names()[source]

Get names of environment-specific metrics.

This method returns the names of custom metrics that this environment computes in the compute_metrics() method. It enables users to discover what metrics are available for hyperparameter optimization.

Return type:

List[str]

Returns:

List of metric names that this environment produces. Default implementation returns empty list for environments without custom metrics.

Note

Subclasses that override compute_metrics() should also override this method to return the names of metrics they produce. Use an Enum to ensure consistency between the names returned here and the names used in compute_metrics().

property grid_size: ndarray
hash_action(action)[source]

Return a hashable key consistent with action equality.

Used by tree-search planners to index action children of a belief node in O(1). The returned key MUST satisfy:

action_a == action_b   (per env's notion of equality)
==> hash_action(action_a) == hash_action(action_b)

Subclasses with non-hashable actions (e.g. np.ndarray) must override to return a hashable surrogate (tobytes() is the standard choice for ndarray actions, which mirrors the np.array_equal semantics used by the linear-scan fallback).

Parameters:

action (Any) – Action to hash.

Return type:

Hashable

Returns:

A hashable key derived from action.

hash_observation(observation)[source]

Return a hashable key consistent with is_equal_observation().

Used by tree-search planners to index belief children by observation in O(1). The returned key MUST satisfy the contract:

is_equal_observation(a, b) implies hash_observation(a) == hash_observation(b)
Parameters:

observation (Any) – Observation to hash.

Returns:

the observation itself when it is already hashable).

Return type:

Hashable

Raises:

NotImplementedError – If the observation is not hashable and the subclass has not provided an override. Subclasses with non-hashable observations (e.g. np.ndarray) MUST override.

initial_observation_dist()[source]

Get the initial observation distribution.

Return type:

Distribution

Returns:

Distribution over initial observations

Note

Subclasses must implement this method to define initial observations.

initial_state_dist()[source]

Get the initial state distribution.

Return type:

Distribution

Returns:

Distribution over initial states

Note

Subclasses must implement this method to define the starting distribution.

is_equal_observation(observation1, observation2)[source]

Check if two observations are equal.

Parameters:
  • observation1 (Any) – First observation to compare

  • observation2 (Any) – Second observation to compare

Return type:

bool

Returns:

True if observations are considered equal, False otherwise

Note

Subclasses must implement this method to define observation equality. This is particularly important for discrete observation spaces.

is_terminal(state)[source]

Check if a state is terminal.

Parameters:

state (ndarray) – State to check for terminal condition

Return type:

bool

Returns:

True if the state is terminal, False otherwise

Note

Subclasses must implement this method to define terminal conditions.

observation_log_probability(next_state, action, observations)[source]

Log-probability of each candidate observation under (next_state, action).

Returns np.ndarray of shape (N,) where N is the number of candidate observations. Subclasses must implement.

Return type:

ndarray

Parameters:
observation_log_probability_per_state(next_states, action, observation)[source]

Log-probability of one observation under each candidate next-state.

Used by particle filters: given N candidate next-states and ONE observation, return N log-likelihoods.

The default implementation falls back to a per-state Python loop delegating to observation_log_probability(). Native-backed envs (those whose observation kernel exposes batch_log_likelihood(next_states_array, observation_array)) should override to avoid the loop.

Parameters:
  • next_states (Any) – A sequence (length N) or ndarray of shape (N, *dim) of candidate next-states.

  • action (ndarray) – The action that was executed.

  • observation (Any) – A single observation.

Return type:

ndarray

Returns:

ndarray of shape (N,) with log-probabilities or log-PDFs.

observation_log_probability_single(next_state, action, observation)[source]

Scalar log-likelihood for one (next_state, observation) pair.

Per-state fast-path used by incremental belief updates (e.g. POMCPOW’s WeightedParticleBeliefStateUpdate.inplace_update()) to skip the per-call numpy setup overhead of the batched observation_log_probability() path on a singleton input.

The default falls back to the batched method with a one-element observations list. Envs with cheap scalar likelihoods (e.g. the 2-D Gaussian on Push or the cached-inverse-cov path on ContinuousLightDark) should override to skip array allocation.

Return type:

float

Parameters:
  • next_state (Any)

  • action (Any)

  • observation (Any)

reward(state, action, next_state=None)[source]

Calculate the immediate reward for a state-action(-next_state) tuple.

next_state is the realised post-transition state when known (e.g. threaded by sample_next_step()), allowing rewards that depend on stochastic transition outcomes to use the same draw as the trajectory instead of resampling. Subclasses whose reward is a pure function of (state, action) may ignore it; subclasses whose reward depends on the realised next state (collision penalties, win bonuses) should consume it when provided and fall back to drawing/computing one when None.

Parameters:
  • state (ndarray) – Current state.

  • action (ndarray) – Action executed from state.

  • next_state (Any) – Realised next state, or None if the caller did not pre-sample one. Defaults to None.

Return type:

float

Returns:

Immediate reward value.

Note

Subclasses must implement this method to define reward structure.

reward_batch(states, action, next_states=None)[source]

Calculate rewards for a batch of states given a single action.

Provides a loop-based default that subclasses can override with vectorized numpy implementations for better performance.

Parameters:
Return type:

ndarray

Returns:

1-D array of reward values with shape (N,).

sample_next_state(state, action, n_samples=1)[source]

Sample one or more next states for (state, action).

Hot-path entry point used by MCTS planners and particle filters. Subclasses must implement.

Returns:

a single next state of the env’s native type. When n_samples > 1: an array-like of length n_samples (numeric envs return np.ndarray of shape (n_samples, *dim); structured envs return List[T]).

Return type:

Any

Parameters:
sample_next_state_batch(states, action)[source]

Sample one next state per input state, all under the same action.

Used by particle filters: given N current particles and one action, draw N next states (one per particle) in a single vectorized call.

The default implementation falls back to a per-state Python loop delegating to sample_next_state(). Native-backed envs (those whose state-transition kernel exposes batch_sample(states_array)) should override to avoid the loop.

Parameters:
  • states (Any) – A sequence (length N) or ndarray of shape (N, *dim) of input particles.

  • action (ndarray) – A single action to apply to every particle.

Returns:

np.ndarray of shape (N, *dim). For structured envs (Tiger strings, Pacman tuples): a list of length N.

Return type:

ndarray

sample_observation(next_state, action, n_samples=1)[source]

Sample one or more observations for (next_state, action).

Hot-path entry point used by MCTS planners and particle filters. Subclasses must implement.

Returns:

a single observation. When n_samples > 1: an array-like of length n_samples.

Return type:

Any

Parameters:
simulate_random_rollout(state, action_sampler, max_depth, discount_factor, depth=0)[source]

Random rollout dispatched to native C++ via cont_simulate_rollout.

Pre-samples actions from action_sampler, packs them into a (N, 3) buffer, and runs the full discounted-return loop inside C++. Results are numerically identical to the Environment.simulate_random_rollout() Python fallback.

When dangerous_area_hit_probability < 1.0, falls back to the Python rollout: the native kernel applies the dangerous-area penalty deterministically per step, which contradicts the stochastic semantics; routing through Python reward() keeps the per-step Bernoulli intact.

Also falls back when dangerous_areas is non-empty: the C++ cont_simulate_rollout kernel scores the danger penalty against the pre-transition robot position, while the Python reward() path (post-fix) consumes the realised post-transition position. Until the C++ kernel is rebuilt this is the only correctness-preserving path for configs with danger areas.

Return type:

float

Parameters:
transition_log_probability(state, action, next_states)[source]

Log-probability of each candidate next state under (state, action).

Returns np.ndarray of shape (N,) where N is the number of candidate next states. Subclasses must implement.

Return type:

ndarray

Parameters:
property walls: ndarray
class POMDPPlanners.environments.laser_tag_pomdp.ContinuousLaserTagPOMDPDiscreteActions(discount_factor, name='ContinuousLaserTagPOMDPDiscreteActions', grid_size=(11.0, 7.0), walls=None, robot_radius=0.3, opponent_radius=0.3, tag_radius=0.5, tag_reward=10.0, tag_penalty=10.0, step_cost=1.0, measurement_noise=1.0, robot_transition_cov_matrix=array([[0.1, 0.], [0., 0.1]]), opponent_transition_cov_matrix=array([[0.05, 0.], [0., 0.05]]), evasion_speed=0.6, dangerous_areas=None, dangerous_area_radius=1.0, dangerous_area_penalty=5.0, dangerous_area_hit_probability=1.0, output_dir=None, debug=False, use_queue_logger=False, initial_state=None, opponent_policy=OpponentPolicy.EVADE)[source]

Bases: ContinuousLaserTagPOMDP, DiscreteActionsEnvironment

Continuous LaserTag POMDP with discrete string actions.

Actions: "up", "down", "right", "left", "tag".

Example

>>> import numpy as np
>>> np.random.seed(42)
>>>
>>> env = ContinuousLaserTagPOMDPDiscreteActions(discount_factor=0.95)
>>>
>>> initial_state = env.initial_state_dist().sample()[0]
>>> actions = env.get_actions()
>>>
>>> action = actions[0]
>>> next_state, observation, reward = env.sample_next_step(initial_state, action)
>>>
>>> env.is_terminal(initial_state)
False
Parameters:
  • discount_factor (float)

  • name (str)

  • grid_size (Tuple[float, float])

  • walls (Optional[List[Tuple[float, float, float, float]]])

  • robot_radius (float)

  • opponent_radius (float)

  • tag_radius (float)

  • tag_reward (float)

  • tag_penalty (float)

  • step_cost (float)

  • measurement_noise (float)

  • robot_transition_cov_matrix (np.ndarray)

  • opponent_transition_cov_matrix (np.ndarray)

  • evasion_speed (float)

  • dangerous_areas (Optional[List[Tuple[float, float]]])

  • dangerous_area_radius (float)

  • dangerous_area_penalty (float)

  • dangerous_area_hit_probability (float)

  • output_dir (Optional[Path])

  • debug (bool)

  • use_queue_logger (bool)

  • initial_state (Optional[np.ndarray])

  • opponent_policy (OpponentPolicy)

get_actions()[source]

Get all possible actions in the discrete action space.

Return type:

List[str]

Returns:

List containing all valid actions that can be executed

Note

Subclasses must implement this method to enumerate all possible actions. This is used by planning algorithms that need to iterate over actions.

hash_action(action)[source]

Return a hashable key consistent with action equality.

Used by tree-search planners to index action children of a belief node in O(1). The returned key MUST satisfy:

action_a == action_b   (per env's notion of equality)
==> hash_action(action_a) == hash_action(action_b)

Subclasses with non-hashable actions (e.g. np.ndarray) must override to return a hashable surrogate (tobytes() is the standard choice for ndarray actions, which mirrors the np.array_equal semantics used by the linear-scan fallback).

Parameters:

action (Any) – Action to hash.

Return type:

Hashable

Returns:

A hashable key derived from action.

observation_log_probability(next_state, action, observations)[source]

Log-probability of each candidate observation under (next_state, action).

Returns np.ndarray of shape (N,) where N is the number of candidate observations. Subclasses must implement.

Return type:

ndarray

Parameters:
observation_log_probability_per_state(next_states, action, observation)[source]

Log-probability of one observation under each candidate next-state.

Used by particle filters: given N candidate next-states and ONE observation, return N log-likelihoods.

The default implementation falls back to a per-state Python loop delegating to observation_log_probability(). Native-backed envs (those whose observation kernel exposes batch_log_likelihood(next_states_array, observation_array)) should override to avoid the loop.

Parameters:
  • next_states (Any) – A sequence (length N) or ndarray of shape (N, *dim) of candidate next-states.

  • action (Any) – The action that was executed.

  • observation (Any) – A single observation.

Return type:

ndarray

Returns:

ndarray of shape (N,) with log-probabilities or log-PDFs.

observation_log_probability_single(next_state, action, observation)[source]

Scalar log-likelihood for one (next_state, observation) pair.

Per-state fast-path used by incremental belief updates (e.g. POMCPOW’s WeightedParticleBeliefStateUpdate.inplace_update()) to skip the per-call numpy setup overhead of the batched observation_log_probability() path on a singleton input.

The default falls back to the batched method with a one-element observations list. Envs with cheap scalar likelihoods (e.g. the 2-D Gaussian on Push or the cached-inverse-cov path on ContinuousLightDark) should override to skip array allocation.

Return type:

float

Parameters:
  • next_state (Any)

  • action (Any)

  • observation (Any)

reward(state, action, next_state=None)[source]

Calculate the immediate reward for a state-action(-next_state) tuple.

next_state is the realised post-transition state when known (e.g. threaded by sample_next_step()), allowing rewards that depend on stochastic transition outcomes to use the same draw as the trajectory instead of resampling. Subclasses whose reward is a pure function of (state, action) may ignore it; subclasses whose reward depends on the realised next state (collision penalties, win bonuses) should consume it when provided and fall back to drawing/computing one when None.

Parameters:
  • state (ndarray) – Current state.

  • action (Any) – Action executed from state.

  • next_state (Any) – Realised next state, or None if the caller did not pre-sample one. Defaults to None.

Return type:

float

Returns:

Immediate reward value.

Note

Subclasses must implement this method to define reward structure.

reward_batch(states, action, next_states=None)[source]

Calculate rewards for a batch of states given a single action.

Provides a loop-based default that subclasses can override with vectorized numpy implementations for better performance.

Parameters:
  • states (Union[ndarray, Sequence[Any]]) – Sequence of states of length N.

  • action (Any) – Action executed from each state.

  • next_states (Union[ndarray, Sequence[Any], None]) – Optional realised next states (length N) threaded through to reward(). Defaults to None.

Return type:

ndarray

Returns:

1-D array of reward values with shape (N,).

sample_next_state(state, action, n_samples=1)[source]

Sample one or more next states for (state, action).

Hot-path entry point used by MCTS planners and particle filters. Subclasses must implement.

Returns:

a single next state of the env’s native type. When n_samples > 1: an array-like of length n_samples (numeric envs return np.ndarray of shape (n_samples, *dim); structured envs return List[T]).

Return type:

Any

Parameters:
sample_next_state_batch(states, action)[source]

Sample one next state per input state, all under the same action.

Used by particle filters: given N current particles and one action, draw N next states (one per particle) in a single vectorized call.

The default implementation falls back to a per-state Python loop delegating to sample_next_state(). Native-backed envs (those whose state-transition kernel exposes batch_sample(states_array)) should override to avoid the loop.

Parameters:
  • states (Any) – A sequence (length N) or ndarray of shape (N, *dim) of input particles.

  • action (Any) – A single action to apply to every particle.

Returns:

np.ndarray of shape (N, *dim). For structured envs (Tiger strings, Pacman tuples): a list of length N.

Return type:

ndarray

sample_observation(next_state, action, n_samples=1)[source]

Sample one or more observations for (next_state, action).

Hot-path entry point used by MCTS planners and particle filters. Subclasses must implement.

Returns:

a single observation. When n_samples > 1: an array-like of length n_samples.

Return type:

Any

Parameters:
simulate_random_rollout(state, action_sampler, max_depth, discount_factor, depth=0)[source]

Random rollout dispatched to native C++ via cont_simulate_rollout.

Pre-samples actions from action_sampler, packs them into a (N, 3) buffer, and runs the full discounted-return loop inside C++. Results are numerically identical to the Environment.simulate_random_rollout() Python fallback.

When dangerous_area_hit_probability < 1.0, falls back to the Python rollout: the native kernel applies the dangerous-area penalty deterministically per step, which contradicts the stochastic semantics; routing through Python reward() keeps the per-step Bernoulli intact.

Also falls back when dangerous_areas is non-empty: the C++ cont_simulate_rollout kernel scores the danger penalty against the pre-transition robot position, while the Python reward() path (post-fix) consumes the realised post-transition position. Until the C++ kernel is rebuilt this is the only correctness-preserving path for configs with danger areas.

Return type:

float

Parameters:
transition_log_probability(state, action, next_states)[source]

Log-probability of each candidate next state under (state, action).

Returns np.ndarray of shape (N,) where N is the number of candidate next states. Subclasses must implement.

Return type:

ndarray

Parameters:
class POMDPPlanners.environments.laser_tag_pomdp.LaserTagPOMDP(discount_factor, name='LaserTagPOMDP', floor_shape=(11, 7), walls={(1, 2), (3, 0), (3, 4), (5, 0), (6, 4), (9, 1), (9, 4), (10, 6)}, tag_reward=10.0, tag_penalty=10.0, step_cost=1.0, measurement_noise=1.0, dangerous_areas={(2, 5), (5, 3), (7, 1)}, dangerous_area_radius=1.0, dangerous_area_penalty=5.0, output_dir=None, debug=False, use_queue_logger=False, initial_state=None, transition_error_prob=0.0, reward_model_type=RewardModelType.CONSTANT_HAZARD_PENALTY, penalty_decay=1.0, opponent_policy=OpponentPolicy.EVADE)[source]

Bases: DiscreteActionsEnvironment

LaserTag POMDP environment implementation.

This is a pursuit-evasion problem where a robot must navigate a grid to tag an opponent. The robot receives noisy observations of the opponent’s position and must decide when and where to attempt tagging.

Problem Structure: - States: numpy array [robot_row, robot_col, opp_row, opp_col, terminal] - Actions: North(0), South(1), East(2), West(3), Tag(4) - Observations: 8-directional laser measurements (N,NE,E,SE,S,SW,W,NW) - Rewards: Tag success(+10), Tag failure(-10), Movement(-1)

Parameters:
floor_shape

Grid dimensions as (rows, cols)

walls

Set of wall positions as (row, col) tuples

tag_reward

Reward for successful tagging

tag_penalty

Penalty for unsuccessful tagging

step_cost

Cost per movement action

measurement_noise

Standard deviation of observation noise

Example

>>> import numpy as np
>>> np.random.seed(42)  # For reproducible results
>>>
>>> # Initialize environment
>>> env = LaserTagPOMDP(discount_factor=0.95)
>>>
>>> # Get initial state and actions
>>> initial_state = env.initial_state_dist().sample()[0]
>>> actions = env.get_actions()
>>>
>>> # Sample complete step using convenience method
>>> action = actions[0]
>>> next_state, observation, reward = env.sample_next_step(initial_state, action)
>>>
>>> # Check terminal condition
>>> env.is_terminal(initial_state)
False
cache_visualization(history, cache_path)[source]

Cache visualization of the LaserTag episode as an animated GIF.

Creates an animated visualization showing: - Robot movement (red circle) - Opponent movement (blue circle) - Walls (black squares) - Dangerous areas (red circles) - Action arrows showing robot’s intended movement - Laser measurements (green rays from robot position) - Belief particles (if available) showing robot’s belief about opponent location - Grid boundaries and coordinate system

Parameters:
  • history (List[StepData]) – The history of states, actions, and observations from an episode

  • cache_path (Path) – Path where to save the visualization GIF

Raises:
  • ValueError – If history is empty or contains invalid data

  • TypeError – If cache_path is not a Path object or doesn’t end with .gif

Return type:

None

compute_metrics(histories)[source]

Compute LaserTag POMDP specific metrics from simulation histories.

Return type:

List[MetricValue]

Parameters:

histories (List[History])

get_actions()[source]

Get all possible actions in the discrete action space.

Return type:

List[int]

get_metric_names()[source]

Get names of LaserTag POMDP specific metrics.

Returns:

tag_success_rate, average_episode_length, average_failed_tag_attempts, average_obstacle_collisions, average_dangerous_area_steps, and average_all_dangerous_encounters

Return type:

List[str]

hash_action(action)[source]

Return a hashable key consistent with action equality.

Used by tree-search planners to index action children of a belief node in O(1). The returned key MUST satisfy:

action_a == action_b   (per env's notion of equality)
==> hash_action(action_a) == hash_action(action_b)

Subclasses with non-hashable actions (e.g. np.ndarray) must override to return a hashable surrogate (tobytes() is the standard choice for ndarray actions, which mirrors the np.array_equal semantics used by the linear-scan fallback).

Parameters:

action (Any) – Action to hash.

Return type:

Hashable

Returns:

A hashable key derived from action.

initial_observation_dist()[source]

Get the initial observation distribution.

Return type:

Distribution

initial_state_dist()[source]

Get the initial state distribution.

Return type:

Distribution

is_equal_observation(observation1, observation2)[source]

Check if two observations are equal.

Observations are 8-dimensional laser measurements or terminal observations.

Return type:

bool

Parameters:
  • observation1 (Any)

  • observation2 (Any)

is_terminal(state)[source]

Check if a state is terminal.

Return type:

bool

Parameters:

state (ndarray)

observation_log_probability(next_state, action, observations)[source]

Log-probability of each candidate observation under (next_state, action).

Returns np.ndarray of shape (N,) where N is the number of candidate observations. Subclasses must implement.

Return type:

ndarray

Parameters:
observation_log_probability_per_state(next_states, action, observation)[source]

Log-probability of one observation under each candidate next-state.

Used by particle filters: given N candidate next-states and ONE observation, return N log-likelihoods.

The default implementation falls back to a per-state Python loop delegating to observation_log_probability(). Native-backed envs (those whose observation kernel exposes batch_log_likelihood(next_states_array, observation_array)) should override to avoid the loop.

Parameters:
  • next_states (Any) – A sequence (length N) or ndarray of shape (N, *dim) of candidate next-states.

  • action (int) – The action that was executed.

  • observation (Any) – A single observation.

Return type:

ndarray

Returns:

ndarray of shape (N,) with log-probabilities or log-PDFs.

reward(state, action, next_state=None)[source]

Calculate the immediate reward for a state-action transition.

The wall / dangerous-area penalty is computed against the realised post-action robot position taken from next_state. When the caller omits next_state (e.g., the open-loop scalar API path) the method resamples a transition via sample_next_state() so the penalty is always scored against an actual draw from the transition kernel — never against the open-loop state + action_vector intended position. Environment.sample_next_step() threads its sampled next_state into this method so trajectory and reward agree on the same realisation.

Return type:

float

Parameters:
reward_batch(states, action, next_states=None)[source]

Vectorised reward for a batch of states under a single action.

When next_states is supplied the danger-area / wall penalty is evaluated against the realised positions in next_states[:, :2] (matching the contract honoured by Environment.sample_next_step()). When it is None the method resamples via sample_next_state_batch() whenever penalty terms exist, then delegates to the reward model so reward and trajectory remain consistent end-to-end.

Return type:

ndarray

Parameters:
  • states (Any)

  • action (int)

  • next_states (Any)

sample_next_state(state, action, n_samples=1)[source]

Sample one or more next states for (state, action).

Hot-path entry point used by MCTS planners and particle filters. Subclasses must implement.

Returns:

a single next state of the env’s native type. When n_samples > 1: an array-like of length n_samples (numeric envs return np.ndarray of shape (n_samples, *dim); structured envs return List[T]).

Return type:

Any

Parameters:
sample_next_state_batch(states, action)[source]

Sample one next state per input state, all under the same action.

Used by particle filters: given N current particles and one action, draw N next states (one per particle) in a single vectorized call.

The default implementation falls back to a per-state Python loop delegating to sample_next_state(). Native-backed envs (those whose state-transition kernel exposes batch_sample(states_array)) should override to avoid the loop.

Parameters:
  • states (Any) – A sequence (length N) or ndarray of shape (N, *dim) of input particles.

  • action (int) – A single action to apply to every particle.

Returns:

np.ndarray of shape (N, *dim). For structured envs (Tiger strings, Pacman tuples): a list of length N.

Return type:

ndarray

sample_observation(next_state, action, n_samples=1)[source]

Sample one or more observations for (next_state, action).

Hot-path entry point used by MCTS planners and particle filters. Subclasses must implement.

Returns:

a single observation. When n_samples > 1: an array-like of length n_samples.

Return type:

Any

Parameters:
simulate_random_rollout(state, action_sampler, max_depth, discount_factor, depth=0)[source]
Return type:

float

Parameters:
  • state (Any)

  • action_sampler (Any)

  • max_depth (int)

  • discount_factor (float)

  • depth (int)

transition_log_probability(state, action, next_states)[source]

Log-probability of each candidate next state under (state, action).

Returns np.ndarray of shape (N,) where N is the number of candidate next states. Subclasses must implement.

Return type:

ndarray

Parameters:
class POMDPPlanners.environments.laser_tag_pomdp.OpponentPolicy(*values)[source]

Bases: Enum

Opponent transition behaviour selectable on the LaserTag environments.

Three policies are offered:

  • EVADE (default): the opponent flees the robot, placing its directional probability mass on the cell that increases distance, and reacts to the robot’s current (pre-move) position. This matches JuliaPOMDP/LaserTag.jl.

  • PURSUE: the opponent chases the robot, placing its directional mass on the cell that decreases distance, and reacts to the robot’s post-move position. This restores the behaviour used before the evader alignment fix.

  • EVADE_WHEN_SPOTTED: a partially-observed reactive opponent. When the robot has a clear line of sight to the opponent (the opponent lies on one of the robot’s unoccluded laser rays, evaluated from the robot’s pre-move position), it behaves exactly like EVADE. Otherwise the unspotted behaviour is environment-specific: the discrete grid env moves randomly (uniformly over the moves, with the usual stay/wall handling), while the continuous env holds its position (only the Gaussian opponent noise jitters it). The opponent is memoryless — visibility is recomputed each step from the current state.

EVADE and PURSUE couple both the directional choice and the reference-position choice, so they are mutually exclusive opposites.

EVADE = 'evade'
EVADE_WHEN_SPOTTED = 'evade_when_spotted'
PURSUE = 'pursue'
property native_code: int

Integer code passed to the C++ kernels.

EVADE = 0, PURSUE = 1, EVADE_WHEN_SPOTTED = 2.

Subpackages

Submodules

POMDPPlanners.environments.laser_tag_pomdp.continuous_laser_tag_geometry module

Geometry utilities for the Continuous LaserTag POMDP.

Provides ray-AABB intersection, ray-circle intersection, wall collision resolution and grid clamping used by the continuous laser-tag environment and its vectorized belief updater.

Wall AABBs are stored as rows (cx, cy, hx, hy) where (cx, cy) is the center and (hx, hy) the half-extents. Entity radii are used for circle-AABB overlap tests during collision resolution.

Functions:
ray_aabb_distances: Vectorized ray-AABB slab intersection for multiple

rays originating from a single point against an array of AABBs.

ray_circle_distance: Distance along a ray to the nearest intersection

with a circle.

compute_laser_measurements: Full 8-direction laser scan from a position. resolve_wall_collision: Push a circular entity out of overlapping AABBs. clamp_to_grid: Clamp a 2-D position to the grid boundaries.

POMDPPlanners.environments.laser_tag_pomdp.continuous_laser_tag_geometry.batch_clamp_to_grid(positions, entity_radius, grid_size)[source]

Clamp an array of positions to the grid.

Parameters:
  • positions (ndarray) – Shape (N, 2).

  • entity_radius (float) – Entity body radius.

  • grid_size (ndarray) – Shape (2,)(width, height).

Return type:

ndarray

Returns:

Shape (N, 2) clamped positions.

POMDPPlanners.environments.laser_tag_pomdp.continuous_laser_tag_geometry.batch_laser_measurements(robot_positions, opponent_positions, opponent_radius, walls, grid_size)[source]

Compute 8-direction laser measurements for many particles.

Parameters:
  • robot_positions (ndarray) – Shape (N, 2).

  • opponent_positions (ndarray) – Shape (N, 2).

  • opponent_radius (float) – Opponent body radius.

  • walls (ndarray) – Shape (M, 4) – wall AABBs.

  • grid_size (ndarray) – Shape (2,)(width, height).

Return type:

ndarray

Returns:

Shape (N, 8) measurement array.

POMDPPlanners.environments.laser_tag_pomdp.continuous_laser_tag_geometry.batch_resolve_wall_collision(positions, entity_radius, walls)[source]

Resolve wall collisions for an array of positions.

Parameters:
  • positions (ndarray) – Shape (N, 2).

  • entity_radius (float) – Entity body radius.

  • walls (ndarray) – Shape (M, 4).

Return type:

ndarray

Returns:

Shape (N, 2) resolved positions.

POMDPPlanners.environments.laser_tag_pomdp.continuous_laser_tag_geometry.clamp_to_grid(position, entity_radius, grid_size)[source]

Clamp a position so the entity circle stays within [0, w] x [0, h].

Parameters:
  • position (ndarray) – Shape (2,) – entity center.

  • entity_radius (float) – Entity body radius.

  • grid_size (ndarray) – Shape (2,)(width, height) of the arena.

Return type:

ndarray

Returns:

Clamped position as shape (2,) array.

POMDPPlanners.environments.laser_tag_pomdp.continuous_laser_tag_geometry.compute_laser_measurements(robot_pos, opponent_pos, opponent_radius, walls, grid_size)[source]

Compute 8-direction laser measurements from the robot.

Each measurement is the distance to the nearest obstacle (wall AABB, opponent circle, or grid boundary) along the corresponding ray in LASER_DIRECTIONS.

Parameters:
  • robot_pos (ndarray) – Shape (2,) – robot (x, y).

  • opponent_pos (ndarray) – Shape (2,) – opponent (x, y).

  • opponent_radius (float) – Opponent body radius.

  • walls (ndarray) – Shape (M, 4) – wall AABBs.

  • grid_size (ndarray) – Shape (2,)(width, height) of the arena.

Return type:

ndarray

Returns:

Shape (8,) array of distances.

POMDPPlanners.environments.laser_tag_pomdp.continuous_laser_tag_geometry.ray_aabb_distances(origin, directions, walls)[source]

Compute distances from origin along each ray to the nearest wall AABB.

Uses the slab method. For each of the D directions the minimum positive intersection distance across all M walls is returned. If a ray does not hit any wall before _RAY_MAX the returned distance is _RAY_MAX.

Parameters:
  • origin (ndarray) – Shape (2,) – ray origin (x, y).

  • directions (ndarray) – Shape (D, 2) – unit direction vectors.

  • walls (ndarray) – Shape (M, 4) – AABBs (cx, cy, hx, hy).

Return type:

ndarray

Returns:

Shape (D,) array of nearest intersection distances (positive).

POMDPPlanners.environments.laser_tag_pomdp.continuous_laser_tag_geometry.ray_circle_distance(origin, direction, center, radius)[source]

Distance along a ray to the nearest intersection with a circle.

Parameters:
  • origin (ndarray) – Shape (2,) – ray origin.

  • direction (ndarray) – Shape (2,) – unit direction.

  • center (ndarray) – Shape (2,) – circle center.

  • radius (float) – Circle radius.

Return type:

float

Returns:

Positive intersection distance, or np.inf if no hit.

POMDPPlanners.environments.laser_tag_pomdp.continuous_laser_tag_geometry.resolve_wall_collision(position, entity_radius, walls)[source]

Push a circular entity out of any overlapping wall AABBs.

For each wall, if the entity circle overlaps the AABB, the entity is pushed along the axis of minimum penetration.

Parameters:
  • position (ndarray) – Shape (2,) – entity center.

  • entity_radius (float) – Entity body radius.

  • walls (ndarray) – Shape (M, 4) – wall AABBs.

Return type:

ndarray

Returns:

Resolved position as shape (2,) array.

POMDPPlanners.environments.laser_tag_pomdp.continuous_laser_tag_pomdp module

Continuous LaserTag POMDP Environment Implementation.

This module implements a continuous-space variant of the LaserTag pursuit-evasion POMDP where a robot must navigate to tag an opponent that moves stochastically through continuous 2-D space.

Two environment classes are provided:

State representation:

np.ndarray shape (5,)[robot_x, robot_y, opponent_x, opponent_y, terminal_flag]

Observation:

np.ndarray shape (8,) – noisy 8-direction laser range measurements. Terminal observation is np.full(8, -1.0).

Opponent behaviour is selectable via opponent_policy (see OpponentPolicy): EVADE (default) flees the robot’s pre-move position at evasion_speed; PURSUE chases the robot’s post-move position. EVADE_WHEN_SPOTTED flees only while the robot has line of sight to it and otherwise holds its position (in this continuous env; the discrete grid env moves randomly instead). evasion_speed is a direction-neutral step magnitude under all policies.

Classes:

ContinuousLaserTagPOMDP: Continuous-action environment. ContinuousLaserTagPOMDPDiscreteActions: Discrete-action variant.

class POMDPPlanners.environments.laser_tag_pomdp.continuous_laser_tag_pomdp.ContinuousLaserTagPOMDP(discount_factor, name='ContinuousLaserTagPOMDP', grid_size=(11.0, 7.0), walls=None, robot_radius=0.3, opponent_radius=0.3, tag_radius=0.5, tag_reward=10.0, tag_penalty=10.0, step_cost=1.0, measurement_noise=1.0, robot_transition_cov_matrix=array([[0.1, 0.], [0., 0.1]]), opponent_transition_cov_matrix=array([[0.05, 0.], [0., 0.05]]), evasion_speed=0.6, dangerous_areas=None, dangerous_area_radius=1.0, dangerous_area_penalty=5.0, dangerous_area_hit_probability=1.0, output_dir=None, debug=False, use_queue_logger=False, initial_state=None, opponent_policy=OpponentPolicy.EVADE)[source]

Bases: Environment

Continuous LaserTag POMDP with continuous [dx, dy, tag_flag] actions.

A pursuit-evasion problem in continuous 2-D space where a robot must navigate to tag an opponent. The robot receives noisy 8-direction laser range observations.

Stochasticity:

The dangerous-area penalty can be applied either deterministically (the default) or stochastically. When dangerous_area_hit_probability == 1.0 (default), the kernel’s deterministic deduction is preserved verbatim, matching legacy behavior. When dangerous_area_hit_probability < 1.0, the accumulated dangerous-area deduction is applied to the reward only with that probability per reward() call, producing a heavy-tailed return distribution suitable for benchmarking risk-sensitive planners (e.g. ICVaR-aware MCTS) against expected-value MCTS on the same env. Note that this makes reward(state, action) non-deterministic given a state-action pair, so any external caching that assumes deterministic rewards must be aware of this. transition_log_probability is unaffected.

Example

>>> import numpy as np
>>> np.random.seed(42)
>>>
>>> # Initialize environment
>>> env = ContinuousLaserTagPOMDP(discount_factor=0.95)
>>>
>>> # Get initial state
>>> initial_state = env.initial_state_dist().sample()[0]
>>>
>>> # Sample complete step
>>> action = np.array([1.0, 0.0, 0.0])
>>> next_state, observation, reward = env.sample_next_step(initial_state, action)
>>>
>>> # Check terminal condition
>>> env.is_terminal(initial_state)
False

Example

Risk-sensitive evaluation – a 10%-tail-risk environment suitable for benchmarking ICVaR-aware planners against expected-value MCTS:

>>> env = ContinuousLaserTagPOMDP(
...     discount_factor=0.95,
...     dangerous_area_penalty=150.0,
...     dangerous_area_hit_probability=0.1,
... )
Parameters:
  • discount_factor (float)

  • name (str)

  • grid_size (Tuple[float, float])

  • walls (Optional[List[Tuple[float, float, float, float]]])

  • robot_radius (float)

  • opponent_radius (float)

  • tag_radius (float)

  • tag_reward (float)

  • tag_penalty (float)

  • step_cost (float)

  • measurement_noise (float)

  • robot_transition_cov_matrix (np.ndarray)

  • opponent_transition_cov_matrix (np.ndarray)

  • evasion_speed (float)

  • dangerous_areas (Optional[List[Tuple[float, float]]])

  • dangerous_area_radius (float)

  • dangerous_area_penalty (float)

  • dangerous_area_hit_probability (float)

  • output_dir (Optional[Path])

  • debug (bool)

  • use_queue_logger (bool)

  • initial_state (Optional[np.ndarray])

  • opponent_policy (OpponentPolicy)

cache_visualization(history, cache_path)[source]

Cache visualization data for an episode history.

This method can be overridden by subclasses to provide environment-specific visualization caching capabilities.

Parameters:
  • history (List[StepData]) – List of step data from an episode

  • cache_path (Path) – Path where visualization data should be cached

Return type:

None

compute_metrics(histories)[source]

Compute environment-specific metrics from episode histories.

This method can be overridden by subclasses to provide custom metric calculations beyond standard return and episode length.

Parameters:

histories (List[History]) – List of episode histories to analyze

Return type:

List[MetricValue]

Returns:

List of computed metrics with confidence intervals

get_metric_names()[source]

Get names of environment-specific metrics.

This method returns the names of custom metrics that this environment computes in the compute_metrics() method. It enables users to discover what metrics are available for hyperparameter optimization.

Return type:

List[str]

Returns:

List of metric names that this environment produces. Default implementation returns empty list for environments without custom metrics.

Note

Subclasses that override compute_metrics() should also override this method to return the names of metrics they produce. Use an Enum to ensure consistency between the names returned here and the names used in compute_metrics().

property grid_size: ndarray
hash_action(action)[source]

Return a hashable key consistent with action equality.

Used by tree-search planners to index action children of a belief node in O(1). The returned key MUST satisfy:

action_a == action_b   (per env's notion of equality)
==> hash_action(action_a) == hash_action(action_b)

Subclasses with non-hashable actions (e.g. np.ndarray) must override to return a hashable surrogate (tobytes() is the standard choice for ndarray actions, which mirrors the np.array_equal semantics used by the linear-scan fallback).

Parameters:

action (Any) – Action to hash.

Return type:

Hashable

Returns:

A hashable key derived from action.

hash_observation(observation)[source]

Return a hashable key consistent with is_equal_observation().

Used by tree-search planners to index belief children by observation in O(1). The returned key MUST satisfy the contract:

is_equal_observation(a, b) implies hash_observation(a) == hash_observation(b)
Parameters:

observation (Any) – Observation to hash.

Returns:

the observation itself when it is already hashable).

Return type:

Hashable

Raises:

NotImplementedError – If the observation is not hashable and the subclass has not provided an override. Subclasses with non-hashable observations (e.g. np.ndarray) MUST override.

initial_observation_dist()[source]

Get the initial observation distribution.

Return type:

Distribution

Returns:

Distribution over initial observations

Note

Subclasses must implement this method to define initial observations.

initial_state_dist()[source]

Get the initial state distribution.

Return type:

Distribution

Returns:

Distribution over initial states

Note

Subclasses must implement this method to define the starting distribution.

is_equal_observation(observation1, observation2)[source]

Check if two observations are equal.

Parameters:
  • observation1 (Any) – First observation to compare

  • observation2 (Any) – Second observation to compare

Return type:

bool

Returns:

True if observations are considered equal, False otherwise

Note

Subclasses must implement this method to define observation equality. This is particularly important for discrete observation spaces.

is_terminal(state)[source]

Check if a state is terminal.

Parameters:

state (ndarray) – State to check for terminal condition

Return type:

bool

Returns:

True if the state is terminal, False otherwise

Note

Subclasses must implement this method to define terminal conditions.

observation_log_probability(next_state, action, observations)[source]

Log-probability of each candidate observation under (next_state, action).

Returns np.ndarray of shape (N,) where N is the number of candidate observations. Subclasses must implement.

Return type:

ndarray

Parameters:
observation_log_probability_per_state(next_states, action, observation)[source]

Log-probability of one observation under each candidate next-state.

Used by particle filters: given N candidate next-states and ONE observation, return N log-likelihoods.

The default implementation falls back to a per-state Python loop delegating to observation_log_probability(). Native-backed envs (those whose observation kernel exposes batch_log_likelihood(next_states_array, observation_array)) should override to avoid the loop.

Parameters:
  • next_states (Any) – A sequence (length N) or ndarray of shape (N, *dim) of candidate next-states.

  • action (ndarray) – The action that was executed.

  • observation (Any) – A single observation.

Return type:

ndarray

Returns:

ndarray of shape (N,) with log-probabilities or log-PDFs.

observation_log_probability_single(next_state, action, observation)[source]

Scalar log-likelihood for one (next_state, observation) pair.

Per-state fast-path used by incremental belief updates (e.g. POMCPOW’s WeightedParticleBeliefStateUpdate.inplace_update()) to skip the per-call numpy setup overhead of the batched observation_log_probability() path on a singleton input.

The default falls back to the batched method with a one-element observations list. Envs with cheap scalar likelihoods (e.g. the 2-D Gaussian on Push or the cached-inverse-cov path on ContinuousLightDark) should override to skip array allocation.

Return type:

float

Parameters:
  • next_state (Any)

  • action (Any)

  • observation (Any)

reward(state, action, next_state=None)[source]

Calculate the immediate reward for a state-action(-next_state) tuple.

next_state is the realised post-transition state when known (e.g. threaded by sample_next_step()), allowing rewards that depend on stochastic transition outcomes to use the same draw as the trajectory instead of resampling. Subclasses whose reward is a pure function of (state, action) may ignore it; subclasses whose reward depends on the realised next state (collision penalties, win bonuses) should consume it when provided and fall back to drawing/computing one when None.

Parameters:
  • state (ndarray) – Current state.

  • action (ndarray) – Action executed from state.

  • next_state (Any) – Realised next state, or None if the caller did not pre-sample one. Defaults to None.

Return type:

float

Returns:

Immediate reward value.

Note

Subclasses must implement this method to define reward structure.

reward_batch(states, action, next_states=None)[source]

Calculate rewards for a batch of states given a single action.

Provides a loop-based default that subclasses can override with vectorized numpy implementations for better performance.

Parameters:
Return type:

ndarray

Returns:

1-D array of reward values with shape (N,).

sample_next_state(state, action, n_samples=1)[source]

Sample one or more next states for (state, action).

Hot-path entry point used by MCTS planners and particle filters. Subclasses must implement.

Returns:

a single next state of the env’s native type. When n_samples > 1: an array-like of length n_samples (numeric envs return np.ndarray of shape (n_samples, *dim); structured envs return List[T]).

Return type:

Any

Parameters:
sample_next_state_batch(states, action)[source]

Sample one next state per input state, all under the same action.

Used by particle filters: given N current particles and one action, draw N next states (one per particle) in a single vectorized call.

The default implementation falls back to a per-state Python loop delegating to sample_next_state(). Native-backed envs (those whose state-transition kernel exposes batch_sample(states_array)) should override to avoid the loop.

Parameters:
  • states (Any) – A sequence (length N) or ndarray of shape (N, *dim) of input particles.

  • action (ndarray) – A single action to apply to every particle.

Returns:

np.ndarray of shape (N, *dim). For structured envs (Tiger strings, Pacman tuples): a list of length N.

Return type:

ndarray

sample_observation(next_state, action, n_samples=1)[source]

Sample one or more observations for (next_state, action).

Hot-path entry point used by MCTS planners and particle filters. Subclasses must implement.

Returns:

a single observation. When n_samples > 1: an array-like of length n_samples.

Return type:

Any

Parameters:
simulate_random_rollout(state, action_sampler, max_depth, discount_factor, depth=0)[source]

Random rollout dispatched to native C++ via cont_simulate_rollout.

Pre-samples actions from action_sampler, packs them into a (N, 3) buffer, and runs the full discounted-return loop inside C++. Results are numerically identical to the Environment.simulate_random_rollout() Python fallback.

When dangerous_area_hit_probability < 1.0, falls back to the Python rollout: the native kernel applies the dangerous-area penalty deterministically per step, which contradicts the stochastic semantics; routing through Python reward() keeps the per-step Bernoulli intact.

Also falls back when dangerous_areas is non-empty: the C++ cont_simulate_rollout kernel scores the danger penalty against the pre-transition robot position, while the Python reward() path (post-fix) consumes the realised post-transition position. Until the C++ kernel is rebuilt this is the only correctness-preserving path for configs with danger areas.

Return type:

float

Parameters:
transition_log_probability(state, action, next_states)[source]

Log-probability of each candidate next state under (state, action).

Returns np.ndarray of shape (N,) where N is the number of candidate next states. Subclasses must implement.

Return type:

ndarray

Parameters:
property walls: ndarray
class POMDPPlanners.environments.laser_tag_pomdp.continuous_laser_tag_pomdp.ContinuousLaserTagPOMDPDiscreteActions(discount_factor, name='ContinuousLaserTagPOMDPDiscreteActions', grid_size=(11.0, 7.0), walls=None, robot_radius=0.3, opponent_radius=0.3, tag_radius=0.5, tag_reward=10.0, tag_penalty=10.0, step_cost=1.0, measurement_noise=1.0, robot_transition_cov_matrix=array([[0.1, 0.], [0., 0.1]]), opponent_transition_cov_matrix=array([[0.05, 0.], [0., 0.05]]), evasion_speed=0.6, dangerous_areas=None, dangerous_area_radius=1.0, dangerous_area_penalty=5.0, dangerous_area_hit_probability=1.0, output_dir=None, debug=False, use_queue_logger=False, initial_state=None, opponent_policy=OpponentPolicy.EVADE)[source]

Bases: ContinuousLaserTagPOMDP, DiscreteActionsEnvironment

Continuous LaserTag POMDP with discrete string actions.

Actions: "up", "down", "right", "left", "tag".

Example

>>> import numpy as np
>>> np.random.seed(42)
>>>
>>> env = ContinuousLaserTagPOMDPDiscreteActions(discount_factor=0.95)
>>>
>>> initial_state = env.initial_state_dist().sample()[0]
>>> actions = env.get_actions()
>>>
>>> action = actions[0]
>>> next_state, observation, reward = env.sample_next_step(initial_state, action)
>>>
>>> env.is_terminal(initial_state)
False
Parameters:
  • discount_factor (float)

  • name (str)

  • grid_size (Tuple[float, float])

  • walls (Optional[List[Tuple[float, float, float, float]]])

  • robot_radius (float)

  • opponent_radius (float)

  • tag_radius (float)

  • tag_reward (float)

  • tag_penalty (float)

  • step_cost (float)

  • measurement_noise (float)

  • robot_transition_cov_matrix (np.ndarray)

  • opponent_transition_cov_matrix (np.ndarray)

  • evasion_speed (float)

  • dangerous_areas (Optional[List[Tuple[float, float]]])

  • dangerous_area_radius (float)

  • dangerous_area_penalty (float)

  • dangerous_area_hit_probability (float)

  • output_dir (Optional[Path])

  • debug (bool)

  • use_queue_logger (bool)

  • initial_state (Optional[np.ndarray])

  • opponent_policy (OpponentPolicy)

get_actions()[source]

Get all possible actions in the discrete action space.

Return type:

List[str]

Returns:

List containing all valid actions that can be executed

Note

Subclasses must implement this method to enumerate all possible actions. This is used by planning algorithms that need to iterate over actions.

hash_action(action)[source]

Return a hashable key consistent with action equality.

Used by tree-search planners to index action children of a belief node in O(1). The returned key MUST satisfy:

action_a == action_b   (per env's notion of equality)
==> hash_action(action_a) == hash_action(action_b)

Subclasses with non-hashable actions (e.g. np.ndarray) must override to return a hashable surrogate (tobytes() is the standard choice for ndarray actions, which mirrors the np.array_equal semantics used by the linear-scan fallback).

Parameters:

action (Any) – Action to hash.

Return type:

Hashable

Returns:

A hashable key derived from action.

observation_log_probability(next_state, action, observations)[source]

Log-probability of each candidate observation under (next_state, action).

Returns np.ndarray of shape (N,) where N is the number of candidate observations. Subclasses must implement.

Return type:

ndarray

Parameters:
observation_log_probability_per_state(next_states, action, observation)[source]

Log-probability of one observation under each candidate next-state.

Used by particle filters: given N candidate next-states and ONE observation, return N log-likelihoods.

The default implementation falls back to a per-state Python loop delegating to observation_log_probability(). Native-backed envs (those whose observation kernel exposes batch_log_likelihood(next_states_array, observation_array)) should override to avoid the loop.

Parameters:
  • next_states (Any) – A sequence (length N) or ndarray of shape (N, *dim) of candidate next-states.

  • action (Any) – The action that was executed.

  • observation (Any) – A single observation.

Return type:

ndarray

Returns:

ndarray of shape (N,) with log-probabilities or log-PDFs.

observation_log_probability_single(next_state, action, observation)[source]

Scalar log-likelihood for one (next_state, observation) pair.

Per-state fast-path used by incremental belief updates (e.g. POMCPOW’s WeightedParticleBeliefStateUpdate.inplace_update()) to skip the per-call numpy setup overhead of the batched observation_log_probability() path on a singleton input.

The default falls back to the batched method with a one-element observations list. Envs with cheap scalar likelihoods (e.g. the 2-D Gaussian on Push or the cached-inverse-cov path on ContinuousLightDark) should override to skip array allocation.

Return type:

float

Parameters:
  • next_state (Any)

  • action (Any)

  • observation (Any)

reward(state, action, next_state=None)[source]

Calculate the immediate reward for a state-action(-next_state) tuple.

next_state is the realised post-transition state when known (e.g. threaded by sample_next_step()), allowing rewards that depend on stochastic transition outcomes to use the same draw as the trajectory instead of resampling. Subclasses whose reward is a pure function of (state, action) may ignore it; subclasses whose reward depends on the realised next state (collision penalties, win bonuses) should consume it when provided and fall back to drawing/computing one when None.

Parameters:
  • state (ndarray) – Current state.

  • action (Any) – Action executed from state.

  • next_state (Any) – Realised next state, or None if the caller did not pre-sample one. Defaults to None.

Return type:

float

Returns:

Immediate reward value.

Note

Subclasses must implement this method to define reward structure.

reward_batch(states, action, next_states=None)[source]

Calculate rewards for a batch of states given a single action.

Provides a loop-based default that subclasses can override with vectorized numpy implementations for better performance.

Parameters:
  • states (Union[ndarray, Sequence[Any]]) – Sequence of states of length N.

  • action (Any) – Action executed from each state.

  • next_states (Union[ndarray, Sequence[Any], None]) – Optional realised next states (length N) threaded through to reward(). Defaults to None.

Return type:

ndarray

Returns:

1-D array of reward values with shape (N,).

sample_next_state(state, action, n_samples=1)[source]

Sample one or more next states for (state, action).

Hot-path entry point used by MCTS planners and particle filters. Subclasses must implement.

Returns:

a single next state of the env’s native type. When n_samples > 1: an array-like of length n_samples (numeric envs return np.ndarray of shape (n_samples, *dim); structured envs return List[T]).

Return type:

Any

Parameters:
sample_next_state_batch(states, action)[source]

Sample one next state per input state, all under the same action.

Used by particle filters: given N current particles and one action, draw N next states (one per particle) in a single vectorized call.

The default implementation falls back to a per-state Python loop delegating to sample_next_state(). Native-backed envs (those whose state-transition kernel exposes batch_sample(states_array)) should override to avoid the loop.

Parameters:
  • states (Any) – A sequence (length N) or ndarray of shape (N, *dim) of input particles.

  • action (Any) – A single action to apply to every particle.

Returns:

np.ndarray of shape (N, *dim). For structured envs (Tiger strings, Pacman tuples): a list of length N.

Return type:

ndarray

sample_observation(next_state, action, n_samples=1)[source]

Sample one or more observations for (next_state, action).

Hot-path entry point used by MCTS planners and particle filters. Subclasses must implement.

Returns:

a single observation. When n_samples > 1: an array-like of length n_samples.

Return type:

Any

Parameters:
simulate_random_rollout(state, action_sampler, max_depth, discount_factor, depth=0)[source]

Random rollout dispatched to native C++ via cont_simulate_rollout.

Pre-samples actions from action_sampler, packs them into a (N, 3) buffer, and runs the full discounted-return loop inside C++. Results are numerically identical to the Environment.simulate_random_rollout() Python fallback.

When dangerous_area_hit_probability < 1.0, falls back to the Python rollout: the native kernel applies the dangerous-area penalty deterministically per step, which contradicts the stochastic semantics; routing through Python reward() keeps the per-step Bernoulli intact.

Also falls back when dangerous_areas is non-empty: the C++ cont_simulate_rollout kernel scores the danger penalty against the pre-transition robot position, while the Python reward() path (post-fix) consumes the realised post-transition position. Until the C++ kernel is rebuilt this is the only correctness-preserving path for configs with danger areas.

Return type:

float

Parameters:
transition_log_probability(state, action, next_states)[source]

Log-probability of each candidate next state under (state, action).

Returns np.ndarray of shape (N,) where N is the number of candidate next states. Subclasses must implement.

Return type:

ndarray

Parameters:
class POMDPPlanners.environments.laser_tag_pomdp.continuous_laser_tag_pomdp.ContinuousLaserTagPOMDPMetrics(*values)[source]

Bases: Enum

Metric names for Continuous LaserTag POMDP.

AVERAGE_ALL_DANGEROUS_ENCOUNTERS = 'average_all_dangerous_encounters'
AVERAGE_DANGEROUS_AREA_STEPS = 'average_dangerous_area_steps'
AVERAGE_EPISODE_LENGTH = 'average_episode_length'
AVERAGE_FAILED_TAG_ATTEMPTS = 'average_failed_tag_attempts'
AVERAGE_WALL_COLLISIONS = 'average_wall_collisions'
GOAL_REACHING_RATE = 'goal_reaching_rate'
TAG_SUCCESS_RATE = 'tag_success_rate'

POMDPPlanners.environments.laser_tag_pomdp.continuous_laser_tag_visualizer module

Continuous LaserTag POMDP Visualization Module.

This module provides visualization for the continuous-space LaserTag environment, creating animated GIF visualizations of episodes.

class POMDPPlanners.environments.laser_tag_pomdp.continuous_laser_tag_visualizer.ContinuousLaserTagVisualizer(grid_size, walls, robot_radius, opponent_radius, dangerous_areas, dangerous_area_radius)[source]

Bases: object

Handles visualization for the Continuous LaserTag POMDP.

Creates animated GIF visualizations showing robot and opponent movement as rendered icons, rectangular walls, laser rays, belief particles, and tag indicators. The robot is shown as a red humanoid and the opponent as a blue wheeled rover.

Parameters:
grid_size

Arena dimensions (width, height) as ndarray.

walls

Shape (M, 4) wall AABB array.

robot_radius

Robot body radius.

opponent_radius

Opponent body radius.

dangerous_areas

Dangerous area centers as (x, y) tuples.

dangerous_area_radius

Radius of dangerous areas.

create_visualization(history, cache_path)[source]

Create animated GIF visualization of a Continuous LaserTag episode.

Parameters:
  • history (List[StepData]) – Episode step data list.

  • cache_path (Path) – Path to save the GIF.

Raises:
Return type:

None

POMDPPlanners.environments.laser_tag_pomdp.laser_tag_pomdp module

LaserTag POMDP Environment Implementation.

This module implements the LaserTag problem, a pursuit-evasion POMDP environment where an agent must navigate a grid to tag an opponent that moves stochastically. The agent has noisy observations of the opponent’s location.

The LaserTag problem features: - A grid-based environment (default 7x11) with optional walls - Robot and opponent moving on discrete grid cells - 5 possible actions: North, South, East, West, Tag - 8-directional laser range measurements with Gaussian noise - Positive reward for successful tagging, negative reward for failed tag attempts - Step cost for each movement action - Opponent moves with 0.4 prob in x-dir, 0.4 prob in y-dir, 0.2 prob stay; the

direction of the 0.4 mass is set by opponent_policy (see OpponentPolicy): EVADE (default) moves away from the robot’s pre-move position, PURSUE moves toward the robot’s post-move position

  • When aligned on an axis, the 0.4 budget is split equally (0.2/0.2) between both directions, regardless of policy

Classes:

LaserTagState: State representation with robot and opponent positions LaserTagPOMDP: Main environment class implementing the LaserTag problem OpponentPolicy: Selectable opponent transition behaviour (evade vs pursue)

class POMDPPlanners.environments.laser_tag_pomdp.laser_tag_pomdp.LaserTagPOMDP(discount_factor, name='LaserTagPOMDP', floor_shape=(11, 7), walls={(1, 2), (3, 0), (3, 4), (5, 0), (6, 4), (9, 1), (9, 4), (10, 6)}, tag_reward=10.0, tag_penalty=10.0, step_cost=1.0, measurement_noise=1.0, dangerous_areas={(2, 5), (5, 3), (7, 1)}, dangerous_area_radius=1.0, dangerous_area_penalty=5.0, output_dir=None, debug=False, use_queue_logger=False, initial_state=None, transition_error_prob=0.0, reward_model_type=RewardModelType.CONSTANT_HAZARD_PENALTY, penalty_decay=1.0, opponent_policy=OpponentPolicy.EVADE)[source]

Bases: DiscreteActionsEnvironment

LaserTag POMDP environment implementation.

This is a pursuit-evasion problem where a robot must navigate a grid to tag an opponent. The robot receives noisy observations of the opponent’s position and must decide when and where to attempt tagging.

Problem Structure: - States: numpy array [robot_row, robot_col, opp_row, opp_col, terminal] - Actions: North(0), South(1), East(2), West(3), Tag(4) - Observations: 8-directional laser measurements (N,NE,E,SE,S,SW,W,NW) - Rewards: Tag success(+10), Tag failure(-10), Movement(-1)

Parameters:
floor_shape

Grid dimensions as (rows, cols)

walls

Set of wall positions as (row, col) tuples

tag_reward

Reward for successful tagging

tag_penalty

Penalty for unsuccessful tagging

step_cost

Cost per movement action

measurement_noise

Standard deviation of observation noise

Example

>>> import numpy as np
>>> np.random.seed(42)  # For reproducible results
>>>
>>> # Initialize environment
>>> env = LaserTagPOMDP(discount_factor=0.95)
>>>
>>> # Get initial state and actions
>>> initial_state = env.initial_state_dist().sample()[0]
>>> actions = env.get_actions()
>>>
>>> # Sample complete step using convenience method
>>> action = actions[0]
>>> next_state, observation, reward = env.sample_next_step(initial_state, action)
>>>
>>> # Check terminal condition
>>> env.is_terminal(initial_state)
False
cache_visualization(history, cache_path)[source]

Cache visualization of the LaserTag episode as an animated GIF.

Creates an animated visualization showing: - Robot movement (red circle) - Opponent movement (blue circle) - Walls (black squares) - Dangerous areas (red circles) - Action arrows showing robot’s intended movement - Laser measurements (green rays from robot position) - Belief particles (if available) showing robot’s belief about opponent location - Grid boundaries and coordinate system

Parameters:
  • history (List[StepData]) – The history of states, actions, and observations from an episode

  • cache_path (Path) – Path where to save the visualization GIF

Raises:
  • ValueError – If history is empty or contains invalid data

  • TypeError – If cache_path is not a Path object or doesn’t end with .gif

Return type:

None

compute_metrics(histories)[source]

Compute LaserTag POMDP specific metrics from simulation histories.

Return type:

List[MetricValue]

Parameters:

histories (List[History])

get_actions()[source]

Get all possible actions in the discrete action space.

Return type:

List[int]

get_metric_names()[source]

Get names of LaserTag POMDP specific metrics.

Returns:

tag_success_rate, average_episode_length, average_failed_tag_attempts, average_obstacle_collisions, average_dangerous_area_steps, and average_all_dangerous_encounters

Return type:

List[str]

hash_action(action)[source]

Return a hashable key consistent with action equality.

Used by tree-search planners to index action children of a belief node in O(1). The returned key MUST satisfy:

action_a == action_b   (per env's notion of equality)
==> hash_action(action_a) == hash_action(action_b)

Subclasses with non-hashable actions (e.g. np.ndarray) must override to return a hashable surrogate (tobytes() is the standard choice for ndarray actions, which mirrors the np.array_equal semantics used by the linear-scan fallback).

Parameters:

action (Any) – Action to hash.

Return type:

Hashable

Returns:

A hashable key derived from action.

initial_observation_dist()[source]

Get the initial observation distribution.

Return type:

Distribution

initial_state_dist()[source]

Get the initial state distribution.

Return type:

Distribution

is_equal_observation(observation1, observation2)[source]

Check if two observations are equal.

Observations are 8-dimensional laser measurements or terminal observations.

Return type:

bool

Parameters:
  • observation1 (Any)

  • observation2 (Any)

is_terminal(state)[source]

Check if a state is terminal.

Return type:

bool

Parameters:

state (ndarray)

observation_log_probability(next_state, action, observations)[source]

Log-probability of each candidate observation under (next_state, action).

Returns np.ndarray of shape (N,) where N is the number of candidate observations. Subclasses must implement.

Return type:

ndarray

Parameters:
observation_log_probability_per_state(next_states, action, observation)[source]

Log-probability of one observation under each candidate next-state.

Used by particle filters: given N candidate next-states and ONE observation, return N log-likelihoods.

The default implementation falls back to a per-state Python loop delegating to observation_log_probability(). Native-backed envs (those whose observation kernel exposes batch_log_likelihood(next_states_array, observation_array)) should override to avoid the loop.

Parameters:
  • next_states (Any) – A sequence (length N) or ndarray of shape (N, *dim) of candidate next-states.

  • action (int) – The action that was executed.

  • observation (Any) – A single observation.

Return type:

ndarray

Returns:

ndarray of shape (N,) with log-probabilities or log-PDFs.

reward(state, action, next_state=None)[source]

Calculate the immediate reward for a state-action transition.

The wall / dangerous-area penalty is computed against the realised post-action robot position taken from next_state. When the caller omits next_state (e.g., the open-loop scalar API path) the method resamples a transition via sample_next_state() so the penalty is always scored against an actual draw from the transition kernel — never against the open-loop state + action_vector intended position. Environment.sample_next_step() threads its sampled next_state into this method so trajectory and reward agree on the same realisation.

Return type:

float

Parameters:
reward_batch(states, action, next_states=None)[source]

Vectorised reward for a batch of states under a single action.

When next_states is supplied the danger-area / wall penalty is evaluated against the realised positions in next_states[:, :2] (matching the contract honoured by Environment.sample_next_step()). When it is None the method resamples via sample_next_state_batch() whenever penalty terms exist, then delegates to the reward model so reward and trajectory remain consistent end-to-end.

Return type:

ndarray

Parameters:
  • states (Any)

  • action (int)

  • next_states (Any)

sample_next_state(state, action, n_samples=1)[source]

Sample one or more next states for (state, action).

Hot-path entry point used by MCTS planners and particle filters. Subclasses must implement.

Returns:

a single next state of the env’s native type. When n_samples > 1: an array-like of length n_samples (numeric envs return np.ndarray of shape (n_samples, *dim); structured envs return List[T]).

Return type:

Any

Parameters:
sample_next_state_batch(states, action)[source]

Sample one next state per input state, all under the same action.

Used by particle filters: given N current particles and one action, draw N next states (one per particle) in a single vectorized call.

The default implementation falls back to a per-state Python loop delegating to sample_next_state(). Native-backed envs (those whose state-transition kernel exposes batch_sample(states_array)) should override to avoid the loop.

Parameters:
  • states (Any) – A sequence (length N) or ndarray of shape (N, *dim) of input particles.

  • action (int) – A single action to apply to every particle.

Returns:

np.ndarray of shape (N, *dim). For structured envs (Tiger strings, Pacman tuples): a list of length N.

Return type:

ndarray

sample_observation(next_state, action, n_samples=1)[source]

Sample one or more observations for (next_state, action).

Hot-path entry point used by MCTS planners and particle filters. Subclasses must implement.

Returns:

a single observation. When n_samples > 1: an array-like of length n_samples.

Return type:

Any

Parameters:
simulate_random_rollout(state, action_sampler, max_depth, discount_factor, depth=0)[source]
Return type:

float

Parameters:
  • state (Any)

  • action_sampler (Any)

  • max_depth (int)

  • discount_factor (float)

  • depth (int)

transition_log_probability(state, action, next_states)[source]

Log-probability of each candidate next state under (state, action).

Returns np.ndarray of shape (N,) where N is the number of candidate next states. Subclasses must implement.

Return type:

ndarray

Parameters:
class POMDPPlanners.environments.laser_tag_pomdp.laser_tag_pomdp.LaserTagPOMDPMetrics(*values)[source]

Bases: Enum

Metric names for LaserTag POMDP environment.

AVERAGE_ALL_DANGEROUS_ENCOUNTERS = 'average_all_dangerous_encounters'
AVERAGE_DANGEROUS_AREA_STEPS = 'average_dangerous_area_steps'
AVERAGE_EPISODE_LENGTH = 'average_episode_length'
AVERAGE_FAILED_TAG_ATTEMPTS = 'average_failed_tag_attempts'
AVERAGE_OBSTACLE_COLLISIONS = 'average_obstacle_collisions'
GOAL_REACHING_RATE = 'goal_reaching_rate'
TAG_SUCCESS_RATE = 'tag_success_rate'
class POMDPPlanners.environments.laser_tag_pomdp.laser_tag_pomdp.RewardModelType(*values)[source]

Bases: Enum

Reward-model variants selectable on LaserTagPOMDP.

CONSTANT_HAZARD_PENALTY = 'constant_hazard_penalty'
DISTANCE_DECAYED_HAZARD_PENALTY = 'distance_decayed_hazard_penalty'
ZERO_MEAN_HAZARD_SHOCK = 'zero_mean_hazard_shock'

POMDPPlanners.environments.laser_tag_pomdp.laser_tag_visualizer module

LaserTag POMDP Visualization Module.

This module provides visualization functionality for LaserTag POMDP environments, creating animated GIF visualizations of episodes.

class POMDPPlanners.environments.laser_tag_pomdp.laser_tag_visualizer.LaserTagVisualizer(floor_shape, walls, dangerous_areas, dangerous_area_radius)[source]

Bases: object

Handles visualization for LaserTag POMDP environments.

Creates animated GIF visualizations showing robot movement, opponent movement, walls, laser measurements, belief particles, and action indicators.

Parameters:
floor_shape

Grid dimensions as (rows, cols)

walls

Set of wall positions as (row, col) tuples

dangerous_areas

List of dangerous area center positions

dangerous_area_radius

Radius around dangerous area centers

create_visualization(history, cache_path)[source]

Create animated GIF visualization of a LaserTag episode.

Creates an animated visualization showing: - Robot movement (red circle with path trail) - Opponent movement (blue circle with path trail) - Walls (black squares) - Dangerous areas (red circles) - Action arrows showing robot’s intended movement - Laser measurements (green rays from robot position) - Belief particles (if available) showing robot’s belief about opponent location - Grid boundaries and coordinate system - Step counter and action labels

Parameters:
  • history (List[StepData]) – The history of states, actions, and observations from an episode

  • cache_path (Path) – Path where to save the visualization GIF

Raises:
  • ValueError – If history is empty or contains invalid data

  • TypeError – If cache_path is not a Path object or doesn’t end with .gif

Return type:

None