Skip to main content

temporal_neural_solver/baselines/
rust_standard.rs

1//! Standard Rust neural network implementation
2//!
3//! This represents what a typical Rust ML library (like Candle, Burn, etc.)
4//! would look like - idiomatic Rust without extreme optimizations.
5
6use std::time::{Duration, Instant};
7
8/// Standard Rust neural network with proper memory management
9#[derive(Clone)]
10pub struct RustStandardNetwork {
11    layer1: LinearLayer,
12    layer2: LinearLayer,
13}
14
15#[derive(Clone)]
16struct LinearLayer {
17    weights: Vec<Vec<f32>>,
18    bias: Vec<f32>,
19    input_size: usize,
20    output_size: usize,
21}
22
23impl LinearLayer {
24    fn new(input_size: usize, output_size: usize) -> Self {
25        use rand::Rng;
26        let mut rng = rand::thread_rng();
27
28        // He initialization for ReLU layers
29        let std_dev = (2.0 / input_size as f32).sqrt();
30
31        let mut weights = Vec::with_capacity(output_size);
32        for _ in 0..output_size {
33            let mut row = Vec::with_capacity(input_size);
34            for _ in 0..input_size {
35                let val: f32 = rng.gen::<f32>() * 2.0 - 1.0; // [-1, 1]
36                row.push(val * std_dev);
37            }
38            weights.push(row);
39        }
40
41        Self {
42            weights,
43            bias: vec![0.0; output_size],
44            input_size,
45            output_size,
46        }
47    }
48
49    fn forward(&self, input: &[f32]) -> Vec<f32> {
50        assert_eq!(input.len(), self.input_size);
51
52        let mut output = Vec::with_capacity(self.output_size);
53
54        for i in 0..self.output_size {
55            let mut sum = self.bias[i];
56
57            for j in 0..self.input_size {
58                sum += self.weights[i][j] * input[j];
59            }
60
61            output.push(sum);
62        }
63
64        output
65    }
66
67    fn forward_relu(&self, input: &[f32]) -> Vec<f32> {
68        let z = self.forward(input);
69        z.into_iter().map(|x| x.max(0.0)).collect()
70    }
71}
72
73impl RustStandardNetwork {
74    pub fn new_standard() -> Self {
75        let layer1 = LinearLayer::new(128, 32);
76        let layer2 = LinearLayer::new(32, 4);
77
78        Self { layer1, layer2 }
79    }
80
81    /// Standard forward pass
82    pub fn forward(&self, input: &[f32; 128]) -> Vec<f32> {
83        // Layer 1 with ReLU
84        let hidden = self.layer1.forward_relu(input);
85
86        // Layer 2 (linear output)
87        self.layer2.forward(&hidden)
88    }
89
90    /// Predict with timing
91    pub fn predict_timed(&self, input: &[f32; 128]) -> (Vec<f32>, Duration) {
92        let start = Instant::now();
93        let output = self.forward(input);
94        let duration = start.elapsed();
95        (output, duration)
96    }
97
98    /// Batch processing (typical Rust style)
99    pub fn predict_batch(&self, inputs: &[[f32; 128]]) -> Vec<Vec<f32>> {
100        inputs.iter().map(|input| self.forward(input)).collect()
101    }
102
103    pub fn predict_batch_timed(&self, inputs: &[[f32; 128]]) -> (Vec<Vec<f32>>, Duration) {
104        let start = Instant::now();
105        let outputs = self.predict_batch(inputs);
106        let duration = start.elapsed();
107        (outputs, duration)
108    }
109}
110
111/// More optimized Rust version (like what tch or candle might do)
112pub struct OptimizedRustNetwork {
113    // Flattened storage for better cache performance
114    w1: Vec<f32>,  // 32 * 128
115    b1: Vec<f32>,  // 32
116    w2: Vec<f32>,  // 4 * 32
117    b2: Vec<f32>,  // 4
118
119    // Working memory
120    hidden_buffer: Vec<f32>,
121}
122
123impl OptimizedRustNetwork {
124    pub fn new_standard() -> Self {
125        use rand::Rng;
126        let mut rng = rand::thread_rng();
127
128        let std1 = (2.0 / 128.0_f32).sqrt();
129        let std2 = (2.0 / 32.0_f32).sqrt();
130
131        let w1: Vec<f32> = (0..32*128)
132            .map(|_| (rng.gen::<f32>() * 2.0 - 1.0) * std1)
133            .collect();
134
135        let w2: Vec<f32> = (0..4*32)
136            .map(|_| (rng.gen::<f32>() * 2.0 - 1.0) * std2)
137            .collect();
138
139        Self {
140            w1,
141            b1: vec![0.0; 32],
142            w2,
143            b2: vec![0.0; 4],
144            hidden_buffer: vec![0.0; 32],
145        }
146    }
147
148    pub fn forward(&mut self, input: &[f32; 128]) -> [f32; 4] {
149        // Layer 1: Matrix multiply + ReLU
150        for i in 0..32 {
151            let mut sum = self.b1[i];
152
153            // Dot product for row i
154            for j in 0..128 {
155                sum += self.w1[i * 128 + j] * input[j];
156            }
157
158            // ReLU and store in buffer
159            self.hidden_buffer[i] = sum.max(0.0);
160        }
161
162        // Layer 2: Matrix multiply
163        let mut output = [0.0f32; 4];
164        for i in 0..4 {
165            let mut sum = self.b2[i];
166
167            for j in 0..32 {
168                sum += self.w2[i * 32 + j] * self.hidden_buffer[j];
169            }
170
171            output[i] = sum;
172        }
173
174        output
175    }
176
177    pub fn predict_timed(&mut self, input: &[f32; 128]) -> ([f32; 4], Duration) {
178        let start = Instant::now();
179        let output = self.forward(input);
180        let duration = start.elapsed();
181        (output, duration)
182    }
183}
184
185/// Iterator-based Rust implementation (functional style)
186pub struct FunctionalRustNetwork {
187    weights1: Vec<Vec<f32>>,
188    bias1: Vec<f32>,
189    weights2: Vec<Vec<f32>>,
190    bias2: Vec<f32>,
191}
192
193impl FunctionalRustNetwork {
194    pub fn new_standard() -> Self {
195        use rand::Rng;
196        let mut rng = rand::thread_rng();
197
198        let std1 = (2.0 / 128.0_f32).sqrt();
199        let std2 = (2.0 / 32.0_f32).sqrt();
200
201        let weights1 = (0..32).map(|_| {
202            (0..128).map(|_| rng.gen::<f32>() * std1 * 2.0 - std1).collect()
203        }).collect();
204
205        let weights2 = (0..4).map(|_| {
206            (0..32).map(|_| rng.gen::<f32>() * std2 * 2.0 - std2).collect()
207        }).collect();
208
209        Self {
210            weights1,
211            bias1: vec![0.0; 32],
212            weights2,
213            bias2: vec![0.0; 4],
214        }
215    }
216
217    pub fn forward(&self, input: &[f32; 128]) -> Vec<f32> {
218        // Layer 1 with functional style
219        let hidden: Vec<f32> = self.weights1
220            .iter()
221            .zip(self.bias1.iter())
222            .map(|(weights, &bias)| {
223                let sum = weights
224                    .iter()
225                    .zip(input.iter())
226                    .map(|(&w, &x)| w * x)
227                    .sum::<f32>() + bias;
228                sum.max(0.0) // ReLU
229            })
230            .collect();
231
232        // Layer 2
233        self.weights2
234            .iter()
235            .zip(self.bias2.iter())
236            .map(|(weights, &bias)| {
237                weights
238                    .iter()
239                    .zip(hidden.iter())
240                    .map(|(&w, &h)| w * h)
241                    .sum::<f32>() + bias
242            })
243            .collect()
244    }
245
246    pub fn predict_timed(&self, input: &[f32; 128]) -> (Vec<f32>, Duration) {
247        let start = Instant::now();
248        let output = self.forward(input);
249        let duration = start.elapsed();
250        (output, duration)
251    }
252}
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257
258    #[test]
259    fn test_rust_standard_network() {
260        let network = RustStandardNetwork::new_standard();
261        let input = [0.1f32; 128];
262        let (output, duration) = network.predict_timed(&input);
263
264        assert_eq!(output.len(), 4);
265        println!("Rust standard latency: {:?}", duration);
266    }
267
268    #[test]
269    fn test_optimized_rust_network() {
270        let mut network = OptimizedRustNetwork::new_standard();
271        let input = [0.1f32; 128];
272        let (output, duration) = network.predict_timed(&input);
273
274        assert_eq!(output.len(), 4);
275        println!("Optimized Rust latency: {:?}", duration);
276    }
277
278    #[test]
279    fn test_functional_rust_network() {
280        let network = FunctionalRustNetwork::new_standard();
281        let input = [0.1f32; 128];
282        let (output, duration) = network.predict_timed(&input);
283
284        assert_eq!(output.len(), 4);
285        println!("Functional Rust latency: {:?}", duration);
286    }
287
288    #[test]
289    fn test_batch_processing() {
290        let network = RustStandardNetwork::new_standard();
291        let inputs = vec![[0.1f32; 128]; 10];
292        let (outputs, duration) = network.predict_batch_timed(&inputs);
293
294        assert_eq!(outputs.len(), 10);
295        assert_eq!(outputs[0].len(), 4);
296        println!("Batch processing latency: {:?}", duration);
297    }
298}