POMDPPlanners.environments.carla_pomdp package

CARLA POMDP environment module.

This module provides a forward-only adapter exposing the CARLA autonomous-driving simulator as a ground-truth world for the POMDPPlanners episode loop, plus the planner-side generative-model interface paired with it and a concrete reference model.

Classes:

CarlaPOMDP: Forward-only adapter exposing a CARLA session as a world Environment. CarlaModelPOMDP: Abstract generative-model interface over the CARLA schema. FactoredCarlaModelPOMDP: Concrete CARLA model with a factored observation model. DreamerCarlaModelPOMDP: Concrete CARLA model backed by a Dreamer world model. PerceivedAgentsBelief: Particle belief that stamps the perception pipeline’s agent block. CarlaPerceptionPipeline: Standalone, swappable perception + prediction stage. CarlaServerPool: Context manager owning N headless CARLA servers for parallel episodes. CarlaServerLease: Connection endpoints of one leased pool server.

class POMDPPlanners.environments.carla_pomdp.CarlaModelPOMDP(discount_factor, action_presets=None, max_tracked_agents=5, observation_models=None, name=None)[source]

Bases: DiscreteActionsEnvironment

Abstract generative-model interface paired with the forward-only CARLA world.

Concrete subclasses supply the dynamics — sample_next_state(), transition_log_probability(), reward(), is_terminal(), and the initial-distribution hooks — while this base owns the fixed CARLA schema shared by every model (the ego + agent-slot state layout, the discrete action set, and the observation-dict hashing/equality) and the observation model. The observation is factored by channel: this base holds a {channel: CarlaObservationModel} map (observation_models) and composes it — sample_observation() perceives each channel of the clean observation built from the state, observation_log_probability() sums the per-channel densities, and encode_observation() (the single raw-observation seam) perceives each channel of the forward-only world’s raw reading into the same perceived space, so the belief filter and planner search operate on one consistent (encoded) observation. Two models differ in their observation only by the channel models they hold; a model that scores observations for a belief update needs every channel to provide a density (supports_density). A subclass whose observation is not a per-channel clean transform (e.g. a learned latent decoder) may instead override sample_observation(), observation_log_probability() and encode_observation() directly.

Parameters:
action_presets

Discrete (throttle, steer, brake) control triples; the discrete action set is the indices into this list.

max_tracked_agents

Number of fixed agent slots in the state/observation.

observation_models

The per-channel {channel: CarlaObservationModel} map composed to produce the observation, or None for a subclass that overrides the observation methods directly.

Note

This is an abstract base class and cannot be instantiated directly. See FactoredCarlaModelPOMDP for a concrete reference implementation.

encode_observation(observation)[source]

Perceive the world’s raw observation into the belief/planner observation space.

The forward-only world emits a raw, fully-detected observation; each channel model degrades its channel (per-slot detection gating, occlusion, additive noise) into the perceived observation the belief filter and planner search operate in. This is the single raw-observation seam — every other observation method works in the perceived (encoded) space. A subclass without channel models (observation_models is None, e.g. a learned latent model) inherits the identity default.

Parameters:

observation (Any) – The raw {gnss, agents} observation emitted by the world.

Return type:

Any

Returns:

The perceived observation the belief and planner consume.

get_actions()[source]

Discrete action set: indices into action_presets.

Return type:

List[int]

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.

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.

observation_log_probability(next_state, action, observations)[source]

Log-density of observations under the per-channel perception given next_state.

Return type:

ndarray

Parameters:
  • next_state (Any)

  • action (Any)

  • observations (Any)

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

Sample n_samples next states for (state, action).

Return type:

ndarray

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

Sample n_samples observations by perceiving next_state’s clean reading.

Return type:

Any

Parameters:
  • next_state (Any)

  • action (Any)

  • n_samples (int)

abstractmethod transition_log_probability(state, action, next_states)[source]

Log-density of next_states under the transition model for (state, action).

Return type:

ndarray

Parameters:
class POMDPPlanners.environments.carla_pomdp.CarlaPOMDP(discount_factor, host='localhost', port=2000, town='Town03', sensor_config=None, action_presets=None, record_camera=False, camera_config=None, include_camera=True, include_lidar=True, include_traffic_light=True, observation_camera_config=None, lidar_config=None, fixed_delta_seconds=0.05, collision_penalty=100.0, desired_speed=8.0, out_lane_thresh=2.0, destination=None, goal_radius=5.0, min_route_length=100.0, success_reward=100.0, num_vehicles=30, num_walkers=10, max_tracked_agents=5, traffic_manager_port=8000, server_pool_dir=None, randomize_spawn=True, observation_extractor=None, vehicle_filter='vehicle.tesla.model3', timeout=10.0, seed=None, name=None, reward_range=None, output_dir=None, debug=False, use_queue_logger=False)[source]

Bases: Environment

Forward-only adapter exposing a CARLA session as a world POMDP.

The wrapper drives a CARLA server as the ground-truth world of an episode. It ticks the simulator exactly once per real interaction and serves the resulting next state, observation and reward from a small cache, because the POMDPPlanners episode loop requests those three quantities through separate method calls while CARLA produces them atomically. The state is the ego vehicle’s ground-truth kinematics; the observation is a native CARLA sensor payload (GNSS by default), so the world is genuinely partially observed. See the module docstring for the exact state and observation variables, units, and frames.

Note

This is a world environment, not a generative model. It cannot sample a transition from an arbitrary state, so belief particle propagation and density queries are unsupported and raise NotImplementedError / RuntimeError. Pair it with a generative model environment on the planner (policy.environment).

Parameters:
host

CARLA server host.

port

CARLA server RPC port.

town

CARLA map name loaded on reset.

sensor_config

GNSS blueprint attributes (e.g. noise stddev) forwarded to the sensor; measurement noise, if any, is CARLA’s own.

action_presets

Discrete (throttle, steer, brake) control triples.

seed

Optional seed applied to the first reset for reproducibility.

Example

The environment is used as the forward-only world of an EpisodeRunner, paired with a separate generative model on the planner. It requires a running CARLA server, so this snippet is illustrative rather than executed:

env = CarlaPOMDP(discount_factor=0.95, town="Town03")
state = env.initial_state_dist().sample()[0]
next_state, observation, reward = env.sample_next_step(state, 0)
# state is [ego(7), nearest-agent slots...]; observation is a
# gnss/agents/camera/lidar dict hiding out-of-range / occluded agents.
cache_visualization(history, output_dir, episode_index)[source]

