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
8 changes: 4 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,16 @@ install-dev:

# Testing
test:
pytest tests/ -v
PYTHONPATH=. pytest tests/ -v

test-cov:
pytest tests/ --cov=ai_evo --cov-report=html --cov-report=term
PYTHONPATH=. pytest tests/ --cov=ai_evo --cov-report=html --cov-report=term

test-determinism:
pytest tests/test_determinism.py -v
PYTHONPATH=. pytest tests/test_determinism.py -v

test-performance:
pytest tests/test_performance.py -v
PYTHONPATH=. pytest tests/test_performance.py -v

# Running
run:
Expand Down
3 changes: 3 additions & 0 deletions ai_evo/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""AI Evolution Environment Package."""

__version__ = "1.0.0"
Binary file added ai_evo/__pycache__/__init__.cpython-312.pyc
Binary file not shown.
Binary file added ai_evo/__pycache__/brain.cpython-312.pyc
Binary file not shown.
Binary file added ai_evo/__pycache__/config.cpython-312.pyc
Binary file not shown.
Binary file added ai_evo/__pycache__/creature.cpython-312.pyc
Binary file not shown.
Binary file added ai_evo/__pycache__/evolution.cpython-312.pyc
Binary file not shown.
Binary file added ai_evo/__pycache__/genome.cpython-312.pyc
Binary file not shown.
Binary file added ai_evo/__pycache__/rng.cpython-312.pyc
Binary file not shown.
Binary file added ai_evo/__pycache__/simulation.cpython-312.pyc
Binary file not shown.
Binary file added ai_evo/__pycache__/spatial.cpython-312.pyc
Binary file not shown.
Binary file added ai_evo/__pycache__/stats.cpython-312.pyc
Binary file not shown.
Binary file added ai_evo/__pycache__/world.cpython-312.pyc
Binary file not shown.
4 changes: 0 additions & 4 deletions ai_evo/_init_.py

This file was deleted.

113 changes: 93 additions & 20 deletions ai_evo/brain.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,96 @@
import numpy as np

class RNG:
"""Central deterministic RNG wrapper."""
def __init__(self, seed: int):
self.seed = seed
self.rs = np.random.RandomState(seed)

# Basic distributions
def normal(self, loc=0.0, scale=1.0, size=None):
return self.rs.normal(loc, scale, size)
"""Neural network brain for creatures."""

def rand(self, *shape):
return self.rs.rand(*shape)

def randint(self, low, high=None, size=None):
return self.rs.randint(low, high, size)
import numpy as np
from typing import Tuple, Optional
from .genome import Genome

def choice(self, a, size=None, replace=True, p=None):
return self.rs.choice(a, size, replace, p)

def random_bool(self, p=0.5, size=None):
return self.rs.rand(*(size if isinstance(size, tuple) else (() if size is None else (size,)))) < p
class CreatureBrain:
"""Simple neural network brain for creature decision making."""

def __init__(self, genome: Genome, config=None):
"""Initialize brain with genome weights."""
self.genome = genome
self.weights = genome.brain_weights
self.input_size = 8 # Basic sensory inputs
self.output_size = 4 # movement directions

def process(self, inputs: np.ndarray) -> np.ndarray:
"""Process sensory inputs to generate actions."""
# Simple feedforward network
if len(inputs) != self.input_size:
inputs = np.pad(inputs, (0, max(0, self.input_size - len(inputs))))[:self.input_size]

if isinstance(self.weights, tuple) and len(self.weights) == 4:
# Handle tuple format (W1, b1, W2, b2)
W1, b1, W2, b2 = self.weights
hidden = np.tanh(np.dot(inputs, W1) + b1)
outputs = np.tanh(np.dot(hidden, W2) + b2)
return outputs
else:
# Handle simple array format - fallback
if hasattr(self.weights, 'shape') and len(self.weights) >= self.input_size * self.output_size:
weight_matrix = self.weights[:self.input_size * self.output_size].reshape(self.input_size, self.output_size)
outputs = np.dot(inputs, weight_matrix)
return np.tanh(outputs) # Activation function
else:
# Fallback for insufficient weights
return np.random.randn(self.output_size)

def get_movement(self, sensory_data: np.ndarray) -> Tuple[float, float]:
"""Get movement direction from sensory input."""
outputs = self.process(sensory_data)
dx = outputs[0] - outputs[1] # left-right
dy = outputs[2] - outputs[3] # up-down
return dx, dy

def get_sensory_input(self, creature, environment, nearby_creatures) -> np.ndarray:
"""Generate sensory input array for the creature."""
# Basic sensory inputs
inputs = np.zeros(self.input_size)

# Position relative to world center
inputs[0] = creature.position[0] / environment.width - 0.5
inputs[1] = creature.position[1] / environment.height - 0.5

# Energy level (normalized)
inputs[2] = creature.energy / 200.0 # Assume max energy is ~200

# Food in current location
x, y = int(creature.position[0]), int(creature.position[1])
x, y = environment.wrap(x, y)
inputs[3] = environment.food[y, x] / 10.0 # Assume max food is ~10

# Nearby creature information
if nearby_creatures:
# Count of nearby herbivores and carnivores
herbivore_count = sum(1 for c in nearby_creatures if c.species == "herbivore")
carnivore_count = sum(1 for c in nearby_creatures if c.species == "carnivore")
inputs[4] = min(herbivore_count / 5.0, 1.0) # Normalize
inputs[5] = min(carnivore_count / 5.0, 1.0) # Normalize

# Distance to nearest threat/prey
if creature.species == "herbivore":
carnivores = [c for c in nearby_creatures if c.species == "carnivore"]
if carnivores:
nearest_distance = min(creature.distance_to(c) for c in carnivores)
inputs[6] = max(0, 1.0 - nearest_distance / creature.genome.perception)
else: # carnivore
herbivores = [c for c in nearby_creatures if c.species == "herbivore"]
if herbivores:
nearest_distance = min(creature.distance_to(c) for c in herbivores)
inputs[7] = max(0, 1.0 - nearest_distance / creature.genome.perception)

return inputs

def forward(self, sensory_input: np.ndarray) -> dict:
"""Forward pass through the neural network, return action dictionary."""
outputs = self.process(sensory_input)

# Convert neural network outputs to action dictionary
return {
"move_x": float(outputs[0]) if len(outputs) > 0 else 0.0,
"move_y": float(outputs[1]) if len(outputs) > 1 else 0.0,
"eat": float(outputs[2]) if len(outputs) > 2 else 0.0,
"reproduce": float(outputs[3]) if len(outputs) > 3 else 0.0
}
76 changes: 76 additions & 0 deletions ai_evo/config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,81 @@
import numpy as np

class Config:
"""Configuration class for simulation parameters."""

def __init__(self,
seed: int = 42,
width: int = 100,
height: int = 100,
max_steps: int = 1000,
init_herbivores: int = 20,
init_carnivores: int = 10,
mutation_rate: float = 0.1,
mutation_strength: float = 0.1,
plant_growth_rate: float = 0.05,
plant_cap: float = 10.0,
max_energy: float = 200.0,
min_energy: float = 0.0,
herbivore_bite_cap: float = 3.0,
carnivore_gain_eff: float = 0.8,
reproduce_threshold: float = 120.0,
reproduce_cost_frac: float = 0.5,
move_cost_base: float = 0.1,
grid_cell: int = 8,
snapshot_every: int = 100,
# Trait bounds
speed_min: float = 0.1,
speed_max: float = 3.0,
size_min: float = 0.5,
size_max: float = 2.0,
aggression_min: float = 0.0,
aggression_max: float = 1.0,
perception_min: float = 0.5,
perception_max: float = 5.0,
energy_efficiency_min: float = 0.5,
energy_efficiency_max: float = 2.0,
reproduction_threshold_min: float = 60.0,
reproduction_threshold_max: float = 150.0,
lifespan_min: int = 500,
lifespan_max: int = 2000):
"""Initialize configuration with default values."""
self.seed = seed
self.width = width
self.height = height
self.max_steps = max_steps
self.init_herbivores = init_herbivores
self.init_carnivores = init_carnivores
self.mutation_rate = mutation_rate
self.mutation_strength = mutation_strength
self.plant_growth_rate = plant_growth_rate
self.plant_cap = plant_cap
self.max_energy = max_energy
self.min_energy = min_energy
self.herbivore_bite_cap = herbivore_bite_cap
self.carnivore_gain_eff = carnivore_gain_eff
self.reproduce_threshold = reproduce_threshold
self.reproduce_cost_frac = reproduce_cost_frac
self.move_cost_base = move_cost_base
self.grid_cell = grid_cell
self.snapshot_every = snapshot_every

# Trait bounds
self.speed_min = speed_min
self.speed_max = speed_max
self.size_min = size_min
self.size_max = size_max
self.aggression_min = aggression_min
self.aggression_max = aggression_max
self.perception_min = perception_min
self.perception_max = perception_max
self.energy_efficiency_min = energy_efficiency_min
self.energy_efficiency_max = energy_efficiency_max
self.reproduction_threshold_min = reproduction_threshold_min
self.reproduction_threshold_max = reproduction_threshold_max
self.lifespan_min = lifespan_min
self.lifespan_max = lifespan_max


class RNG:
"""Central deterministic RNG wrapper."""
def __init__(self, seed: int):
Expand Down
51 changes: 51 additions & 0 deletions ai_evo/creature.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""Creature class for individual animals in the simulation."""

