Implement FlexibleNeuralNetwork and enhance CellBrain with input normalization and neural network integration #2
| @ -4,6 +4,7 @@ | |||||||
| 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: | ||||||
|  | |||||||
							
								
								
									
										107
									
								
								core/simulation_engine.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								core/simulation_engine.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,107 @@ | |||||||
|  | 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,117 +1,5 @@ | |||||||
| import math | from core.simulation_engine import SimulationEngine | ||||||
| 
 |  | ||||||
| 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__": | ||||||
|     main() |     engine = SimulationEngine() | ||||||
|  |     engine.run() | ||||||
|  | |||||||
| @ -4,6 +4,7 @@ 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", | ||||||
|  | |||||||
							
								
								
									
										250
									
								
								ui/hud.py
									
									
									
									
									
								
							
							
						
						
									
										250
									
								
								ui/hud.py
									
									
									
									
									
								
							| @ -3,6 +3,8 @@ | |||||||
| 
 | 
 | ||||||
| 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: | ||||||
| @ -124,3 +126,251 @@ class HUD: | |||||||
|             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,6 +43,7 @@ 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" }, | ||||||
| @ -56,6 +57,7 @@ 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" }, | ||||||
| @ -101,6 +103,64 @@ 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,45 +1,331 @@ | |||||||
|  | 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 |  | ||||||
|         self.inputs = { |  | ||||||
|             'distance': 0.0,  # Distance from a food object |  | ||||||
|             'angle': 0.0  # Relative angle to a food object |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         # Define output keys |         # Define input and output keys | ||||||
|         self.outputs = { |         self.input_keys = ['distance', 'angle'] | ||||||
|             'linear_acceleration': 0.0,  # Linear acceleration |         self.output_keys = ['linear_acceleration', 'angular_acceleration'] | ||||||
|             'angular_acceleration': 0.0  # Angular acceleration |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         self.weights = { |         # Initialize inputs and outputs | ||||||
|             'distance': 0.1, |         self.inputs = {key: 0.0 for key in self.input_keys} | ||||||
|             'angle': 0.5 |         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 | ||||||
|  |         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 | ||||||
|  | 
 | ||||||
|  |     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 | ||||||
| 
 | 
 | ||||||
|     def tick(self, input_data) -> dict: |     def tick(self, input_data) -> dict: | ||||||
|         """ |         """ | ||||||
|         Process inputs and produce corresponding outputs. |         Process inputs through neural network and produce outputs. | ||||||
| 
 | 
 | ||||||
|         :param input_data: Dictionary containing 'distance' and 'angle' values |         :param input_data: Dictionary containing input values | ||||||
|         :return: Dictionary with 'linear_acceleration' and 'angular_acceleration' values |         :return: Dictionary with output values | ||||||
|         """ |         """ | ||||||
|         # Update internal input state |         # Update internal input state | ||||||
|         self.inputs['distance'] = input_data.get('distance', 0.0) |         for key in self.input_keys: | ||||||
|         self.inputs['angle'] = input_data.get('angle', 0.0) |             self.inputs[key] = input_data.get(key, 0.0) | ||||||
| 
 | 
 | ||||||
|         # Initialize output dictionary |         # Normalize inputs | ||||||
|         self.outputs = {'linear_acceleration': self.inputs['distance'] * self.weights['distance'], |         input_array = [self._normalize_input(key, self.inputs[key]) for key in self.input_keys] | ||||||
| 					   'angular_acceleration': self.inputs['angle'] * self.weights['angle']} |  | ||||||
| 
 | 
 | ||||||
|         return self.outputs |         # 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() | ||||||
| 
 | 
 | ||||||
|     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()} | ||||||
|         weights = {key: round(value, 5) for key, value in self.weights.items()} |         network_info = self.get_network_info() | ||||||
|         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']})") | ||||||
							
								
								
									
										0
									
								
								world/base/neural.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								world/base/neural.py
									
									
									
									
									
										Normal file
									
								
							| @ -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}, behavioral_model={self.behavioral_model})" |         return f"DefaultCell(position={position}, velocity={velocity}, acceleration={acceleration}" | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user