How to Analyze CANN Models?

Goal: By the end of this guide, you’ll be able to visualize CANN dynamics using energy landscape plots.

Estimated Reading Time: 12 minutes

Introduction

After running a CANN simulation, you need to visualize the results to understand the network’s behavior. Did the bump form correctly? Is it tracking the stimulus? Is the activity stable?

The Analyzer module provides visualization tools specifically designed for CANN dynamics. This guide focuses on the most essential visualization: energy landscape plots.

You’ll learn to:

  1. Create static energy landscape plots

  2. Generate animated visualizations

  3. Compare different models (CANN vs. CANN with SFA [9, 10])

  4. Use the PlotConfig system for consistent styling

What is an Energy Landscape?

An energy landscape shows how neural activity evolves over time:

  • X-axis: Neuron position (for 1D CANNs, this is angular position from -π to π)

  • Y-axis: Neural activity or synaptic input

  • Animation: Time evolution of the activity pattern

This visualization is perfect for observing:

  • Bump formation: Does a localized activity bump emerge?

  • Tracking: Does the bump follow external stimuli?

  • Stability: Does the bump maintain its shape and position?

The PlotConfig System

Before diving into examples, understand the visualization architecture.

The library uses PlotConfig dataclasses to manage plot settings. This provides:

  • Consistency: Same parameters across different plot types

  • Reusability: Save and reuse configurations

  • Type safety: Clear parameter names and validation

[ ]:
from canns.analyzer.visualization import PlotConfigs

# Create a configuration for animation
config = PlotConfigs.energy_landscape_1d_animation(
    time_steps_per_second=100,  # Simulation speed (100 steps/sec)
    fps=20,                     # Animation frame rate
    title='My CANN Simulation',
    xlabel='Position (rad)',
    ylabel='Activity',
    save_path='output.gif',
    show=False                  # Don't display, just save
)

Why PlotConfig? Instead of passing 10+ arguments to a function, you configure once and reuse. This becomes especially valuable when creating multiple similar plots.

Complete Workflow: From Model to Visualization

Let’s build a full example—creating, simulating, and visualizing a CANN:

[1]:
import brainpy.math as bm  # :cite:p:`wang2023brainpy`
from canns.models.basic import CANN1D
from canns.task.tracking import SmoothTracking1D
from canns.analyzer.visualization import PlotConfigs, energy_landscape_1d_animation

# 1. Setup
bm.set_dt(0.1)

# 2. Create model (auto-initializes)
cann = CANN1D(num=512)

# 3. Create task
task = SmoothTracking1D(
    cann_instance=cann,
    Iext=(1.0, 0.75, 2.0, 1.75, 3.0),
    duration=(10.0, 10.0, 10.0, 10.0),
    time_step=bm.get_dt(),
)
task.get_data()

# 4. Run simulation
def run_step(t, inputs):
    cann(inputs)
    return cann.u.value, cann.r.value

us, rs = bm.for_loop(
    run_step,
    operands=(task.run_steps, task.data),
    progress_bar=10
)

print("Simulation complete! Now visualizing...")

# 5. Create visualization config
config = PlotConfigs.energy_landscape_1d_animation(
    time_steps_per_second=100,
    fps=20,
    title='CANN1D Smooth Tracking',
    xlabel='Position (rad)',
    ylabel='Activity',
    repeat=True,
    show=True,
)

# 6. Generate animation
energy_landscape_1d_animation(
    data_sets={
        'u': (cann.x, us),      # Synaptic input over time
        'Iext': (cann.x, task.data)  # External stimulus
    },
    config=config
)
<SmoothTracking1D> Generating Task data: 400it [00:00, 1848.42it/s]
Simulation complete! Now visualizing...

What’s happening in step 6:

  • data_sets: Dictionary mapping labels to (positions, data) tuples

  • cann.x: Neuron positions (x-axis values)

  • us: Activity over time (shape [time_steps, num_neurons])

  • task.data: Original input stimuli for comparison

The animation will show two curves:

  • Blue (u): The CANN’s internal activity

  • Orange (Iext): The external input stimulus

You’ll see the activity bump tracking the moving stimulus!

Static Plots for Quick Inspection

For quick checks without generating full animations:

[2]:
from canns.analyzer.visualization import PlotConfigs, energy_landscape_1d_static

# Configuration for static plot
config_static = PlotConfigs.energy_landscape_1d_static(
    time_steps_per_second=100,
    title='Activity at t=200',
    xlabel='Position (rad)',
    ylabel='Activity',
    save_path='snapshot_t200.png',
    show=True  # Display the plot
)

# Plot activity at specific time step (e.g., t=200)
energy_landscape_1d_static(
    data_sets={'Activity': (cann.x, us[200])},  # Single time step
    config=config_static
)
Plot saved to: snapshot_t200.png
../../_images/en_1_quick_starts_03_analyze_model_5_1.png
[2]:
(<Figure size 1000x600 with 1 Axes>,
 <Axes: title={'center': 'Activity at t=200'}, xlabel='Position (rad)', ylabel='Activity'>)

