ncps 0.1.1

Neural Circuit Policies - Sparse RNNs inspired by C. elegans, implemented in Rust with Burn
Documentation
//! Training Demo - Simple Sequence Learning Task
//!
//! This example demonstrates how to train NCPS networks on a simple
//! sequence prediction task. Note: This requires setting up a burn backend
//! with autodiff support (e.g., WGPU or Candle).

use burn::backend::NdArray;
use burn::tensor::Tensor;
use ncps::rnn::CfC;

fn main() {
    println!("=== NCPS Training Example ===\n");

    // Note: For real training, use an Autodiff backend like:
    // type Backend = Autodiff<NdArray<f32>>;
    // Or: Autodiff<Wgpu<f32, MixedPrecision>>

    type Backend = NdArray<f32>;
    let device = Default::default();

    // Create a small CfC network
    // CfC takes (input_size, hidden_size, device)
    let model = CfC::<Backend>::new(8, 16, &device);

    println!("Training setup:");
    println!("  Model: CfC with 8 input, 16 hidden units");
    println!("  Task: Predict next value in sinusoidal sequence");
    println!();

    // Generate synthetic training data
    // Simple task: predict shifted sine wave
    let seq_len = 20;
    let batch_size = 4;
    let input_features = 8;

    println!(
        "Generating {} synthetic sequences of length {}",
        batch_size, seq_len
    );

    let input = Tensor::<Backend, 3>::random(
        [batch_size, seq_len, input_features],
        burn::tensor::Distribution::Uniform(-1.0, 1.0),
        &device,
    );

    let target = Tensor::<Backend, 3>::random(
        [batch_size, seq_len, input_features],
        burn::tensor::Distribution::Uniform(-1.0, 1.0),
        &device,
    );

    println!(
        "  Input shape:  {:?}",
        [batch_size, seq_len, input_features]
    );
    println!(
        "  Target shape: {:?}",
        [batch_size, seq_len, input_features]
    );
    println!();

    // Forward pass (training step)
    println!("Forward pass...");
    let (predictions, _) = model.forward(input, None, None);

    println!("  Prediction shape: {:?}", predictions.dims());
    println!();

    // Note: For full training implementation, you would:
    // 1. Define loss function (e.g., MSE for regression)
    // 2. Set up optimizer (e.g., Adam)
    // 3. Run backward pass with .backward()
    // 4. Update parameters with optimizer.step()
    //
    // Example:
    // let loss = mse_loss(predictions, target);
    // let grads = loss.backward();
    // model.update_params(grads, optimizer);

    println!("Training loop structure:");
    println!("  1. Forward: predictions = model.forward(input)");
    println!("  2. Loss:    loss = mse(predictions, target)");
    println!("  3. Backward: grads = loss.backward()");
    println!("  4. Update:  model.update_params(grads, optimizer)");
    println!();

    // Demonstrate state persistence across batches
    println!("State persistence demo:");
    let initial_state = None;
    let (out1, state1) = model.forward(
        Tensor::<Backend, 3>::random(
            [1, 5, 8],
            burn::tensor::Distribution::Uniform(-1.0, 1.0),
            &device,
        ),
        initial_state,
        None,
    );

    let (out2, state2) = model.forward(
        Tensor::<Backend, 3>::random(
            [1, 5, 8],
            burn::tensor::Distribution::Uniform(-1.0, 1.0),
            &device,
        ),
        Some(state1),
        None,
    );

    println!("  Batch 1 processed, state updated");
    println!("  Batch 2 processed with previous state");
    println!("  This is useful for continuous learning!");
    println!();

    println!("=== Training Example completed! ===");
    println!("\nNext steps:");
    println!("  - Set up an autodiff backend (WGPU or Candle)");
    println!("  - Define your loss function");
    println!("  - Create training data loaders");
    println!("  - Implement optimization loop");
}