import numpy as np
from typing import Optional, Tuple
from .genome import Genome


class Creature:
"""Represents an individual creature with genetics, behavior, and state."""

def __init__(self, id: str, genome: Genome, species: str, position: np.ndarray, energy: float,
generation: int = 0, parent_ids: Optional[list] = None):
"""Initialize a creature with basic properties."""
self.id = id
self.genome = genome
self.species = species
self.position = position.copy()
self.energy = energy
self.age = 0
self.generation = generation
self.parent_ids = parent_ids or []
self.alive = True

def can_reproduce(self) -> bool:
"""Check if creature has enough energy to reproduce."""
return self.energy >= self.genome.reproduction_threshold

def is_alive(self) -> bool:
"""Check if creature is still alive."""
return self.alive and self.energy > 0

def consume_energy(self, amount: float, min_energy: float, max_energy: float):
"""Consume energy, ensuring it stays within bounds."""
self.energy = max(min_energy, self.energy - amount)
if self.energy <= min_energy:
self.alive = False

def gain_energy(self, amount: float, max_energy: float):
"""Gain energy, ensuring it doesn't exceed maximum."""
self.energy = min(max_energy, self.energy + amount)

def update_position(self, dx: float, dy: float, world_width: int, world_height: int):
"""Update position with world boundary wrapping."""
self.position[0] = (self.position[0] + dx) % world_width
self.position[1] = (self.position[1] + dy) % world_height

def distance_to(self, other: 'Creature') -> float:
"""Calculate distance to another creature."""
dx = self.position[0] - other.position[0]
dy = self.position[1] - other.position[1]
return np.sqrt(dx*dx + dy*dy)
23 changes: 0 additions & 23 deletions ai_evo/creatures.py

This file was deleted.

Loading