Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ terra/digbench/
data/*
!data/custom/
!data/custom/**
*.pkl
*.pkl
.DS_Store
170 changes: 0 additions & 170 deletions isaac_sim/extract_plan.py

This file was deleted.

Binary file removed isaac_sim/test_map/dumpability.npy
Binary file not shown.
Binary file removed isaac_sim/test_map/image.npy
Binary file not shown.
1 change: 0 additions & 1 deletion isaac_sim/test_map/metadata.json

This file was deleted.

Binary file removed isaac_sim/test_map/occupancy.npy
Binary file not shown.
3 changes: 1 addition & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
"tensorflow_probability",
"osmnx",
"opencv-python",
"pathlib",
"scikit-image",
]

Expand All @@ -24,5 +23,5 @@
description="Minimalistic grid map environment built with JAX",
packages=find_packages(),
install_requires=requires,
python_requires=">=3.12",
python_requires=">=3.10",
)
143 changes: 128 additions & 15 deletions terra/agent.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import NamedTuple
from typing import NamedTuple, Optional, Tuple

import jax
import jax.numpy as jnp
Expand Down Expand Up @@ -49,21 +49,82 @@ def new(
max_traversable_y: int,
padding_mask: Array,
action_map: Array,
custom_pos: Optional[Tuple[int, int]] = None,
custom_angle: Optional[int] = None,

) -> tuple["Agent", jax.random.PRNGKey]:
"""
Create a new agent with specified parameters.

Args:
key: JAX random key
env_cfg: Environment configuration
max_traversable_x: Maximum traversable x coordinate
max_traversable_y: Maximum traversable y coordinate
padding_mask: Mask indicating obstacles
custom_pos: Optional custom position (x, y) to place the agent
custom_angle: Optional custom angle for the agent

Returns:
New agent instance and updated random key
"""
# Handle custom position or default based on config
has_custom_args = (custom_pos is not None) or (custom_angle is not None)

def use_custom_position(k):
# Create position based on custom args or defaults
temp_pos = IntMap(jnp.array(custom_pos)) if custom_pos is not None else IntMap(jnp.array([-1, -1]))
temp_angle = jnp.full((1,), custom_angle, dtype=IntMap) if custom_angle is not None else jnp.full((1,), -1, dtype=IntMap)

Copilot AI Aug 20, 2025

Copy link

Choose a reason for hiding this comment

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

The sentinel value -1 for unset angle should be defined as a constant to improve code clarity.

Suggested change
temp_angle = jnp.full((1,), custom_angle, dtype=IntMap) if custom_angle is not None else jnp.full((1,), -1, dtype=IntMap)
temp_angle = jnp.full((1,), custom_angle, dtype=IntMap) if custom_angle is not None else jnp.full((1,), UNSET_ANGLE, dtype=IntMap)

Copilot uses AI. Check for mistakes.

# Get default position for missing components
def_pos, def_angle, _ = _get_top_left_init_state(k, env_cfg)

# Combine custom and default values
pos = jnp.where(jnp.any(temp_pos < 0), def_pos, temp_pos)
angle = jnp.where(jnp.any(temp_angle < 0), def_angle, temp_angle)

# Check validity and return result using jax.lax.cond
valid = _validate_agent_position(
pos, angle, env_cfg, padding_mask,
env_cfg.agent.width, env_cfg.agent.height
)

# Define the true and false branches for jax.lax.cond
def true_fn(_):
return (pos, angle, k)

def false_fn(_):
return jax.lax.cond(
env_cfg.agent.random_init_state,
lambda k_inner: _get_random_init_state(
k_inner, env_cfg, max_traversable_x, max_traversable_y,
padding_mask, action_map, env_cfg.agent.width, env_cfg.agent.height,
),
lambda k_inner: _get_top_left_init_state(k_inner, env_cfg),
k
)

# Use jax.lax.cond to handle the validity check
return jax.lax.cond(valid, true_fn, false_fn, None)

def use_default_position(k):
# Use existing logic for random or top-left position
return jax.lax.cond(
env_cfg.agent.random_init_state,
lambda k_inner: _get_random_init_state(
k_inner, env_cfg, max_traversable_x, max_traversable_y,
padding_mask, action_map, env_cfg.agent.width, env_cfg.agent.height,
),
lambda k_inner: _get_top_left_init_state(k_inner, env_cfg),
k
)

# Use jax.lax.cond for JAX-compatible control flow
pos_base, angle_base, key = jax.lax.cond(
env_cfg.agent.random_init_state,
lambda k: _get_random_init_state(
k,
env_cfg,
max_traversable_x,
max_traversable_y,
padding_mask,
action_map,
env_cfg.agent.width,
env_cfg.agent.height,
),
lambda k: _get_top_left_init_state(k, env_cfg),
key,
has_custom_args,
use_custom_position,
use_default_position,
key
)

agent_state = AgentState(
Expand All @@ -82,6 +143,58 @@ def new(
return Agent(agent_state=agent_state, width=width, height=height, moving_dumped_dirt=moving_dumped_dirt), key


def _validate_agent_position(

Copilot AI Aug 20, 2025

Copy link

Choose a reason for hiding this comment

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

The function docstring should document the expected types and ranges for the parameters, particularly the Array types and what constitutes valid position/angle values.

Copilot uses AI. Check for mistakes.
pos_base: Array,
angle_base: Array,
env_cfg: EnvConfig,
padding_mask: Array,
agent_width: int,
agent_height: int,
) -> Array:
"""
Validate if an agent position is valid (within bounds and not intersecting obstacles).

Returns:
JAX array with boolean value indicating if the position is valid
"""
map_width = padding_mask.shape[0]
map_height = padding_mask.shape[1]

# Check if position is within bounds
max_center_coord = jnp.ceil(
jnp.max(jnp.array([agent_width / 2 - 1, agent_height / 2 - 1]))
).astype(IntMap)

max_w = jnp.minimum(env_cfg.maps.edge_length_px, map_width)
max_h = jnp.minimum(env_cfg.maps.edge_length_px, map_height)

within_bounds = jnp.logical_and(
jnp.logical_and(pos_base[0] >= max_center_coord, pos_base[0] < max_w - max_center_coord),
jnp.logical_and(pos_base[1] >= max_center_coord, pos_base[1] < max_h - max_center_coord)
)

# Check if position intersects with obstacles
def check_obstacle_intersection(_):
agent_corners_xy = get_agent_corners(
pos_base, angle_base, agent_width, agent_height, env_cfg.agent.angles_base
)
polygon_mask = compute_polygon_mask(agent_corners_xy, map_width, map_height)
has_obstacle = jnp.any(jnp.logical_and(polygon_mask, padding_mask == 1))
return jnp.logical_not(has_obstacle)

def return_false(_):
return jnp.array(False)

# Only check obstacles if we're within bounds (to avoid unnecessary computations)
valid = jax.lax.cond(
within_bounds,
check_obstacle_intersection,
return_false,
None
)

return valid

def _get_top_left_init_state(key: jax.random.PRNGKey, env_cfg: EnvConfig):
max_center_coord = jnp.ceil(
jnp.max(
Expand Down Expand Up @@ -174,4 +287,4 @@ def _check_intersection():
),
)

return pos_base, angle_base, key
return pos_base, angle_base, key
Loading