Save the episode as CARLA’s own chase-camera MP4 footage.

The episode history is unused: the video is the native camera rendering buffered live while the world was stepped, not a plot reconstructed from the step data. The environment must have been constructed with record_camera=True.

Parameters:
  • history (List[StepData]) – Episode step data (unused; kept for the hook signature).

  • output_dir (Path) – Directory into which the .mp4 video is written.

  • episode_index (int) – Zero-based episode index, used to name the file.

Return type:

None

compute_metrics(histories)[source]

Compute CARLA driving-quality metrics from episode histories.

Parameters:

histories (List[History]) – Episode histories to summarise.

Return type:

List[MetricValue]

Returns:

A list of MetricValue with 95% confidence bounds across episodes:

  • collision_rate: fraction of episodes that ended in a terminal state without reaching the destination (i.e. in a collision).

  • success_rate: fraction of episodes whose final state is within goal_radius of the episode destination.

  • route_completion: mean over episodes of the fraction of the planned route’s arc length covered by the end of the episode.

  • average_progress: mean per-episode ground distance travelled by the ego, in metres.

  • average_speed: mean ego speed over the driven trajectory, in m/s.

  • red_light_violation_rate: fraction of functioning-light stop-line crossings taken while the light was red (averaged over episodes that crossed at least one working light).

  • red_light_violation_count: mean number of red-light crossings per episode.

  • traffic_light_malfunction_count: mean number of crossings per episode where the light was off / unknown — recorded separately and never counted as a violation, since the light was not operating.

  • near_miss_count: mean number of near-miss events per episode (a run within _NEAR_MISS_DISTANCE of another vehicle that did not become a collision).

  • min_vehicle_distance: mean over episodes of the closest the ego came to any vehicle, in metres (a safety-margin metric; episodes that saw no vehicle are excluded).

get_metric_names()[source]

Names of the CARLA-specific evaluation metrics.

Returns:

