Compare commits
	
		
			No commits in common. "8f6b9c83220969022a67b13004c22b8d2bc023a0" and "5b9b6df9977b06a7d50fde724ab5171c90cee322" have entirely different histories.
		
	
	
		
			8f6b9c8322
			...
			5b9b6df997
		
	
		
| @ -4,7 +4,6 @@ | |||||||
| import pygame | import pygame | ||||||
| import math | import math | ||||||
| from config.constants import * | from config.constants import * | ||||||
| from world.base.brain import CellBrain |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Renderer: | class Renderer: | ||||||
| @ -239,4 +238,4 @@ class Renderer: | |||||||
|             screen_x, screen_y = camera.world_to_screen(obj_x, obj_y) |             screen_x, screen_y = camera.world_to_screen(obj_x, obj_y) | ||||||
|             size = camera.get_relative_size(width) |             size = camera.get_relative_size(width) | ||||||
|             rect = pygame.Rect(screen_x - size // 2, screen_y - size // 2, size, size) |             rect = pygame.Rect(screen_x - size // 2, screen_y - size // 2, size, size) | ||||||
|             pygame.draw.rect(self.screen, SELECTION_BLUE, rect, 1) |             pygame.draw.rect(self.screen, SELECTION_BLUE, rect, 1) | ||||||
| @ -1,107 +0,0 @@ | |||||||
| import pygame |  | ||||||
| import time |  | ||||||
| import random |  | ||||||
| import sys |  | ||||||
| 
 |  | ||||||
| from world.world import World, Position, Rotation |  | ||||||
| from world.objects import FoodObject, DefaultCell |  | ||||||
| from world.simulation_interface import Camera |  | ||||||
| from config.constants import * |  | ||||||
| from core.input_handler import InputHandler |  | ||||||
| from core.renderer import Renderer |  | ||||||
| from ui.hud import HUD |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class SimulationEngine: |  | ||||||
|     def __init__(self): |  | ||||||
|         pygame.init() |  | ||||||
|         self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT), vsync=1) |  | ||||||
|         pygame.display.set_caption("Dynamic Abstraction System Testing") |  | ||||||
|         self.clock = pygame.time.Clock() |  | ||||||
|         self.camera = Camera(SCREEN_WIDTH, SCREEN_HEIGHT, RENDER_BUFFER) |  | ||||||
| 
 |  | ||||||
|         self.last_tick_time = time.perf_counter() |  | ||||||
|         self.last_tps_time = time.perf_counter() |  | ||||||
|         self.tick_counter = 0 |  | ||||||
|         self.actual_tps = 0 |  | ||||||
|         self.total_ticks = 0 |  | ||||||
| 
 |  | ||||||
|         self.world = self._setup_world() |  | ||||||
|         self.input_handler = InputHandler(self.camera, self.world) |  | ||||||
|         self.renderer = Renderer(self.screen) |  | ||||||
|         self.hud = HUD() |  | ||||||
| 
 |  | ||||||
|         self.running = True |  | ||||||
| 
 |  | ||||||
|     def _setup_world(self): |  | ||||||
|         world = World(CELL_SIZE, (CELL_SIZE * GRID_WIDTH, CELL_SIZE * GRID_HEIGHT)) |  | ||||||
|         random.seed(0) |  | ||||||
| 
 |  | ||||||
|         if FOOD_SPAWNING: |  | ||||||
|             world.add_object(FoodObject(Position(x=random.randint(-100, 100), y=random.randint(-100, 100)))) |  | ||||||
| 
 |  | ||||||
|         for _ in range(10): |  | ||||||
|             world.add_object(DefaultCell(Position(x=random.randint(-100, 100), y=random.randint(-100, 100)), Rotation(angle=0))) |  | ||||||
| 
 |  | ||||||
|         return world |  | ||||||
| 
 |  | ||||||
|     def run(self): |  | ||||||
|         while self.running: |  | ||||||
|             self._handle_frame() |  | ||||||
| 
 |  | ||||||
|         pygame.quit() |  | ||||||
|         sys.exit() |  | ||||||
| 
 |  | ||||||
|     def _handle_frame(self): |  | ||||||
|         deltatime = self.clock.get_time() / 1000.0 |  | ||||||
|         tick_interval = 1.0 / self.input_handler.tps |  | ||||||
| 
 |  | ||||||
|         # Handle events |  | ||||||
|         self.running = self.input_handler.handle_events(pygame.event.get()) |  | ||||||
| 
 |  | ||||||
|         if not self.input_handler.is_paused: |  | ||||||
|             current_time = time.perf_counter() |  | ||||||
|             while current_time - self.last_tick_time >= tick_interval: |  | ||||||
|                 self.last_tick_time += tick_interval |  | ||||||
|                 self.tick_counter += 1 |  | ||||||
|                 self.total_ticks += 1 |  | ||||||
| 
 |  | ||||||
|                 self.input_handler.update_selected_objects() |  | ||||||
|                 self.world.tick_all() |  | ||||||
| 
 |  | ||||||
|             if current_time - self.last_tps_time >= 1.0: |  | ||||||
|                 self.actual_tps = self.tick_counter |  | ||||||
|                 self.tick_counter = 0 |  | ||||||
|                 self.last_tps_time += 1.0 |  | ||||||
|         else: |  | ||||||
|             self.last_tick_time = time.perf_counter() |  | ||||||
|             self.last_tps_time = time.perf_counter() |  | ||||||
| 
 |  | ||||||
|         self._update(deltatime) |  | ||||||
|         self._render() |  | ||||||
| 
 |  | ||||||
|     def _update(self, deltatime): |  | ||||||
|         keys = pygame.key.get_pressed() |  | ||||||
|         self.input_handler.update_camera(keys, deltatime) |  | ||||||
| 
 |  | ||||||
|     def _render(self): |  | ||||||
|         self.renderer.clear_screen() |  | ||||||
|         self.renderer.draw_grid(self.camera, self.input_handler.show_grid) |  | ||||||
|         self.renderer.render_world(self.world, self.camera) |  | ||||||
|         self.renderer.render_interaction_radius(self.world, self.camera, self.input_handler.selected_objects, self.input_handler.show_interaction_radius) |  | ||||||
|         self.renderer.render_selection_rectangle(self.input_handler.get_selection_rect()) |  | ||||||
|         self.renderer.render_selected_objects_outline(self.input_handler.selected_objects, self.camera) |  | ||||||
| 
 |  | ||||||
|         self.hud.render_mouse_position(self.screen, self.camera) |  | ||||||
|         self.hud.render_fps(self.screen, self.clock) |  | ||||||
|         self.hud.render_tps(self.screen, self.actual_tps) |  | ||||||
|         self.hud.render_tick_count(self.screen, self.total_ticks) |  | ||||||
|         self.hud.render_selected_objects_info(self.screen, self.input_handler.selected_objects) |  | ||||||
|         self.hud.render_legend(self.screen, self.input_handler.show_legend) |  | ||||||
|         self.hud.render_pause_indicator(self.screen, self.input_handler.is_paused) |  | ||||||
| 
 |  | ||||||
|         if self.input_handler.selected_objects: |  | ||||||
|             self.hud.render_neural_network_visualization(self.screen, self.input_handler.selected_objects[0]) |  | ||||||
| 
 |  | ||||||
|         pygame.display.flip() |  | ||||||
|         self.clock.tick(MAX_FPS) |  | ||||||
							
								
								
									
										118
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										118
									
								
								main.py
									
									
									
									
									
								
							| @ -1,5 +1,117 @@ | |||||||
| from core.simulation_engine import SimulationEngine | import math | ||||||
|  | 
 | ||||||
|  | import pygame | ||||||
|  | import time | ||||||
|  | import sys | ||||||
|  | import random | ||||||
|  | 
 | ||||||
|  | from world.world import World, Position, Rotation | ||||||
|  | from world.objects import FoodObject, TestVelocityObject, DefaultCell | ||||||
|  | from world.simulation_interface import Camera | ||||||
|  | 
 | ||||||
|  | from config.constants import * | ||||||
|  | 
 | ||||||
|  | from core.input_handler import InputHandler | ||||||
|  | from core.renderer import Renderer | ||||||
|  | 
 | ||||||
|  | from ui.hud import HUD | ||||||
|  | 
 | ||||||
|  | # Initialize Pygame | ||||||
|  | pygame.init() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def setup(world: World): | ||||||
|  |     if FOOD_SPAWNING: | ||||||
|  |         world.add_object(FoodObject(Position(x=random.randint(-100, 100), y=random.randint(-100, 100)))) | ||||||
|  | 
 | ||||||
|  |     for i in range(100): | ||||||
|  |         world.add_object(DefaultCell(Position(x=random.randint(-100, 100),y=random.randint(-100, 100)), Rotation(angle=0))) | ||||||
|  | 
 | ||||||
