Compare commits
	
		
			No commits in common. "8f6b9c83220969022a67b13004c22b8d2bc023a0" and "5b9b6df9977b06a7d50fde724ab5171c90cee322" have entirely different histories.
		
	
	
		
			8f6b9c8322
			...
			5b9b6df997
		
	
		
| @ -4,7 +4,6 @@ | ||||
| import pygame | ||||
| import math | ||||
| from config.constants import * | ||||
| from world.base.brain import CellBrain | ||||
| 
 | ||||
| 
 | ||||
| class Renderer: | ||||
| @ -239,4 +238,4 @@ class Renderer: | ||||
|             screen_x, screen_y = camera.world_to_screen(obj_x, obj_y) | ||||
|             size = camera.get_relative_size(width) | ||||
|             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__": | ||||
|     engine = SimulationEngine() | ||||
|     engine.run() | ||||
|     main() | ||||
|  | ||||
| @ -4,7 +4,6 @@ version = "0.1.0" | ||||
| description = "Add your description here" | ||||
| requires-python = ">=3.11" | ||||
| dependencies = [ | ||||
|     "numpy>=2.3.0", | ||||
|     "pre-commit>=4.2.0", | ||||
|     "pydantic>=2.11.5", | ||||
|     "pygame>=2.6.1", | ||||
|  | ||||
							
								
								
									
										252
									
								
								ui/hud.py
									
									
									
									
									
								
							
							
						
						
									
										252
									
								
								ui/hud.py
									
									
									
									
									
								
							| @ -3,8 +3,6 @@ | ||||
| 
 | ||||
| import pygame | ||||
| from config.constants import * | ||||
| from world.base.brain import CellBrain, FlexibleNeuralNetwork | ||||
| from world.objects import DefaultCell | ||||
| 
 | ||||
| 
 | ||||
| class HUD: | ||||
| @ -125,252 +123,4 @@ class HUD: | ||||
|             text_rect = text.get_rect() | ||||
|             text_rect.left = legend_x + left_width + column_gap | ||||
|             text_rect.top = legend_y + 5 + i * legend_font_height | ||||
|             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) | ||||
|             screen.blit(text, text_rect) | ||||
							
								
								
									
										60
									
								
								uv.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										60
									
								
								uv.lock
									
									
									
										generated
									
									
									
								
							| @ -43,7 +43,6 @@ name = "dynamicsystemabstraction" | ||||
| version = "0.1.0" | ||||
| source = { virtual = "." } | ||||
| dependencies = [ | ||||
|     { name = "numpy" }, | ||||
|     { name = "pre-commit" }, | ||||
|     { name = "pydantic" }, | ||||
|     { name = "pygame" }, | ||||
| @ -57,7 +56,6 @@ dev = [ | ||||
| 
 | ||||
| [package.metadata] | ||||
| requires-dist = [ | ||||
|     { name = "numpy", specifier = ">=2.3.0" }, | ||||
|     { name = "pre-commit", specifier = ">=4.2.0" }, | ||||
|     { name = "pydantic", specifier = ">=2.11.5" }, | ||||
|     { 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" }, | ||||
| ] | ||||
| 
 | ||||
| [[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]] | ||||
| name = "packaging" | ||||
| version = "25.0" | ||||
|  | ||||
| @ -1,331 +1,45 @@ | ||||
| import numpy as np | ||||
| import random | ||||
| from copy import deepcopy | ||||
| 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): | ||||
|     """ | ||||
|     Enhanced CellBrain using a flexible neural network with input normalization. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, neural_network=None, input_ranges=None): | ||||
|     def __init__(self): | ||||
|         super().__init__() | ||||
| 
 | ||||
|         # Define input and output keys | ||||
|         self.input_keys = ['distance', 'angle'] | ||||
|         self.output_keys = ['linear_acceleration', 'angular_acceleration'] | ||||
| 
 | ||||
|         # 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) | ||||
|         # Define input keys | ||||
|         self.inputs = { | ||||
|             'distance': 0.0,  # Distance from a food object | ||||
|             'angle': 0.0  # Relative angle to a food object | ||||
|         } | ||||
|         self.input_ranges = input_ranges if input_ranges is not None else default_ranges | ||||
| 
 | ||||
|         # Use provided network or create new one | ||||
|         if neural_network is None: | ||||
|             self.neural_network = FlexibleNeuralNetwork( | ||||
|                 input_size=len(self.input_keys), | ||||
|                 output_size=len(self.output_keys) | ||||
|             ) | ||||
|         else: | ||||
|             self.neural_network = neural_network | ||||
|         # Define output keys | ||||
|         self.outputs = { | ||||
|             'linear_acceleration': 0.0,  # Linear acceleration | ||||
|             'angular_acceleration': 0.0  # Angular acceleration | ||||
|         } | ||||
| 
 | ||||
|     def _normalize_input(self, key, value): | ||||
|         min_val, max_val = self.input_ranges.get(key, (0.0, 1.0)) | ||||
|         # Avoid division by zero | ||||
|         if max_val == min_val: | ||||
|             return 0.0 | ||||
|         # Normalize to [-1, 1] | ||||
|         return 2 * (value - min_val) / (max_val - min_val) - 1 | ||||
|         self.weights = { | ||||
|             'distance': 0.1, | ||||
|             'angle': 0.5 | ||||
|         } | ||||
| 
 | ||||
|     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 | ||||
|         :return: Dictionary with output values | ||||
|         :param input_data: Dictionary containing 'distance' and 'angle' values | ||||
|         :return: Dictionary with 'linear_acceleration' and 'angular_acceleration' values | ||||
|         """ | ||||
|         # Update internal input state | ||||
|         for key in self.input_keys: | ||||
|             self.inputs[key] = input_data.get(key, 0.0) | ||||
|         self.inputs['distance'] = input_data.get('distance', 0.0) | ||||
|         self.inputs['angle'] = input_data.get('angle', 0.0) | ||||
| 
 | ||||
|         # Normalize inputs | ||||
|         input_array = [self._normalize_input(key, self.inputs[key]) for key in self.input_keys] | ||||
|         # Initialize output dictionary | ||||
|         self.outputs = {'linear_acceleration': self.inputs['distance'] * self.weights['distance'], | ||||
| 					   'angular_acceleration': self.inputs['angle'] * self.weights['angle']} | ||||
| 
 | ||||
|         # Process through neural network | ||||
|         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() | ||||
|         return self.outputs | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|         inputs = {key: round(value, 5) for key, value in self.inputs.items()} | ||||
|         outputs = {key: round(value, 5) for key, value in self.outputs.items()} | ||||
|         network_info = self.get_network_info() | ||||
| 
 | ||||
|         return (f"CellBrain(inputs={inputs}, outputs={outputs}, " | ||||
|                 f"network_layers={network_info['layer_sizes']}, " | ||||
|                 f"connections={network_info['total_connections']})") | ||||
|         weights = {key: round(value, 5) for key, value in self.weights.items()} | ||||
|         return f"CellBrain(inputs={inputs}, outputs={outputs}, weights={weights})" | ||||
|  | ||||
| @ -418,4 +418,4 @@ class DefaultCell(BaseEntity): | ||||
|         position = f"({round(self.position.x, 1)}, {round(self.position.y, 1)})" | ||||
|         velocity = tuple(round(value, 1) for value in self.velocity) | ||||
|         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