Use case: Quickly check if the model reached a stable state at a particular time.

Comparing Models: CANN vs. CANN-SFA

This section compares standard CANN with CANN-SFA [9, 10] models.

One powerful visualization technique is comparing different models side-by-side. Let’s compare a standard CANN with a CANN that includes Spike Frequency Adaptation (SFA):

[4]:
import brainpy.math as bm  # :cite:p:`wang2023brainpy`
from canns.models.basic import CANN1D, CANN1D_SFA  # SFA: :cite:p:`mi2014spike,li2025dynamics`
from canns.task.tracking import SmoothTracking1D
from canns.analyzer.visualization import PlotConfigs, energy_landscape_1d_animation

bm.set_dt(0.1)

# Create two models (auto-initialize)
cann_standard = CANN1D(num=512)
cann_sfa = CANN1D_SFA(num=512, tau_v=50.0)  # SFA with adaptation time constant

# Same task for both
task = SmoothTracking1D(
    cann_instance=cann_standard,  # Use one model as reference
    Iext=(1.0, 0.75, 2.0, 1.75, 3.0),
    duration=(10.0, 10.0, 10.0, 10.0),
    time_step=0.1,
)
task.get_data()

# Simulate standard CANN
def step_standard(t, inputs):
    cann_standard(inputs)
    return cann_standard.u.value

us_standard = bm.for_loop(
    step_standard,
    operands=(task.run_steps, task.data),
    progress_bar=10
)

# Simulate CANN-SFA
def step_sfa(t, inputs):
    cann_sfa(inputs)
    return cann_sfa.u.value

us_sfa = bm.for_loop(
    step_sfa,
    operands=(task.run_steps, task.data),
    progress_bar=10
)

# Visualize both in one animation
config_comparison = PlotConfigs.energy_landscape_1d_animation(
    time_steps_per_second=100,
    fps=20,
    title='CANN vs CANN-SFA: Oscillatory Tracking',
    xlabel='Position (rad)',
    ylabel='Activity',
    repeat=True,
    show=True,
)

energy_landscape_1d_animation(
    data_sets={
        'CANN (Standard)': (cann_standard.x, us_standard),
        'CANN-SFA': (cann_sfa.x, us_sfa),
        'Input': (cann_standard.x, task.data)
    },
    config=config_comparison
)
<SmoothTracking1D> Generating Task data: 400it [00:00, 9277.33it/s]

What you’ll observe:

  • Standard CANN: Bump follows input closely

  • CANN-SFA: Bump may show oscillatory behavior or delayed tracking due to adaptation

  • Insight: SFA introduces history-dependent dynamics—visible as different tracking patterns

This comparison helps you understand how model variants behave differently under the same conditions.

Customizing Visualizations

Adjusting Animation Speed

[ ]:
config_slow = PlotConfigs.energy_landscape_1d_animation(
    time_steps_per_second=50,   # Show 50 steps per second (slower)
    fps=10,                     # 10 frames per second (smoother)
    ...
)

Rule of thumb: time_steps_per_second / fps = steps per frame

  • Higher ratio = faster animation

  • Lower ratio = slower, more detailed animation

Changing Appearance

[ ]:
config_styled = PlotConfigs.energy_landscape_1d_animation(
    figsize=(10, 4),            # Wider figure
    title='Custom Styled Plot',
    xlabel='Angular Position',
    ylabel='Firing Rate (Hz)',
    title_fontsize=14,
    save_path='styled_output.gif',
    show=False
)

Saving vs. Displaying

[ ]:
# Save only (for batch processing)
config_save = PlotConfigs.energy_landscape_1d_animation(
    save_path='output.gif',
    show=False
)

# Display only (for interactive exploration)
config_display = PlotConfigs.energy_landscape_1d_animation(
    save_path=None,
    show=True
)

# Both
config_both = PlotConfigs.energy_landscape_1d_animation(
    save_path='output.gif',
    show=True
)

More Analysis Tools

This guide focuses on energy landscapes, but the analyzer module includes many other tools:

  • Tuning curves: Neuron selectivity plots

  • Firing fields: Spatial response maps

  • Bump fitting: Extract bump position and width

  • Spike plots: Raster plots for spike-based models

These are covered in detail in the Full Details: Model Analyzer section.

Next Steps

Now that you can visualize CANN dynamics, you’re ready to:

  1. Analyze experimental data—Apply similar techniques to real neural recordings

  2. Explore Model Analyzer—Learn about all available analysis methods

  3. Full Visualization API—Complete reference for PlotConfig and all plot functions

Questions? Check the PlotConfig Guide or GitHub Discussions.