|  |     return world | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def main(): | ||||||
|  |     screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT), vsync=1) | ||||||
|  |     pygame.display.set_caption("Dynamic Abstraction System Testing") | ||||||
|  |     clock = pygame.time.Clock() | ||||||
|  |     camera = Camera(SCREEN_WIDTH, SCREEN_HEIGHT, RENDER_BUFFER) | ||||||
|  | 
 | ||||||
|  |     last_tick_time = time.perf_counter()  # Tracks the last tick time | ||||||
|  |     last_tps_time = time.perf_counter()  # Tracks the last TPS calculation time | ||||||
|  |     tick_counter = 0  # Counts ticks executed | ||||||
|  |     actual_tps = 0  # Stores the calculated TPS | ||||||
|  |     total_ticks = 0  # Total ticks executed | ||||||
|  | 
 | ||||||
|  |     # Initialize world | ||||||
|  |     world = World(CELL_SIZE, (CELL_SIZE * GRID_WIDTH, CELL_SIZE * GRID_HEIGHT)) | ||||||
|  | 
 | ||||||
|  |     # sets seed to 67 >_< | ||||||
|  |     random.seed(0) | ||||||
|  | 
 | ||||||
|  |     world = setup(world) | ||||||
|  | 
 | ||||||
|  |     input_handler = InputHandler(camera, world) | ||||||
|  |     renderer = Renderer(screen) | ||||||
|  |     hud = HUD() | ||||||
|  | 
 | ||||||
|  |     running = True | ||||||
|  |     while running: | ||||||
|  |         deltatime = clock.get_time() / 1000.0  # Convert milliseconds to seconds | ||||||
|  |         tick_interval = 1.0 / input_handler.tps # Time per tick | ||||||
|  | 
 | ||||||
|  |         # Handle events | ||||||
|  |         running = input_handler.handle_events(pygame.event.get()) | ||||||
|  | 
 | ||||||
|  |         if not input_handler.is_paused: | ||||||
|  |             # Tick logic (runs every tick interval) | ||||||
|  |             current_time = time.perf_counter() | ||||||
|  |             while current_time - last_tick_time >= tick_interval: | ||||||
|  |                 last_tick_time += tick_interval | ||||||
|  |                 tick_counter += 1 | ||||||
|  |                 total_ticks += 1 | ||||||
|  | 
 | ||||||
|  |                 # ensure selected objects are still valid or have not changed position, if so, reselect them | ||||||
|  |                 input_handler.update_selected_objects() | ||||||
|  | 
 | ||||||
|  |                 world.tick_all() | ||||||
|  | 
 | ||||||
|  |             # Calculate TPS every second | ||||||
|  |             if current_time - last_tps_time >= 1.0: | ||||||
|  |                 actual_tps = tick_counter | ||||||
|  |                 tick_counter = 0 | ||||||
|  |                 last_tps_time += 1.0 | ||||||
|  |         else: | ||||||
|  |             last_tick_time = time.perf_counter() | ||||||
|  |             last_tps_time = time.perf_counter() | ||||||
|  | 
 | ||||||
|  |         # Get pressed keys for smooth movement | ||||||
|  |         keys = pygame.key.get_pressed() | ||||||
|  |         input_handler.update_camera(keys, deltatime) | ||||||
|  | 
 | ||||||
|  |         renderer.clear_screen() | ||||||
|  |         renderer.draw_grid(camera, input_handler.show_grid) | ||||||
|  |         renderer.render_world(world, camera) | ||||||
|  | 
 | ||||||
|  |         renderer.render_interaction_radius(world, camera, input_handler.selected_objects, input_handler.show_interaction_radius) | ||||||
|  | 
 | ||||||
|  |         renderer.render_selection_rectangle(input_handler.get_selection_rect()) | ||||||
|  |         renderer.render_selected_objects_outline(input_handler.selected_objects, camera) | ||||||
|  | 
 | ||||||
|  |         hud.render_mouse_position(screen, camera) | ||||||
|  |         hud.render_fps(screen, clock) | ||||||
|  |         hud.render_tps(screen, actual_tps) | ||||||
|  |         hud.render_tick_count(screen, total_ticks) | ||||||
|  |         hud.render_selected_objects_info(screen, input_handler.selected_objects) | ||||||
|  |         hud.render_legend(screen, input_handler.show_legend) | ||||||
|  |         hud.render_pause_indicator(screen, input_handler.is_paused) | ||||||
|  | 
 | ||||||
|  |         # Update display | ||||||
|  |         pygame.display.flip() | ||||||
|  |         clock.tick(MAX_FPS) | ||||||
|  | 
 | ||||||
|  |     pygame.quit() | ||||||
|  |     sys.exit() | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == "__main__": | if __name__ == "__main__": | ||||||
|     engine = SimulationEngine() |     main() | ||||||
|     engine.run() |  | ||||||
|  | |||||||
| @ -4,7 +4,6 @@ version = "0.1.0" | |||||||
| description = "Add your description here" | description = "Add your description here" | ||||||
| requires-python = ">=3.11" | requires-python = ">=3.11" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|     "numpy>=2.3.0", |  | ||||||
|     "pre-commit>=4.2.0", |     "pre-commit>=4.2.0", | ||||||
|     "pydantic>=2.11.5", |     "pydantic>=2.11.5", | ||||||
|     "pygame>=2.6.1", |     "pygame>=2.6.1", | ||||||
|  | |||||||
							
								
								
									
										252
									
								
								ui/hud.py
									
									
									
									
									
								
							
							
						
						
									
										252
									
								
								ui/hud.py
									
									
									
									
									
								
							| @ -3,8 +3,6 @@ | |||||||
| 
 | 
 | ||||||
| import pygame | import pygame | ||||||
| from config.constants import * | from config.constants import * | ||||||
| from world.base.brain import CellBrain, FlexibleNeuralNetwork |  | ||||||
| from world.objects import DefaultCell |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class HUD: | class HUD: | ||||||
| @ -125,252 +123,4 @@ class HUD: | |||||||
|             text_rect = text.get_rect() |             text_rect = text.get_rect() | ||||||
|             text_rect.left = legend_x + left_width + column_gap |             text_rect.left = legend_x + left_width + column_gap | ||||||
|             text_rect.top = legend_y + 5 + i * legend_font_height |             text_rect.top = legend_y + 5 + i * legend_font_height | ||||||
|             screen.blit(text, text_rect) |             screen.blit(text, text_rect) | ||||||
| 
 |  | ||||||
|     def render_neural_network_visualization(self, screen, cell: DefaultCell) -> None: |  | ||||||
|         """Render neural network visualization. This is fixed to the screen size and is not dependent on zoom or camera position.""" |  | ||||||
| 
 |  | ||||||
|         # Visualization layout constants |  | ||||||
|         VIZ_WIDTH = 280  # Width of the neural network visualization area |  | ||||||
|         VIZ_HEIGHT = 300  # Height of the neural network visualization area |  | ||||||
|         VIZ_RIGHT_MARGIN = VIZ_WIDTH + 50  # Distance from right edge of screen to visualization |  | ||||||
| 
 |  | ||||||
|         # Background styling constants |  | ||||||
|         BACKGROUND_PADDING = 30  # Padding around the visualization background |  | ||||||
|         BACKGROUND_BORDER_WIDTH = 2  # Width of the background border |  | ||||||
|         BACKGROUND_COLOR = (30, 30, 30)  # Dark gray background color |  | ||||||
| 
 |  | ||||||
|         # Title positioning constants |  | ||||||
|         TITLE_TOP_MARGIN = 30  # Distance above visualization for title |  | ||||||
| 
 |  | ||||||
|         # Neuron appearance constants |  | ||||||
|         NEURON_RADIUS = 8  # Radius of neuron circles |  | ||||||
|         NEURON_BORDER_WIDTH = 2  # Width of neuron circle borders |  | ||||||
| 
 |  | ||||||
|         # Layer spacing constants |  | ||||||
|         LAYER_VERTICAL_MARGIN = 30  # Top and bottom margin within visualization for neurons |  | ||||||
| 
 |  | ||||||
|         # Connection appearance constants |  | ||||||
|         WEIGHT_NORMALIZATION_DIVISOR = 2  # Divisor for normalizing weights to [-1, 1] range |  | ||||||
|         MAX_CONNECTION_THICKNESS = 3  # Maximum thickness for connection lines |  | ||||||
|         MIN_CONNECTION_THICKNESS = 1  # Minimum thickness for connection lines |  | ||||||
| 
 |  | ||||||
|         # Connection colors (RGB values) |  | ||||||
|         CONNECTION_BASE_INTENSITY = 128  # Base color intensity for connections |  | ||||||
|         CONNECTION_POSITIVE_GREEN = 128  # Green component for positive weights |  | ||||||
|         CONNECTION_NEGATIVE_RED = 128  # Red component for negative weights |  | ||||||
| 
 |  | ||||||
