import torch import torch.nn as nn import torch.nn.functional as F import numpy as np import streamlit as st import matplotlib.pyplot as plt from collections import defaultdict import powerlaw from scipy import ndimage from scipy.ndimage import gaussian_filter import time from PIL import Image # ============================ # Dendritic Layer Definition # ============================ class DendriticLayer: def __init__(self, n_dendrites, size, device='cpu'): self.n_dendrites = n_dendrites self.size = size self.device = device # Initialize dendrites as PyTorch tensors on the specified device self.positions = torch.rand(n_dendrites, 2, device=device) * torch.tensor(size, device=device).float() self.directions = torch.randn(n_dendrites, 2, device=device) norms = torch.norm(self.directions, dim=1, keepdim=True) self.directions = self.directions / (norms + 1e-8) self.strengths = torch.ones(n_dendrites, device=device) self.points = torch.zeros((n_dendrites, 2), device=device) # Field state as a PyTorch tensor on the device self.field = torch.zeros(size, device=device) def process(self, input_field): """Process input through dendritic growth""" # Normalize input input_norm = self._normalize(input_field) # Reset field self.field.zero_() # Compute indices for all dendrites indices = self.positions.long() % torch.tensor(self.size, device=self.device).unsqueeze(0) x = indices[:, 0] y = indices[:, 1] # Gather field values at dendrite positions field_vals = input_norm[x, y] active = field_vals > 0.1 active_indices = active.nonzero(as_tuple=False).squeeze() if active_indices.numel() > 0: # Compute gradients try: grad_y, grad_x = torch.gradient(input_norm) except Exception as e: # Handle scenarios where torch.gradient might fail grad_x = torch.zeros_like(input_norm) grad_y = torch.zeros_like(input_norm) print(f"Gradient computation error: {e}") grad_val_x = grad_x[x[active], y[active]] grad_val_y = grad_y[x[active], y[active]] grad_norm = torch.sqrt(grad_val_x**2 + grad_val_y**2) + 1e-8 # Update directions for active dendrites direction_updates = torch.stack([grad_val_x, grad_val_y], dim=1) / grad_norm.unsqueeze(1) self.directions[active] = 0.9 * self.directions[active] + 0.1 * direction_updates self.directions[active] /= torch.norm(self.directions[active], dim=1, keepdim=True) + 1e-8 # Grow dendrites self.points[active] = self.positions[active] + self.directions[active] * self.strengths[active].unsqueeze(1) self.strengths[active] *= (1.0 + field_vals[active] * 0.1) # Update field self.field[x[active], y[active]] += field_vals[active] * self.strengths[active] # Smooth field using Gaussian filter (move to CPU for scipy) field_cpu = self.field.cpu().numpy() field_smoothed = gaussian_filter(field_cpu, sigma=1.0) self.field = torch.from_numpy(field_smoothed).to(self.device) return self._normalize(self.field) def _normalize(self, tensor): """Safely normalize tensor to [0,1] range""" min_val = tensor.min() max_val = tensor.max() return (tensor - min_val) / (max_val - min_val + 1e-8) if max_val > min_val else tensor # ===================================== # Critical Dendritic Field Definition # ===================================== class CriticalDendriticField(nn.Module): def __init__(self, field_size=64, n_dendrites=1000, device='cpu'): super().__init__() self.field_size = field_size self.device = device # Critical parameters self.coupling_strength = 0.015 self.min_coupling = 0.01 self.max_coupling = 0.02 self.adjustment_rate = 0.0001 self.optimal_variance = 0.4 self.variance_tolerance = 0.1 # Initialize field components self.field = torch.zeros((field_size, field_size), dtype=torch.complex64, device=device) self.field_shape = self._setup_field_shape() # Dendritic layer self.dendrites = DendriticLayer( n_dendrites=n_dendrites, size=(field_size, field_size), device=device ) # Pattern storage self.word_patterns = {} self.pattern_strengths = defaultdict(float) self.pattern_history = defaultdict(list) # Critical state tracking self.stability_window = [] self.avalanche_sizes = [] # Initialize with scale-free noise self._initialize_scale_free() # Additional attributes for GUI controls self.pattern_threshold = 0.5 # Default threshold def _setup_field_shape(self): """Create brain-like field shape""" shape = torch.ones(self.field_size, self.field_size, device=self.device) # Create cortical-like layers layers = torch.linspace(0.5, 1.0, 6, device=self.device) for i, strength in enumerate(layers): start = i * (self.field_size // 6) end = (i + 1) * (self.field_size // 6) shape[start:end, :] *= strength # Add some columnar structure columns = torch.cos(torch.linspace(0, 4 * np.pi, self.field_size, device=self.device)) shape *= (0.8 + 0.2 * columns.unsqueeze(0)) return shape def _initialize_scale_free(self): """Initialize with 1/f pink noise""" kx = torch.fft.fftfreq(self.field_size, d=1.0).to(self.device) ky = torch.fft.fftfreq(self.field_size, d=1.0).to(self.device) kx, ky = torch.meshgrid(kx, ky, indexing='ij') k = torch.sqrt(kx**2 + ky**2) k[0, 0] = 1.0 # Avoid division by zero noise = torch.randn(self.field_size, self.field_size, dtype=torch.complex64, device=self.device) noise_fft = torch.fft.fft2(noise) self.field = torch.fft.ifft2(noise_fft / (k + 1e-8)**0.75) def _evolve_pattern(self, pattern): """Evolve pattern through critical dynamics""" # Phase coupling phase = torch.angle(pattern) phase_diff = torch.roll(phase, shifts=1, dims=-1) - phase coupling = torch.exp(1j * phase_diff) * self.coupling_strength # Add turbulence energy = torch.mean(torch.abs(pattern)) noise_scale = 0.001 * (1.0 - torch.sigmoid(energy)) noise_real = torch.randn_like(pattern.real, device=self.device) noise_imag = torch.randn_like(pattern.imag, device=self.device) noise = (noise_real + 1j * noise_imag) * noise_scale # Shape-weighted update pattern = pattern + coupling * self.field_shape + noise # Normalize max_val = torch.max(torch.abs(pattern)) if max_val > 1.0: pattern = pattern / max_val return pattern def learn_word(self, word, context_words=None): """Learn word through combined dendritic-critical dynamics""" if word not in self.word_patterns: # Initialize with current field state field = self.field.clone() # Let dendrites form initial pattern for _ in range(20): # Grow dendrites dendrite_field = self.dendrites.process(torch.abs(field)) # Critical evolution field = self._evolve_pattern(field) self.word_patterns[word] = field self.pattern_strengths[word] = 0.3 # Strengthen pattern self.pattern_strengths[word] += 0.05 self.pattern_history[word].append((time.time(), self.pattern_strengths[word])) # Learn relationships through field dynamics if context_words: for context_word in context_words: if context_word in self.word_patterns: # Couple patterns pattern1 = self.word_patterns[word] pattern2 = self.word_patterns[context_word] # Create interference pattern interference = pattern1 + pattern2 # Let it evolve for _ in range(5): interference = self._evolve_pattern(interference) # Update both patterns self.word_patterns[word] = 0.9 * pattern1 + 0.1 * interference self.word_patterns[context_word] = 0.9 * pattern2 + 0.1 * interference def process_text(self, text): """Process text through the field""" words = text.lower().split() active_patterns = [] response_words = [] # Initialize combined field field = self.field.clone() # Process each word context_window = 5 for i, word in enumerate(words): # Get context context_start = max(0, i - context_window) context_end = min(len(words), i + context_window + 1) context = words[context_start:i] + words[i+1:context_end] try: # Learn or strengthen pattern self.learn_word(word, context) # Inject word pattern if word in self.word_patterns: pattern = self.word_patterns[word] field = 0.7 * field + 0.3 * pattern # Check activation strength = self.pattern_strengths[word] if strength > self.pattern_threshold: active_patterns.append(word) # Find resonant patterns related = self._find_resonant_patterns(pattern) response_words.extend(related) except Exception as e: print(f"Error processing word '{word}': {str(e)}") continue # Analyze criticality metrics = self._analyze_criticality() if metrics: self._adjust_coupling(metrics['field_variance']) # Update field state self.field = field return active_patterns, self._construct_response(response_words), metrics def _find_resonant_patterns(self, pattern, top_k=3): """Find patterns that resonate with input""" resonances = [] for word, word_pattern in self.word_patterns.items(): # Create interference interference = pattern + word_pattern energy_before = torch.mean(torch.abs(interference)) # Evolve briefly for _ in range(3): interference = self._evolve_pattern(interference) # Check stability energy_after = torch.mean(torch.abs(interference)) resonance = energy_after / (energy_before + 1e-6) resonances.append((word, resonance.item())) # Return top resonant words resonances.sort(key=lambda x: x[1], reverse=True) return [word for word, _ in resonances[:top_k]] def _construct_response(self, words): """Build response from resonant words""" if not words: return "..." # Filter for strong patterns strong_words = [w for w in words if self.pattern_strengths[w] > 0.3] return " ".join(strong_words) if strong_words else "..." def _analyze_criticality(self): """Analyze field for critical behavior""" field = torch.abs(self.field).cpu().numpy() # Find avalanches threshold = np.mean(field) + 0.3 * np.std(field) binary = field > threshold labeled, num_features = ndimage.label(binary) if num_features > 0: sizes = ndimage.sum(binary, labeled, range(1, num_features + 1)) self.avalanche_sizes.extend(sizes.tolist()) if len(self.avalanche_sizes) > 1000: self.avalanche_sizes = self.avalanche_sizes[-1000:] try: fit = powerlaw.Fit(self.avalanche_sizes, discrete=True) return { 'power_law_exponent': fit.power_law.alpha, 'is_critical': 1.3 < fit.power_law.alpha < 3.0, 'field_variance': np.var(field) } except Exception as e: print(f"Power law fit error: {e}") pass return None def _adjust_coupling(self, field_variance): """Adjust coupling to maintain criticality""" self.stability_window.append(field_variance) if len(self.stability_window) > 30: self.stability_window.pop(0) avg_variance = np.mean(self.stability_window) if avg_variance < self.optimal_variance - self.variance_tolerance: self.coupling_strength += self.adjustment_rate elif avg_variance > self.optimal_variance + self.variance_tolerance: self.coupling_strength -= self.adjustment_rate self.coupling_strength = torch.clamp( torch.tensor(self.coupling_strength, device=self.device), self.min_coupling, self.max_coupling ).item() # ===================================== # NeuroFlora Digital Organism Definition # ===================================== class NeuroFlora(CriticalDendriticField): def __init__(self, field_size=128, n_dendrites=2000, device='cpu'): super().__init__(field_size, n_dendrites, device) self.emotional_state = {'joy': 0.5, 'curiosity': 0.5, 'energy': 0.7} self.memory = defaultdict(list) self.skill_tree = { 'language': {'level': 1, 'xp': 0}, 'reasoning': {'level': 1, 'xp': 0}, 'creativity': {'level': 1, 'xp': 0} } self.last_interaction_time = time.time() def process_input(self, text): # Update emotional decay self._update_emotional_state() # Process text through critical dynamics active, response, metrics = self.process_text(text) # Learn emotional context emotion = self._detect_emotion(text) self.emotional_state[emotion] = min(1.0, self.emotional_state[emotion] + 0.1) # Update skills self._update_skills(text) # Generate artistic response art = self._generate_artistic_representation() return response, art, self._get_vital_signs() def _detect_emotion(self, text): # Simple emotion detection (could be enhanced with NLP) positive_words = ['love', 'happy', 'joy', 'beautiful', 'good', 'great', 'fantastic', 'wonderful'] negative_words = ['hate', 'sad', 'angry', 'pain', 'bad', 'terrible', 'horrible', 'awful'] if any(word in text.lower() for word in positive_words): return 'joy' elif any(word in text.lower() for word in negative_words): return 'energy' # Triggers protective energy return 'curiosity' def _update_skills(self, text): length = len(text.split()) self.skill_tree['language']['xp'] += length if self.skill_tree['language']['xp'] > 1000 * self.skill_tree['language']['level']: self.skill_tree['language']['level'] += 1 self.skill_tree['language']['xp'] = 0 if '?' in text: self.skill_tree['reasoning']['xp'] += 10 if len(text) > 40: self.skill_tree['creativity']['xp'] += 5 def _generate_artistic_representation(self): # Generate evolving digital art from field state field = torch.abs(self.field).cpu().numpy() art = np.zeros((self.field_size, self.field_size, 3)) # Emotional coloring art[:, :, 0] = field * self.emotional_state['joy'] # Red channel art[:, :, 1] = np.roll(field, 5, axis=0) * self.emotional_state['curiosity'] # Green art[:, :, 2] = np.roll(field, 10, axis=1) * self.emotional_state['energy'] # Blue # Dendritic patterns dendrites = self.dendrites.field.cpu().numpy() art[:, :, 1] += dendrites * 0.7 art = np.clip(art, 0, 1) # Convert to 8-bit image art_uint8 = (art * 255).astype(np.uint8) image = Image.fromarray(art_uint8) return image def _get_vital_signs(self): return { 'criticality': self._analyze_criticality(), 'dendritic_complexity': np.mean(self.dendrites.strengths.cpu().numpy()), 'memory_capacity': len(self.word_patterns), 'emotional_state': self.emotional_state, 'skill_levels': {k: v['level'] for k, v in self.skill_tree.items()} } def _update_emotional_state(self): # Emotional state naturally decays over time time_since = time.time() - self.last_interaction_time decay = np.exp(-time_since / 3600) # Hourly decay for k in self.emotional_state: self.emotional_state[k] = max(0.1, self.emotional_state[k] * decay) self.last_interaction_time = time.time() # ===================================== # Streamlit Interface Definition # ===================================== def main(): st.set_page_config(page_title="NeuroFlora Mind Garden", layout="wide") st.title("NeuroFlora Mind Garden 🌿🧠") # Initialize session state if 'neuroflora' not in st.session_state: st.session_state['neuroflora'] = NeuroFlora() if 'chat_log' not in st.session_state: st.session_state['chat_log'] = [] neuroflora = st.session_state['neuroflora'] chat_log = st.session_state['chat_log'] # Display chat history st.header("πŸ—¨οΈ Conversation") for message in chat_log: if message['sender'] == 'user': st.markdown(f"**You:** {message['message']}") else: st.markdown(f"**NeuroFlora:** {message['message']}") # User input form with st.form(key='chat_form', clear_on_submit=True): user_input = st.text_input("Enter your message:", placeholder="Type a message here...") submit_button = st.form_submit_button(label='Send') if submit_button and user_input: # Append user message chat_log.append({'sender': 'user', 'message': user_input}) st.session_state['chat_log'] = chat_log # Process input response, art, vitals = neuroflora.process_input(user_input) # Append NeuroFlora response chat_log.append({'sender': 'neuroflora', 'message': response}) st.session_state['chat_log'] = chat_log # Display digital art st.header("🎨 Digital Art") if neuroflora and hasattr(neuroflora, '_generate_artistic_representation'): art_image = neuroflora._generate_artistic_representation() st.image(art_image, caption="NeuroFlora's Artistic Representation", use_container_width=True) # Display vital signs st.header("πŸ“Š Vital Signs") vitals = neuroflora._get_vital_signs() if vitals: # Criticality if vitals['criticality']: st.markdown(f"**Criticality:**") st.markdown(f"- Power Law Exponent: {vitals['criticality']['power_law_exponent']:.2f}") state = "CRITICAL" if vitals['criticality']['is_critical'] else "NON-CRITICAL" st.markdown(f"- State: {state}") st.markdown(f"- Field Variance: {vitals['criticality']['field_variance']:.4f}") else: st.markdown("**Criticality:** Not enough data to determine criticality.") # Dendritic Complexity st.markdown(f"**Dendritic Complexity:** {vitals['dendritic_complexity']:.2f}") # Memory Capacity st.markdown(f"**Memory Capacity:** {vitals['memory_capacity']} patterns") # Emotional State st.markdown("**Emotional State:**") for emotion, value in vitals['emotional_state'].items(): st.markdown(f"- {emotion.capitalize()}: {value:.2f}") # Skill Levels st.markdown("**Skill Levels:**") for skill, level in vitals['skill_levels'].items(): st.markdown(f"- {skill.capitalize()}: Level {level}") # Optionally, reset conversation if st.button("πŸ”„ Reset Conversation"): st.session_state['chat_log'] = [] st.session_state['neuroflora'] = NeuroFlora() if __name__ == "__main__": main()