ruv_swarm_wasm/
lib.rs

1use wasm_bindgen::prelude::*;
2
3mod utils;
4mod simd_ops;
5mod simd_tests;
6mod memory_pool;
7
8pub use utils::{set_panic_hook, RuntimeFeatures};
9pub use simd_ops::{SimdVectorOps, SimdMatrixOps, SimdBenchmark, detect_simd_capabilities};
10pub use simd_tests::{run_simd_verification_suite, simd_performance_report, validate_simd_implementation};
11pub use memory_pool::MemoryPool;
12
13// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
14// allocator.
15#[cfg(feature = "wee_alloc")]
16#[global_allocator]
17static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
18
19#[wasm_bindgen(start)]
20pub fn init() {
21    set_panic_hook();
22}
23
24#[wasm_bindgen]
25#[derive(Debug, Clone, Copy)]
26pub enum ActivationFunction {
27    Linear,
28    Sigmoid,
29    SymmetricSigmoid,
30    Tanh,
31    ReLU,
32    LeakyReLU,
33    Swish,
34    Gaussian,
35    Elliot,
36    SymmetricElliot,
37    Sine,
38    Cosine,
39    SinSymmetric,
40    CosSymmetric,
41    ThresholdSymmetric,
42    Threshold,
43    StepSymmetric,
44    Step,
45}
46
47#[wasm_bindgen]
48pub struct WasmNeuralNetwork {
49    layers: Vec<usize>,
50    weights: Vec<f64>,
51    biases: Vec<f64>,
52    activation: ActivationFunction,
53}
54
55#[wasm_bindgen]
56impl WasmNeuralNetwork {
57    #[wasm_bindgen(constructor)]
58    pub fn new(layers: &[usize], activation: ActivationFunction) -> Self {
59        let total_weights = layers.windows(2).map(|w| w[0] * w[1]).sum();
60        let total_biases = layers[1..].iter().sum();
61        
62        Self {
63            layers: layers.to_vec(),
64            weights: vec![0.0; total_weights],
65            biases: vec![0.0; total_biases],
66            activation,
67        }
68    }
69
70    #[wasm_bindgen]
71    pub fn randomize_weights(&mut self, min: f64, max: f64) {
72        use js_sys::Math;
73        for weight in &mut self.weights {
74            *weight = min + (max - min) * Math::random();
75        }
76        for bias in &mut self.biases {
77            *bias = min + (max - min) * Math::random();
78        }
79    }
80
81    #[wasm_bindgen]
82    pub fn set_weights(&mut self, weights: &[f64]) {
83        if weights.len() == self.weights.len() {
84            self.weights.copy_from_slice(weights);
85        }
86    }
87
88    #[wasm_bindgen]
89    pub fn get_weights(&self) -> Vec<f64> {
90        self.weights.clone()
91    }
92
93    #[wasm_bindgen]
94    pub fn run(&self, inputs: &[f64]) -> Vec<f64> {
95        let mut current_outputs = inputs.to_vec();
96        
97        for layer_idx in 1..self.layers.len() {
98            let prev_size = self.layers[layer_idx - 1];
99            let curr_size = self.layers[layer_idx];
100            
101            // Extract weights for this layer as a matrix
102            let mut layer_weights = vec![0.0; prev_size * curr_size];
103            for curr_neuron in 0..curr_size {
104                for prev_neuron in 0..prev_size {
105                    let weight_idx = self.get_weight_index(layer_idx - 1, prev_neuron, curr_neuron);
106                    layer_weights[curr_neuron * prev_size + prev_neuron] = self.weights[weight_idx] as f64;
107                }
108            }
109            
110            // Convert to f32 for SIMD operations
111            let current_f32: Vec<f32> = current_outputs.iter().map(|&x| x as f32).collect();
112            let weights_f32: Vec<f32> = layer_weights.iter().map(|&x| x as f32).collect();
113            
114            // Use SIMD-optimized matrix-vector multiplication if available
115            let simd_ops = crate::simd_ops::SimdMatrixOps::new();
116            let simd_result = simd_ops.matrix_vector_multiply(
117                &weights_f32, 
118                &current_f32, 
119                curr_size, 
120                prev_size
121            );
122            
123            // Add biases and apply activation
124            let mut new_outputs = vec![0.0; curr_size];
125            for curr_neuron in 0..curr_size {
126                let bias_idx = self.get_bias_index(layer_idx, curr_neuron);
127                let sum = simd_result[curr_neuron] as f64 + self.biases[bias_idx];
128                new_outputs[curr_neuron] = self.apply_activation(sum);
129            }
130            
131            current_outputs = new_outputs;
132        }
133        
134        current_outputs
135    }
136
137    fn get_weight_index(&self, from_layer: usize, from_neuron: usize, to_neuron: usize) -> usize {
138        let mut index = 0;
139        for layer in 0..from_layer {
140            index += self.layers[layer] * self.layers[layer + 1];
141        }
142        index + from_neuron * self.layers[from_layer + 1] + to_neuron
143    }
144
145    fn get_bias_index(&self, layer: usize, neuron: usize) -> usize {
146        let mut index = 0;
147        for l in 1..layer {
148            index += self.layers[l];
149        }
150        index + neuron
151    }
152
153    fn apply_activation(&self, x: f64) -> f64 {
154        match self.activation {
155            ActivationFunction::Linear => x,
156            ActivationFunction::Sigmoid => 1.0 / (1.0 + (-x).exp()),
157            ActivationFunction::SymmetricSigmoid => 2.0 / (1.0 + (-x).exp()) - 1.0,
158            ActivationFunction::Tanh => x.tanh(),
159            ActivationFunction::ReLU => x.max(0.0),
160            ActivationFunction::LeakyReLU => if x > 0.0 { x } else { 0.01 * x },
161            ActivationFunction::Swish => x * (1.0 / (1.0 + (-x).exp())),
162            ActivationFunction::Gaussian => (-x * x).exp(),
163            ActivationFunction::Elliot => x / (1.0 + x.abs()),
164            ActivationFunction::SymmetricElliot => 2.0 * x / (1.0 + x.abs()),
165            ActivationFunction::Sine => x.sin(),
166            ActivationFunction::Cosine => x.cos(),
167            ActivationFunction::SinSymmetric => 2.0 * x.sin() - 1.0,
168            ActivationFunction::CosSymmetric => 2.0 * x.cos() - 1.0,
169            ActivationFunction::ThresholdSymmetric => if x > 0.0 { 1.0 } else { -1.0 },
170            ActivationFunction::Threshold => if x > 0.0 { 1.0 } else { 0.0 },
171            ActivationFunction::StepSymmetric => if x > 0.0 { 1.0 } else { -1.0 },
172            ActivationFunction::Step => if x > 0.0 { 1.0 } else { 0.0 },
173        }
174    }
175}
176
177#[wasm_bindgen]
178pub struct WasmSwarmOrchestrator {
179    agents: Vec<WasmAgent>,
180    topology: String,
181    task_counter: u32,
182}
183
184#[wasm_bindgen]
185#[derive(Clone)]
186pub struct WasmAgent {
187    id: String,
188    agent_type: String,
189    status: String,
190    capabilities: Vec<String>,
191}
192
193#[wasm_bindgen]
194impl WasmAgent {
195    #[wasm_bindgen(constructor)]
196    pub fn new(id: &str, agent_type: &str) -> Self {
197        Self {
198            id: id.to_string(),
199            agent_type: agent_type.to_string(),
200            status: "idle".to_string(),
201            capabilities: Vec::new(),
202        }
203    }
204
205    #[wasm_bindgen(getter)]
206    pub fn id(&self) -> String {
207        self.id.clone()
208    }
209
210    #[wasm_bindgen(getter)]
211    pub fn agent_type(&self) -> String {
212        self.agent_type.clone()
213    }
214
215    #[wasm_bindgen(getter)]
216    pub fn status(&self) -> String {
217        self.status.clone()
218    }
219
220    #[wasm_bindgen]
221    pub fn set_status(&mut self, status: &str) {
222        self.status = status.to_string();
223    }
224
225    #[wasm_bindgen]
226    pub fn add_capability(&mut self, capability: &str) {
227        self.capabilities.push(capability.to_string());
228    }
229
230    #[wasm_bindgen]
231    pub fn has_capability(&self, capability: &str) -> bool {
232        self.capabilities.contains(&capability.to_string())
233    }
234}
235
236#[wasm_bindgen]
237pub struct WasmTaskResult {
238    task_id: String,
239    description: String,
240    status: String,
241    assigned_agents: Vec<String>,
242    priority: String,
243}
244
245#[wasm_bindgen]
246impl WasmTaskResult {
247    #[wasm_bindgen(getter)]
248    pub fn task_id(&self) -> String {
249        self.task_id.clone()
250    }
251
252    #[wasm_bindgen(getter)]
253    pub fn description(&self) -> String {
254        self.description.clone()
255    }
256
257    #[wasm_bindgen(getter)]
258    pub fn status(&self) -> String {
259        self.status.clone()
260    }
261
262    #[wasm_bindgen(getter)]
263    pub fn assigned_agents(&self) -> Vec<String> {
264        self.assigned_agents.clone()
265    }
266
267    #[wasm_bindgen(getter)]
268    pub fn priority(&self) -> String {
269        self.priority.clone()
270    }
271}
272
273#[wasm_bindgen]
274impl WasmSwarmOrchestrator {
275    #[wasm_bindgen(constructor)]
276    pub fn new(topology: &str) -> Self {
277        Self {
278            agents: Vec::new(),
279            topology: topology.to_string(),
280            task_counter: 0,
281        }
282    }
283
284    #[wasm_bindgen]
285    pub fn spawn(&mut self, config: &str) -> String {
286        // Parse JSON config (simplified for demo)
287        let agent_id = format!("agent-{}", self.agents.len() + 1);
288        let mut agent = WasmAgent::new(&agent_id, "researcher");
289        
290        // Add some default capabilities based on type
291        agent.add_capability("research");
292        agent.add_capability("analysis");
293        
294        self.agents.push(agent);
295        
296        // Return JSON result
297        serde_json::json!({
298            "agent_id": agent_id,
299            "name": format!("Agent-{}", self.agents.len()),
300            "type": "researcher",
301            "capabilities": ["research", "analysis"],
302            "cognitive_pattern": "adaptive",
303            "neural_network_id": format!("nn-{}", self.agents.len())
304        }).to_string()
305    }
306
307    #[wasm_bindgen]
308    pub fn orchestrate(&mut self, config: &str) -> WasmTaskResult {
309        self.task_counter += 1;
310        let task_id = format!("task-{}", self.task_counter);
311        
312        // Parse config (simplified JSON parsing)
313        let description = "Sample task"; // Would parse from config
314        let priority = "medium"; // Would parse from config
315        
316        // Select available agents (simple strategy for now)
317        let mut assigned_agents = Vec::new();
318        for agent in &mut self.agents {
319            if agent.status == "idle" && assigned_agents.len() < 3 {
320                agent.set_status("busy");
321                assigned_agents.push(agent.id());
322            }
323        }
324        
325        WasmTaskResult {
326            task_id,
327            description: description.to_string(),
328            status: "orchestrated".to_string(),
329            assigned_agents,
330            priority: priority.to_string(),
331        }
332    }
333
334    #[wasm_bindgen]
335    pub fn add_agent(&mut self, agent_id: &str) {
336        let agent = WasmAgent::new(agent_id, "generic");
337        self.agents.push(agent);
338    }
339
340    #[wasm_bindgen]
341    pub fn get_agent_count(&self) -> usize {
342        self.agents.len()
343    }
344
345    #[wasm_bindgen]
346    pub fn get_topology(&self) -> String {
347        self.topology.clone()
348    }
349
350    #[wasm_bindgen]
351    pub fn get_status(&self, detailed: bool) -> String {
352        let idle_count = self.agents.iter().filter(|a| a.status == "idle").count();
353        let busy_count = self.agents.iter().filter(|a| a.status == "busy").count();
354        
355        if detailed {
356            serde_json::json!({
357                "agents": {
358                    "total": self.agents.len(),
359                    "idle": idle_count,
360                    "busy": busy_count
361                },
362                "topology": self.topology,
363                "agent_details": self.agents.iter().map(|a| {
364                    serde_json::json!({
365                        "id": a.id(),
366                        "type": a.agent_type(),
367                        "status": a.status()
368                    })
369                }).collect::<Vec<_>>()
370            }).to_string()
371        } else {
372            serde_json::json!({
373                "agents": {
374                    "total": self.agents.len(),
375                    "idle": idle_count,
376                    "busy": busy_count
377                },
378                "topology": self.topology
379            }).to_string()
380        }
381    }
382}
383
384#[wasm_bindgen]
385pub struct WasmForecastingModel {
386    model_type: String,
387    parameters: Vec<f64>,
388}
389
390#[wasm_bindgen]
391impl WasmForecastingModel {
392    #[wasm_bindgen(constructor)]
393    pub fn new(model_type: &str) -> Self {
394        Self {
395            model_type: model_type.to_string(),
396            parameters: Vec::new(),
397        }
398    }
399
400    #[wasm_bindgen]
401    pub fn predict(&self, input: &[f64]) -> Vec<f64> {
402        // Simple placeholder forecasting
403        match self.model_type.as_str() {
404            "linear" => {
405                let slope = if input.len() > 1 {
406                    input[input.len() - 1] - input[input.len() - 2]
407                } else {
408                    0.0
409                };
410                vec![input[input.len() - 1] + slope]
411            }
412            "mean" => {
413                let mean = input.iter().sum::<f64>() / input.len() as f64;
414                vec![mean]
415            }
416            _ => vec![input[input.len() - 1]]
417        }
418    }
419
420    #[wasm_bindgen]
421    pub fn get_model_type(&self) -> String {
422        self.model_type.clone()
423    }
424}
425
426// Export functions for use from JavaScript
427#[wasm_bindgen]
428pub fn create_neural_network(layers: &[usize], activation: ActivationFunction) -> WasmNeuralNetwork {
429    WasmNeuralNetwork::new(layers, activation)
430}
431
432#[wasm_bindgen]
433pub fn create_swarm_orchestrator(topology: &str) -> WasmSwarmOrchestrator {
434    WasmSwarmOrchestrator::new(topology)
435}
436
437#[wasm_bindgen]
438pub fn create_forecasting_model(model_type: &str) -> WasmForecastingModel {
439    WasmForecastingModel::new(model_type)
440}
441
442#[wasm_bindgen]
443pub fn get_version() -> String {
444    "0.1.0".to_string()
445}
446
447#[wasm_bindgen]
448pub fn get_features() -> String {
449    let simd_support = detect_simd_support();
450    serde_json::json!({
451        "neural_networks": true,
452        "forecasting": true,
453        "swarm_orchestration": true,
454        "cognitive_diversity": true,
455        "simd_support": simd_support,
456        "simd_capabilities": detect_simd_capabilities()
457    }).to_string()
458}
459
460/// Runtime SIMD support detection - fixed to properly detect SIMD compilation
461fn detect_simd_support() -> bool {
462    // For WebAssembly builds
463    #[cfg(target_arch = "wasm32")]
464    {
465        // Primary check: simd feature flag indicates SIMD support is compiled in
466        #[cfg(feature = "simd")]
467        {
468            // Always return true when compiled with simd feature - the operations are available
469            true
470        }
471        
472        // If no simd feature, try runtime test
473        #[cfg(not(feature = "simd"))]
474        {
475            false  // Without simd feature, SIMD operations are not available
476        }
477    }
478    
479    // For non-WASM platforms, use target features
480    #[cfg(not(target_arch = "wasm32"))]
481    {
482        #[cfg(any(target_feature = "sse", target_feature = "avx", target_feature = "avx2", target_feature = "neon"))]
483        {
484            true
485        }
486        #[cfg(not(any(target_feature = "sse", target_feature = "avx", target_feature = "avx2", target_feature = "neon")))]
487        {
488            false
489        }
490    }
491}
492
493/// Test SIMD functionality at runtime by attempting a simple operation
494fn test_simd_runtime() -> bool {
495    // Try to use the SIMD operations and catch any panics
496    let test_vec_a = vec![1.0f32, 2.0, 3.0, 4.0];
497    let test_vec_b = vec![2.0f32, 3.0, 4.0, 5.0];
498    
499    // Use std::panic::catch_unwind to safely test SIMD operations
500    let result = std::panic::catch_unwind(|| {
501        let ops = crate::simd_ops::SimdVectorOps::new();
502        ops.dot_product(&test_vec_a, &test_vec_b)
503    });
504    
505    match result {
506        Ok(value) => {
507            // Check if the result is approximately correct (should be 40.0)
508            (value - 40.0).abs() < 0.1
509        }
510        Err(_) => false
511    }
512}
513
514// Performance monitoring for optimization targets
515#[wasm_bindgen]
516pub struct PerformanceMonitor {
517    load_time: f64,
518    spawn_times: Vec<f64>,
519    memory_usage: usize,
520    simd_enabled: bool,
521}
522
523#[wasm_bindgen]
524impl PerformanceMonitor {
525    #[wasm_bindgen(constructor)]
526    pub fn new() -> Self {
527        Self {
528            load_time: 0.0,
529            spawn_times: Vec::new(),
530            memory_usage: 0,
531            simd_enabled: detect_simd_support(),
532        }
533    }
534    
535    pub fn record_load_time(&mut self, time: f64) {
536        self.load_time = time;
537    }
538    
539    pub fn record_spawn_time(&mut self, time: f64) {
540        self.spawn_times.push(time);
541    }
542    
543    pub fn update_memory_usage(&mut self, bytes: usize) {
544        self.memory_usage = bytes;
545    }
546    
547    pub fn get_average_spawn_time(&self) -> f64 {
548        if self.spawn_times.is_empty() {
549            0.0
550        } else {
551            self.spawn_times.iter().sum::<f64>() / self.spawn_times.len() as f64
552        }
553    }
554    
555    pub fn get_memory_usage_mb(&self) -> f64 {
556        (self.memory_usage as f64) / (1024.0 * 1024.0)
557    }
558    
559    pub fn meets_performance_targets(&self) -> bool {
560        self.load_time < 500.0 && 
561        self.get_average_spawn_time() < 100.0 &&
562        self.get_memory_usage_mb() < 50.0
563    }
564    
565    pub fn get_report(&self) -> String {
566        format!(
567            "Performance Report:\n\
568             - Load Time: {:.2}ms (Target: <500ms) {}\n\
569             - Avg Spawn Time: {:.2}ms (Target: <100ms) {}\n\
570             - Memory Usage: {:.2}MB (Target: <50MB) {}\n\
571             - SIMD Enabled: {}\n\
572             - All Targets Met: {}",
573            self.load_time,
574            if self.load_time < 500.0 { "✓" } else { "✗" },
575            self.get_average_spawn_time(),
576            if self.get_average_spawn_time() < 100.0 { "✓" } else { "✗" },
577            self.get_memory_usage_mb(),
578            if self.get_memory_usage_mb() < 50.0 { "✓" } else { "✗" },
579            self.simd_enabled,
580            self.meets_performance_targets()
581        )
582    }
583}
584
585// Optimized agent spawning with memory pooling
586#[wasm_bindgen]
587pub struct OptimizedAgentSpawner {
588    memory_pool: memory_pool::AgentMemoryPool,
589    performance_monitor: PerformanceMonitor,
590    active_agents: Vec<OptimizedAgent>,
591}
592
593#[wasm_bindgen]
594pub struct OptimizedAgent {
595    id: String,
596    agent_type: String,
597    memory_size: usize,
598    #[wasm_bindgen(skip)]
599    memory: Vec<u8>,
600}
601
602#[wasm_bindgen]
603impl OptimizedAgentSpawner {
604    #[wasm_bindgen(constructor)]
605    pub fn new() -> Self {
606        Self {
607            memory_pool: memory_pool::AgentMemoryPool::new(),
608            performance_monitor: PerformanceMonitor::new(),
609            active_agents: Vec::new(),
610        }
611    }
612    
613    pub fn spawn_agent(&mut self, agent_type: &str, complexity: &str) -> Result<String, JsValue> {
614        let start = js_sys::Date::now();
615        
616        // Allocate memory from pool
617        let memory = self.memory_pool
618            .allocate_for_agent(complexity)
619            .ok_or_else(|| JsValue::from_str("Memory allocation failed"))?;
620        
621        let memory_size = memory.len();
622        let agent_id = format!("agent-{}-{}", agent_type, js_sys::Math::random());
623        
624        let agent = OptimizedAgent {
625            id: agent_id.clone(),
626            agent_type: agent_type.to_string(),
627            memory_size,
628            memory,
629        };
630        
631        self.active_agents.push(agent);
632        
633        // Record metrics
634        let spawn_time = js_sys::Date::now() - start;
635        self.performance_monitor.record_spawn_time(spawn_time);
636        self.performance_monitor.update_memory_usage(
637            self.memory_pool.total_memory_usage_mb() as usize * 1024 * 1024
638        );
639        
640        Ok(agent_id)
641    }
642    
643    pub fn release_agent(&mut self, agent_id: &str) -> Result<(), JsValue> {
644        if let Some(pos) = self.active_agents.iter().position(|a| a.id == agent_id) {
645            let agent = self.active_agents.remove(pos);
646            self.memory_pool.deallocate_agent_memory(agent.memory);
647            self.performance_monitor.update_memory_usage(
648                self.memory_pool.total_memory_usage_mb() as usize * 1024 * 1024
649            );
650            Ok(())
651        } else {
652            Err(JsValue::from_str("Agent not found"))
653        }
654    }
655    
656    pub fn get_performance_report(&self) -> String {
657        self.performance_monitor.get_report()
658    }
659    
660    pub fn get_active_agent_count(&self) -> usize {
661        self.active_agents.len()
662    }
663    
664    pub fn is_within_memory_target(&self) -> bool {
665        self.memory_pool.is_within_memory_target()
666    }
667}