Add EntityConfig for entity hyperparameters and integrate into simulation configuration

This commit is contained in:
Sam 2025-11-09 18:58:09 -06:00
parent 19b946949d
commit e2d56ffb76
12 changed files with 157 additions and 89 deletions

View File

@ -5,7 +5,8 @@ from .simulation_config import (
HeadlessConfig,
InteractiveConfig,
ExperimentConfig,
OutputConfig
OutputConfig,
EntityConfig
)
from .config_loader import ConfigLoader
@ -15,5 +16,6 @@ __all__ = [
'InteractiveConfig',
'ExperimentConfig',
'OutputConfig',
'EntityConfig',
'ConfigLoader'
]

View File

@ -101,7 +101,14 @@ class ConfigLoader:
# Extract simulation config if present
sim_data = data.get('simulation', {})
simulation_config = SimulationConfig(**sim_data)
# Extract entities config if present
entities_data = sim_data.get('entities', {})
entities_config = EntityConfig(**entities_data)
# Create simulation config with entities
sim_data_without_entities = {k: v for k, v in sim_data.items() if k != 'entities'}
simulation_config = SimulationConfig(entities=entities_config, **sim_data_without_entities)
return HeadlessConfig(
max_ticks=data.get('max_ticks'),
@ -115,7 +122,14 @@ class ConfigLoader:
"""Convert dictionary to InteractiveConfig."""
# Extract simulation config if present
sim_data = data.get('simulation', {})
simulation_config = SimulationConfig(**sim_data)
# Extract entities config if present
entities_data = sim_data.get('entities', {})
entities_config = EntityConfig(**entities_data)
# Create simulation config with entities
sim_data_without_entities = {k: v for k, v in sim_data.items() if k != 'entities'}
simulation_config = SimulationConfig(entities=entities_config, **sim_data_without_entities)
return InteractiveConfig(
window_width=data.get('window_width', 0),
@ -190,5 +204,5 @@ class ConfigLoader:
print(f"Sample configs created in {output_path}")
# Import OutputConfig for the loader
from .simulation_config import OutputConfig, SimulationConfig
# Import Config classes for the loader
from .simulation_config import OutputConfig, SimulationConfig, EntityConfig

View File

@ -1,21 +1,50 @@
"""Simulation configuration classes for different modes."""
from dataclasses import dataclass, field
from typing import List, Optional
from config.constants import *
from typing import List, Optional, Dict, Any
@dataclass
class EntityConfig:
"""Configuration for entity hyperparameters."""
# Global entity settings (apply to all entity types unless overridden)
max_acceleration: float = 0.125
max_angular_acceleration: float = 0.25
max_velocity: float = 1.0
max_rotational_velocity: float = 3.0
# Entity type specific configs (all entity parameters should be defined here)
entity_types: Dict[str, Dict[str, Any]] = field(default_factory=lambda: {
"default_cell": {
"reproduction_energy": 1700,
"starting_energy": 1000,
"interaction_radius": 50,
"drag_coefficient": 0.02,
"energy_cost_base": 1.5,
"neural_network_complexity_cost": 0.08,
"movement_cost": 0.25,
"food_energy_value": 140,
"max_visual_width": 10,
"reproduction_count": 2,
"mutation_rate": 0.05,
"offspring_offset_range": 10
}
})
@dataclass
class SimulationConfig:
"""Configuration for simulation setup."""
grid_width: int = GRID_WIDTH
grid_height: int = GRID_HEIGHT
cell_size: int = CELL_SIZE
grid_width: int = 50
grid_height: int = 50
cell_size: int = 20
initial_cells: int = 50
initial_food: int = FOOD_OBJECTS_COUNT
food_spawning: bool = FOOD_SPAWNING
random_seed: int = RANDOM_SEED
default_tps: float = DEFAULT_TPS
initial_food: int = 500
food_spawning: bool = True
random_seed: int = 0
default_tps: float = 40.0
entities: EntityConfig = field(default_factory=EntityConfig)
@dataclass
@ -44,16 +73,7 @@ class HeadlessConfig:
output: OutputConfig = field(default_factory=OutputConfig)
# Simulation core config
simulation: SimulationConfig = field(default_factory=lambda: SimulationConfig(
grid_width=GRID_WIDTH,
grid_height=GRID_HEIGHT,
cell_size=CELL_SIZE,
initial_cells=50,
initial_food=FOOD_OBJECTS_COUNT,
food_spawning=FOOD_SPAWNING,
random_seed=RANDOM_SEED,
default_tps=DEFAULT_TPS
))
simulation: SimulationConfig = field(default_factory=SimulationConfig)
@dataclass
@ -75,16 +95,7 @@ class InteractiveConfig:
console_height: int = 120
# Simulation core config
simulation: SimulationConfig = field(default_factory=lambda: SimulationConfig(
grid_width=GRID_WIDTH,
grid_height=GRID_HEIGHT,
cell_size=CELL_SIZE,
initial_cells=350,
initial_food=FOOD_OBJECTS_COUNT,
food_spawning=FOOD_SPAWNING,
random_seed=RANDOM_SEED,
default_tps=DEFAULT_TPS
))
simulation: SimulationConfig = field(default_factory=lambda: SimulationConfig(initial_cells=350))
@dataclass

View File

@ -4,7 +4,6 @@
import pygame
import math
from config.constants import *
from world.base.brain import CellBrain
from world.objects import DefaultCell, FoodObject

View File

@ -9,7 +9,7 @@ from world.world import World, Position, Rotation
from world.simulation_interface import Camera
from .event_bus import EventBus, EventType, Event
from .timing import TimingController
from config.constants import *
from config.constants import RENDER_BUFFER
from config.simulation_config import SimulationConfig
@ -82,7 +82,8 @@ class SimulationCore:
x=random.randint(-half_width // 2, half_width // 2),
y=random.randint(-half_height // 2, half_height // 2)
),
Rotation(angle=0)
Rotation(angle=0),
entity_config=getattr(self.config, 'entities', None)
)
# Mutate the initial behavioral model for variety
cell.behavioral_model = cell.behavioral_model.mutate(3)

View File

@ -4,23 +4,22 @@ import sys
from pygame_gui import UIManager
from world.simulation_interface import Camera
from config.constants import *
from config.constants import BLACK, DEFAULT_TPS, MAX_FPS
from core.input_handler import InputHandler
from core.renderer import Renderer
from core.simulation_core import SimulationCore, SimulationConfig
from core.simulation_core import SimulationCore
from core.event_bus import EventBus
from ui.hud import HUD
import cProfile
import pstats
class SimulationEngine:
"""Interactive simulation engine with UI (wrapper around SimulationCore)."""
def __init__(self):
def __init__(self, config=None):
pygame.init()
self.config = config
self.event_bus = EventBus()
self._init_window()
self._init_simulation()
@ -47,11 +46,23 @@ class SimulationEngine:
def _init_window(self):
info = pygame.display.Info()
self.window_width = int(info.current_w // 1.5)
self.window_height = int(info.current_h // 1.5)
# Use config or defaults
if self.config:
self.window_width = self.config.window_width or int(info.current_w // 1.5)
self.window_height = self.config.window_height or int(info.current_h // 1.5)
vsync = 1 if self.config.vsync else 0
resizable = self.config.resizable
else:
self.window_width = int(info.current_w // 1.5)
self.window_height = int(info.current_h // 1.5)
vsync = 1
resizable = True
screen_flags = pygame.RESIZABLE if resizable else 0
self.screen = pygame.display.set_mode(
(self.window_width, self.window_height),
pygame.RESIZABLE, vsync=1
screen_flags, vsync=vsync
)
pygame.display.set_caption("Dynamic Abstraction System Testing")
self.clock = pygame.time.Clock()
@ -67,26 +78,30 @@ class SimulationEngine:
# Set HUD reference in input handler after both are created
self.input_handler.set_hud(self.hud)
# Pass config settings to HUD and input handler
if self.config:
self.input_handler.show_grid = self.config.show_grid
self.input_handler.show_interaction_radius = self.config.show_interaction_radius
self.input_handler.show_legend = self.config.show_legend
self._update_simulation_view()
def _init_simulation(self):
# Initialize default sim view rect (will be updated by _init_ui)
self.sim_view_width = self.window_width - 400 # Rough estimate for inspector width
self.sim_view_height = self.window_height - 200 # Rough estimate for control bar height
if self.config:
self.sim_view_width = self.window_width - self.config.inspector_width
self.sim_view_height = self.window_height - self.config.control_bar_height
else:
self.sim_view_width = self.window_width - 400 # Rough estimate for inspector width
self.sim_view_height = self.window_height - 200 # Rough estimate for control bar height
self.sim_view = pygame.Surface((self.sim_view_width, self.sim_view_height))
self.sim_view_rect = self.sim_view.get_rect(topleft=(200, 48)) # Rough estimate
# Create simulation core
sim_config = SimulationConfig(
grid_width=GRID_WIDTH,
grid_height=GRID_HEIGHT,
cell_size=CELL_SIZE,
initial_cells=350,
initial_food=FOOD_OBJECTS_COUNT,
food_spawning=FOOD_SPAWNING,
random_seed=RANDOM_SEED,
default_tps=DEFAULT_TPS
)
# Create simulation core with config
if self.config and self.config.simulation:
sim_config = self.config.simulation
else:
raise(ValueError("Simulation configuration must be provided for SimulationEngine."))
self.simulation_core = SimulationCore(sim_config, self.event_bus)
@ -147,7 +162,6 @@ class SimulationEngine:
def run(self):
"""Run the interactive simulation engine."""
print(f"World buffer: {self.simulation_core.world.current_buffer}")
self.simulation_core.start()
while self.running:

View File

@ -2,7 +2,6 @@
import time
import signal
import sys
from typing import Dict, Any, Optional, List
from dataclasses import dataclass

View File

@ -9,7 +9,7 @@ from pathlib import Path
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))
from config import ConfigLoader, InteractiveConfig
from config import ConfigLoader
from core.simulation_engine import SimulationEngine
@ -49,7 +49,7 @@ def main():
# Run simulation
try:
print("Starting interactive simulation...")
engine = SimulationEngine()
engine = SimulationEngine(config)
engine.run()
except KeyboardInterrupt:

View File

@ -8,6 +8,7 @@ dependencies = [
"numpy>=2.3.0",
"pandas>=2.3.3",
"pre-commit>=4.2.0",
"psutil>=7.0.0",
"pydantic>=2.11.5",
"pygame>=2.6.1",
"pygame-gui>=0.6.14",

2
uv.lock generated
View File

@ -142,6 +142,7 @@ dependencies = [
{ name = "numpy" },
{ name = "pandas" },
{ name = "pre-commit" },
{ name = "psutil" },
{ name = "pydantic" },
{ name = "pygame" },
{ name = "pygame-gui" },
@ -162,6 +163,7 @@ requires-dist = [
{ name = "numpy", specifier = ">=2.3.0" },
{ name = "pandas", specifier = ">=2.3.3" },
{ name = "pre-commit", specifier = ">=4.2.0" },
{ name = "psutil", specifier = ">=7.0.0" },
{ name = "pydantic", specifier = ">=2.11.5" },
{ name = "pygame", specifier = ">=2.6.1" },
{ name = "pygame-gui", specifier = ">=0.6.14" },

View File

@ -2,17 +2,14 @@ import math
import random
from config.constants import MAX_VELOCITY, MAX_ACCELERATION, MAX_ROTATIONAL_VELOCITY, MAX_ANGULAR_ACCELERATION
from typing import List, Any, Union, Optional
from world.base.brain import CellBrain
from world.behavioral import BehavioralModel
from world.world import Position, BaseEntity, Rotation
import pygame
from typing import Optional, List, Any, Union
from world.utils import get_distance_between_objects
from world.physics import Physics
from math import atan2, degrees
class DebugRenderObject(BaseEntity):
"""
@ -254,15 +251,45 @@ class DefaultCell(BaseEntity):
"""
Cell object
"""
def __init__(self, starting_position: Position, starting_rotation: Rotation) -> None:
def __init__(self, starting_position: Position, starting_rotation: Rotation, entity_config=None) -> None:
"""
Initializes the cell.
:param starting_position: The position of the object.
:param entity_config: Configuration for entity hyperparameters.
"""
super().__init__(starting_position, starting_rotation)
self.drag_coefficient: float = 0.1
# Use entity config or defaults
if entity_config:
cell_config = entity_config.entity_types.get("default_cell", {})
self.drag_coefficient: float = cell_config.get("drag_coefficient", 0.02)
self.energy: int = cell_config.get("starting_energy", 1000)
self.max_visual_width: int = cell_config.get("max_visual_width", 10)
self.interaction_radius: int = cell_config.get("interaction_radius", 50)
self.reproduction_energy: int = cell_config.get("reproduction_energy", 1700)
self.food_energy_value: int = cell_config.get("food_energy_value", 140)
self.energy_cost_base: float = cell_config.get("energy_cost_base", 1.5)
self.neural_network_complexity_cost: float = cell_config.get("neural_network_complexity_cost", 0.08)
self.movement_cost: float = cell_config.get("movement_cost", 0.25)
self.reproduction_count: int = cell_config.get("reproduction_count", 2)
self.mutation_rate: float = cell_config.get("mutation_rate", 0.05)
self.offspring_offset_range: int = cell_config.get("offspring_offset_range", 10)
else:
# Fallback to hardcoded defaults
self.drag_coefficient: float = 0.02
self.energy: int = 1000
self.max_visual_width: int = 10
self.interaction_radius: int = 50
self.reproduction_energy: int = 1700
self.food_energy_value: int = 140
self.energy_cost_base: float = 1.5
self.neural_network_complexity_cost: float = 0.08
self.movement_cost: float = 0.25
self.reproduction_count: int = 2
self.mutation_rate: float = 0.05
self.offspring_offset_range: int = 10
self.velocity: tuple[int, int] = (0, 0)
self.acceleration: tuple[int, int] = (0, 0)
@ -270,20 +297,17 @@ class DefaultCell(BaseEntity):
self.rotational_velocity: int = 0
self.angular_acceleration: int = 0
self.energy: int = 1000
self.behavioral_model: CellBrain = CellBrain()
self.max_visual_width: int = 10
self.interaction_radius: int = 50
self.flags: dict[str, bool] = {
"death": False,
"can_interact": True,
}
self.tick_count = 0
self.entity_config = entity_config
self.physics = Physics(0.02, 0.05)
self.physics = Physics(self.drag_coefficient, self.drag_coefficient*1.5)
def set_brain(self, behavioral_model: CellBrain) -> None:
@ -321,27 +345,28 @@ class DefaultCell(BaseEntity):
if distance_to_food < self.max_visual_width and food_objects:
# Use atomic consumption to prevent race conditions
if food_object.try_claim():
self.energy += 140
self.energy += self.food_energy_value
food_object.flag_for_death()
return self
if self.energy >= 1700:
if self.energy >= self.reproduction_energy:
# too much energy, split
duplicate_x, duplicate_y = self.position.get_position()
duplicate_x += random.randint(-self.max_visual_width, self.max_visual_width)
duplicate_y += random.randint(-self.max_visual_width, self.max_visual_width)
offspring = []
duplicate_x_2, duplicate_y_2 = self.position.get_position()
duplicate_x_2 += random.randint(-self.max_visual_width, self.max_visual_width)
duplicate_y_2 += random.randint(-self.max_visual_width, self.max_visual_width)
for _ in range(self.reproduction_count):
duplicate_x, duplicate_y = self.position.get_position()
duplicate_x += random.randint(-self.offspring_offset_range, self.offspring_offset_range)
duplicate_y += random.randint(-self.offspring_offset_range, self.offspring_offset_range)
new_cell = DefaultCell(Position(x=int(duplicate_x), y=int(duplicate_y)), Rotation(angle=random.randint(0, 359)))
new_cell.set_brain(self.behavioral_model.mutate(0.05))
new_cell = DefaultCell(
Position(x=int(duplicate_x), y=int(duplicate_y)),
Rotation(angle=random.randint(0, 359)),
entity_config=getattr(self, 'entity_config', None)
)
new_cell.set_brain(self.behavioral_model.mutate(self.mutation_rate))
offspring.append(new_cell)
new_cell_2 = DefaultCell(Position(x=int(duplicate_x_2), y=int(duplicate_y_2)), Rotation(angle=random.randint(0, 359)))
new_cell_2.set_brain(self.behavioral_model.mutate(0.05))
return [new_cell, new_cell_2]
return offspring
input_data = {
"distance": distance_to_food,
@ -371,7 +396,7 @@ class DefaultCell(BaseEntity):
movement_cost = abs(output_data["angular_acceleration"]) + abs(output_data["linear_acceleration"])
self.energy -= (self.behavioral_model.neural_network.network_cost * 0.08) + 1.5 + (0.25 * movement_cost)
self.energy -= (self.behavioral_model.neural_network.network_cost * self.neural_network_complexity_cost) + self.energy_cost_base + (self.movement_cost * movement_cost)
return self

View File

@ -1,6 +1,6 @@
from collections import defaultdict
from abc import ABC, abstractmethod
from typing import List, Dict, Tuple, Optional, Any, TypeVar, Union
from typing import List, Dict, Tuple, Optional, Any, TypeVar
from pydantic import BaseModel, Field
T = TypeVar("T", bound="BaseEntity")