|         # Neuron activation colors |  | ||||||
|         NEURON_BASE_INTENSITY = 100  # Base color intensity for neurons |  | ||||||
|         NEURON_ACTIVATION_INTENSITY = 155  # Additional intensity based on activation |  | ||||||
| 
 |  | ||||||
|         # Text positioning constants |  | ||||||
|         ACTIVATION_TEXT_OFFSET = 15  # Distance below neuron for activation value text |  | ||||||
|         ACTIVATION_DISPLAY_THRESHOLD = 0.01  # Minimum activation value to display as text |  | ||||||
|         ACTIVATION_TEXT_PRECISION = 2  # Decimal places for activation values |  | ||||||
| 
 |  | ||||||
|         # Layer label positioning constants |  | ||||||
|         LAYER_LABEL_BOTTOM_MARGIN = 15  # Distance below visualization for layer labels |  | ||||||
| 
 |  | ||||||
|         # Info text positioning constants |  | ||||||
|         INFO_TEXT_TOP_MARGIN = 35  # Distance below visualization for info text |  | ||||||
|         INFO_TEXT_LINE_SPACING = 15  # Vertical spacing between info text lines |  | ||||||
| 
 |  | ||||||
|         # Activation value clamping |  | ||||||
|         ACTIVATION_CLAMP_MIN = -1  # Minimum activation value for visualization |  | ||||||
|         ACTIVATION_CLAMP_MAX = 1  # Maximum activation value for visualization |  | ||||||
| 
 |  | ||||||
|         if not hasattr(cell, 'behavioral_model'): |  | ||||||
|             return |  | ||||||
| 
 |  | ||||||
|         cell_brain: CellBrain = cell.behavioral_model |  | ||||||
| 
 |  | ||||||
|         if not hasattr(cell_brain, 'neural_network'): |  | ||||||
|             return |  | ||||||
| 
 |  | ||||||
|         network: FlexibleNeuralNetwork = cell_brain.neural_network |  | ||||||
| 
 |  | ||||||
