Daniel Ari Friedman e555897efa Updates
2025-02-07 09:36:14 -08:00

166 строки
5.8 KiB
Python

"""
Nestmate Agent Implementation
This module implements a simplified version of the Nestmate agent class,
representing an individual ant in the colony.
"""
import numpy as np
from enum import Enum
from dataclasses import dataclass
class TaskType(Enum):
"""Possible task types for a Nestmate agent."""
FORAGING = "foraging"
MAINTENANCE = "maintenance"
NURSING = "nursing"
DEFENSE = "defense"
EXPLORATION = "exploration"
@dataclass
class Position:
"""2D position with orientation."""
x: float
y: float
theta: float = 0.0
class Nestmate:
"""Individual ant agent with basic behaviors."""
def __init__(self, config: dict):
"""Initialize Nestmate agent."""
self.config = config
# Physical state
self.position = Position(0.0, 0.0, 0.0)
self.velocity = np.zeros(2)
self.energy = config['physical']['energy']['initial']
# Task state
self.current_task = TaskType.EXPLORATION
self.carrying = None
# Sensors
self.sensor_range = config['physical']['sensor_range']
# Movement parameters
self.max_speed = config['physical']['max_speed']
self.turn_rate = config['physical']['turn_rate']
def sense(self, world_state: dict) -> dict:
"""Process sensory inputs from environment."""
# Get nearby entities within sensor range
nearby = {
'food': [],
'nestmates': [],
'obstacles': [],
'pheromones': {}
}
# Process food sources
for food in world_state['resources']:
dist = self._distance_to(food.position)
if dist <= self.sensor_range:
nearby['food'].append((food, dist))
# Process other agents
for agent in world_state['agents']:
if agent != self:
dist = self._distance_to(agent.position)
if dist <= self.sensor_range:
nearby['nestmates'].append((agent, dist))
# Process pheromones
for p_type, value in world_state['pheromones'].items():
nearby['pheromones'][p_type] = value
return nearby
def decide_action(self, sensed: dict) -> tuple:
"""Decide next action based on current state and sensory input."""
# Default behavior: random walk
speed = self.max_speed
turn = np.random.uniform(-self.turn_rate, self.turn_rate)
# Task-specific behaviors
if self.current_task == TaskType.FORAGING:
# If carrying food, head back to nest
if self.carrying:
turn = self._angle_to_nest()
# Otherwise, follow food pheromones or explore
elif 'food' in sensed['pheromones']:
pheromone_gradient = sensed['pheromones']['food']
if np.any(pheromone_gradient > 0):
turn = self._follow_gradient(pheromone_gradient)
elif self.current_task == TaskType.EXPLORATION:
# Random walk with longer persistence
if np.random.random() < 0.1: # 10% chance to change direction
turn = np.random.uniform(-np.pi, np.pi)
return speed, turn
def update(self, dt: float, world_state: dict):
"""Update agent state."""
# Sense environment
sensed = self.sense(world_state)
# Decide action
speed, turn = self.decide_action(sensed)
# Update position and orientation
self.position.theta += turn * dt
self.position.theta = self.position.theta % (2 * np.pi)
dx = speed * np.cos(self.position.theta) * dt
dy = speed * np.sin(self.position.theta) * dt
self.position.x += dx
self.position.y += dy
# Update energy
self.energy -= self.config['physical']['energy']['consumption_rate'] * dt
if self.carrying:
self.energy -= self.config['physical']['energy']['consumption_rate'] * dt
# Consider task switching
self._consider_task_switch(sensed)
def _distance_to(self, other_pos: Position) -> float:
"""Calculate distance to another position."""
dx = other_pos.x - self.position.x
dy = other_pos.y - self.position.y
return np.sqrt(dx*dx + dy*dy)
def _angle_to_nest(self) -> float:
"""Calculate turn angle towards nest."""
# Simplified: assume nest is at (0,0)
dx = -self.position.x
dy = -self.position.y
target_angle = np.arctan2(dy, dx)
current_angle = self.position.theta
# Calculate shortest turn
diff = target_angle - current_angle
while diff > np.pi:
diff -= 2*np.pi
while diff < -np.pi:
diff += 2*np.pi
return np.clip(diff, -self.turn_rate, self.turn_rate)
def _follow_gradient(self, gradient: np.ndarray) -> float:
"""Calculate turn angle to follow a pheromone gradient."""
# Simplified: assume gradient gives us desired direction
target_angle = np.arctan2(gradient[1], gradient[0])
return self._angle_to_nest() # Reuse angle calculation
def _consider_task_switch(self, sensed: dict):
"""Consider switching current task."""
# Simple task switching based on energy and random chance
if self.energy < self.config['physical']['energy']['critical_level']:
self.current_task = TaskType.FORAGING
elif np.random.random() < 0.001: # 0.1% chance to switch tasks
available_tasks = list(TaskType)
available_tasks.remove(self.current_task)
self.current_task = np.random.choice(available_tasks)