collision_rate, success_rate, route_completion, average_progress, average_speed, red_light_violation_rate, red_light_violation_count, traffic_light_malfunction_count, near_miss_count and min_vehicle_distance.

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.

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 (Any) – 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:
  • next_state (Any)

  • action (Any)

  • observations (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 (Any) – 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.

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:

ndarray

Parameters:
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:
  • next_state (Any)

  • action (Any)

  • n_samples (int)

save_camera_video(cache_path, fps=20)[source]

Write CARLA’s own chase-camera footage to an MP4 video.

This is the native CARLA rendering (an RGB camera following the ego), not a reconstructed plot. Frames are captured live while the world is stepped, so the environment must have been constructed with record_camera=True and driven for at least one tick before calling this.

Parameters:
  • cache_path (Path) – File path ending in .mp4 where the video is saved.

  • fps (int) – Playback frame rate. Defaults to 20.

Raises:

RuntimeError – If camera recording is disabled or no frames were captured.

Return type:

None

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.carla_pomdp.CarlaPerceptionPipeline(max_tracked_agents=5, perception=None, tracker=None, sensor_fusion=True, stop_for_traffic_lights=True, obstacle_detection_range=30.0, dt=0.05, lidar_corridor_halfwidth=1.5, tracks=None)[source]

Bases: object

Standalone perception + prediction stage: raw observation -> agent slots + obstacle.

Composes a PerceptionModel (single-frame) and a MotionTracker (temporal), owns the tracker state, and produces the ego-frame agent block a belief stamps onto its particles plus a fused forward-obstacle distance. Immutable: process() returns a PerceptionOutput carrying a successor pipeline with the advanced tracks.

It is a whole-observation sensor-fusion stage (fusing lidar/camera into the agent block), not a per-channel CarlaObservationModel: it perceives a whole observation and exposes no observation density. The world threads its tracker state forward via process(); the perceive() convenience method returns a single perceived observation without carrying the advanced tracks, for callers that only need one reading.

Parameters:
max_tracked_agents

Number of agent slots produced in the agent block.

perception

The single-frame PerceptionModel.

tracker

The temporal MotionTracker.

sensor_fusion

Whether the fused lidar/camera forward obstacle is reported.

stop_for_traffic_lights

Whether a red/amber light is reported as a forward obstacle.

obstacle_detection_range

Only obstacles nearer than this (m) are reported.

dt

Tracker time step (s).

Example

>>> import numpy as np
>>> pipeline = CarlaPerceptionPipeline(max_tracked_agents=1)
>>> obs = {"lidar": np.zeros((0, 4)), "camera": np.zeros((8, 8, 3), dtype=np.uint8)}
>>> output = pipeline.process(obs)
>>> output.agent_rows.shape
(1, 5)
perceive(clean_observation)[source]

Perceive one observation, replacing its agents block with tracked slots.

Unlike process() this discards the advanced tracker state: it yields a single perceived observation for callers that only need one reading. Use process() when the successor pipeline (advanced tracks) must be threaded forward.

Parameters:

clean_observation (Mapping[str, Any]) – The world’s raw observation dict.

Return type:

dict

Returns:

The perceived observation dict with the tracked agents block.

process(observation)[source]

Perceive and track one observation into an agent block and forward obstacle.

Parameters:

observation (Mapping[str, Any]) – The world’s raw observation dict.

Return type:

PerceptionOutput

Returns:

A PerceptionOutput with the agent block, the fused obstacle distance (or None), and the successor pipeline carrying the advanced tracks.

class POMDPPlanners.environments.carla_pomdp.CarlaServerLease(host, rpc_port, traffic_manager_port)[source]

Bases: object

Connection endpoints of one leased pool server.

Parameters:
  • host (str)

  • rpc_port (int)

  • traffic_manager_port (int)

host

Hostname the pool’s servers listen on.

rpc_port

CARLA RPC port of the leased server.

traffic_manager_port

Client-side Traffic Manager port reserved for the lease holder (unique per server so parallel clients never collide).

host: str
rpc_port: int
traffic_manager_port: int
class POMDPPlanners.environments.carla_pomdp.CarlaServerPool(n_servers, pool_dir=None, carla_root=None, rpc_port_base=2000, tm_port_base=8000, gpu_indices=None, extra_args=None, ready_timeout=120.0, command_factory=None)[source]

Bases: object

Context manager owning N headless CARLA servers plus their lease directory.

On start() (or with entry) it spawns n_servers headless CARLA servers — RPC ports rpc_port_base + RPC_PORT_STRIDE * i, Traffic Manager ports tm_port_base + i — writes the pool spec and lease files into pool_dir, and waits for every server to accept connections. On shutdown() (or with exit, or interpreter exit) it terminates them.

Worker processes claim a server with acquire_pool_lease(), or transparently by constructing CarlaPOMDP with server_pool_dir=pool.pool_dir. Run at most n_servers workers (e.g. JoblibConfig(n_jobs=n_servers) — the default n_jobs=-1 uses all cores and will exhaust the pool).

Parameters:
n_servers

Number of servers the pool launches.

handles

Live CarlaServerHandle objects (empty until started).

Example

Illustrative — requires a CARLA installation at $CARLA_ROOT:

with CarlaServerPool(n_servers=2, gpu_indices=[0, 1]) as pool:
    env = CarlaPOMDP(discount_factor=0.95, server_pool_dir=pool.pool_dir)
handles: List[CarlaServerHandle]
property pool_dir: Path

The pool directory holding the spec, lease, and log files.

Raises:

RuntimeError – If accessed before start() and no explicit pool_dir was configured.

shutdown()[source]

Terminate every server in the pool. Idempotent.

Return type:

None

start()[source]

Launch all servers, write the pool spec, and wait until every one is ready.

Launches every server first so their (slow) startups overlap, then blocks on readiness. On any failure the already-launched servers are terminated before the error propagates.

Return type:

CarlaServerPool

Returns:

This pool, for chaining.

Raises:
  • RuntimeError – If a server process exits before becoming ready.

  • TimeoutError – If a server is not ready within ready_timeout.

class POMDPPlanners.environments.carla_pomdp.DreamerCarlaModelPOMDP(world_model, discount_factor, action_presets=None, max_tracked_agents=5, continue_threshold=0.5, initial_observation=None, name=None)[source]

Bases: CarlaModelPOMDP

Concrete CARLA generative model whose dynamics are a trained Dreamer world model.

The planner-side state is the Dreamer latent; transitions, observations, reward, and termination are served by the injected DreamerWorldModel. The belief is seeded by encoding the world’s initial observation with the posterior.

Parameters:
world_model

The trained Dreamer RSSM backing every dynamic quantity.

continue_threshold

Termination fires when the continue head’s probability drops below this value.

Note

Reward comes from the world model’s learned reward head, not the analytic driving_quality_reward(); a Dreamer model predicts reward directly from its latent.

Example

>>> import numpy as np
>>>
>>> class _IdentityWorldModel:
...     latent_dim = 4
...     def encode(self, observation):
...         return np.zeros(self.latent_dim)
...     def imagine(self, latents, controls):
...         return np.asarray(latents, dtype=float)
...     def decode(self, latents):
...         batch = np.asarray(latents).shape[0]
...         return {"gnss": np.zeros((batch, 3)), "agents": np.zeros((batch, 25))}
...     def decode_log_prob(self, latents, observation):
...         return np.zeros(np.asarray(latents).shape[0])
...     def reward(self, latents):
...         return np.zeros(np.asarray(latents).shape[0])
...     def continue_prob(self, latents):
...         return np.ones(np.asarray(latents).shape[0])
>>>
>>> obs = {"gnss": np.zeros(3), "agents": np.zeros(25)}
>>> env = DreamerCarlaModelPOMDP(
...     _IdentityWorldModel(), discount_factor=0.95, initial_observation=obs)
>>>
>>> state = env.initial_state_dist().sample()[0]
>>> action = env.get_actions()[0]
>>> next_state, observation, reward = env.sample_next_step(state, action)
>>> sorted(observation)
['agents', 'gnss']
>>> env.is_terminal(state)
False
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_terminal(state)[source]

Check if a state is terminal.

Parameters:

state (Any) – 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-density of observations under the per-channel perception given next_state.

Return type:

ndarray

Parameters:
  • next_state (Any)

  • action (Any)

  • observations (Any)

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.

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 (Any) – 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.

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

Sample n_samples next states for (state, action).

Return type:

ndarray

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 n_samples observations by perceiving next_state’s clean reading.

Return type:

Any

Parameters:
  • next_state (Any)

  • action (Any)

  • n_samples (int)

transition_log_probability(state, action, next_states)[source]

Log-density of next_states under the transition model for (state, action).

Return type:

ndarray

Parameters:
class POMDPPlanners.environments.carla_pomdp.FactoredCarlaModelPOMDP(discount_factor, action_presets=None, max_tracked_agents=5, perception_range=50.0, occlusion_radius=1.5, pose_std=0.5, gnss_std=1e-05, detect_prob=0.95, desired_speed=8.0, out_lane_thresh=2.0, collision_penalty=100.0, observation=None, name=None)[source]

Bases: CarlaModelPOMDP

Concrete CARLA generative model pairing placeholder dynamics with factored perception.

The observation is composed per channel (held as self.observation_models): a FactoredAgentObservationModel on agents (detection gated by perception range and geometric occlusion, additive Gaussian pose noise) and a GnssObservationModel on gnss. The reward is the shared gym-carla driving-quality reward. The transition is a documented identity placeholder to be replaced with real dynamics per study.

Parameters:
observation_models

The per-channel {channel: CarlaObservationModel} map carrying the observation parameters (perception_range, occlusion_radius, pose_std, gnss_std, detect_prob).

desired_speed

Target longitudinal speed (m/s) used by the reward.

out_lane_thresh

Lateral offset (m) beyond which the reward penalises out-of-lane.

collision_penalty

Penalty scale applied on a terminal collision in the reward.

Example

>>> import numpy as np
>>> np.random.seed(42)  # For reproducible results
>>>
>>> from POMDPPlanners.environments.carla_pomdp.carla_pomdp import (
...     AGENT_SLOT_WIDTH, EGO_STATE_WIDTH)
>>> env = FactoredCarlaModelPOMDP(discount_factor=0.95)
>>>
>>> width = EGO_STATE_WIDTH + env.max_tracked_agents * AGENT_SLOT_WIDTH
>>> state = np.zeros(width)
>>> action = env.get_actions()[0]
>>>
>>> next_state, observation, reward = env.sample_next_step(state, action)
>>> sorted(observation)
['agents', 'gnss']
>>> env.is_terminal(state)
False
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_terminal(state)[source]

Check if a state is terminal.

Parameters:

state (Any) – 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.

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 (Any) – 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.

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

Sample n_samples next states for (state, action).

Return type:

ndarray

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

Log-density of next_states under the transition model for (state, action).

Return type:

ndarray

Parameters:
class POMDPPlanners.environments.carla_pomdp.LidarCameraPerceptionModel(lidar_corridor_halfwidth=1.5, traffic_light_source='camera')[source]

Bases: PerceptionModel

Default CARLA perception: lidar vehicle clustering + camera obstacle/traffic-light cues.

Vehicles are clustered from the lidar cloud, the forward obstacle fuses the lidar corridor clearance with the camera looming cue, and the traffic light is inferred from the camera image (no ground-truth channel is consulted) unless traffic_light_source='channel'.

Parameters:
  • lidar_corridor_halfwidth (float)

  • traffic_light_source (str)

lidar_corridor_halfwidth

Half-width (m) of the forward corridor scanned for an obstacle.

traffic_light_source

'camera' infers the light from the RGB image; 'channel' reads it from the observation’s traffic_light key.

Example

>>> import numpy as np
>>> model = LidarCameraPerceptionModel()
>>> obs = {"lidar": np.zeros((0, 4)), "camera": np.zeros((8, 8, 3), dtype=np.uint8)}
>>> model.detect(obs).vehicle_positions.shape
(0, 3)
detect(observation)[source]

Perceive vehicles, a forward obstacle, and a traffic light from one observation.

Parameters:

observation (Mapping[str, Any]) – The world’s raw observation dict (lidar/camera/agents/ traffic_light keys, any subset present).

Return type:

Detections

Returns:

The single-frame Detections for this observation.

class POMDPPlanners.environments.carla_pomdp.MotionTracker[source]

Bases: ABC

Abstract temporal prediction interface: prior tracks + detections -> tracks with velocity.

Note

This is an abstract base class and cannot be instantiated directly.

abstractmethod update(tracks, vehicle_positions, dt)[source]

Advance the tracker one step and return the (N, 5) track set with velocity.

Parameters:
  • tracks (Optional[ndarray]) – Prior (N, 5) tracks [rel_x, rel_y, vx, vy, confidence], or None.

  • vehicle_positions (ndarray) – (M, 3) detections [rel_x, rel_y, confidence].

  • dt (float) – Time step (s) since the previous update.

Return type:

ndarray

Returns:

The updated (N, 5) track set.

class POMDPPlanners.environments.carla_pomdp.OracleAgentPerceptionModel(max_tracked_agents=5, lidar_corridor_halfwidth=1.5)[source]

Bases: PerceptionModel

Ground-truth-agent perception for studies/tests: vehicles from the agents channel.

Reads the observation’s ground-truth agents rows as vehicle detections (position with confidence 1.0), still fusing lidar/camera for the forward obstacle and reading the light from the traffic_light channel. Use when a study wants exact agent positions rather than inferred ones; the tracker then re-estimates velocity from the position stream.

Parameters:
  • max_tracked_agents (int)

  • lidar_corridor_halfwidth (float)

max_tracked_agents

Number of fixed agent slots in the ground-truth agents channel.

lidar_corridor_halfwidth

Half-width (m) of the forward obstacle corridor.

Example

>>> import numpy as np
>>> model = OracleAgentPerceptionModel(max_tracked_agents=1)
>>> obs = {"agents": np.array([1.0, 8.0, 0.0, 0.0, 5.0])}
>>> model.detect(obs).vehicle_positions.shape
(1, 3)
detect(observation)[source]

Perceive vehicles, a forward obstacle, and a traffic light from one observation.

Parameters:

observation (Mapping[str, Any]) – The world’s raw observation dict (lidar/camera/agents/ traffic_light keys, any subset present).

Return type:

Detections

Returns:

The single-frame Detections for this observation.

class POMDPPlanners.environments.carla_pomdp.PerceivedAgentsBelief(particles, log_weights, max_tracked_agents=5, agent_pose_jitter=0.3, resampling=True, ess_factor=0.5)[source]

Bases: WeightedParticleBeliefReinvigoration

Weighted particle belief that stamps the observation’s agent block onto every particle.

After the standard particle-filter weight update and resample, the reinvigoration step writes the current observation’s agents block into every particle’s agent slots (plus optional per-particle jitter), leaving the ego block to the filter. The belief holds no perception state — perception is the planner model’s, applied upstream by encode_observation — so a plain observation with a perceived agents block is all it needs.

Parameters:
max_tracked_agents

Number of fixed agent slots carried in each particle.

agent_pose_jitter

Std of Gaussian noise added to each stamped agent’s [rel_x, rel_y, rel_yaw, rel_speed] pose, for particle diversity.

Example

>>> import numpy as np
>>> np.random.seed(0)
>>> from POMDPPlanners.environments.carla_pomdp.carla_pomdp import (
...     AGENT_SLOT_WIDTH, EGO_STATE_WIDTH)
>>> width = EGO_STATE_WIDTH + 1 * AGENT_SLOT_WIDTH
>>> particles = [np.zeros(width) for _ in range(4)]
>>> belief = PerceivedAgentsBelief(
...     particles=particles,
...     log_weights=np.log(np.ones(4) / 4),
...     max_tracked_agents=1,
... )
>>> observation = {  # a perceived agent 8 m ahead
...     "gnss": np.zeros(2),
...     "agents": np.array([1.0, 8.0, 0.0, 0.0, 5.0]),
... }
>>> base = WeightedParticleBelief(particles=particles, log_weights=belief.log_weights)
>>> refreshed = belief.reinvigorate("noop", observation, None, base)
>>> bool(np.asarray(refreshed.particles)[0, EGO_STATE_WIDTH] == 1.0)  # slot now present
True
reinvigorate(action, observation, pomdp, belief)[source]

Stamp the observation’s perceived agent block onto every particle.

Return type:

PerceivedAgentsBelief

Parameters:
class POMDPPlanners.environments.carla_pomdp.PerceptionModel[source]

Bases: ABC

Abstract single-frame perception interface: raw sensor dict -> Detections.

Note

This is an abstract base class and cannot be instantiated directly.

abstractmethod detect(observation)[source]

Perceive vehicles, a forward obstacle, and a traffic light from one observation.

Parameters:

observation (Mapping[str, Any]) – The world’s raw observation dict (lidar/camera/agents/ traffic_light keys, any subset present).

Return type:

Detections

Returns:

The single-frame Detections for this observation.

POMDPPlanners.environments.carla_pomdp.acquire_pool_lease(pool_dir)[source]

Claim one server from a CarlaServerPool for the current process.

The first call locks a free server’s lease file (exclusive non-blocking flock) and caches the result; subsequent calls from the same process with the same pool directory return the cached lease. The lock is held for the process lifetime and released by the kernel when the process exits, so a recycled worker’s server returns to the pool automatically.

Parameters:

pool_dir (Union[str, Path]) – Directory written by CarlaServerPool.start().

Return type:

CarlaServerLease

Returns:

The leased server’s connection endpoints.

Raises:
  • FileNotFoundError – If pool_dir does not contain a pool spec.

  • RuntimeError – If every server in the pool is already leased by another process (run at most n_servers workers).

Subpackages

Submodules

POMDPPlanners.environments.carla_pomdp.carla_belief module

Plain particle belief that stamps the observed agent block onto its particles.

Perception lives on the planner’s generative model, not here and not in the world: the forward-only CarlaPOMDP emits a raw, ground-truth observation, and the model’s encode_observation() degrades it into the perceived observation the belief receives, so the agents channel the belief sees is already the tracked object list. The belief therefore does no perception at all.

It still cannot be a bare particle filter, though: a weight-only update can reweight and propagate the agents a particle was seeded with, but it can never acquire a vehicle that appears mid-episode, and a slot seeded empty stays empty forever. PerceivedAgentsBelief closes that gap in the minimal way — after the ordinary particle-filter weight update and resample, it replaces every particle’s ego-frame agent block with the observation’s agents block (plus optional per-particle pose jitter for diversity), leaving the ego block to the filter. The perceived agents are the observation’s estimate, trusted rather than re-filtered as a per-particle latent.

The returned belief is itself a PerceivedAgentsBelief, so the stamping repeats on every step of the episode.

Note

The belief’s max_tracked_agents must match the width of the observation’s agents block, since that block is written straight into each particle’s fixed agent slots.

Classes:

PerceivedAgentsBelief: Particle belief that stamps the observed agent block onto particles.

class POMDPPlanners.environments.carla_pomdp.carla_belief.PerceivedAgentsBelief(particles, log_weights, max_tracked_agents=5, agent_pose_jitter=0.3, resampling=True, ess_factor=0.5)[source]

Bases: WeightedParticleBeliefReinvigoration

Weighted particle belief that stamps the observation’s agent block onto every particle.

After the standard particle-filter weight update and resample, the reinvigoration step writes the current observation’s agents block into every particle’s agent slots (plus optional per-particle jitter), leaving the ego block to the filter. The belief holds no perception state — perception is the planner model’s, applied upstream by encode_observation — so a plain observation with a perceived agents block is all it needs.

Parameters:
max_tracked_agents

Number of fixed agent slots carried in each particle.

agent_pose_jitter

Std of Gaussian noise added to each stamped agent’s [rel_x, rel_y, rel_yaw, rel_speed] pose, for particle diversity.

Example

>>> import numpy as np
>>> np.random.seed(0)
>>> from POMDPPlanners.environments.carla_pomdp.carla_pomdp import (
...     AGENT_SLOT_WIDTH, EGO_STATE_WIDTH)
>>> width = EGO_STATE_WIDTH + 1 * AGENT_SLOT_WIDTH
>>> particles = [np.zeros(width) for _ in range(4)]
>>> belief = PerceivedAgentsBelief(
...     particles=particles,
...     log_weights=np.log(np.ones(4) / 4),
...     max_tracked_agents=1,
... )
>>> observation = {  # a perceived agent 8 m ahead
...     "gnss": np.zeros(2),
...     "agents": np.array([1.0, 8.0, 0.0, 0.0, 5.0]),
... }
>>> base = WeightedParticleBelief(particles=particles, log_weights=belief.log_weights)
>>> refreshed = belief.reinvigorate("noop", observation, None, base)
>>> bool(np.asarray(refreshed.particles)[0, EGO_STATE_WIDTH] == 1.0)  # slot now present
True
reinvigorate(action, observation, pomdp, belief)[source]

Stamp the observation’s perceived agent block onto every particle.

Return type:

PerceivedAgentsBelief

Parameters:

POMDPPlanners.environments.carla_pomdp.carla_pomdp module

CARLA POMDP world environment.

This module adapts the CARLA autonomous-driving simulator to the POMDPPlanners Environment interface so it can serve as the ground-truth world in an EpisodeRunner.

CARLA is forward-only: it is a live Unreal server driven over a Python client that exposes reset/tick on a single true state and cannot be queried for a transition/observation density nor re-run from an arbitrary injected state. It therefore cannot act as a planner’s generative model. In the two-environment episode design the planner keeps its own generative model (policy.environment) and this wrapper only advances the single true state forward, one step per real interaction. Consequently CarlaPOMDP.transition_log_probability() and CarlaPOMDP.observation_log_probability() intentionally raise NotImplementedError — in the intended world/model split they are never called.

Unlike GymPOMDP (which is fully observed: observation equals state), CARLA is genuinely partially observed.

The world is populated each reset with surrounding autopilot traffic and walking pedestrians (via CARLA’s Traffic Manager), so it poses a genuine multi-agent perception problem rather than an empty course.

The state is the ego vehicle’s ground-truth kinematics and lane pose, [x, y, yaw, vx, vy, lat, heading_err], followed by fixed slots for the ``max_tracked_agents`` nearest other vehicles (ground truth). Each agent slot is [present, rel_x, rel_y, rel_yaw, rel_speed] expressed in the ego frame (rel_x forward, rel_y left, rel_yaw in radians, rel_speed in m/s); present is 1 for a filled slot and 0 for padding. The ego part is read straight from the simulator, where:

  • x, y: ego position in the CARLA map (world) frame, in metres, read from the actor transform’s location.

  • yaw: ego heading about the world Z axis, in degrees (CARLA convention), read from the actor transform’s rotation.

  • vx, vy: ego linear-velocity components in the world frame, in metres per second, read from the actor’s velocity vector.

  • lat: signed lateral offset from the centre of the nearest driving lane, in metres (positive to the lane’s left), from the CARLA map’s lane geometry.

  • heading_err: ego heading minus the lane direction, wrapped to [-pi, pi], in radians.

The state ends with one traffic-light slot, [present, rel_x, rel_y, state_code, time_to_change] (ego frame; state_code is a TRAFFIC_LIGHT_* code, time_to_change in seconds), carrying the light governing the ego as ground truth (present == 0 when none affects it). It is always in the state — used by the red-light-violation metrics — and is independent of whether the observation exposes the light (include_traffic_light); a planner can thus be scored for running reds even when it is given no light information.

(The vertical axis z and roll/pitch are intentionally omitted; the ego is modelled on the ground plane.)

The lane-relative terms (lat, heading_err) drive a gym-carla-style driving-quality reward: it rewards longitudinal progress along the lane while penalising overspeed, drifting out of lane, and harsh / high-speed steering, plus a per-step time cost and a terminal collision penalty. See REWARD_SPEED_WEIGHT and the sibling weights for the fixed coefficients.

The observation is a multi-modal dict of native CARLA sensor payloads:

  • "gnss": [lat, lon, alt] (always present) — latitude and longitude in degrees and altitude in metres, from a sensor.other.gnss reading.

  • "agents" (always present): the max_tracked_agents agent slots of the state flattened, reported raw at their true ego-frame poses. The world applies no perception, so this is the ground-truth channel; range-gating, occlusion and sensor noise are the planner model’s observation model, not the world’s.

  • "camera" (present iff include_camera): a front-facing RGB image, (H, W, 3) uint8, from a sensor.camera.rgb.

  • "lidar" (present iff include_lidar): a point cloud, (N, 4) float32 with rows [x, y, z, intensity] in the LiDAR sensor frame (metres; intensity normalised to [0, 1]), from a sensor.lidar.ray_cast. N varies per tick.

  • "traffic_light" (present iff include_traffic_light): [should_stop, distance_m]should_stop is 1.0 when the ego is affected by a red or yellow light (else 0.0) and distance_m is the forward distance to that light’s stop line. This is a privileged ground-truth read (no noise), letting a planner treat a red light as a virtual obstacle to stop for; disable it to withhold the signal entirely.

Any measurement noise is CARLA’s own, configured through the sensor blueprint attributes — the wrapper adds none.

Classes:

CarlaPOMDP: Forward-only adapter exposing a CARLA session as a world Environment.

class POMDPPlanners.environments.carla_pomdp.carla_pomdp.CarlaPOMDP(discount_factor, host='localhost', port=2000, town='Town03', sensor_config=None, action_presets=None, record_camera=False, camera_config=None, include_camera=True, include_lidar=True, include_traffic_light=True, observation_camera_config=None, lidar_config=None, fixed_delta_seconds=0.05, collision_penalty=100.0, desired_speed=8.0, out_lane_thresh=2.0, destination=None, goal_radius=5.0, min_route_length=100.0, success_reward=100.0, num_vehicles=30, num_walkers=10, max_tracked_agents=5, traffic_manager_port=8000, server_pool_dir=None, randomize_spawn=True, observation_extractor=None, vehicle_filter='vehicle.tesla.model3', timeout=10.0, seed=None, name=None, reward_range=None, output_dir=None, debug=False, use_queue_logger=False)[source]

Bases: Environment

Forward-only adapter exposing a CARLA session as a world POMDP.

The wrapper drives a CARLA server as the ground-truth world of an episode. It ticks the simulator exactly once per real interaction and serves the resulting next state, observation and reward from a small cache, because the POMDPPlanners episode loop requests those three quantities through separate method calls while CARLA produces them atomically. The state is the ego vehicle’s ground-truth kinematics; the observation is a native CARLA sensor payload (GNSS by default), so the world is genuinely partially observed. See the module docstring for the exact state and observation variables, units, and frames.

Note

This is a world environment, not a generative model. It cannot sample a transition from an arbitrary state, so belief particle propagation and density queries are unsupported and raise NotImplementedError / RuntimeError. Pair it with a generative model environment on the planner (policy.environment).

Parameters:
host

CARLA server host.

port

CARLA server RPC port.

town

CARLA map name loaded on reset.

sensor_config

GNSS blueprint attributes (e.g. noise stddev) forwarded to the sensor; measurement noise, if any, is CARLA’s own.

action_presets

Discrete (throttle, steer, brake) control triples.

seed

Optional seed applied to the first reset for reproducibility.

Example

The environment is used as the forward-only world of an EpisodeRunner, paired with a separate generative model on the planner. It requires a running CARLA server, so this snippet is illustrative rather than executed:

env = CarlaPOMDP(discount_factor=0.95, town="Town03")
state = env.initial_state_dist().sample()[0]
next_state, observation, reward = env.sample_next_step(state, 0)
# state is [ego(7), nearest-agent slots...]; observation is a
# gnss/agents/camera/lidar dict hiding out-of-range / occluded agents.
cache_visualization(history, output_dir, episode_index)[source]

Save the episode as CARLA’s own chase-camera MP4 footage.

The episode history is unused: the video is the native camera rendering buffered live while the world was stepped, not a plot reconstructed from the step data. The environment must have been constructed with record_camera=True.

Parameters:
  • history (List[StepData]) – Episode step data (unused; kept for the hook signature).

  • output_dir (Path) – Directory into which the .mp4 video is written.

  • episode_index (int) – Zero-based episode index, used to name the file.

Return type:

None

compute_metrics(histories)[source]

Compute CARLA driving-quality metrics from episode histories.

Parameters:

histories (List[History]) – Episode histories to summarise.

Return type:

List[MetricValue]

Returns:

A list of MetricValue with 95% confidence bounds across episodes:

  • collision_rate: fraction of episodes that ended in a terminal state without reaching the destination (i.e. in a collision).

  • success_rate: fraction of episodes whose final state is within goal_radius of the episode destination.

  • route_completion: mean over episodes of the fraction of the planned route’s arc length covered by the end of the episode.

  • average_progress: mean per-episode ground distance travelled by the ego, in metres.

  • average_speed: mean ego speed over the driven trajectory, in m/s.

  • red_light_violation_rate: fraction of functioning-light stop-line crossings taken while the light was red (averaged over episodes that crossed at least one working light).

  • red_light_violation_count: mean number of red-light crossings per episode.

  • traffic_light_malfunction_count: mean number of crossings per episode where the light was off / unknown — recorded separately and never counted as a violation, since the light was not operating.

  • near_miss_count: mean number of near-miss events per episode (a run within _NEAR_MISS_DISTANCE of another vehicle that did not become a collision).

  • min_vehicle_distance: mean over episodes of the closest the ego came to any vehicle, in metres (a safety-margin metric; episodes that saw no vehicle are excluded).

get_metric_names()[source]

Names of the CARLA-specific evaluation metrics.

Returns:

collision_rate, success_rate, route_completion, average_progress, average_speed, red_light_violation_rate, red_light_violation_count, traffic_light_malfunction_count, near_miss_count and min_vehicle_distance.

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.

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 (Any) – 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:
  • next_state (Any)

  • action (Any)

  • observations (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 (Any) – 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.

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:

ndarray

Parameters:
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:
  • next_state (Any)

  • action (Any)

  • n_samples (int)

save_camera_video(cache_path, fps=20)[source]

Write CARLA’s own chase-camera footage to an MP4 video.

This is the native CARLA rendering (an RGB camera following the ego), not a reconstructed plot. Frames are captured live while the world is stepped, so the environment must have been constructed with record_camera=True and driven for at least one tick before calling this.

Parameters:
  • cache_path (Path) – File path ending in .mp4 where the video is saved.

  • fps (int) – Playback frame rate. Defaults to 20.

Raises:

RuntimeError – If camera recording is disabled or no frames were captured.

Return type:

None

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.carla_pomdp.carla_pomdp.CarlaPOMDPMetrics(*values)[source]

Bases: Enum

Metric names for the CARLA POMDP environment.

AVERAGE_PROGRESS = 'average_progress'
AVERAGE_SPEED = 'average_speed'
COLLISION_RATE = 'collision_rate'
MIN_VEHICLE_DISTANCE = 'min_vehicle_distance'
NEAR_MISS_COUNT = 'near_miss_count'
RED_LIGHT_VIOLATION_COUNT = 'red_light_violation_count'
RED_LIGHT_VIOLATION_RATE = 'red_light_violation_rate'
ROUTE_COMPLETION = 'route_completion'
SUCCESS_RATE = 'success_rate'
TRAFFIC_LIGHT_MALFUNCTION_COUNT = 'traffic_light_malfunction_count'
POMDPPlanners.environments.carla_pomdp.carla_pomdp.driving_quality_reward(next_state, steer, collided, desired_speed, out_lane_thresh, collision_penalty, success=False, success_reward=0.0)[source]

Score a transition with a gym-carla-style driving-quality reward.

Rewards along-route progress and penalises overspeed, drifting off the route, harsh / high-speed steering, each elapsed step, and a terminal collision; reaching the destination earns a terminal success bonus. Shared by the CarlaPOMDP world and the planner-side factored model so the two score a transition identically by construction.

Parameters:
  • next_state (ndarray) – Resulting ego state [x, y, yaw(deg), vx, vy, lat, heading_err].

  • steer (float) – Steering command applied on the transition (from the action preset).

  • collided (bool) – Whether the transition ended in a terminal collision.

  • desired_speed (float) – Target longitudinal speed (m/s); exceeding it is penalised.

  • out_lane_thresh (float) – Lateral offset (m) beyond which the ego is treated as off route.

  • collision_penalty (float) – Penalty scale applied on a terminal collision.

  • success (bool) – Whether the transition reached the destination. Defaults to False.

  • success_reward (float) – Bonus applied on a successful arrival. Defaults to 0.0.

Return type:

float

Returns:

The scalar reward for the transition.

POMDPPlanners.environments.carla_pomdp.carla_server_pool module

Headless CARLA server pool for parallel episode simulation.

A single CARLA server serves one client at a time, so parallel episode execution (e.g. JoblibTaskManager with n_jobs > 1) needs one server per worker process. This module provides:

  • CarlaServerPool — a context manager that launches n_servers headless CARLA servers (CarlaUE4.sh -RenderOffScreen -nosound -carla-rpc-port=<port>), each on its own RPC port, and terminates them on exit.

  • acquire_pool_lease() — the worker-side counterpart: a process claims exactly one server from the pool via an flock-based lease and reuses it for the lifetime of the process.

Pool directory layout (written by CarlaServerPool.start()):

  • pool.json — the pool spec: host and per-server rpc_port / traffic_manager_port / lease-file name.

  • server_<i>.lease — one lock file per server. A worker holds a server by holding an exclusive flock on its lease file; the kernel releases the lock automatically when the worker process dies, so a recycled joblib worker frees its server for the replacement worker.

  • server_<i>.log — each server’s combined stdout/stderr, for diagnosing startup failures.

Wiring into the episode loop is transparent: pass server_pool_dir=pool.pool_dir to CarlaPOMDP and its lazily-built session resolves its connection ports from the per-process lease instead of the static host/port/traffic_manager_port.

Limitation: the lease is per process, so all CARLA environments in one worker process that share a pool directory share one server. This matches the simulator’s one-world-per-episode design.

Classes:

CarlaServerLease: Connection endpoints of one leased pool server. CarlaServerHandle: One spawned headless CARLA server subprocess. CarlaServerPool: Context manager owning N headless CARLA servers.

Example

Launch four headless servers and run parallel episodes against them (illustrative — requires a CARLA installation at $CARLA_ROOT):

from POMDPPlanners.environments.carla_pomdp import CarlaPOMDP, CarlaServerPool

with CarlaServerPool(n_servers=4) as pool:
    env = CarlaPOMDP(discount_factor=0.95, server_pool_dir=pool.pool_dir)
    # Hand ``env`` to POMDPSimulator with JoblibConfig(n_jobs=4); each
    # joblib worker process leases its own server on first connection.

Or manage a long-lived pool manually from the command line:

python -m POMDPPlanners.environments.carla_pomdp.carla_server_pool --n-servers 4
class POMDPPlanners.environments.carla_pomdp.carla_server_pool.CarlaServerHandle(process, rpc_port, traffic_manager_port, log_path, gpu_index=None)[source]

Bases: object

One spawned headless CARLA server subprocess.

Owns the process for its lifetime: readiness polling on the RPC port and process-group termination. Instances are created by CarlaServerPool.

Parameters:
process

The spawned server subprocess (its own session/process group).

rpc_port

RPC port the server was asked to listen on.

traffic_manager_port

Traffic Manager port reserved for this server’s client.

log_path

File receiving the server’s combined stdout/stderr.

gpu_index

GPU the server was pinned to, or None.

property is_running: bool

Whether the server process is still alive.

terminate(grace_seconds=10.0)[source]

Terminate the server’s process group (SIGTERM, then SIGKILL).

Parameters:

grace_seconds (float) – Seconds to wait after SIGTERM before escalating.

Return type:

None

wait_until_ready(timeout=120.0)[source]

Block until the server accepts TCP connections on its RPC port.

Parameters:

timeout (float) – Maximum seconds to wait.

Raises:
  • RuntimeError – If the server process exits before becoming ready.

  • TimeoutError – If the port is not accepting connections within timeout.

Return type:

None

class POMDPPlanners.environments.carla_pomdp.carla_server_pool.CarlaServerLease(host, rpc_port, traffic_manager_port)[source]

Bases: object

Connection endpoints of one leased pool server.

Parameters:
  • host (str)

  • rpc_port (int)

  • traffic_manager_port (int)

host

Hostname the pool’s servers listen on.

rpc_port

CARLA RPC port of the leased server.

traffic_manager_port

Client-side Traffic Manager port reserved for the lease holder (unique per server so parallel clients never collide).

host: str
rpc_port: int
traffic_manager_port: int
class POMDPPlanners.environments.carla_pomdp.carla_server_pool.CarlaServerPool(n_servers, pool_dir=None, carla_root=None, rpc_port_base=2000, tm_port_base=8000, gpu_indices=None, extra_args=None, ready_timeout=120.0, command_factory=None)[source]

Bases: object

Context manager owning N headless CARLA servers plus their lease directory.

On start() (or with entry) it spawns n_servers headless CARLA servers — RPC ports rpc_port_base + RPC_PORT_STRIDE * i, Traffic Manager ports tm_port_base + i — writes the pool spec and lease files into pool_dir, and waits for every server to accept connections. On shutdown() (or with exit, or interpreter exit) it terminates them.

Worker processes claim a server with acquire_pool_lease(), or transparently by constructing CarlaPOMDP with server_pool_dir=pool.pool_dir. Run at most n_servers workers (e.g. JoblibConfig(n_jobs=n_servers) — the default n_jobs=-1 uses all cores and will exhaust the pool).

Parameters:
n_servers

Number of servers the pool launches.

handles

Live CarlaServerHandle objects (empty until started).

Example

Illustrative — requires a CARLA installation at $CARLA_ROOT:

with CarlaServerPool(n_servers=2, gpu_indices=[0, 1]) as pool:
    env = CarlaPOMDP(discount_factor=0.95, server_pool_dir=pool.pool_dir)
handles: List[CarlaServerHandle]
property pool_dir: Path

The pool directory holding the spec, lease, and log files.

Raises:

RuntimeError – If accessed before start() and no explicit pool_dir was configured.

shutdown()[source]

Terminate every server in the pool. Idempotent.

Return type:

None

start()[source]

Launch all servers, write the pool spec, and wait until every one is ready.

Launches every server first so their (slow) startups overlap, then blocks on readiness. On any failure the already-launched servers are terminated before the error propagates.

Return type:

CarlaServerPool

Returns:

This pool, for chaining.

Raises:
  • RuntimeError – If a server process exits before becoming ready.

  • TimeoutError – If a server is not ready within ready_timeout.

POMDPPlanners.environments.carla_pomdp.carla_server_pool.acquire_pool_lease(pool_dir)[source]

Claim one server from a CarlaServerPool for the current process.

The first call locks a free server’s lease file (exclusive non-blocking flock) and caches the result; subsequent calls from the same process with the same pool directory return the cached lease. The lock is held for the process lifetime and released by the kernel when the process exits, so a recycled worker’s server returns to the pool automatically.

Parameters:

pool_dir (Union[str, Path]) – Directory written by CarlaServerPool.start().

Return type:

CarlaServerLease

Returns:

The leased server’s connection endpoints.

Raises:
  • FileNotFoundError – If pool_dir does not contain a pool spec.

  • RuntimeError – If every server in the pool is already leased by another process (run at most n_servers workers).

POMDPPlanners.environments.carla_pomdp.carla_server_pool.main(argv=None)[source]

CLI entry point: start a pool, print its directory, run until interrupted.

Parameters:

argv (Optional[Sequence[str]]) – CLI arguments; defaults to sys.argv[1:].

Return type:

int

Returns:

Process exit code (0 on clean shutdown).

POMDPPlanners.environments.carla_pomdp.carla_video module

Encode CARLA RGB camera frames as an MP4 video.

CarlaPOMDP can attach a chase RGB camera to the ego vehicle and buffer one rendered frame per simulator tick. This module turns that buffer of (H, W, 3) uint8 frames into an H.264 MP4 by piping the raw RGB bytes straight to an ffmpeg subprocess. Streaming the pixels to ffmpeg avoids matplotlib’s per-frame figure re-render, so the saved footage is CARLA’s own rendering and encoding it is roughly 2-3x faster than the previous matplotlib writer.

Functions:

write_frames_to_mp4: Encode a list of RGB frames as an MP4 video.

POMDPPlanners.environments.carla_pomdp.carla_video.write_frames_to_mp4(frames, cache_path, fps=20)[source]

Encode buffered CARLA chase-camera frames as an MP4 video.

The frames are streamed as raw rgb24 bytes to an ffmpeg subprocess, which encodes them to an H.264 MP4. This is CARLA’s own rendering, not a reconstructed plot.

Parameters:
  • frames (List[ndarray]) – Non-empty list of (H, W, 3) uint8 RGB frames, one per tick.

  • cache_path (Path) – File path ending in .mp4 where the video is saved.

  • fps (int) – Playback frame rate. Defaults to 20.

Raises:
  • TypeError – If cache_path is not a Path object.

  • ValueError – If frames is empty or cache_path does not end in .mp4.

  • RuntimeError – If ffmpeg is not on PATH or the encode fails.

Return type:

None