Skip to main content

ncps/wirings/
mod.rs

1//! # Wiring Configurations for Neural Circuit Policies
2//!
3//! This module provides different connectivity patterns (wirings) that define how neurons
4//! connect to each other in Neural Circuit Policy networks. Wiring determines the **sparsity**
5//! and **structure** of the neural network.
6//!
7//! ## Key Concepts
8//!
9//! ### What is Wiring?
10//!
11//! Traditional RNNs use fully-connected layers where every neuron connects to every other neuron.
12//! NCPs use **sparse, structured connectivity** inspired by biological neural circuits (specifically
13//! the nervous system of *C. elegans*, a nematode with only 302 neurons).
14//!
15//! Wiring defines:
16//! - **Which neurons connect** to which other neurons (adjacency matrix)
17//! - **Synapse polarity** (+1 excitatory, -1 inhibitory)
18//! - **Layer structure** (sensory → inter → command → motor in NCP)
19//!
20//! ### The `.build()` Method - IMPORTANT!
21//!
22//! **You must call `.build(input_dim)` before using any wiring with an RNN layer.**
23//!
24//! ```rust
25//! use ncps::wirings::{AutoNCP, Wiring};
26//!
27//! // Create wiring configuration
28//! let mut wiring = AutoNCP::new(32, 8, 0.5, 42);
29//!
30//! // REQUIRED: Build with input dimension before use
31//! wiring.build(16);  // 16 input features
32//!
33//! // Now the wiring is ready
34//! assert!(wiring.is_built());
35//! assert_eq!(wiring.input_dim(), Some(16));
36//! ```
37//!
38//! **Why is `.build()` needed?**
39//! - The sensory adjacency matrix (input → neurons) can only be created once we know the input size
40//! - This allows the same wiring type to work with different input dimensions
41//! - Calling `.build()` a second time with the same dimension is a no-op; with a different dimension it panics
42//!
43//! ## Choosing a Wiring Type
44//!
45//! | Wiring | Use Case | Sparsity | Structure |
46//! |--------|----------|----------|-----------|
47//! | [`AutoNCP`] | **Recommended default** - automatic parameter selection | Medium-High | 4-layer biological |
48//! | [`NCP`] | Fine-grained control over layer sizes and connectivity | Configurable | 4-layer biological |
49//! | [`FullyConnected`] | Baseline comparison, maximum expressiveness | None (dense) | Single layer |
50//! | [`Random`] | Unstructured sparse networks for ablation studies | Configurable | Random |
51//!
52//! ## NCP Architecture (4-Layer Biological Structure)
53//!
54//! ```text
55//!                    ┌─────────────────────────────────────────┐
56//!                    │           Motor Neurons (output)        │
57//!                    │  - Final output layer                   │
58//!                    │  - Size = output_dim                    │
59//!                    └────────────────▲────────────────────────┘
60//!                                     │ motor_fanin connections
61//!                    ┌────────────────┴────────────────────────┐
62//!                    │          Command Neurons                │
63//!                    │  - Decision/integration layer           │
64//!                    │  - Has recurrent connections            │
65//!                    └────────────────▲────────────────────────┘
66//!                                     │ inter_fanout connections
67//!                    ┌────────────────┴────────────────────────┐
68//!                    │           Inter Neurons                 │
69//!                    │  - Feature processing layer             │
70//!                    │  - Receives from sensory inputs         │
71//!                    └────────────────▲────────────────────────┘
72//!                                     │ sensory_fanout connections
73//!                    ┌────────────────┴────────────────────────┐
74//!                    │         Sensory Inputs (input)          │
75//!                    │  - External input features              │
76//!                    │  - Size = input_dim (set by .build())   │
77//!                    └─────────────────────────────────────────┘
78//! ```
79//!
80//! ## Quick Examples
81//!
82//! ### Using AutoNCP (Recommended)
83//!
84//! ```rust
85//! use ncps::wirings::{AutoNCP, Wiring};
86//!
87//! // 32 total neurons, 8 motor outputs, 50% sparsity
88//! let mut wiring = AutoNCP::new(32, 8, 0.5, 42);
89//! wiring.build(16);  // 16 input features
90//!
91//! println!("Total neurons: {}", wiring.units());        // 32
92//! println!("Output size: {:?}", wiring.output_dim());   // Some(8)
93//! println!("Internal synapses: {}", wiring.synapse_count());
94//! println!("Sensory synapses: {}", wiring.sensory_synapse_count());
95//! ```
96//!
97//! ### Using Manual NCP Configuration
98//!
99//! ```rust
100//! use ncps::wirings::{NCP, Wiring};
101//!
102//! // Fine-grained control: 10 inter, 8 command, 4 motor neurons
103//! let mut wiring = NCP::new(
104//!     10,  // inter_neurons
105//!     8,   // command_neurons
106//!     4,   // motor_neurons (output)
107//!     4,   // sensory_fanout: each sensory connects to 4 inter neurons
108//!     4,   // inter_fanout: each inter connects to 4 command neurons
109//!     6,   // recurrent_command_synapses
110//!     4,   // motor_fanin: each motor receives from 4 command neurons
111//!     42,  // seed for reproducibility
112//! );
113//! wiring.build(16);
114//! ```
115//!
116//! ### Inspecting Connectivity
117//!
118//! ```rust
119//! use ncps::wirings::{AutoNCP, Wiring};
120//!
121//! let mut wiring = AutoNCP::new(32, 8, 0.5, 42);
122//! wiring.build(16);
123//!
124//! // Check neuron types
125//! for i in 0..wiring.units() {
126//!     let neuron_type = wiring.get_type_of_neuron(i);
127//!     // Returns "motor", "command", or "inter"
128//! }
129//!
130//! // Access adjacency matrices for visualization
131//! let adj = wiring.adjacency_matrix();  // [units x units]
132//! let sensory_adj = wiring.sensory_adjacency_matrix();  // [input_dim x units]
133//! ```
134
135use serde::{Deserialize, Serialize};
136
137mod base;
138mod ncp;
139mod random;
140
141pub use base::{FullyConnected, Wiring, Wiring as WiringTrait};
142pub use ncp::{AutoNCP, NCP};
143pub use random::Random;
144
145/// Configuration struct for serialization/deserialization of wiring structures
146#[derive(Clone, Debug, Serialize, Deserialize)]
147pub struct WiringConfig {
148    pub units: usize,
149    pub adjacency_matrix: Option<Vec<Vec<i32>>>,
150    pub sensory_adjacency_matrix: Option<Vec<Vec<i32>>>,
151    pub input_dim: Option<usize>,
152    pub output_dim: Option<usize>,
153    // FullyConnected fields
154    pub erev_init_seed: Option<u64>,
155    pub self_connections: Option<bool>,
156    // NCP fields
157    pub num_inter_neurons: Option<usize>,
158    pub num_command_neurons: Option<usize>,
159    pub num_motor_neurons: Option<usize>,
160    pub sensory_fanout: Option<usize>,
161    pub inter_fanout: Option<usize>,
162    pub recurrent_command_synapses: Option<usize>,
163    pub motor_fanin: Option<usize>,
164    pub seed: Option<u64>,
165    // Random fields
166    pub sparsity_level: Option<f64>,
167    pub random_seed: Option<u64>,
168}