Identity Substrate Demo¶

SC-NeuroCore v3.13.3 — Persistent Spiking Networks for Identity Continuity

Create a persistent spiking neural network, inject experiences as spike patterns, observe STDP-driven weight modification, save/restore via the Lazarus checkpoint protocol, and extract high-level state with the decoder.

© 1998–2026 Miroslav Šotek. All rights reserved.
License: GNU AFFERO GENERAL PUBLIC LICENSE v3 | Commercial Licensing Available
Contact: www.anulum.li | protoscience@anulum.li

In [1]:
import numpy as np
import matplotlib.pyplot as plt

from sc_neurocore.identity.substrate import IdentitySubstrate
from sc_neurocore.identity.encoder import TraceEncoder
from sc_neurocore.identity.decoder import StateDecoder
from sc_neurocore.identity.checkpoint import Checkpoint

print("SC-NeuroCore Identity Substrate Demo")
Detected IPython. Loading juliacall extension. See https://juliapy.github.io/PythonCall.jl/stable/compat/#IPython
SC-NeuroCore Identity Substrate Demo

1. Create the Substrate¶

Three biologically distinct populations:

  • Cortical (Hodgkin-Huxley, 200 neurons): fast processing
  • Inhibitory (Wang-Buzsaki, 80 neurons): oscillation control
  • Memory (Hindmarsh-Rose, 50 neurons): burst-based trace storage
In [2]:
substrate = IdentitySubstrate(
    n_cortical=200,
    n_inhibitory=80,
    n_memory=50,
    seed=42,
)

# Warm up — let the network reach a baseline state
substrate.run(duration=0.15, dt=0.001)

health = substrate.health_check()
print(f"Mean rate: {health['mean_rate']:.1f} Hz")
print(f"CV: {health['cv']:.1f} Hz")
print(f"Fano factor: {health['fano']:.1f} Hz")
Mean rate: 0.0 Hz
CV: nan Hz
Fano factor: nan Hz

2. Encode and Inject Experiences¶

Text is converted to spike patterns via locality-sensitive hashing. STDP modifies weights based on co-activation.

In [3]:
experiences = [
    "Stochastic computing encodes values as random bitstreams",
    "An AND gate multiplies two probabilities",
    "Q8.8 fixed-point uses 8 integer and 8 fractional bits",
    "The Rust engine achieves 41.3 Gbit/s on AVX-512",
    "NIR maps spiking networks between frameworks",
]

# Record E-E weights before and after injection
weights_before = substrate.ee_weights.copy()

for exp in experiences:
    substrate.inject_experience(exp)
    substrate.run(duration=0.06, dt=0.001)
    print(f"  Injected: {exp[:50]}...")

weights_after = substrate.ee_weights
weight_change = np.abs(weights_after - weights_before).mean()
print(f"\nMean absolute weight change: {weight_change:.6f}")
  Injected: Stochastic computing encodes values as random bits...
  Injected: An AND gate multiplies two probabilities...
  Injected: Q8.8 fixed-point uses 8 integer and 8 fractional b...
  Injected: The Rust engine achieves 41.3 Gbit/s on AVX-512...
  Injected: NIR maps spiking networks between frameworks...

Mean absolute weight change: 0.000001

3. Visualize Weight Changes¶

In [4]:
fig, axes = plt.subplots(1, 3, figsize=(15, 4))

# ee_weights is the flat CSR value array of the sparse E->E
# projection; rebuild the dense matrix for visualisation.
from scipy.sparse import csr_matrix

n = substrate.n_cortical
indptr, indices = substrate.proj_ee.indptr, substrate.proj_ee.indices
W_before = csr_matrix((weights_before, indices, indptr), shape=(n, n)).toarray()
W_after = csr_matrix((weights_after, indices, indptr), shape=(n, n)).toarray()

im0 = axes[0].imshow(W_before[:50, :50], cmap='coolwarm', aspect='auto')
axes[0].set_title('Before experiences')
axes[0].set_xlabel('Post neuron')
axes[0].set_ylabel('Pre neuron')
plt.colorbar(im0, ax=axes[0])

im1 = axes[1].imshow(W_after[:50, :50], cmap='coolwarm', aspect='auto')
axes[1].set_title('After experiences')
axes[1].set_xlabel('Post neuron')
plt.colorbar(im1, ax=axes[1])

