scirs2_optimize/neuromorphic/
mod.rs1use crate::error::OptimizeResult;
25use crate::result::OptimizeResults;
26use ndarray::{Array1, Array2, ArrayView1};
27use rand::Rng;
28use scirs2_core::error::CoreResult as Result;
29
30pub mod event_driven;
31pub mod liquid_state_machines;
32pub mod memristive_optimization;
33pub mod neural_ode_optimization;
34pub mod spiking_networks;
35pub mod stdp_learning;
36
37#[allow(ambiguous_glob_reexports)]
39pub use event_driven::*;
40#[allow(ambiguous_glob_reexports)]
41pub use liquid_state_machines::*;
42#[allow(ambiguous_glob_reexports)]
43pub use memristive_optimization::*;
44#[allow(ambiguous_glob_reexports)]
45pub use neural_ode_optimization::*;
46#[allow(ambiguous_glob_reexports)]
47pub use spiking_networks::*;
48#[allow(ambiguous_glob_reexports)]
49pub use stdp_learning::*;
50
51#[derive(Debug, Clone)]
53pub struct NeuromorphicConfig {
54 pub dt: f64,
56 pub total_time: f64,
58 pub num_neurons: usize,
60 pub spike_threshold: f64,
62 pub refractory_period: f64,
64 pub learning_rate: f64,
66 pub membrane_decay: f64,
68 pub noise_level: f64,
70 pub event_driven: bool,
72}
73
74impl Default for NeuromorphicConfig {
75 fn default() -> Self {
76 Self {
77 dt: 0.001, total_time: 1.0, num_neurons: 100, spike_threshold: 1.0, refractory_period: 0.002, learning_rate: 0.01,
83 membrane_decay: 0.95, noise_level: 0.01,
85 event_driven: true,
86 }
87 }
88}
89
90#[derive(Debug, Clone, Copy)]
92pub struct SpikeEvent {
93 pub time: f64,
95 pub neuron_id: usize,
97 pub weight: f64,
99}
100
101#[derive(Debug, Clone)]
103pub struct NeuronState {
104 pub potential: f64,
106 pub last_spike_time: Option<f64>,
108 pub weights: Array1<f64>,
110 pub input_current: f64,
112 pub adaptation: f64,
114}
115
116impl NeuronState {
117 pub fn new(num_connections: usize) -> Self {
119 Self {
120 potential: 0.0,
121 last_spike_time: None,
122 weights: Array1::zeros(num_connections),
123 input_current: 0.0,
124 adaptation: 0.0,
125 }
126 }
127
128 pub fn is_refractory(&self, current_time: f64, refractory_period: f64) -> bool {
130 if let Some(last_spike) = self.last_spike_time {
131 current_time - last_spike < refractory_period
132 } else {
133 false
134 }
135 }
136
137 pub fn update_potential(&mut self, dt: f64, decay: f64, input: f64) {
139 if !self.is_refractory(0.0, 0.0) {
140 self.potential = self.potential * decay + input * dt;
142 }
143 }
144
145 pub fn should_spike(&self, threshold: f64) -> bool {
147 self.potential >= threshold
148 }
149
150 pub fn fire_spike(&mut self, time: f64) {
152 self.potential = 0.0; self.last_spike_time = Some(time);
154 }
155}
156
157#[derive(Debug, Clone)]
159pub struct NeuromorphicNetwork {
160 config: NeuromorphicConfig,
162 neurons: Vec<NeuronState>,
164 connectivity: Array2<f64>,
166 current_time: f64,
168 spike_queue: Vec<SpikeEvent>,
170 objective_history: Vec<f64>,
172 parameters: Array1<f64>,
174}
175
176impl NeuromorphicNetwork {
177 pub fn new(config: NeuromorphicConfig, num_parameters: usize) -> Self {
179 let mut neurons = Vec::with_capacity(config.num_neurons);
180 for _ in 0..config.num_neurons {
181 neurons.push(NeuronState::new(config.num_neurons));
182 }
183
184 let mut connectivity = Array2::zeros((config.num_neurons, config.num_neurons));
186 for i in 0..config.num_neurons {
187 for j in 0..config.num_neurons {
188 if i != j {
189 connectivity[[i, j]] = rand::rng().random_range(-0.05..0.05);
191 }
192 }
193 }
194
195 Self {
196 config,
197 neurons,
198 connectivity,
199 current_time: 0.0,
200 spike_queue: Vec::new(),
201 objective_history: Vec::new(),
202 parameters: Array1::zeros(num_parameters),
203 }
204 }
205
206 pub fn encode_parameters(&mut self, parameters: &ArrayView1<f64>) {
208 let n_params = parameters.len();
209 let neurons_per_param = self.config.num_neurons / n_params;
210
211 for (i, ¶m) in parameters.iter().enumerate() {
212 let start_idx = i * neurons_per_param;
213 let end_idx = ((i + 1) * neurons_per_param).min(self.config.num_neurons);
214
215 let firing_rate = (param + 1.0) * 10.0; for j in start_idx..end_idx {
219 self.neurons[j].input_current = firing_rate * 0.01;
221 }
222 }
223 }
224
225 pub fn decode_parameters(&self) -> Array1<f64> {
227 let n_params = self.parameters.len();
228 let neurons_per_param = self.config.num_neurons / n_params;
229 let mut decoded = Array1::zeros(n_params);
230
231 for i in 0..n_params {
232 let start_idx = i * neurons_per_param;
233 let end_idx = ((i + 1) * neurons_per_param).min(self.config.num_neurons);
234
235 let mut sum = 0.0;
237 for j in start_idx..end_idx {
238 sum += self.neurons[j].potential;
239 }
240
241 if end_idx > start_idx {
242 decoded[i] = sum / (end_idx - start_idx) as f64;
243 }
244 }
245
246 decoded
247 }
248
249 pub fn simulate_step(&mut self, objective_value: f64) -> Result<()> {
251 if self.config.event_driven {
253 self.process_spike_events()?;
254 }
255
256 for i in 0..self.config.num_neurons {
258 let mut synaptic_input = 0.0;
260 for j in 0..self.config.num_neurons {
261 if i != j {
262 synaptic_input += self.connectivity[[j, i]] * self.neurons[j].potential;
263 }
264 }
265
266 let external_input = self.compute_external_input(i, objective_value);
268 let total_input = synaptic_input + external_input + self.neurons[i].input_current;
269
270 self.neurons[i].update_potential(
272 self.config.dt,
273 self.config.membrane_decay,
274 total_input,
275 );
276
277 if self.config.noise_level > 0.0 {
279 let noise =
280 rand::rng().random_range(-self.config.noise_level..self.config.noise_level);
281 self.neurons[i].potential += noise;
282 }
283
284 if self.neurons[i].should_spike(self.config.spike_threshold)
286 && !self.neurons[i].is_refractory(self.current_time, self.config.refractory_period)
287 {
288 self.neurons[i].fire_spike(self.current_time);
289
290 self.spike_queue.push(SpikeEvent {
292 time: self.current_time,
293 neuron_id: i,
294 weight: 1.0,
295 });
296 }
297 }
298
299 self.update_connectivity(objective_value)?;
301
302 self.current_time += self.config.dt;
304
305 self.objective_history.push(objective_value);
307
308 Ok(())
309 }
310
311 fn process_spike_events(&mut self) -> Result<()> {
313 self.spike_queue
315 .sort_by(|a, b| a.time.partial_cmp(&b.time).unwrap());
316
317 while let Some(event) = self.spike_queue.first() {
319 if event.time <= self.current_time {
320 let event = self.spike_queue.remove(0);
321 self.process_spike_event(event)?;
322 } else {
323 break;
324 }
325 }
326
327 Ok(())
328 }
329
330 fn process_spike_event(&mut self, event: SpikeEvent) -> Result<()> {
332 for i in 0..self.config.num_neurons {
334 if i != event.neuron_id {
335 let connection_strength = self.connectivity[[event.neuron_id, i]];
336 self.neurons[i].potential += connection_strength * event.weight;
337 }
338 }
339
340 Ok(())
341 }
342
343 fn compute_external_input(&self, neuron_id: usize, objective_value: f64) -> f64 {
345 let feedback_strength = 0.1;
347 let normalized_objective = -objective_value; let phase = neuron_id as f64 / self.config.num_neurons as f64 * 2.0 * std::f64::consts::PI;
351 feedback_strength * normalized_objective * phase.sin()
352 }
353
354 fn update_connectivity(&mut self, objective_value: f64) -> Result<()> {
356 let performance_factor = if self.objective_history.len() > 1 {
358 let prev_objective = self.objective_history[self.objective_history.len() - 2];
359 if objective_value < prev_objective {
360 1.0
361 } else {
362 -0.1
363 }
364 } else {
365 0.0
366 };
367
368 for i in 0..self.config.num_neurons {
370 for j in 0..self.config.num_neurons {
371 if i != j {
372 let correlation = self.neurons[i].potential * self.neurons[j].potential;
374 let weight_change =
375 self.config.learning_rate * performance_factor * correlation;
376
377 self.connectivity[[i, j]] += weight_change;
378
379 self.connectivity[[i, j]] = self.connectivity[[i, j]].max(-1.0).min(1.0);
381 }
382 }
383 }
384
385 Ok(())
386 }
387}
388
389pub trait NeuromorphicOptimizer {
391 fn config(&self) -> &NeuromorphicConfig;
393
394 fn optimize<F>(
396 &mut self,
397 objective: F,
398 initial_params: &ArrayView1<f64>,
399 ) -> OptimizeResult<OptimizeResults<f64>>
400 where
401 F: Fn(&ArrayView1<f64>) -> f64;
402
403 fn network(&self) -> &NeuromorphicNetwork;
405
406 fn reset(&mut self);
408}
409
410#[derive(Debug, Clone)]
412pub struct BasicNeuromorphicOptimizer {
413 network: NeuromorphicNetwork,
414 best_params: Array1<f64>,
415 best_objective: f64,
416 nit: usize,
417}
418
419impl BasicNeuromorphicOptimizer {
420 pub fn new(config: NeuromorphicConfig, num_parameters: usize) -> Self {
422 let network = NeuromorphicNetwork::new(config, num_parameters);
423
424 Self {
425 network,
426 best_params: Array1::zeros(num_parameters),
427 best_objective: f64::INFINITY,
428 nit: 0,
429 }
430 }
431}
432
433impl NeuromorphicOptimizer for BasicNeuromorphicOptimizer {
434 fn config(&self) -> &NeuromorphicConfig {
435 &self.network.config
436 }
437
438 fn optimize<F>(
439 &mut self,
440 objective: F,
441 initial_params: &ArrayView1<f64>,
442 ) -> OptimizeResult<OptimizeResults<f64>>
443 where
444 F: Fn(&ArrayView1<f64>) -> f64,
445 {
446 self.network.parameters = initial_params.to_owned();
447 self.best_params = initial_params.to_owned();
448 self.best_objective = objective(initial_params);
449
450 let max_nit = (self.network.config.total_time / self.network.config.dt) as usize;
451
452 for iteration in 0..max_nit {
453 let params = self.network.parameters.clone();
455 self.network.encode_parameters(¶ms.view());
456
457 let current_objective = objective(&self.network.parameters.view());
459
460 self.network.simulate_step(current_objective)?;
462
463 self.network.parameters = self.network.decode_parameters();
465
466 if current_objective < self.best_objective {
468 self.best_objective = current_objective;
469 self.best_params = self.network.parameters.clone();
470 }
471
472 self.nit = iteration + 1;
473
474 if current_objective < 1e-6 {
476 break;
477 }
478 }
479
480 Ok(OptimizeResults::<f64> {
481 x: self.best_params.clone(),
482 fun: self.best_objective,
483 success: self.best_objective < 1e-3,
484 nit: self.nit,
485 nfev: self.nit,
486 njev: 0,
487 nhev: 0,
488 maxcv: 0,
489 status: 0,
490 jac: None,
491 hess: None,
492 constr: None,
493 message: "Neuromorphic optimization completed".to_string(),
494 })
495 }
496
497 fn network(&self) -> &NeuromorphicNetwork {
498 &self.network
499 }
500
501 fn reset(&mut self) {
502 self.network.current_time = 0.0;
503 self.network.spike_queue.clear();
504 self.network.objective_history.clear();
505 self.best_objective = f64::INFINITY;
506 self.nit = 0;
507
508 for neuron in &mut self.network.neurons {
510 neuron.potential = 0.0;
511 neuron.last_spike_time = None;
512 neuron.input_current = 0.0;
513 neuron.adaptation = 0.0;
514 }
515 }
516}
517
518impl BasicNeuromorphicOptimizer {
519 pub fn network_mut(&mut self) -> &mut NeuromorphicNetwork {
521 &mut self.network
522 }
523}
524
525#[allow(dead_code)]
527pub fn neuromorphic_optimize<F>(
528 objective: F,
529 initial_params: &ArrayView1<f64>,
530 config: Option<NeuromorphicConfig>,
531) -> OptimizeResult<OptimizeResults<f64>>
532where
533 F: Fn(&ArrayView1<f64>) -> f64,
534{
535 let config = config.unwrap_or_default();
536 let mut optimizer = BasicNeuromorphicOptimizer::new(config, initial_params.len());
537 optimizer.optimize(objective, initial_params)
538}
539
540#[cfg(test)]
541mod tests {
542 use super::*;
543
544 #[test]
545 fn test_neuron_state_creation() {
546 let neuron = NeuronState::new(10);
547 assert_eq!(neuron.weights.len(), 10);
548 assert_eq!(neuron.potential, 0.0);
549 assert!(neuron.last_spike_time.is_none());
550 }
551
552 #[test]
553 fn test_neuron_spike_behavior() {
554 let mut neuron = NeuronState::new(5);
555 neuron.potential = 1.5;
556
557 assert!(neuron.should_spike(1.0));
558 neuron.fire_spike(0.1);
559
560 assert_eq!(neuron.potential, 0.0);
561 assert_eq!(neuron.last_spike_time, Some(0.1));
562 }
563
564 #[test]
565 fn test_neuromorphic_network_creation() {
566 let config = NeuromorphicConfig::default();
567 let network = NeuromorphicNetwork::new(config, 3);
568
569 assert_eq!(network.neurons.len(), 100); assert_eq!(network.parameters.len(), 3);
571 assert_eq!(network.current_time, 0.0);
572 }
573
574 #[test]
575 fn test_parameter_encoding_decoding() {
576 let config = NeuromorphicConfig::default();
577 let mut network = NeuromorphicNetwork::new(config, 2);
578
579 let params = Array1::from(vec![0.5, -0.3]);
580 network.encode_parameters(¶ms.view());
581
582 assert!(network.neurons.iter().any(|n| n.input_current != 0.0));
584
585 let decoded = network.decode_parameters();
587 assert_eq!(decoded.len(), 2);
588 }
589
590 #[test]
591 fn test_basic_optimizer() {
592 let config = NeuromorphicConfig {
593 total_time: 0.1, num_neurons: 20, ..Default::default()
596 };
597
598 let mut optimizer = BasicNeuromorphicOptimizer::new(config, 2);
599
600 let objective = |x: &ArrayView1<f64>| x[0].powi(2) + x[1].powi(2);
602 let initial = Array1::from(vec![1.0, 1.0]);
603
604 let result = optimizer.optimize(objective, &initial.view()).unwrap();
605
606 assert!(result.nit > 0);
607 assert!(result.fun < 2.0); }
609
610 #[test]
611 fn test_convenience_function() {
612 let config = NeuromorphicConfig {
613 total_time: 0.05,
614 num_neurons: 10,
615 ..Default::default()
616 };
617
618 let objective = |x: &ArrayView1<f64>| (x[0] - 1.0).powi(2);
619 let initial = Array1::from(vec![0.0]);
620
621 let result = neuromorphic_optimize(objective, &initial.view(), Some(config)).unwrap();
622
623 assert!(result.nit > 0);
624 assert!(result.x.len() == 1);
625 }
626
627 #[test]
628 fn test_spike_queue_processing() {
629 let config = NeuromorphicConfig::default();
630 let mut network = NeuromorphicNetwork::new(config, 2);
631
632 network.spike_queue.push(SpikeEvent {
634 time: 0.001,
635 neuron_id: 0,
636 weight: 1.0,
637 });
638
639 network.current_time = 0.002;
640 network.process_spike_events().unwrap();
641
642 assert!(network.spike_queue.is_empty());
644 }
645}