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()
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)")
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.