diff = W_after[:50, :50] - W_before[:50, :50]
im2 = axes[2].imshow(diff, cmap='RdBu_r', aspect='auto')
axes[2].set_title('Weight change (STDP)')
axes[2].set_xlabel('Post neuron')
plt.colorbar(im2, ax=axes[2])

plt.tight_layout()
plt.show()
No description has been provided for this image

4. Decode State¶

In [5]:
decoder = StateDecoder(substrate)

# Dominant activity patterns (PCA on spike trains)
patterns = decoder.extract_dominant_patterns(n_components=5)
print(f"Dominant patterns shape: {patterns.shape}")

# Connectivity signature (weight matrix fingerprint)
signature = decoder.extract_connectivity_signature()
print(f"Connectivity signature: {signature[:5]}...")

# Generate priming context
context = decoder.generate_priming_context()
print(f"\nPriming context:\n{context}")
Dominant patterns shape: (5, 100)
Connectivity signature: [[1.         0.         0.         0.         0.         0.
  0.         0.         0.         0.         0.         0.
  0.         0.         0.         0.         0.         0.
  0.         0.         0.         0.         0.         0.
  0.         0.         0.         0.         0.         0.        ]
 [0.         1.         0.         0.         0.         0.
  0.         0.         0.         0.         0.         0.
  0.         0.         0.         0.         0.         0.
  0.         0.         0.         0.         0.         0.
  0.         0.         0.         0.         0.         0.        ]
 [0.         0.         1.         0.         0.         0.
  0.         0.         0.         0.         0.         0.
  0.         0.         0.         0.         0.         0.
  0.         0.         0.         0.         0.         0.
  0.         0.         0.         0.         0.         0.        ]
 [0.         0.         0.         1.         0.         0.
  0.         0.         0.         0.         0.         0.
  0.         0.         0.         0.         0.         0.
  0.         0.         0.         0.         0.         0.
  0.         0.         0.         0.         0.         0.        ]
 [0.         0.         0.         0.         1.         0.
  0.         0.         0.49714299 0.         0.         0.
  0.         0.         0.         0.         0.0030754  0.
  0.         0.         0.         0.         0.         0.57555288
  0.         0.         0.         0.         0.         0.        ]]...

Priming context:
Substrate active: 1450 steps. Dominant patterns: 5. Stable attractors: 0. Mean rate: 0.7 Hz, CV: 0.62. Health: OK. E-E weights: mean=0.5000, std=0.0000.

5. Checkpoint: Save and Restore¶

In [6]:
import tempfile, os

# Save current state
with tempfile.TemporaryDirectory() as tmpdir:
    path = os.path.join(tmpdir, "identity_demo.npz")
    Checkpoint.save(substrate, path)
    print(f"Saved checkpoint: {os.path.getsize(path)} bytes")

    # Restore from checkpoint
    restored = Checkpoint.load(path)
    
    # Verify: weights match
    w_orig = substrate.ee_weights
    w_rest = restored.ee_weights
    match = np.allclose(w_orig, w_rest)
    print(f"Weights match after restore: {match}")
    
    # The restored substrate retains all learned patterns
    decoder_restored = StateDecoder(restored)
    sig_restored = decoder_restored.extract_connectivity_signature()
    sig_match = np.allclose(signature, sig_restored)
    print(f"Connectivity signature match: {sig_match}")
Saved checkpoint: 7110 bytes
Weights match after restore: True
Connectivity signature match: True

6. Spike History Visualization¶

In [7]:
# Get spike history from the last run
history = substrate.spike_history

if len(history) > 0:
    # Plot the last 100 timesteps of spike activity
    recent = np.array(history[-100:])
    fig, ax = plt.subplots(figsize=(12, 4))
    ax.imshow(recent.T, aspect='auto', cmap='binary', interpolation='none')
    ax.set_xlabel('Time step')
    ax.set_ylabel('Neuron index')
    ax.set_title(f'Recent spike activity ({recent.shape[1]} neurons × {recent.shape[0]} steps)')
    plt.tight_layout()
    plt.show()
else:
    print("No spike history available (run the substrate first)")
No description has been provided for this image

Summary¶

The identity substrate provides:

Feature Implementation
Encoding LSH text → sparse spike patterns
Storage STDP weight modification
Retrieval Attractor dynamics + PCA decoding
Persistence Lazarus checkpoint (.npz)
Self-regulation L16 Director controller

See Tutorial 32 for the full API walkthrough.