|         # Calculate visualization position |  | ||||||
|         viz_x = SCREEN_WIDTH - VIZ_RIGHT_MARGIN  # Right side of screen |  | ||||||
|         viz_y = (SCREEN_HEIGHT // 2) - (VIZ_HEIGHT // 2)  # Centered vertically |  | ||||||
| 
 |  | ||||||
|         layer_spacing = VIZ_WIDTH // max(1, len(network.layers) - 1) if len(network.layers) > 1 else VIZ_WIDTH |  | ||||||
| 
 |  | ||||||
|         # Draw background |  | ||||||
|         background_rect = pygame.Rect(viz_x - BACKGROUND_PADDING, viz_y - BACKGROUND_PADDING, |  | ||||||
|                                       VIZ_WIDTH + 2 * BACKGROUND_PADDING, VIZ_HEIGHT + 2 * BACKGROUND_PADDING) |  | ||||||
|         pygame.draw.rect(screen, BACKGROUND_COLOR, background_rect) |  | ||||||
|         pygame.draw.rect(screen, WHITE, background_rect, BACKGROUND_BORDER_WIDTH) |  | ||||||
| 
 |  | ||||||
|         # Title |  | ||||||
|         title_text = self.font.render("Neural Network", True, WHITE) |  | ||||||
|         title_rect = title_text.get_rect() |  | ||||||
|         title_rect.centerx = viz_x + VIZ_WIDTH // 2 |  | ||||||
|         title_rect.top = viz_y - TITLE_TOP_MARGIN |  | ||||||
|         screen.blit(title_text, title_rect) |  | ||||||
| 
 |  | ||||||
|         # Get current activations by running a forward pass with current inputs |  | ||||||
|         input_values = [cell_brain.inputs[key] for key in cell_brain.input_keys] |  | ||||||
| 
 |  | ||||||
|         # Store activations for each layer |  | ||||||
|         activations = [input_values]  # Input layer |  | ||||||
| 
 |  | ||||||
|         # Calculate activations for each layer |  | ||||||
|         for layer_idx in range(1, len(network.layers)): |  | ||||||
|             layer_activations = [] |  | ||||||
| 
 |  | ||||||
|             for neuron in network.layers[layer_idx]: |  | ||||||
|                 if neuron['type'] == 'input': |  | ||||||
|                     continue |  | ||||||
| 
 |  | ||||||
|                 # Calculate weighted sum |  | ||||||
|                 weighted_sum = neuron.get('bias', 0) |  | ||||||
| 
 |  | ||||||
|                 for source_layer, source_neuron, weight in neuron.get('connections', []): |  | ||||||
|                     if source_layer < len(activations) and source_neuron < len(activations[source_layer]): |  | ||||||
|                         weighted_sum += activations[source_layer][source_neuron] * weight |  | ||||||
| 
 |  | ||||||
|                 # Apply activation function |  | ||||||
|                 activation = max(ACTIVATION_CLAMP_MIN, min(ACTIVATION_CLAMP_MAX, weighted_sum)) |  | ||||||
|                 layer_activations.append(activation) |  | ||||||
| 
 |  | ||||||
|             activations.append(layer_activations) |  | ||||||
| 
 |  | ||||||
|         # Calculate neuron positions |  | ||||||
|         neuron_positions = {} |  | ||||||
| 
 |  | ||||||
|         for layer_idx, layer in enumerate(network.layers): |  | ||||||
|             layer_neurons = [n for n in layer if n['type'] != 'input' or layer_idx == 0] |  | ||||||
|             layer_size = len(layer_neurons) |  | ||||||
| 
 |  | ||||||
|             if layer_size == 0: |  | ||||||
|                 continue |  | ||||||
| 
 |  | ||||||
|             # X position based on layer |  | ||||||
|             if len(network.layers) == 1: |  | ||||||
|                 x = viz_x + VIZ_WIDTH // 2 |  | ||||||
|             else: |  | ||||||
|                 x = viz_x + (layer_idx * layer_spacing) |  | ||||||
| 
 |  | ||||||
|             # Y positions distributed vertically |  | ||||||
|             if layer_size == 1: |  | ||||||
|                 y_positions = [viz_y + VIZ_HEIGHT // 2] |  | ||||||
|             else: |  | ||||||
|                 y_start = viz_y + LAYER_VERTICAL_MARGIN |  | ||||||
|                 y_end = viz_y + VIZ_HEIGHT - LAYER_VERTICAL_MARGIN |  | ||||||
|                 y_positions = [y_start + i * (y_end - y_start) / (layer_size - 1) for i in range(layer_size)] |  | ||||||
| 
 |  | ||||||
|             for neuron_idx, neuron in enumerate(layer_neurons): |  | ||||||
|                 if neuron_idx < len(y_positions): |  | ||||||
|                     neuron_positions[(layer_idx, neuron_idx)] = (int(x), int(y_positions[neuron_idx])) |  | ||||||
| 
 |  | ||||||
|         # Draw connections first (so they appear behind neurons) |  | ||||||
|         for layer_idx in range(1, len(network.layers)): |  | ||||||
|             for neuron_idx, neuron in enumerate(network.layers[layer_idx]): |  | ||||||
|                 if neuron['type'] == 'input': |  | ||||||
|                     continue |  | ||||||
| 
 |  | ||||||
|                 target_pos = neuron_positions.get((layer_idx, neuron_idx)) |  | ||||||
|                 if not target_pos: |  | ||||||
|                     continue |  | ||||||
| 
 |  | ||||||
|                 for source_layer, source_neuron, weight in neuron.get('connections', []): |  | ||||||
|                     source_pos = neuron_positions.get((source_layer, source_neuron)) |  | ||||||
|                     if not source_pos: |  | ||||||
|                         continue |  | ||||||
| 
 |  | ||||||
|                     # Color based on weight: red for negative, green for positive |  | ||||||
|                     weight_normalized = max(ACTIVATION_CLAMP_MIN, |  | ||||||
|                                             min(ACTIVATION_CLAMP_MAX, weight / WEIGHT_NORMALIZATION_DIVISOR)) |  | ||||||
| 
 |  | ||||||
|                     if weight_normalized >= 0: |  | ||||||
|                         # Positive weight: interpolate from gray to green |  | ||||||
|                         intensity = int(weight_normalized * 255) |  | ||||||
|                         color = (max(0, CONNECTION_BASE_INTENSITY - intensity), |  | ||||||
|                                  CONNECTION_BASE_INTENSITY + intensity // 2, |  | ||||||
|                                  max(0, CONNECTION_BASE_INTENSITY - intensity)) |  | ||||||
|                     else: |  | ||||||
|                         # Negative weight: interpolate from gray to red |  | ||||||
|                         intensity = int(-weight_normalized * 255) |  | ||||||
|                         color = (CONNECTION_BASE_INTENSITY + intensity // 2, |  | ||||||
|                                  max(0, CONNECTION_BASE_INTENSITY - intensity), |  | ||||||
|                                  max(0, CONNECTION_BASE_INTENSITY - intensity)) |  | ||||||
| 
 |  | ||||||
|                     # Line thickness based on weight magnitude |  | ||||||
|                     thickness = max(MIN_CONNECTION_THICKNESS, int(abs(weight_normalized) * MAX_CONNECTION_THICKNESS)) |  | ||||||
| 
 |  | ||||||
|                     pygame.draw.line(screen, color, source_pos, target_pos, thickness) |  | ||||||
| 
 |  | ||||||
|         # Draw neurons |  | ||||||
|         for layer_idx, layer in enumerate(network.layers): |  | ||||||
|             layer_activations = activations[layer_idx] if layer_idx < len(activations) else [] |  | ||||||
| 
 |  | ||||||
|             for neuron_idx, neuron in enumerate(layer): |  | ||||||
|                 if neuron['type'] == 'input' and layer_idx != 0: |  | ||||||
|                     continue |  | ||||||
| 
 |  | ||||||
|                 pos = neuron_positions.get((layer_idx, neuron_idx)) |  | ||||||
|                 if not pos: |  | ||||||
|                     continue |  | ||||||
| 
 |  | ||||||
|                 # Get activation value |  | ||||||
|                 activation = 0 |  | ||||||
|                 if neuron_idx < len(layer_activations): |  | ||||||
|                     activation = layer_activations[neuron_idx] |  | ||||||
| 
 |  | ||||||
|                 # Color based on activation: brightness represents magnitude |  | ||||||
|                 activation_normalized = max(ACTIVATION_CLAMP_MIN, min(ACTIVATION_CLAMP_MAX, activation)) |  | ||||||
|                 activation_intensity = int(abs(activation_normalized) * NEURON_ACTIVATION_INTENSITY) |  | ||||||
| 
 |  | ||||||
|                 if activation_normalized >= 0: |  | ||||||
|                     # Positive activation: blue tint |  | ||||||
|                     color = (NEURON_BASE_INTENSITY, NEURON_BASE_INTENSITY, NEURON_BASE_INTENSITY + activation_intensity) |  | ||||||
|                 else: |  | ||||||
|                     # Negative activation: red tint |  | ||||||
|                     color = (NEURON_BASE_INTENSITY + activation_intensity, NEURON_BASE_INTENSITY, NEURON_BASE_INTENSITY) |  | ||||||
| 
 |  | ||||||
|                 # Draw neuron |  | ||||||
|                 pygame.draw.circle(screen, color, pos, NEURON_RADIUS) |  | ||||||
|                 pygame.draw.circle(screen, WHITE, pos, NEURON_RADIUS, NEURON_BORDER_WIDTH) |  | ||||||
| 
 |  | ||||||
|                 # Draw activation value as text |  | ||||||
|                 if abs(activation) > ACTIVATION_DISPLAY_THRESHOLD: |  | ||||||
|                     activation_text = self.legend_font.render(f"{activation:.{ACTIVATION_TEXT_PRECISION}f}", True, |  | ||||||
|                                                               WHITE) |  | ||||||
|                     text_rect = activation_text.get_rect() |  | ||||||
|                     text_rect.center = (pos[0], pos[1] + NEURON_RADIUS + ACTIVATION_TEXT_OFFSET) |  | ||||||
|                     screen.blit(activation_text, text_rect) |  | ||||||
| 
 |  | ||||||
|         # Draw layer labels |  | ||||||
|         layer_labels = ["Input", "Hidden", "Output"] |  | ||||||
|         for layer_idx in range(len(network.layers)): |  | ||||||
|             if layer_idx >= len(layer_labels): |  | ||||||
|                 label = f"Layer {layer_idx}" |  | ||||||
|             else: |  | ||||||
|                 label = layer_labels[layer_idx] if layer_idx < len(layer_labels) else f"Hidden {layer_idx - 1}" |  | ||||||
| 
 |  | ||||||
|             # Find average x position for this layer |  | ||||||
|             x_positions = [pos[0] for (l_idx, n_idx), pos in neuron_positions.items() if l_idx == layer_idx] |  | ||||||
|             if x_positions: |  | ||||||
|                 avg_x = sum(x_positions) // len(x_positions) |  | ||||||
| 
 |  | ||||||
|                 label_text = self.legend_font.render(label, True, WHITE) |  | ||||||
|                 label_rect = label_text.get_rect() |  | ||||||
|                 label_rect.centerx = avg_x |  | ||||||
|                 label_rect.bottom = viz_y + VIZ_HEIGHT + LAYER_LABEL_BOTTOM_MARGIN |  | ||||||
|                 screen.blit(label_text, label_rect) |  | ||||||
| 
 |  | ||||||
|         # Draw network info |  | ||||||
|         info = network.get_structure_info() |  | ||||||
|         info_lines = [ |  | ||||||
|             f"Layers: {info['total_layers']}", |  | ||||||
|             f"Neurons: {info['total_neurons']}", |  | ||||||
|             f"Connections: {info['total_connections']}" |  | ||||||
|         ] |  | ||||||
| 
 |  | ||||||
|         for i, line in enumerate(info_lines): |  | ||||||
|             info_text = self.legend_font.render(line, True, WHITE) |  | ||||||
|             info_rect = info_text.get_rect() |  | ||||||
|             info_rect.left = viz_x |  | ||||||
|             info_rect.top = viz_y + VIZ_HEIGHT + INFO_TEXT_TOP_MARGIN + i * INFO_TEXT_LINE_SPACING |  | ||||||
|             screen.blit(info_text, info_rect) |  | ||||||
							
								
								
									
										60
									
								
								uv.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										60
									
								
								uv.lock
									
									
									
										generated
									
									
									
								
							| @ -43,7 +43,6 @@ name = "dynamicsystemabstraction" | |||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| source = { virtual = "." } | source = { virtual = "." } | ||||||
| dependencies = [ | dependencies = [ | ||||||
|     { name = "numpy" }, |  | ||||||
|     { name = "pre-commit" }, |     { name = "pre-commit" }, | ||||||
|     { name = "pydantic" }, |     { name = "pydantic" }, | ||||||
|     { name = "pygame" }, |     { name = "pygame" }, | ||||||
| @ -57,7 +56,6 @@ dev = [ | |||||||
| 
 | 
 | ||||||
| [package.metadata] | [package.metadata] | ||||||
| requires-dist = [ | requires-dist = [ | ||||||
|     { name = "numpy", specifier = ">=2.3.0" }, |  | ||||||
|     { name = "pre-commit", specifier = ">=4.2.0" }, |     { name = "pre-commit", specifier = ">=4.2.0" }, | ||||||
|     { name = "pydantic", specifier = ">=2.11.5" }, |     { name = "pydantic", specifier = ">=2.11.5" }, | ||||||
|     { name = "pygame", specifier = ">=2.6.1" }, |     { name = "pygame", specifier = ">=2.6.1" }, | ||||||
| @ -103,64 +101,6 @@ wheels = [ | |||||||
|     { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, |     { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "numpy" |  | ||||||
| version = "2.3.0" |  | ||||||
| source = { registry = "https://pypi.org/simple" } |  | ||||||
| sdist = { url = "https://files.pythonhosted.org/packages/f3/db/8e12381333aea300890829a0a36bfa738cac95475d88982d538725143fd9/numpy-2.3.0.tar.gz", hash = "sha256:581f87f9e9e9db2cba2141400e160e9dd644ee248788d6f90636eeb8fd9260a6", size = 20382813, upload-time = "2025-06-07T14:54:32.608Z" } |  | ||||||
| wheels = [ |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/fd/5f/df67435257d827eb3b8af66f585223dc2c3f2eb7ad0b50cb1dae2f35f494/numpy-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c3c9fdde0fa18afa1099d6257eb82890ea4f3102847e692193b54e00312a9ae9", size = 21199688, upload-time = "2025-06-07T14:36:52.067Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/e5/ce/aad219575055d6c9ef29c8c540c81e1c38815d3be1fe09cdbe53d48ee838/numpy-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46d16f72c2192da7b83984aa5455baee640e33a9f1e61e656f29adf55e406c2b", size = 14359277, upload-time = "2025-06-07T14:37:15.325Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/29/6b/2d31da8e6d2ec99bed54c185337a87f8fbeccc1cd9804e38217e92f3f5e2/numpy-2.3.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a0be278be9307c4ab06b788f2a077f05e180aea817b3e41cebbd5aaf7bd85ed3", size = 5376069, upload-time = "2025-06-07T14:37:25.636Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/7d/2a/6c59a062397553ec7045c53d5fcdad44e4536e54972faa2ba44153bca984/numpy-2.3.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:99224862d1412d2562248d4710126355d3a8db7672170a39d6909ac47687a8a4", size = 6913057, upload-time = "2025-06-07T14:37:37.215Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/d5/5a/8df16f258d28d033e4f359e29d3aeb54663243ac7b71504e89deeb813202/numpy-2.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2393a914db64b0ead0ab80c962e42d09d5f385802006a6c87835acb1f58adb96", size = 14568083, upload-time = "2025-06-07T14:37:59.337Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/0a/92/0528a563dfc2cdccdcb208c0e241a4bb500d7cde218651ffb834e8febc50/numpy-2.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:7729c8008d55e80784bd113787ce876ca117185c579c0d626f59b87d433ea779", size = 16929402, upload-time = "2025-06-07T14:38:24.343Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/e4/2f/e7a8c8d4a2212c527568d84f31587012cf5497a7271ea1f23332142f634e/numpy-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:06d4fb37a8d383b769281714897420c5cc3545c79dc427df57fc9b852ee0bf58", size = 15879193, upload-time = "2025-06-07T14:38:48.007Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/e2/c3/dada3f005953847fe35f42ac0fe746f6e1ea90b4c6775e4be605dcd7b578/numpy-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c39ec392b5db5088259c68250e342612db82dc80ce044cf16496cf14cf6bc6f8", size = 18665318, upload-time = "2025-06-07T14:39:15.794Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/3b/ae/3f448517dedefc8dd64d803f9d51a8904a48df730e00a3c5fb1e75a60620/numpy-2.3.0-cp311-cp311-win32.whl", hash = "sha256:ee9d3ee70d62827bc91f3ea5eee33153212c41f639918550ac0475e3588da59f", size = 6601108, upload-time = "2025-06-07T14:39:27.176Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/8c/4a/556406d2bb2b9874c8cbc840c962683ac28f21efbc9b01177d78f0199ca1/numpy-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:43c55b6a860b0eb44d42341438b03513cf3879cb3617afb749ad49307e164edd", size = 13021525, upload-time = "2025-06-07T14:39:46.637Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/ed/ee/bf54278aef30335ffa9a189f869ea09e1a195b3f4b93062164a3b02678a7/numpy-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:2e6a1409eee0cb0316cb64640a49a49ca44deb1a537e6b1121dc7c458a1299a8", size = 10170327, upload-time = "2025-06-07T14:40:02.703Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/89/59/9df493df81ac6f76e9f05cdbe013cdb0c9a37b434f6e594f5bd25e278908/numpy-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:389b85335838155a9076e9ad7f8fdba0827496ec2d2dc32ce69ce7898bde03ba", size = 20897025, upload-time = "2025-06-07T14:40:33.558Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/2f/86/4ff04335901d6cf3a6bb9c748b0097546ae5af35e455ae9b962ebff4ecd7/numpy-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9498f60cd6bb8238d8eaf468a3d5bb031d34cd12556af53510f05fcf581c1b7e", size = 14129882, upload-time = "2025-06-07T14:40:55.034Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/71/8d/a942cd4f959de7f08a79ab0c7e6cecb7431d5403dce78959a726f0f57aa1/numpy-2.3.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:622a65d40d8eb427d8e722fd410ac3ad4958002f109230bc714fa551044ebae2", size = 5110181, upload-time = "2025-06-07T14:41:04.4Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/86/5d/45850982efc7b2c839c5626fb67fbbc520d5b0d7c1ba1ae3651f2f74c296/numpy-2.3.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b9446d9d8505aadadb686d51d838f2b6688c9e85636a0c3abaeb55ed54756459", size = 6647581, upload-time = "2025-06-07T14:41:14.695Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/1a/c0/c871d4a83f93b00373d3eebe4b01525eee8ef10b623a335ec262b58f4dc1/numpy-2.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:50080245365d75137a2bf46151e975de63146ae6d79f7e6bd5c0e85c9931d06a", size = 14262317, upload-time = "2025-06-07T14:41:35.862Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/b7/f6/bc47f5fa666d5ff4145254f9e618d56e6a4ef9b874654ca74c19113bb538/numpy-2.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c24bb4113c66936eeaa0dc1e47c74770453d34f46ee07ae4efd853a2ed1ad10a", size = 16633919, upload-time = "2025-06-07T14:42:00.622Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/f5/b4/65f48009ca0c9b76df5f404fccdea5a985a1bb2e34e97f21a17d9ad1a4ba/numpy-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4d8d294287fdf685281e671886c6dcdf0291a7c19db3e5cb4178d07ccf6ecc67", size = 15567651, upload-time = "2025-06-07T14:42:24.429Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/f1/62/5367855a2018578e9334ed08252ef67cc302e53edc869666f71641cad40b/numpy-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6295f81f093b7f5769d1728a6bd8bf7466de2adfa771ede944ce6711382b89dc", size = 18361723, upload-time = "2025-06-07T14:42:51.167Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/d4/75/5baed8cd867eabee8aad1e74d7197d73971d6a3d40c821f1848b8fab8b84/numpy-2.3.0-cp312-cp312-win32.whl", hash = "sha256:e6648078bdd974ef5d15cecc31b0c410e2e24178a6e10bf511e0557eed0f2570", size = 6318285, upload-time = "2025-06-07T14:43:02.052Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/bc/49/d5781eaa1a15acb3b3a3f49dc9e2ff18d92d0ce5c2976f4ab5c0a7360250/numpy-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:0898c67a58cdaaf29994bc0e2c65230fd4de0ac40afaf1584ed0b02cd74c6fdd", size = 12732594, upload-time = "2025-06-07T14:43:21.071Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/c2/1c/6d343e030815c7c97a1f9fbad00211b47717c7fe446834c224bd5311e6f1/numpy-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:bd8df082b6c4695753ad6193018c05aac465d634834dca47a3ae06d4bb22d9ea", size = 9891498, upload-time = "2025-06-07T14:43:36.332Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/73/fc/1d67f751fd4dbafc5780244fe699bc4084268bad44b7c5deb0492473127b/numpy-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5754ab5595bfa2c2387d241296e0381c21f44a4b90a776c3c1d39eede13a746a", size = 20889633, upload-time = "2025-06-07T14:44:06.839Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/e8/95/73ffdb69e5c3f19ec4530f8924c4386e7ba097efc94b9c0aff607178ad94/numpy-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d11fa02f77752d8099573d64e5fe33de3229b6632036ec08f7080f46b6649959", size = 14151683, upload-time = "2025-06-07T14:44:28.847Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/64/d5/06d4bb31bb65a1d9c419eb5676173a2f90fd8da3c59f816cc54c640ce265/numpy-2.3.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:aba48d17e87688a765ab1cd557882052f238e2f36545dfa8e29e6a91aef77afe", size = 5102683, upload-time = "2025-06-07T14:44:38.417Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/12/8b/6c2cef44f8ccdc231f6b56013dff1d71138c48124334aded36b1a1b30c5a/numpy-2.3.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4dc58865623023b63b10d52f18abaac3729346a7a46a778381e0e3af4b7f3beb", size = 6640253, upload-time = "2025-06-07T14:44:49.359Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/62/aa/fca4bf8de3396ddb59544df9b75ffe5b73096174de97a9492d426f5cd4aa/numpy-2.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:df470d376f54e052c76517393fa443758fefcdd634645bc9c1f84eafc67087f0", size = 14258658, upload-time = "2025-06-07T14:45:10.156Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/1c/12/734dce1087eed1875f2297f687e671cfe53a091b6f2f55f0c7241aad041b/numpy-2.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:87717eb24d4a8a64683b7a4e91ace04e2f5c7c77872f823f02a94feee186168f", size = 16628765, upload-time = "2025-06-07T14:45:35.076Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/48/03/ffa41ade0e825cbcd5606a5669962419528212a16082763fc051a7247d76/numpy-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d8fa264d56882b59dcb5ea4d6ab6f31d0c58a57b41aec605848b6eb2ef4a43e8", size = 15564335, upload-time = "2025-06-07T14:45:58.797Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/07/58/869398a11863310aee0ff85a3e13b4c12f20d032b90c4b3ee93c3b728393/numpy-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e651756066a0eaf900916497e20e02fe1ae544187cb0fe88de981671ee7f6270", size = 18360608, upload-time = "2025-06-07T14:46:25.687Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/2f/8a/5756935752ad278c17e8a061eb2127c9a3edf4ba2c31779548b336f23c8d/numpy-2.3.0-cp313-cp313-win32.whl", hash = "sha256:e43c3cce3b6ae5f94696669ff2a6eafd9a6b9332008bafa4117af70f4b88be6f", size = 6310005, upload-time = "2025-06-07T14:50:13.138Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/08/60/61d60cf0dfc0bf15381eaef46366ebc0c1a787856d1db0c80b006092af84/numpy-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:81ae0bf2564cf475f94be4a27ef7bcf8af0c3e28da46770fc904da9abd5279b5", size = 12729093, upload-time = "2025-06-07T14:50:31.82Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/66/31/2f2f2d2b3e3c32d5753d01437240feaa32220b73258c9eef2e42a0832866/numpy-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:c8738baa52505fa6e82778580b23f945e3578412554d937093eac9205e845e6e", size = 9885689, upload-time = "2025-06-07T14:50:47.888Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/f1/89/c7828f23cc50f607ceb912774bb4cff225ccae7131c431398ad8400e2c98/numpy-2.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:39b27d8b38942a647f048b675f134dd5a567f95bfff481f9109ec308515c51d8", size = 20986612, upload-time = "2025-06-07T14:46:56.077Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/dd/46/79ecf47da34c4c50eedec7511e53d57ffdfd31c742c00be7dc1d5ffdb917/numpy-2.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0eba4a1ea88f9a6f30f56fdafdeb8da3774349eacddab9581a21234b8535d3d3", size = 14298953, upload-time = "2025-06-07T14:47:18.053Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/59/44/f6caf50713d6ff4480640bccb2a534ce1d8e6e0960c8f864947439f0ee95/numpy-2.3.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:b0f1f11d0a1da54927436505a5a7670b154eac27f5672afc389661013dfe3d4f", size = 5225806, upload-time = "2025-06-07T14:47:27.524Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/a6/43/e1fd1aca7c97e234dd05e66de4ab7a5be54548257efcdd1bc33637e72102/numpy-2.3.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:690d0a5b60a47e1f9dcec7b77750a4854c0d690e9058b7bef3106e3ae9117808", size = 6735169, upload-time = "2025-06-07T14:47:38.057Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/84/89/f76f93b06a03177c0faa7ca94d0856c4e5c4bcaf3c5f77640c9ed0303e1c/numpy-2.3.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:8b51ead2b258284458e570942137155978583e407babc22e3d0ed7af33ce06f8", size = 14330701, upload-time = "2025-06-07T14:47:59.113Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/aa/f5/4858c3e9ff7a7d64561b20580cf7cc5d085794bd465a19604945d6501f6c/numpy-2.3.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:aaf81c7b82c73bd9b45e79cfb9476cb9c29e937494bfe9092c26aece812818ad", size = 16692983, upload-time = "2025-06-07T14:48:24.196Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/08/17/0e3b4182e691a10e9483bcc62b4bb8693dbf9ea5dc9ba0b77a60435074bb/numpy-2.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f420033a20b4f6a2a11f585f93c843ac40686a7c3fa514060a97d9de93e5e72b", size = 15641435, upload-time = "2025-06-07T14:48:47.712Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/4e/d5/463279fda028d3c1efa74e7e8d507605ae87f33dbd0543cf4c4527c8b882/numpy-2.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d344ca32ab482bcf8735d8f95091ad081f97120546f3d250240868430ce52555", size = 18433798, upload-time = "2025-06-07T14:49:14.866Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/0e/1e/7a9d98c886d4c39a2b4d3a7c026bffcf8fbcaf518782132d12a301cfc47a/numpy-2.3.0-cp313-cp313t-win32.whl", hash = "sha256:48a2e8eaf76364c32a1feaa60d6925eaf32ed7a040183b807e02674305beef61", size = 6438632, upload-time = "2025-06-07T14:49:25.67Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/fe/ab/66fc909931d5eb230107d016861824f335ae2c0533f422e654e5ff556784/numpy-2.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ba17f93a94e503551f154de210e4d50c5e3ee20f7e7a1b5f6ce3f22d419b93bb", size = 12868491, upload-time = "2025-06-07T14:49:44.898Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/ee/e8/2c8a1c9e34d6f6d600c83d5ce5b71646c32a13f34ca5c518cc060639841c/numpy-2.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f14e016d9409680959691c109be98c436c6249eaf7f118b424679793607b5944", size = 9935345, upload-time = "2025-06-07T14:50:02.311Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/6a/a2/f8c1133f90eaa1c11bbbec1dc28a42054d0ce74bc2c9838c5437ba5d4980/numpy-2.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:80b46117c7359de8167cc00a2c7d823bdd505e8c7727ae0871025a86d668283b", size = 21070759, upload-time = "2025-06-07T14:51:18.241Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/6c/e0/4c05fc44ba28463096eee5ae2a12832c8d2759cc5bcec34ae33386d3ff83/numpy-2.3.0-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:5814a0f43e70c061f47abd5857d120179609ddc32a613138cbb6c4e9e2dbdda5", size = 5301054, upload-time = "2025-06-07T14:51:27.413Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/8a/3b/6c06cdebe922bbc2a466fe2105f50f661238ea223972a69c7deb823821e7/numpy-2.3.0-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:ef6c1e88fd6b81ac6d215ed71dc8cd027e54d4bf1d2682d362449097156267a2", size = 6817520, upload-time = "2025-06-07T14:51:38.015Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/9d/a3/1e536797fd10eb3c5dbd2e376671667c9af19e241843548575267242ea02/numpy-2.3.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33a5a12a45bb82d9997e2c0b12adae97507ad7c347546190a18ff14c28bbca12", size = 14398078, upload-time = "2025-06-07T14:52:00.122Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/7c/61/9d574b10d9368ecb1a0c923952aa593510a20df4940aa615b3a71337c8db/numpy-2.3.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:54dfc8681c1906d239e95ab1508d0a533c4a9505e52ee2d71a5472b04437ef97", size = 16751324, upload-time = "2025-06-07T14:52:25.077Z" }, |  | ||||||
|     { url = "https://files.pythonhosted.org/packages/39/de/bcad52ce972dc26232629ca3a99721fd4b22c1d2bda84d5db6541913ef9c/numpy-2.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e017a8a251ff4d18d71f139e28bdc7c31edba7a507f72b1414ed902cbe48c74d", size = 12924237, upload-time = "2025-06-07T14:52:44.713Z" }, |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "packaging" | name = "packaging" | ||||||
| version = "25.0" | version = "25.0" | ||||||
|  | |||||||
| @ -1,331 +1,45 @@ | |||||||
| import numpy as np |  | ||||||
| import random |  | ||||||
| from copy import deepcopy |  | ||||||
| from world.behavioral import BehavioralModel | from world.behavioral import BehavioralModel | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class FlexibleNeuralNetwork: |  | ||||||
|     """ |  | ||||||
|     A flexible neural network that can mutate its structure and weights. |  | ||||||
|     Supports variable topology with cross-layer connections. |  | ||||||
|     """ |  | ||||||
| 
 |  | ||||||
|     def __init__(self, input_size=2, output_size=2): |  | ||||||
|         self.input_size = input_size |  | ||||||
|         self.output_size = output_size |  | ||||||
| 
 |  | ||||||
|         # Network structure: list of layers, each layer is a list of neurons |  | ||||||
|         # Each neuron is represented by its connections and bias |  | ||||||
|         self.layers = [] |  | ||||||
| 
 |  | ||||||
|         # Initialize with just input and output layers (no hidden layers) |  | ||||||
|         self._initialize_basic_network() |  | ||||||
| 
 |  | ||||||
|     def _initialize_basic_network(self): |  | ||||||
|         """Initialize a basic network with input->output connections only.""" |  | ||||||
|         # Input layer (no actual neurons, just placeholders) |  | ||||||
|         input_layer = [{'type': 'input', 'id': i} for i in range(self.input_size)] |  | ||||||
| 
 |  | ||||||
|         # Output layer with connections to all inputs |  | ||||||
|         output_layer = [] |  | ||||||
|         for i in range(self.output_size): |  | ||||||
|             neuron = { |  | ||||||
|                 'type': 'output', |  | ||||||
|                 'id': f'out_{i}', |  | ||||||
|                 'bias': random.uniform(-1, 1), |  | ||||||
|                 'connections': []  # List of (source_layer, source_neuron, weight) |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             # Connect to all input neurons |  | ||||||
|             for j in range(self.input_size): |  | ||||||
|                 neuron['connections'].append((0, j, random.uniform(-2, 2))) |  | ||||||
| 
 |  | ||||||
|             output_layer.append(neuron) |  | ||||||
| 
 |  | ||||||
|         self.layers = [input_layer, output_layer] |  | ||||||
| 
 |  | ||||||
|     def forward(self, inputs): |  | ||||||
|         """ |  | ||||||
|         Forward pass through the network. |  | ||||||
| 
 |  | ||||||
|         :param inputs: List or array of input values |  | ||||||
|         :return: List of output values |  | ||||||
|         """ |  | ||||||
|         if len(inputs) != self.input_size: |  | ||||||
|             raise ValueError(f"Expected {self.input_size} inputs, got {len(inputs)}") |  | ||||||
| 
 |  | ||||||
|         # Store activations for each layer |  | ||||||
|         activations = [inputs]  # Input layer activations |  | ||||||
| 
 |  | ||||||
|         # Process each subsequent layer |  | ||||||
|         for layer_idx in range(1, len(self.layers)): |  | ||||||
|             layer_activations = [] |  | ||||||
| 
 |  | ||||||
|             for neuron in self.layers[layer_idx]: |  | ||||||
|                 if neuron['type'] == 'input': |  | ||||||
|                     continue  # Skip input neurons in hidden layers |  | ||||||
| 
 |  | ||||||
|                 # Calculate weighted sum of inputs |  | ||||||
|                 weighted_sum = neuron['bias'] |  | ||||||
| 
 |  | ||||||
|                 for source_layer, source_neuron, weight in neuron['connections']: |  | ||||||
|                     if source_layer < len(activations): |  | ||||||
|                         if source_neuron < len(activations[source_layer]): |  | ||||||
|                             weighted_sum += activations[source_layer][source_neuron] * weight |  | ||||||
| 
 |  | ||||||
|                 # Apply activation function (tanh for bounded output) |  | ||||||
|                 activation = np.tanh(weighted_sum) |  | ||||||
|                 layer_activations.append(activation) |  | ||||||
| 
 |  | ||||||
|             activations.append(layer_activations) |  | ||||||
| 
 |  | ||||||
|         return activations[-1]  # Return output layer activations |  | ||||||
| 
 |  | ||||||
|     def mutate(self, mutation_rate=0.1): |  | ||||||
|         """ |  | ||||||
|         Create a mutated copy of this network. |  | ||||||
| 
 |  | ||||||
|         :param mutation_rate: Probability of each type of mutation |  | ||||||
|         :return: New mutated FlexibleNeuralNetwork instance |  | ||||||
|         """ |  | ||||||
|         mutated = deepcopy(self) |  | ||||||
| 
 |  | ||||||
|         # Different types of mutations |  | ||||||
|         mutations = [ |  | ||||||
|             mutated._mutate_weights, |  | ||||||
|             mutated._mutate_biases, |  | ||||||
|             mutated._add_connection, |  | ||||||
|             mutated._remove_connection, |  | ||||||
|             mutated._add_neuron, |  | ||||||
|             mutated._remove_neuron |  | ||||||
|         ] |  | ||||||
| 
 |  | ||||||
|         # Apply random mutations |  | ||||||
|         for mutation_func in mutations: |  | ||||||
|             if random.random() < mutation_rate: |  | ||||||
|                 mutation_func() |  | ||||||
| 
 |  | ||||||
|         return mutated |  | ||||||
| 
 |  | ||||||
|     def _mutate_weights(self): |  | ||||||
|         """Slightly modify existing connection weights.""" |  | ||||||
|         for layer in self.layers[1:]:  # Skip input layer |  | ||||||
|             for neuron in layer: |  | ||||||
|                 if 'connections' in neuron: |  | ||||||
|                     for i in range(len(neuron['connections'])): |  | ||||||
|                         if random.random() < 0.3:  # 30% chance to mutate each weight |  | ||||||
|                             source_layer, source_neuron, weight = neuron['connections'][i] |  | ||||||
|                             # Add small random change |  | ||||||
|                             new_weight = weight + random.uniform(-0.5, 0.5) |  | ||||||
|                             neuron['connections'][i] = (source_layer, source_neuron, new_weight) |  | ||||||
| 
 |  | ||||||
|     def _mutate_biases(self): |  | ||||||
|         """Slightly modify neuron biases.""" |  | ||||||
|         for layer in self.layers[1:]:  # Skip input layer |  | ||||||
|             for neuron in layer: |  | ||||||
|                 if 'bias' in neuron and random.random() < 0.3: |  | ||||||
|                     neuron['bias'] += random.uniform(-0.5, 0.5) |  | ||||||
| 
 |  | ||||||
|     def _add_connection(self): |  | ||||||
|         """Add a new random connection.""" |  | ||||||
|         if len(self.layers) < 2: |  | ||||||
|             return |  | ||||||
| 
 |  | ||||||
|         # Pick a random target neuron (not in input layer) |  | ||||||
|         target_layer_idx = random.randint(1, len(self.layers) - 1) |  | ||||||
|         target_neuron_idx = random.randint(0, len(self.layers[target_layer_idx]) - 1) |  | ||||||
|         target_neuron = self.layers[target_layer_idx][target_neuron_idx] |  | ||||||
| 
 |  | ||||||
|         if 'connections' not in target_neuron: |  | ||||||
|             return |  | ||||||
| 
 |  | ||||||
|         # Pick a random source (from any previous layer) |  | ||||||
|         source_layer_idx = random.randint(0, target_layer_idx - 1) |  | ||||||
|         if len(self.layers[source_layer_idx]) == 0: |  | ||||||
|             return |  | ||||||
| 
 |  | ||||||
|         source_neuron_idx = random.randint(0, len(self.layers[source_layer_idx]) - 1) |  | ||||||
| 
 |  | ||||||
|         # Check if connection already exists |  | ||||||
|         for conn in target_neuron['connections']: |  | ||||||
|             if conn[0] == source_layer_idx and conn[1] == source_neuron_idx: |  | ||||||
|                 return  # Connection already exists |  | ||||||
| 
 |  | ||||||
|         # Add new connection |  | ||||||
|         new_weight = random.uniform(-2, 2) |  | ||||||
|         target_neuron['connections'].append((source_layer_idx, source_neuron_idx, new_weight)) |  | ||||||
| 
 |  | ||||||
|     def _remove_connection(self): |  | ||||||
|         """Remove a random connection.""" |  | ||||||
|         for layer in self.layers[1:]: |  | ||||||
|             for neuron in layer: |  | ||||||
|                 if 'connections' in neuron and len(neuron['connections']) > 1: |  | ||||||
|                     if random.random() < 0.1:  # 10% chance to remove a connection |  | ||||||
|                         neuron['connections'].pop(random.randint(0, len(neuron['connections']) - 1)) |  | ||||||
| 
 |  | ||||||
|     def _add_neuron(self): |  | ||||||
|         """Add a new neuron to a random hidden layer or create a new hidden layer.""" |  | ||||||
|         if random.random() < 0.05:  # 5% chance to add neuron |  | ||||||
|             if len(self.layers) == 2:  # Only input and output layers |  | ||||||
|                 # Create a new hidden layer |  | ||||||
|                 hidden_neuron = { |  | ||||||
|                     'type': 'hidden', |  | ||||||
|                     'id': f'hidden_{random.randint(1000, 9999)}', |  | ||||||
|                     'bias': random.uniform(-1, 1), |  | ||||||
|                     'connections': [] |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 # Connect to some input neurons |  | ||||||
|                 for i in range(self.input_size): |  | ||||||
|                     if random.random() < 0.7:  # 70% chance to connect to each input |  | ||||||
|                         hidden_neuron['connections'].append((0, i, random.uniform(-2, 2))) |  | ||||||
| 
 |  | ||||||
|                 # Insert hidden layer |  | ||||||
|                 self.layers.insert(1, [hidden_neuron]) |  | ||||||
| 
 |  | ||||||
|                 # Update output layer connections to potentially use new hidden neuron |  | ||||||
|                 for neuron in self.layers[-1]:  # Output layer |  | ||||||
|                     if random.random() < 0.5:  # 50% chance to connect to new hidden neuron |  | ||||||
|                         neuron['connections'].append((1, 0, random.uniform(-2, 2))) |  | ||||||
| 
 |  | ||||||
|             else: |  | ||||||
|                 # Add neuron to existing hidden layer |  | ||||||
|                 hidden_layer_idx = random.randint(1, len(self.layers) - 2) |  | ||||||
|                 new_neuron = { |  | ||||||
|                     'type': 'hidden', |  | ||||||
|                     'id': f'hidden_{random.randint(1000, 9999)}', |  | ||||||
|                     'bias': random.uniform(-1, 1), |  | ||||||
|                     'connections': [] |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 # Connect to some neurons from previous layers |  | ||||||
|                 for layer_idx in range(hidden_layer_idx): |  | ||||||
|                     for neuron_idx in range(len(self.layers[layer_idx])): |  | ||||||
|                         if random.random() < 0.3:  # 30% chance to connect |  | ||||||
|                             new_neuron['connections'].append((layer_idx, neuron_idx, random.uniform(-2, 2))) |  | ||||||
| 
 |  | ||||||
|                 self.layers[hidden_layer_idx].append(new_neuron) |  | ||||||
| 
 |  | ||||||
|     def _remove_neuron(self): |  | ||||||
|         """Remove a random neuron from hidden layers.""" |  | ||||||
|         if len(self.layers) > 2:  # Has hidden layers |  | ||||||
|             for layer_idx in range(1, len(self.layers) - 1):  # Only hidden layers |  | ||||||
|                 if len(self.layers[layer_idx]) > 0 and random.random() < 0.02:  # 2% chance |  | ||||||
|                     neuron_idx = random.randint(0, len(self.layers[layer_idx]) - 1) |  | ||||||
|                     self.layers[layer_idx].pop(neuron_idx) |  | ||||||
| 
 |  | ||||||
|                     # Remove connections to this neuron from later layers |  | ||||||
|                     for later_layer_idx in range(layer_idx + 1, len(self.layers)): |  | ||||||
|                         for neuron in self.layers[later_layer_idx]: |  | ||||||
|                             if 'connections' in neuron: |  | ||||||
|                                 neuron['connections'] = [ |  | ||||||
|                                     (src_layer, src_neuron, weight) |  | ||||||
|                                     for src_layer, src_neuron, weight in neuron['connections'] |  | ||||||
|                                     if not (src_layer == layer_idx and src_neuron == neuron_idx) |  | ||||||
|                                 ] |  | ||||||
|                     break |  | ||||||
| 
 |  | ||||||
|     def get_structure_info(self): |  | ||||||
|         """Return information about the network structure.""" |  | ||||||
|         info = { |  | ||||||
|             'total_layers': len(self.layers), |  | ||||||
|             'layer_sizes': [len(layer) for layer in self.layers], |  | ||||||
|             'total_connections': 0, |  | ||||||
|             'total_neurons': sum(len(layer) for layer in self.layers) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         for layer in self.layers[1:]: |  | ||||||
|             for neuron in layer: |  | ||||||
|                 if 'connections' in neuron: |  | ||||||
|                     info['total_connections'] += len(neuron['connections']) |  | ||||||
| 
 |  | ||||||
|         return info |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class CellBrain(BehavioralModel): | class CellBrain(BehavioralModel): | ||||||
|     """ |     def __init__(self): | ||||||
|     Enhanced CellBrain using a flexible neural network with input normalization. |  | ||||||
|     """ |  | ||||||
| 
 |  | ||||||
|     def __init__(self, neural_network=None, input_ranges=None): |  | ||||||
|         super().__init__() |         super().__init__() | ||||||
| 
 |         # Define input keys | ||||||
|         # Define input and output keys |         self.inputs = { | ||||||
|         self.input_keys = ['distance', 'angle'] |             'distance': 0.0,  # Distance from a food object | ||||||
|         self.output_keys = ['linear_acceleration', 'angular_acceleration'] |             'angle': 0.0  # Relative angle to a food object | ||||||
| 
 |  | ||||||
|         # Initialize inputs and outputs |  | ||||||
|         self.inputs = {key: 0.0 for key in self.input_keys} |  | ||||||
|         self.outputs = {key: 0.0 for key in self.output_keys} |  | ||||||
| 
 |  | ||||||
|         # Set input ranges for normalization |  | ||||||
|         default_ranges = { |  | ||||||
|             'distance': (0, 50), |  | ||||||
|             'angle': (-180, 180) |  | ||||||
|         } |         } | ||||||
|         self.input_ranges = input_ranges if input_ranges is not None else default_ranges |  | ||||||
| 
 | 
 | ||||||
|         # Use provided network or create new one |         # Define output keys | ||||||
|         if neural_network is None: |         self.outputs = { | ||||||
|             self.neural_network = FlexibleNeuralNetwork( |             'linear_acceleration': 0.0,  # Linear acceleration | ||||||
|                 input_size=len(self.input_keys), |             'angular_acceleration': 0.0  # Angular acceleration | ||||||
|                 output_size=len(self.output_keys) |         } | ||||||
|             ) |  | ||||||
|         else: |  | ||||||
|             self.neural_network = neural_network |  | ||||||
| 
 | 
 | ||||||
|     def _normalize_input(self, key, value): |         self.weights = { | ||||||
|         min_val, max_val = self.input_ranges.get(key, (0.0, 1.0)) |             'distance': 0.1, | ||||||
|         # Avoid division by zero |             'angle': 0.5 | ||||||
|         if max_val == min_val: |         } | ||||||
|             return 0.0 |  | ||||||
|         # Normalize to [-1, 1] |  | ||||||
|         return 2 * (value - min_val) / (max_val - min_val) - 1 |  | ||||||
| 
 | 
 | ||||||
|     def tick(self, input_data) -> dict: |     def tick(self, input_data) -> dict: | ||||||
|         """ |         """ | ||||||
|         Process inputs through neural network and produce outputs. |         Process inputs and produce corresponding outputs. | ||||||
| 
 | 
 | ||||||
|         :param input_data: Dictionary containing input values |         :param input_data: Dictionary containing 'distance' and 'angle' values | ||||||
|         :return: Dictionary with output values |         :return: Dictionary with 'linear_acceleration' and 'angular_acceleration' values | ||||||
|         """ |         """ | ||||||
|         # Update internal input state |         # Update internal input state | ||||||
|         for key in self.input_keys: |         self.inputs['distance'] = input_data.get('distance', 0.0) | ||||||
|             self.inputs[key] = input_data.get(key, 0.0) |         self.inputs['angle'] = input_data.get('angle', 0.0) | ||||||
| 
 | 
 | ||||||
|         # Normalize inputs |         # Initialize output dictionary | ||||||
|         input_array = [self._normalize_input(key, self.inputs[key]) for key in self.input_keys] |         self.outputs = {'linear_acceleration': self.inputs['distance'] * self.weights['distance'], | ||||||
|  | 					   'angular_acceleration': self.inputs['angle'] * self.weights['angle']} | ||||||
| 
 | 
 | ||||||
|         # Process through neural network |         return self.outputs | ||||||
|         output_array = self.neural_network.forward(input_array) |  | ||||||
| 
 |  | ||||||
|         # Map outputs back to dictionary |  | ||||||
|         self.outputs = { |  | ||||||
|             key: output_array[i] if i < len(output_array) else 0.0 |  | ||||||
|             for i, key in enumerate(self.output_keys) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return self.outputs.copy() |  | ||||||
| 
 |  | ||||||
|     def mutate(self, mutation_rate=0.1): |  | ||||||
|         """ |  | ||||||
|         Create a mutated copy of this CellBrain. |  | ||||||
| 
 |  | ||||||
|         :param mutation_rate: Rate of mutation for the neural network |  | ||||||
|         :return: New CellBrain with mutated neural network |  | ||||||
|         """ |  | ||||||
|         mutated_network = self.neural_network.mutate(mutation_rate) |  | ||||||
|         return CellBrain(neural_network=mutated_network, input_ranges=self.input_ranges.copy()) |  | ||||||
| 
 |  | ||||||
|     def get_network_info(self): |  | ||||||
|         """Get information about the underlying neural network.""" |  | ||||||
|         return self.neural_network.get_structure_info() |  | ||||||
| 
 | 
 | ||||||
|     def __repr__(self): |     def __repr__(self): | ||||||
|         inputs = {key: round(value, 5) for key, value in self.inputs.items()} |         inputs = {key: round(value, 5) for key, value in self.inputs.items()} | ||||||
|         outputs = {key: round(value, 5) for key, value in self.outputs.items()} |         outputs = {key: round(value, 5) for key, value in self.outputs.items()} | ||||||
|         network_info = self.get_network_info() |         weights = {key: round(value, 5) for key, value in self.weights.items()} | ||||||
| 
 |         return f"CellBrain(inputs={inputs}, outputs={outputs}, weights={weights})" | ||||||
|         return (f"CellBrain(inputs={inputs}, outputs={outputs}, " |  | ||||||
|                 f"network_layers={network_info['layer_sizes']}, " |  | ||||||
|                 f"connections={network_info['total_connections']})") |  | ||||||
|  | |||||||
| @ -418,4 +418,4 @@ class DefaultCell(BaseEntity): | |||||||
|         position = f"({round(self.position.x, 1)}, {round(self.position.y, 1)})" |         position = f"({round(self.position.x, 1)}, {round(self.position.y, 1)})" | ||||||
|         velocity = tuple(round(value, 1) for value in self.velocity) |         velocity = tuple(round(value, 1) for value in self.velocity) | ||||||
|         acceleration = tuple(round(value, 1) for value in self.acceleration) |         acceleration = tuple(round(value, 1) for value in self.acceleration) | ||||||
|         return f"DefaultCell(position={position}, velocity={velocity}, acceleration={acceleration}" |         return f"DefaultCell(position={position}, velocity={velocity}, acceleration={acceleration}, behavioral_model={self.behavioral_model})" | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user