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}