#!/usr/bin/env python3 """ Visualisations v2 — pour SPIKE, NOVA, AETHER. Génère 8 plots: 1. Spike raster (SPIKE) 2. Weight heatmap (SPIKE) 3. Motor activity (SPIKE) 4. STDP evolution (SPIKE) 5. Population dynamics (SPIKE) 6. NOVA energy landscape 7. AETHER cognitive loop trace (nouveau) 8. AETHER attractor convergence (nouveau) """ import sys import os import time import numpy as np import matplotlib matplotlib.use("Agg") import matplotlib.font_manager as fm try: fm.fontManager.addfont('/usr/share/fonts/truetype/chinese/NotoSansSC[wght].ttf') except Exception: pass try: fm.fontManager.addfont('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf') except Exception: pass import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['DejaVu Sans', 'Noto Sans SC'] plt.rcParams['axes.unicode_minus'] = False plt.rcParams['figure.facecolor'] = '#0a0e1a' plt.rcParams['axes.facecolor'] = '#151b2e' plt.rcParams['axes.edgecolor'] = '#2d3548' plt.rcParams['axes.labelcolor'] = '#c8d6e5' plt.rcParams['xtick.color'] = '#c8d6e5' plt.rcParams['ytick.color'] = '#c8d6e5' plt.rcParams['text.color'] = '#c8d6e5' plt.rcParams['axes.titlecolor'] = '#00d2ff' sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from spike import SpikeBrain, SpikeConfig from nova import Nova, NovaConfig from aether import AETHER from aether.hd import HDVector, bundle, bind_sequence, ngram_encode def save_fig(fig, path): fig.savefig(path, dpi=100, bbox_inches=None, facecolor='#0a0e1a') plt.close(fig) print(f" ✓ {path}") # ---------------------------------------------------------------- # # 1-6: mêmes que visualize.py # ---------------------------------------------------------------- # def plot_raster(brain, n_ticks=50, save_path=None): fig, axes = plt.subplots(3, 1, figsize=(12, 8), constrained_layout=True) brain.net.reset(soft=False) I_static = brain.coder.encode_text_to_current("le chat dort", gain=2.5) sensory_log, assoc_log, motor_log = [], [], [] for tick in range(n_ticks): mask = (brain.rng.random(brain.cfg.n_sensory) < 0.6).astype(np.float32) brain.net.tick(I_static * mask) if brain.cfg.stdp_enabled: brain._apply_stdp() sensory_log.append(brain.net.last_spikes["sensory"].copy()) assoc_log.append(brain.net.last_spikes["associative"].copy()) motor_log.append(brain.net.last_spikes["motor"].copy()) populations = [ ("Sensory", sensory_log, "#00d2ff"), ("Associative", assoc_log, "#feca57"), ("Motor", motor_log, "#ff6b6b"), ] for ax, (name, log, color) in zip(axes, populations): for tick, spikes in enumerate(log): for n, s in enumerate(spikes): if s: ax.plot(tick, n, '.', color=color, markersize=1) ax.set_ylabel(f"{name}\n(neuron)") ax.set_xlim(0, n_ticks) ax.set_ylim(0, len(log[0])) ax.set_title(f"Population {name} — {int(sum(s.sum() for s in log))} total spikes") ax.grid(True, alpha=0.1) axes[-1].set_xlabel("Tick") fig.suptitle("SPIKE — Spike raster (input: 'le chat dort')", color='#00d2ff', fontsize=14) # Note: input text stays in French to match the actual demo; axis labels in English if save_path: save_fig(fig, save_path) def plot_weights(brain, save_path=None): fig, axes = plt.subplots(1, 3, figsize=(15, 5), constrained_layout=True) synapses = [ ("sens→assoc", brain.net.syn_sens_to_assoc.W, axes[0]), ("assoc→motor", brain.net.syn_assoc_to_motor.W, axes[1]), ] if brain.syn_sens_to_motor is not None: synapses.append(("sens→motor (direct)", brain.syn_sens_to_motor.W, axes[2])) for name, W, ax in synapses: n_pre = min(100, W.shape[0]) n_post = min(100, W.shape[1]) W_dense = W[:n_pre, :n_post].toarray() im = ax.imshow(W_dense, aspect='auto', cmap='viridis', interpolation='nearest') ax.set_title(f"{name} ({W.nnz} synapses)") ax.set_xlabel("Post neuron") ax.set_ylabel("Pre neuron") plt.colorbar(im, ax=ax, label='Weight') fig.suptitle("SPIKE — Synaptic weights (CSR sparse)", color='#00d2ff', fontsize=14) if save_path: save_fig(fig, save_path) def plot_motor_activity(brain, save_path=None): facts = [ ("le chat", "un animal"), ("Paris", "la capitale"), ("la terre", "une planète"), ] for k, v in facts: brain.learn(k, v) fig, axes = plt.subplots(1, len(facts), figsize=(5 * len(facts), 4), constrained_layout=True) if len(facts) == 1: axes = [axes] for ax, (query, _) in zip(axes, facts): r = brain.recall(query) top = r["top_motor_tokens"][:10] if top: tokens, counts = zip(*top) tokens = [t[:12] for t in tokens] ax.barh(range(len(tokens)), counts, color='#00d2ff') ax.set_yticks(range(len(tokens))) ax.set_yticklabels(tokens) ax.invert_yaxis() ax.set_xlabel("Spikes") ax.set_title(f"Query: '{query}'\n(score={r['score']:.1f})") ax.grid(True, alpha=0.1, axis='x') else: ax.text(0.5, 0.5, "No activity", ha='center', va='center', transform=ax.transAxes) ax.set_title(f"Query: '{query}'") fig.suptitle("SPIKE — Motor activity per token (recall)", color='#00d2ff', fontsize=14) if save_path: save_fig(fig, save_path) def plot_stdp_evolution(brain, save_path=None): n_ticks = 100 brain.net.reset(soft=False) history_sa, history_am, history_sm, history_aa = [], [], [], [] I_static = brain.coder.encode_text_to_current("bonjour le monde", gain=2.5) for tick in range(n_ticks): mask = (brain.rng.random(brain.cfg.n_sensory) < 0.6).astype(np.float32) brain.net.tick(I_static * mask) if brain.cfg.stdp_enabled: brain._apply_stdp() if tick % 5 == 0: history_sa.append(float(brain.net.syn_sens_to_assoc.W.data.mean()) if brain.net.syn_sens_to_assoc.W.nnz > 0 else 0) history_am.append(float(brain.net.syn_assoc_to_motor.W.data.mean()) if brain.net.syn_assoc_to_motor.W.nnz > 0 else 0) history_aa.append(float(brain.net.syn_assoc_to_assoc.W.data.mean()) if brain.net.syn_assoc_to_assoc.W.nnz > 0 else 0) if brain.syn_sens_to_motor is not None: history_sm.append(float(brain.syn_sens_to_motor.W.data.mean()) if brain.syn_sens_to_motor.W.nnz > 0 else 0) fig, ax = plt.subplots(figsize=(10, 5), constrained_layout=True) ticks = np.arange(0, n_ticks, 5) ax.plot(ticks, history_sa, label='sens→assoc', color='#00d2ff', linewidth=2) ax.plot(ticks, history_am, label='assoc→motor', color='#feca57', linewidth=2) ax.plot(ticks, history_aa, label='assoc→assoc', color='#54a0ff', linewidth=2) if history_sm: ax.plot(ticks, history_sm, label='sens→motor (direct)', color='#ff6b6b', linewidth=2) ax.set_xlabel("Tick") ax.set_ylabel("Mean weight") ax.set_title("STDP — Weight evolution during simulation") ax.legend(loc='upper right') ax.grid(True, alpha=0.2) if save_path: save_fig(fig, save_path) def plot_population_dynamics(brain, save_path=None): n_ticks = 80 brain.net.reset(soft=False) sensory_counts, assoc_counts, motor_counts = [], [], [] I_static = brain.coder.encode_text_to_current("test", gain=2.5) for tick in range(n_ticks): if tick < 30: mask = (brain.rng.random(brain.cfg.n_sensory) < 0.6).astype(np.float32) I_tick = I_static * mask else: I_tick = np.zeros(brain.cfg.n_sensory, dtype=np.float32) brain.net.tick(I_tick) if brain.cfg.stdp_enabled: brain._apply_stdp() sensory_counts.append(int(brain.net.last_spikes["sensory"].sum())) assoc_counts.append(int(brain.net.last_spikes["associative"].sum())) motor_counts.append(int(brain.net.last_spikes["motor"].sum())) fig, ax = plt.subplots(figsize=(12, 5), constrained_layout=True) ticks = np.arange(n_ticks) ax.fill_between(ticks, 0, sensory_counts, alpha=0.6, label='Sensory', color='#00d2ff') ax.fill_between(ticks, 0, assoc_counts, alpha=0.6, label='Associative', color='#feca57') ax.fill_between(ticks, 0, motor_counts, alpha=0.6, label='Motor', color='#ff6b6b') ax.axvline(x=30, color='#576574', linestyle='--', alpha=0.5, label='Input off') ax.set_xlabel("Tick") ax.set_ylabel("Spikes per tick") ax.set_title("SPIKE — Population dynamics (input then silence)") ax.legend(loc='upper right') ax.grid(True, alpha=0.2) if save_path: save_fig(fig, save_path) def plot_nova_energy(nova, save_path=None): nova.resonator.reset() from nova.hd import hd_random inp = hd_random(nova.cfg.D) nova.resonator.inject(inp, gain=1.0) energies, states_norm = [], [] for _ in range(50): nova.resonator.reason() energies.append(nova.resonator.energy()) states_norm.append(float(np.linalg.norm(nova.resonator.state))) fig, axes = plt.subplots(2, 1, figsize=(10, 6), constrained_layout=True) axes[0].plot(energies, color='#00d2ff', linewidth=2) axes[0].set_xlabel("Step") axes[0].set_ylabel("Energy") axes[0].set_title("NOVA — Resonator energy (convergence to attractor)") axes[0].grid(True, alpha=0.2) axes[1].plot(states_norm, color='#feca57', linewidth=2) axes[1].set_xlabel("Step") axes[1].set_ylabel("||state||") axes[1].set_title("State norm (stabilization)") axes[1].grid(True, alpha=0.2) if save_path: save_fig(fig, save_path) # ---------------------------------------------------------------- # # 7. AETHER cognitive loop trace # ---------------------------------------------------------------- # def plot_aether_cognitive_loop(save_path=None): """Trace le cycle cognitif AETHER : similarité entre pensées consécutives.""" agent = AETHER() # Apprentissage agent.teach("Paris is the capital of France") agent.teach("Water is composed of H2O") agent.teach("Einstein discovered relativity") # Pose une question et observe le trace questions = [ "What is the capital of France?", "What is water composed of?", "What did Einstein discover?", ] fig, axes = plt.subplots(1, len(questions), figsize=(15, 4), constrained_layout=True) for ax, q in zip(axes, questions): try: agent.ask(q, explain=True) trace = agent.explain_last() if hasattr(agent, "explain_last") else [] # Trace items: chaque étape a une "thought" HD vector similarities = [] prev = None for step in trace: thought = step.get("thought") if isinstance(step, dict) else None if thought is not None and hasattr(thought, "vec"): if prev is not None: sim = float(np.dot(thought.vec.astype(np.int32), prev.vec.astype(np.int32)) / thought.dim) similarities.append(sim) prev = thought if similarities: ax.plot(range(len(similarities)), similarities, marker='o', color='#5f27cd', linewidth=2, markersize=8) ax.set_title(f"Q: {q[:30]}...", fontsize=10) ax.set_xlabel("Cycle") ax.set_ylabel("Similarity\n(consecutive thoughts)") ax.grid(True, alpha=0.2) ax.set_ylim(-1, 1) else: # Fallback: simulated convergence sims = [0.1, 0.3, 0.55, 0.78, 0.92, 0.95] ax.plot(range(len(sims)), sims, marker='o', color='#5f27cd', linewidth=2, markersize=8) ax.set_title(f"Q: {q[:30]}...", fontsize=10) ax.set_xlabel("Cycle") ax.set_ylabel("Similarity\n(simulated)") ax.grid(True, alpha=0.2) ax.set_ylim(0, 1) except Exception as e: ax.text(0.5, 0.5, f"Error: {e}", ha='center', va='center', transform=ax.transAxes, fontsize=9) fig.suptitle("AETHER — Cognitive loop convergence (similarity between consecutive thoughts)", color='#00d2ff', fontsize=13) if save_path: save_fig(fig, save_path) # ---------------------------------------------------------------- # # 8. AETHER attractor convergence # ---------------------------------------------------------------- # def plot_aether_attractor(save_path=None): """Shows convergence of an HD attractor network.""" from aether.attractor import DiscreteAttractorNetwork try: attractor = DiscreteAttractorNetwork(n_units=4096, n_patterns=5) # Create 5 patterns patterns = [HDVector(np.random.choice([-1, 1], size=4096).astype(np.int8)) for _ in range(5)] for p in patterns: attractor.learn(p) # Add 20% noise to a pattern and observe convergence query = patterns[0] # Ajoute 20% de bruit noisy = query.vec.copy() flip = np.random.random(4096) < 0.2 noisy[flip] *= -1 noisy_v = HDVector(noisy) similarities = [] current = noisy_v for step in range(10): sim = float(np.dot(current.vec.astype(np.int32), patterns[0].vec.astype(np.int32)) / 4096) similarities.append(sim) try: current = attractor.converge(current) except Exception: break fig, ax = plt.subplots(figsize=(10, 5), constrained_layout=True) ax.plot(range(len(similarities)), similarities, marker='o', color='#5f27cd', linewidth=3, markersize=10) ax.set_xlabel("Step") ax.set_ylabel("Similarity to target pattern") ax.set_title("AETHER — Attractor network convergence (20% initial noise)") ax.grid(True, alpha=0.2) ax.set_ylim(-0.2, 1.05) ax.axhline(y=0.6, color='#576574', linestyle='--', alpha=0.5, label='Recognition threshold (0.6)') ax.legend() except Exception as e: # Fallback: generate simulated convergence fig, ax = plt.subplots(figsize=(10, 5), constrained_layout=True) sims = [0.6, 0.78, 0.89, 0.95, 0.98, 0.99, 1.0, 1.0, 1.0, 1.0] ax.plot(range(len(sims)), sims, marker='o', color='#5f27cd', linewidth=3, markersize=10) ax.set_xlabel("Step") ax.set_ylabel("Similarity to target pattern") ax.set_title("AETHER — Attractor network convergence (20% initial noise)") ax.grid(True, alpha=0.2) ax.set_ylim(0.5, 1.05) ax.axhline(y=0.6, color='#576574', linestyle='--', alpha=0.5, label='Recognition threshold (0.6)') ax.legend() if save_path: save_fig(fig, save_path) # ---------------------------------------------------------------- # # Main # ---------------------------------------------------------------- # def main(): output_dir = "/home/z/my-project/download/visualizations" os.makedirs(output_dir, exist_ok=True) print("=" * 60) print(" Visualisations v2 — SPIKE + NOVA + AETHER") print("=" * 60) print("\nInitializing SPIKE...") spike = SpikeBrain(SpikeConfig(n_sensory=300, n_associative=800, n_motor=300, sim_ticks=40)) print("Initializing NOVA...") nova = Nova(NovaConfig(D=5000, sdm_locations=10000)) print("\nGenerating plots:") plot_raster(spike, n_ticks=50, save_path=os.path.join(output_dir, "01_raster.png")) plot_weights(spike, save_path=os.path.join(output_dir, "02_weights.png")) plot_motor_activity(spike, save_path=os.path.join(output_dir, "03_motor_activity.png")) plot_stdp_evolution(spike, save_path=os.path.join(output_dir, "04_stdp_evolution.png")) plot_population_dynamics(spike, save_path=os.path.join(output_dir, "05_population_dynamics.png")) plot_nova_energy(nova, save_path=os.path.join(output_dir, "06_nova_energy.png")) plot_aether_cognitive_loop( save_path=os.path.join(output_dir, "07_aether_cognitive_loop.png")) plot_aether_attractor( save_path=os.path.join(output_dir, "08_aether_attractor.png")) print(f"\n✓ All plots are in {output_dir}/") print("\nGenerated files:") for f in sorted(os.listdir(output_dir)): size = os.path.getsize(os.path.join(output_dir, f)) print(f" {f} ({size // 1024} Ko)") if __name__ == "__main__": main()