Skip to main content

feagi_services/impls/
neuron_service_impl.rs

1// Copyright 2025 Neuraville Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4/*!
5Neuron service implementation.
6
7Copyright 2025 Neuraville Inc.
8Licensed under the Apache License, Version 2.0
9*/
10
11use crate::traits::NeuronService;
12use crate::types::*;
13use async_trait::async_trait;
14use feagi_brain_development::ConnectomeManager;
15use feagi_structures::genomic::cortical_area::CorticalID;
16use parking_lot::RwLock;
17use std::sync::Arc;
18use tracing::debug;
19
20/// Default implementation of NeuronService
21pub struct NeuronServiceImpl {
22    connectome: Arc<RwLock<ConnectomeManager>>,
23}
24
25impl NeuronServiceImpl {
26    pub fn new(connectome: Arc<RwLock<ConnectomeManager>>) -> Self {
27        Self { connectome }
28    }
29}
30
31#[async_trait]
32impl NeuronService for NeuronServiceImpl {
33    async fn create_neuron(&self, params: CreateNeuronParams) -> ServiceResult<NeuronInfo> {
34        debug!(target: "feagi-services", "Creating neuron in area {} at {:?}", params.cortical_id, params.coordinates);
35
36        // Convert String to CorticalID
37        let cortical_id_typed = CorticalID::try_from_base_64(&params.cortical_id)
38            .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?;
39
40        let mut manager = self.connectome.write();
41
42        // Extract neural parameters from properties or use defaults
43        let props = params.properties.as_ref();
44
45        let firing_threshold = props
46            .and_then(|p| p.get("firing_threshold"))
47            .and_then(|v| v.as_f64())
48            .unwrap_or(1.0) as f32;
49
50        let leak_coefficient = props
51            .and_then(|p| p.get("leak_coefficient"))
52            .and_then(|v| v.as_f64())
53            .unwrap_or(0.0) as f32;
54
55        let resting_potential = props
56            .and_then(|p| p.get("resting_potential"))
57            .and_then(|v| v.as_f64())
58            .unwrap_or(0.0) as f32;
59
60        let is_inhibitory = props
61            .and_then(|p| p.get("is_inhibitory"))
62            .and_then(|v| v.as_bool())
63            .unwrap_or(false);
64
65        let refractory_period = props
66            .and_then(|p| p.get("refractory_period"))
67            .and_then(|v| v.as_i64())
68            .unwrap_or(0) as u16;
69
70        let excitability = props
71            .and_then(|p| p.get("excitability"))
72            .and_then(|v| v.as_f64())
73            .unwrap_or(1.0) as f32;
74
75        // SIMD-friendly encoding: 0 means no limit, convert to MAX
76        let consecutive_fire_limit_raw = props
77            .and_then(|p| p.get("consecutive_fire_limit"))
78            .and_then(|v| v.as_i64())
79            .unwrap_or(0) as u16;
80        let consecutive_fire_limit = if consecutive_fire_limit_raw == 0 {
81            u16::MAX // SIMD-friendly encoding: MAX = no limit
82        } else {
83            consecutive_fire_limit_raw
84        };
85
86        let snooze_length = props
87            .and_then(|p| p.get("snooze_length"))
88            .and_then(|v| v.as_i64())
89            .unwrap_or(0) as u16;
90
91        let mp_charge_accumulation = props
92            .and_then(|p| p.get("mp_charge_accumulation"))
93            .and_then(|v| v.as_bool())
94            .unwrap_or(false);
95
96        // SIMD-friendly encoding: 0.0 means no limit, convert to MAX
97        let firing_threshold_limit_raw = props
98            .and_then(|p| p.get("firing_threshold_limit"))
99            .and_then(|v| v.as_f64())
100            .unwrap_or(0.0) as f32;
101        let firing_threshold_limit = if firing_threshold_limit_raw == 0.0 {
102            f32::MAX // SIMD-friendly encoding: MAX = no limit
103        } else {
104            firing_threshold_limit_raw
105        };
106
107        // Add neuron via ConnectomeManager
108        let neuron_id = manager
109            .add_neuron(
110                &cortical_id_typed,
111                params.coordinates.0,
112                params.coordinates.1,
113                params.coordinates.2,
114                firing_threshold,
115                firing_threshold_limit,
116                leak_coefficient,
117                resting_potential,
118                if is_inhibitory { 1 } else { 0 },
119                refractory_period,
120                excitability,
121                consecutive_fire_limit,
122                snooze_length,
123                mp_charge_accumulation,
124            )
125            .map_err(ServiceError::from)?;
126
127        let cortical_idx = manager
128            .get_cortical_idx(&cortical_id_typed)
129            .ok_or_else(|| ServiceError::NotFound {
130                resource: "CorticalArea".to_string(),
131                id: params.cortical_id.clone(),
132            })?;
133
134        Ok(NeuronInfo {
135            id: neuron_id,
136            cortical_id: params.cortical_id.clone(),
137            cortical_idx,
138            coordinates: params.coordinates,
139            properties: params.properties.unwrap_or_default(),
140        })
141    }
142
143    async fn delete_neuron(&self, neuron_id: u64) -> ServiceResult<()> {
144        debug!(target: "feagi-services","Deleting neuron {}", neuron_id);
145
146        let mut manager = self.connectome.write();
147        let deleted = manager
148            .delete_neuron(neuron_id)
149            .map_err(ServiceError::from)?;
150
151        if !deleted {
152            return Err(ServiceError::NotFound {
153                resource: "Neuron".to_string(),
154                id: neuron_id.to_string(),
155            });
156        }
157
158        Ok(())
159    }
160
161    async fn get_neuron(&self, neuron_id: u64) -> ServiceResult<NeuronInfo> {
162        debug!(target: "feagi-services","Getting neuron {}", neuron_id);
163
164        let manager = self.connectome.read();
165
166        // Check if neuron exists
167        if !manager.has_neuron(neuron_id) {
168            return Err(ServiceError::NotFound {
169                resource: "Neuron".to_string(),
170                id: neuron_id.to_string(),
171            });
172        }
173
174        let coordinates = manager.get_neuron_coordinates(neuron_id);
175        let cortical_idx = manager.get_neuron_cortical_idx(neuron_id);
176        let cortical_id = manager
177            .get_neuron_cortical_id(neuron_id)
178            .map(|id| id.as_base_64())
179            .unwrap_or_else(|| "unknown".to_string());
180
181        Ok(NeuronInfo {
182            id: neuron_id,
183            cortical_id,
184            cortical_idx,
185            coordinates,
186            properties: std::collections::HashMap::new(),
187        })
188    }
189
190    async fn get_neuron_at_coordinates(
191        &self,
192        cortical_id: &str,
193        coordinates: (u32, u32, u32),
194    ) -> ServiceResult<Option<NeuronInfo>> {
195        debug!(target: "feagi-services","Looking up neuron in area {} at {:?}", cortical_id, coordinates);
196
197        let manager = self.connectome.read();
198
199        // Verify area exists
200        if !manager.has_cortical_area(
201            &CorticalID::try_from_base_64(cortical_id)
202                .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?,
203        ) {
204            return Err(ServiceError::NotFound {
205                resource: "CorticalArea".to_string(),
206                id: cortical_id.to_string(),
207            });
208        }
209
210        // Get all neurons in the area and find one at coordinates
211        let neurons = manager.get_neurons_in_area(
212            &CorticalID::try_from_base_64(cortical_id)
213                .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?,
214        );
215
216        for neuron_id in neurons {
217            let neuron_coords = manager.get_neuron_coordinates(neuron_id);
218            if neuron_coords == coordinates {
219                let cortical_idx = manager.get_neuron_cortical_idx(neuron_id);
220                return Ok(Some(NeuronInfo {
221                    id: neuron_id,
222                    cortical_id: cortical_id.to_string(),
223                    cortical_idx,
224                    coordinates,
225                    properties: std::collections::HashMap::new(),
226                }));
227            }
228        }
229
230        // No neuron at these coordinates
231        Ok(None)
232    }
233
234    async fn list_neurons_in_area(
235        &self,
236        cortical_id: &str,
237        limit: Option<usize>,
238    ) -> ServiceResult<Vec<NeuronInfo>> {
239        debug!(target: "feagi-services","Listing neurons in area: {}", cortical_id);
240
241        // Get neurons from ConnectomeManager
242        let manager = self.connectome.read();
243        let neuron_ids = manager.get_neurons_in_area(
244            &CorticalID::try_from_base_64(cortical_id)
245                .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?,
246        );
247
248        let neurons: Vec<NeuronInfo> = neuron_ids
249            .iter()
250            .take(limit.unwrap_or(usize::MAX))
251            .map(|&id| {
252                // Get coordinates and cortical idx
253                let coordinates = manager.get_neuron_coordinates(id);
254                let cortical_idx = manager.get_neuron_cortical_idx(id);
255
256                // CRITICAL: (0,0,0) is a VALID coordinate for 1x1x1 areas like _power!
257                // Do NOT filter out neurons at (0,0,0) - it's a legitimate position
258                NeuronInfo {
259                    id,
260                    cortical_id: cortical_id.to_string(),
261                    cortical_idx,
262                    coordinates,
263                    properties: std::collections::HashMap::new(), // TODO: Get properties
264                }
265            })
266            .collect();
267
268        Ok(neurons)
269    }
270
271    async fn get_neuron_count(&self, cortical_id: &str) -> ServiceResult<usize> {
272        debug!(target: "feagi-services","Getting neuron count for area: {}", cortical_id);
273
274        // Convert String to CorticalID
275        let cortical_id_typed = CorticalID::try_from_base_64(cortical_id)
276            .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?;
277
278        let count = self
279            .connectome
280            .read()
281            .get_neuron_count_in_area(&cortical_id_typed);
282
283        Ok(count)
284    }
285
286    async fn neuron_exists(&self, neuron_id: u64) -> ServiceResult<bool> {
287        debug!(target: "feagi-services","Checking if neuron exists: {}", neuron_id);
288
289        let exists = self.connectome.read().has_neuron(neuron_id);
290
291        Ok(exists)
292    }
293}