Skip to content
Merged
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
7 changes: 6 additions & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
When doing a code review, keep your suggestions focused on real issues in readability, maintainability, performance, security, and adherence to best practices. Avoid suggesting trivial minor issues like unused imports, American vs British spelling or minor formatting tweaks unless they significantly impact the quality of the code.
# Copilot Instructions

- Do not comment on docstring grammar or punctuation.
- Focus on logic, correctness, and API design
- Assume Black and Ruff enforce formatting
- Ignore trivial, non functional changes, namely unused imports and formatting changes.
2 changes: 1 addition & 1 deletion utama_core/run/strategy_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,7 @@ def run_test(
signal.signal(signal.SIGINT, self._handle_sigint)

passed = True
n_episodes = test_manager.get_n_episodes()
n_episodes = test_manager.n_episodes
if not rsim_headless and self.rsim_env:
self.rsim_env.render_mode = "human"
if self.sim_controller is None:
Expand Down
43 changes: 32 additions & 11 deletions utama_core/tests/common/abstract_test_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,19 @@ class TestingStatus(Enum):


class AbstractTestManager(ABC):
"""Abstract base class for test managers to run strategy tests."""

### To be specified in your test manager ###
n_episodes: int # number of episodes for the test
############################################

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What does the comment above mean? Can you explain? @energy-in-joles

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

just trying to box the area that needs to be edited. Let me make it clearer

def __init_subclass__(cls):
super().__init_subclass__()
if not hasattr(cls, "n_episodes"):
raise NotImplementedError(f"Subclass {cls.__name__} must define 'n_episodes' class attribute.")

def __init__(self):
# change episode_i to current_episode_i
self.episode_i = 0
self.current_episode_number = 0
self.my_strategy: AbstractStrategy = None
self.opp_strategy: AbstractStrategy = None

Expand All @@ -28,22 +38,33 @@ def load_strategies(self, my_strategy: AbstractStrategy, opp_strategy: AbstractS
self.my_strategy = my_strategy
self.opp_strategy = opp_strategy

def update_episode_n(self, episode_i: int):
def update_episode_n(self, current_episode_number: int):
"""Method is used to sync test_manager on the iteration number that strategyRunner thinks it is on."""
self.episode_i = episode_i
self.current_episode_number = current_episode_number

### START OF FUNCTIONS TO BE IMPLEMENTED FOR YOUR MANAGER ###

@abstractmethod
def reset_field(self, sim_controller: AbstractSimController, game: Game):
"""Method is called at start of each test episode in strategyRunner.run_test Reset position of robots and ball
for the next strategy test."""
"""
Method is called at start of each test episode in strategyRunner.run_test().
Use this to reset position of robots and ball for the next episode.
Args:
sim_controller (AbstractSimController): The simulation controller to manipulate robot positions.
game (Game): The current game state.
"""
...

@abstractmethod
def eval_status(self, game: Game) -> TestingStatus:
"""Method is called on each iteration in strategyRunner.run_test Evaluate the status of the test episode."""
...
"""
Method is called on each iteration in strategyRunner.run_test Evaluate the status of the test episode.

@abstractmethod
def get_n_episodes(self) -> int:
"""Method is called at start of strategyRunner.run_test Get the number of episodes to run for the test."""
Returns the current status of the test episode:
- TestingStatus.SUCCESS: test passed (terminate the episode with success)
- TestingStatus.FAILURE: test failed (terminate the episode with failure)
- TestingStatus.IN_PROGRESS: test still ongoing (continue running the episode)
"""
...

### END OF FUNCTIONS TO BE IMPLEMENTED FOR YOUR MANAGER ###
6 changes: 2 additions & 4 deletions utama_core/tests/motion_planning/multiple_robots_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ class MultiRobotScenario:
class MultiRobotTestManager(AbstractTestManager):
"""Test manager for multiple robots with collision detection."""

n_episodes = 1

def __init__(self, scenario: MultiRobotScenario):
super().__init__()
self.scenario = scenario
self.n_episodes = 1
self.all_reached = False
self.collision_detected = False
self.min_distance = float("inf")
Expand Down Expand Up @@ -151,9 +152,6 @@ def eval_status(self, game: Game):

return TestingStatus.IN_PROGRESS

def get_n_episodes(self):
return self.n_episodes


def test_mirror_swap(
headless: bool,
Expand Down
6 changes: 2 additions & 4 deletions utama_core/tests/motion_planning/random_movement_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@ class RandomMovementScenario:
class RandomMovementTestManager(AbstractTestManager):
"""Test manager for random movement with collision detection."""

n_episodes = 1

def __init__(self, scenario: RandomMovementScenario):
super().__init__()
self.scenario = scenario
self.n_episodes = 1
self.collision_detected = False
self.min_distance = float("inf")
self.targets_reached_count: Dict[int, int] = {}
Expand Down Expand Up @@ -122,9 +123,6 @@ def eval_status(self, game: Game):

return TestingStatus.IN_PROGRESS

def get_n_episodes(self):
return self.n_episodes

def update_target_reached(self, robot_id: int):
"""Called by strategy when a robot reaches a target."""
if robot_id in self.targets_reached_count:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,12 @@ class MovingObstacleScenario:
class MovingObstacleTestManager(AbstractTestManager):
"""Test manager that validates dynamic obstacle avoidance and target completion."""

n_episodes = 1

def __init__(self, scenario: MovingObstacleScenario, robot_id: int):
super().__init__()
self.scenario = scenario
self.robot_id = robot_id
self.n_episodes = 1
self.endpoint_reached = False
self.collision_detected = False
self.min_obstacle_distance = float("inf")
Expand Down Expand Up @@ -137,9 +138,6 @@ def eval_status(self, game: Game):

return TestingStatus.IN_PROGRESS

def get_n_episodes(self):
return self.n_episodes


@pytest.mark.parametrize(
"obstacle_scenario",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@ class CollisionAvoidanceScenario:
class CollisionAvoidanceTestManager(AbstractTestManager):
"""Test manager that validates obstacle avoidance and target completion."""

n_episodes = 1

def __init__(self, scenario: CollisionAvoidanceScenario, robot_id: int):
super().__init__()
self.scenario = scenario
self.robot_id = robot_id
self.n_episodes = 1
self.endpoint_reached = False
self.collision_detected = False
self.min_obstacle_distance = float("inf")
Expand Down Expand Up @@ -120,9 +121,6 @@ def eval_status(self, game: Game):

return TestingStatus.IN_PROGRESS

def get_n_episodes(self):
return self.n_episodes


@pytest.mark.parametrize(
"obstacle_config",
Expand Down
Loading