Tutorial 2: Task Generation and CANN Simulation¶
This tutorial teaches you how to generate task data using the Task module and run simulations with CANN models.
1. Task Module Overview¶
The CANNs Task module generates experimental paradigms and input data. The relationship between Task and Model:
Task: Generates external stimulus sequences (input data)
Model: Consumes input data, runs in simulation loop
Task Categories¶
CANNs provides two main task types:
Tracking Tasks:
PopulationCoding1D/2D—Population codingTemplateMatching1D/2D—Template matchingSmoothTracking1D/2D—Smooth tracking
Navigation Tasks:
ClosedLoopNavigation—Closed-loop navigationOpenLoopNavigation—Open-loop navigation
Note
This tutorial uses the simplest PopulationCoding1D as an example. Other tasks follow similar usage patterns with different initialization parameters. We’ll demonstrate different tasks in later tutorials.
2. PopulationCoding1D in Detail¶
PopulationCoding1D is a simple population coding task: no stimulus → stimulus → no stimulus. This tests the network’s ability to form and maintain a memory bump.
2.1 Import and Create Task¶
[ ]:
from canns.task.tracking import PopulationCoding1D
from canns.models.basic import CANN1D
import brainpy.math as bm # :cite:p:`wang2023brainpy`
# First create model instance
bm.set_dt(0.1)
model = CANN1D(num=256, tau=1.0, k=8.1, a=0.5, A=10, J0=4.0)
# Create task
task = PopulationCoding1D(
cann_instance=model, # CANN model instance
before_duration=10.0, # Duration before stimulus
after_duration=50.0, # Duration after stimulus
Iext=1.0, # Stimulus position in feature space
duration=10.0, # Stimulus duration
time_step=0.1, # Time step
)
2.2 Parameter Descriptions¶
Parameter |
Type |
Description |
|---|---|---|
|
BaseCANN1D |
CANN model instance—task calls its |
|
float |
Duration before stimulus presentation (no input period) |
|
float |
Duration after stimulus ends (observe bump maintenance) |
|
float |
Stimulus position in feature space, typically in |
|
float |
Duration of stimulus presentation |
|
float |
Simulation time step—should match |
Why these parameters matter:
cann_instanceis required because the task needs to call the model’sget_stimulus_by_pos()method to generate appropriate stimulusbefore_durationandafter_durationallow observing bump formation and maintenanceIextdetermines where the bump will formAll durations use the same unit as
time_step
2.3 Getting Task Data¶
After creating a task, call get_data() to generate and store input data in task.data:
[2]:
# Generate task data
task.get_data()
# Access task properties
print(f"Total time steps: {task.total_steps}")
print(f"Total duration: {task.total_duration}")
print(f"Data shape: {task.data.shape}")
<PopulationCoding1D>Generating Task data(No For Loop)
Total time steps: 700
Total duration: 70.0
Data shape: (700, 256)
Important
get_data() does not return a value—it modifies task.data in-place. Access the data via task.data.
3. Running Simulations with bm.for_loop¶
3.1 Why use for_loop?¶
BrainPy [18] provides bm.for_loop for efficient simulation loops. Compared to Python for loops, it offers:
JIT Compilation: Entire loop compiled to efficient machine code
GPU Acceleration: Automatic GPU utilization
Auto-vectorization: Better memory access patterns
See also
See BrainPy Loops Tutorial for detailed for_loop usage.
3.2 Basic Usage¶
[3]:
import brainpy.math as bm # :cite:p:`wang2023brainpy`
# Define step function
def run_step(t, inp):
"""
Single simulation step.
Args:
t: Current time step index
inp: Input data at current time step
Returns:
State variables to record
"""
model(inp) # Or model.update(inp)
return model.u.value, model.r.value
# Run simulation using task.data
results = bm.for_loop(
run_step, # Step function
operands=(task.run_steps, task.data), # Number of time steps and input data
progress_bar=10 # Optional progress bar (updates every 10 steps)
)
3.3 Handling Return Values¶
for_loop returns values corresponding to step function returns:
[4]:
# results is a tuple of return values across all time steps
u_history, r_history = results
print(f"Membrane potential history shape: {u_history.shape}") # (run_steps, num)
print(f"Firing rate history shape: {r_history.shape}") # (run_steps, num)
Membrane potential history shape: (700, 256)
Firing rate history shape: (700, 256)
3.4 JIT Compilation Benefits¶
First run includes compilation time (few seconds), but subsequent runs are much faster:
[9]:
import time
model = CANN1D(num=256, tau=1.0, k=8.1, a=0.5, A=10, J0=4.0)
# Create a new longer task
task = PopulationCoding1D(
cann_instance=model, # CANN model instance
before_duration=10000.0, # Duration before stimulus
after_duration=10000.0, # Duration after stimulus
Iext=1.0, # Stimulus position in feature space
duration=10000.0, # Stimulus duration
time_step=0.1, # Time step
)
task.get_data()
# First run (includes compilation)
start = time.time()
results = bm.for_loop(run_step, operands=(task.run_steps, task.data))
print(f"First run: {time.time() - start:.2f}s")
# Second run (already compiled)
start = time.time()
results = bm.for_loop(run_step, operands=(task.run_steps, task.data))
print(f"Second run: {time.time() - start:.2f}s")
<PopulationCoding1D>Generating Task data(No For Loop)
First run: 0.75s
Second run: 0.24s
4. Complete Example¶
Here’s a complete example from model creation to simulation:
[10]:
import brainpy.math as bm # :cite:p:`wang2023brainpy`
from canns.models.basic import CANN1D
from canns.task.tracking import PopulationCoding1D
# ============================================================
# Step 1: Setup environment and create model
# ============================================================
bm.set_dt(0.1)
model = CANN1D(num=256, tau=1.0, k=8.1, a=0.5, A=10, J0=4.0)
# ============================================================
# Step 2: Create task
# ============================================================
task = PopulationCoding1D(
cann_instance=model,
before_duration=10.0,
after_duration=50.0,
Iext=0.0,
duration=10.0,
time_step=0.1,
)
# Get task data
task.get_data()
print("Task Information:")
print(f" Total time steps: {task.total_steps}")
print(f" Total duration: {task.total_duration}")
print(f" Data shape: {task.data.shape}")
# ============================================================
# Step 3: Define simulation step function
# ============================================================
def run_step(t, inp):
model.update(inp)
return model.u.value, model.r.value
# ============================================================
# Step 4: Run simulation
# ============================================================
u_history, r_history = bm.for_loop(
run_step,
operands=(task.run_steps, task.data),
)
# ============================================================
# Step 5: Inspect results
# ============================================================
print("\nSimulation Results:")
print(f" Membrane potential history shape: {u_history.shape}")
print(f" Firing rate history shape: {r_history.shape}")
# Check states at different phases
before_steps = int(10.0 / 0.1) # Before stimulus
stim_end = int(20.0 / 0.1) # End of stimulus
after_steps = int(70.0 / 0.1) # End of simulation
print(f"\nBefore stimulus (t={before_steps-1}) max firing rate: {bm.max(r_history[before_steps-1]):.6f}")
print(f"During stimulus (t={stim_end-1}) max firing rate: {bm.max(r_history[stim_end-1]):.6f}")
print(f"After stimulus (t={after_steps-1}) max firing rate: {bm.max(r_history[after_steps-1]):.6f}")
<PopulationCoding1D>Generating Task data(No For Loop)
Task Information:
Total time steps: 700
Total duration: 70.0
Data shape: (700, 256)
Simulation Results:
Membrane potential history shape: (700, 256)
Firing rate history shape: (700, 256)
Before stimulus (t=99) max firing rate: 0.000000
During stimulus (t=199) max firing rate: 0.002426
After stimulus (t=699) max firing rate: 0.002348
Expected output:
Before stimulus: firing rate ≈0
During stimulus: firing rate increases (bump forms)
After stimulus: firing rate maintained (memory persists)
5. Next Steps¶
Congratulations! You now understand how to generate tasks and run CANN simulations. You’ve learned:
How to create smooth tracking tasks with
SmoothTracking1DandSmoothTracking2DHow to generate navigation tasks with
OpenLoopNavigationTaskHow to run simulations using
bm.for_loopfor efficiencyHow to record and analyze network states over time
The difference between open-loop (pre-generated trajectory) and closed-loop (interactive) tasks
What You’ve Learned¶
- Task Generation
You can create various input patterns for CANN models—from simple tracking to complex navigation trajectories.
- Simulation Workflows
You understand the standard simulation pattern: initialize model → create task → run loop → analyze results.
- Performance Optimization
You know how to use JIT-compiled loops for fast simulation of long time series.
- Data Organization
You can structure simulation outputs for downstream analysis and visualization.
Continue Learning¶
Now proceed to visualization and analysis:
Tutorial 3: Analysis and Visualization—Learn how to create energy landscapes, firing field plots, and animations
Then continue with:
Tutorial 4: Parameter Effects—Systematic exploration of how parameters affect dynamics
Tutorial 5: Hierarchical Path Integration—Multi-scale navigation with grid cells
Tutorial 6: Theta Sweep System—Theta-modulated head direction and grid cells
Or explore other workflows:
Scenario 2: Data Analysis—Analyze experimental neural recordings
Scenario 3: Brain-Inspired Learning—Train networks with Hebbian learning
Scenario 4: Pipeline—End-to-end research workflows
Key Takeaways¶
Tasks define inputs—Task modules generate the external stimuli that drive CANN dynamics
Separation of concerns—Models define dynamics, tasks define inputs, analyzers handle visualization
Flexibility—The same model can be used with different tasks for various applications
Efficiency matters—Use compiled loops (
bm.for_loop) for production simulations
Best Practices¶
When running simulations:
Start small—Test with short durations first, then scale up
Monitor memory—Long simulations with many neurons can consume significant memory
Save intermediate results—Use
np.saveto checkpoint long simulationsDocument parameters—Keep track of task and model parameters for reproducibility