feagi-structures 0.0.50

The most core library, defines the basic data types used by FEAGI, as well as some processors to modify them
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
use crate::genomic::cortical_area::descriptors::CorticalSubUnitIndex;
use crate::genomic::cortical_area::descriptors::CorticalUnitIndex;
use crate::genomic::cortical_area::io_cortical_area_configuration_flag::{
    FrameChangeHandling, PercentageNeuronPositioning,
};
use crate::genomic::cortical_area::{
    CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
};
use crate::sensor_cortical_units;
use paste;
use serde_json::{Map, Value};
use std::collections::HashMap;
use std::fmt::{Display, Formatter};

#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] // TODO move me!
pub struct UnitTopology {
    pub relative_position: [i32; 3],
    pub channel_dimensions_default: [u32; 3],
    pub channel_dimensions_min: [u32; 3],
    pub channel_dimensions_max: [u32; 3],
}

macro_rules! default_firing_threshold_impl {
    () => {
        None
    };
    ($value:expr) => {
        Some($value)
    };
}

macro_rules! default_mp_charge_accumulation_impl {
    () => {
        None
    };
    ($value:expr) => {
        Some($value)
    };
}

macro_rules! default_firing_threshold_increment_impl {
    () => {
        None
    };
    ([$x:expr, $y:expr, $z:expr]) => {
        Some([$x, $y, $z])
    };
}

macro_rules! define_sensory_cortical_units_enum {
    (
        SensoryCorticalUnit {
            $(
                $(#[doc = $doc:expr])?
                $variant_name:ident => {
                    friendly_name: $friendly_name:expr,
                    accepted_wrapped_io_data_type: $accepted_wrapped_io_data_type:expr,
                    cortical_id_unit_reference: $cortical_id_unit_reference:expr,
                    number_cortical_areas: $number_cortical_areas:expr,
                    $(default_firing_threshold: $default_firing_threshold:expr,)?
                    $(default_firing_threshold_increment: [$default_firing_threshold_increment_x:expr, $default_firing_threshold_increment_y:expr, $default_firing_threshold_increment_z:expr],)?
                    $(default_mp_charge_accumulation: $default_mp_charge_accumulation:expr,)?
                    cortical_type_parameters: {
                        $($param_name:ident: $param_type:ty),* $(,)?
                    },
                    $(allowed_frame_change_handling: [$($allowed_frame:ident),* $(,)?],)? // TODO delete this!
                    cortical_area_properties: {
                        $($cortical_sub_unit_index:tt => ($io_cortical_area_configuration_flag_expr:expr, relative_position: [$rel_x:expr, $rel_y:expr, $rel_z:expr], channel_dimensions_default: [$dim_default_x:expr, $dim_default_y:expr, $dim_default_z:expr], channel_dimensions_min: [$dim_min_x:expr, $dim_min_y:expr, $dim_min_z:expr], channel_dimensions_max: [$dim_max_x:expr, $dim_max_y:expr, $dim_max_z:expr])),* $(,)?
                    }
                }
            ),* $(,)?
        }
    ) => {
        #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize)]
        pub enum SensoryCorticalUnit {
            $(
                $(#[doc = $doc])?
                $variant_name,
            )*
        }

        impl SensoryCorticalUnit {
            $(
                paste::paste! {
                    #[doc = "Get cortical area types array for " $friendly_name " using individual parameters."]
                    pub const fn [<get_cortical_area_types_array_for_ $variant_name:snake _with_parameters >](
                        $($param_name: $param_type),*) -> [CorticalAreaType; $number_cortical_areas] {
                        [
                            $(CorticalAreaType::BrainInput($io_cortical_area_configuration_flag_expr)),*
                        ]
                    }

                    #[doc = "Get cortical IDs array for " $friendly_name " using individual parameters."]
                    pub const fn [<get_cortical_ids_array_for_ $variant_name:snake _with_parameters >](
                        $($param_name: $param_type,)* cortical_unit_index: CorticalUnitIndex) -> [CorticalID; $number_cortical_areas] {
                        let cortical_unit_identifier: [u8; 3] = $cortical_id_unit_reference;
                        [
                            $(
                                $io_cortical_area_configuration_flag_expr .as_io_cortical_id(true, cortical_unit_identifier, cortical_unit_index, CorticalSubUnitIndex::from($cortical_sub_unit_index))
                            ),*
                        ]
                    }
                }
            )*

            pub const fn get_snake_case_name(&self) -> &'static str {
                match self {
                    $(
                        SensoryCorticalUnit::$variant_name => paste::paste!{ stringify!([<$variant_name:snake>]) },
                    )*
                }
            }

            /// Parse a sensory cortical unit from its snake_case name
            ///
            /// # Arguments
            /// * `name` - The snake_case name (e.g., "simple_vision", "raw_i_m_u")
            ///
            /// # Returns
            /// * `Some(SensoryCorticalUnit)` - If name matches a known type
            /// * `None` - If name is not recognized
            pub fn from_snake_case_name(name: &str) -> Option<SensoryCorticalUnit> {
                match name {
                    $(
                        paste::paste!{ stringify!([<$variant_name:snake>]) } => Some(SensoryCorticalUnit::$variant_name),
                    )*
                    _ => None,
                }
            }

            /// Returns all available sensory cortical unit types.
            /// This is useful for enumerating all possible sensor types in the system.
            pub const fn list_all() -> &'static [SensoryCorticalUnit] {
                &[
                    $(
                        SensoryCorticalUnit::$variant_name,
                    )*
                ]
            }

            /// Returns the friendly (human-readable) name for this sensory cortical unit type.
            pub const fn get_friendly_name(&self) -> &'static str {
                match self {
                    $(
                        SensoryCorticalUnit::$variant_name => $friendly_name,
                    )*
                }
            }

            /// Returns the 3-byte cortical ID unit reference for this type. // TODO delete me!
            pub const fn get_cortical_id_unit_reference(&self) -> [u8; 3] {
                match self {
                    $(
                        SensoryCorticalUnit::$variant_name => $cortical_id_unit_reference,
                    )*
                }
            }

            /// Returns the number of cortical areas this type creates.
            pub const fn get_number_cortical_areas(&self) -> usize {
                match self {
                    $(
                        SensoryCorticalUnit::$variant_name => $number_cortical_areas,
                    )*
                }
            }

            /// Returns the template-defined default firing threshold for auto-created areas.
            /// `None` means no template override is defined.
            pub const fn get_default_firing_threshold(&self) -> Option<f64> {
                match self {
                    $(
                        SensoryCorticalUnit::$variant_name => {
                            default_firing_threshold_impl!($($default_firing_threshold)?)
                        }
                    )*
                }
            }

            /// Returns template-defined default per-axis firing threshold increment [x, y, z].
            /// `None` means no template override is defined.
            pub const fn get_default_firing_threshold_increment(&self) -> Option<[f64; 3]> {
                match self {
                    $(
                        SensoryCorticalUnit::$variant_name => {
                            default_firing_threshold_increment_impl!(
                                $([$default_firing_threshold_increment_x, $default_firing_threshold_increment_y, $default_firing_threshold_increment_z])?
                            )
                        }
                    )*
                }
            }

            /// Returns the template-defined default mp_charge_accumulation for auto-created areas.
            /// `None` means no template override is defined.
            pub const fn get_default_mp_charge_accumulation(&self) -> Option<bool> {
                match self {
                    $(
                        SensoryCorticalUnit::$variant_name => {
                            default_mp_charge_accumulation_impl!($($default_mp_charge_accumulation)?)
                        }
                    )*
                }
            }

            /// Returns the accepted wrapped IO data type name for this sensory unit type.
            pub const fn get_accepted_wrapped_io_data_type(&self) -> &'static str { // TODO delete me!
                match self {
                    $(
                        SensoryCorticalUnit::$variant_name => stringify!($accepted_wrapped_io_data_type),
                    )*
                }
            }

            /// Returns the default topology for all units of this cortical type.
            pub fn get_unit_default_topology(&self) -> HashMap<CorticalSubUnitIndex, UnitTopology> {
                match self {
                    $(
                        SensoryCorticalUnit::$variant_name => {
                            let mut topology = HashMap::new();
                            $(
                                topology.insert(
                                    CorticalSubUnitIndex::from($cortical_sub_unit_index),
                                    UnitTopology {
                                        relative_position: [$rel_x, $rel_y, $rel_z],
                                        channel_dimensions_default: [$dim_default_x, $dim_default_y, $dim_default_z],
                                        channel_dimensions_min: [$dim_min_x, $dim_min_y, $dim_min_z],
                                        channel_dimensions_max: [$dim_max_x, $dim_max_y, $dim_max_z],
                                    }
                                );
                            )*
                            topology
                        }
                    )*
                }
            }


            /// Returns the allowed frame change handling modes from the template, if restricted.
            /// If None is returned, all frame change handling modes are allowed.
            /// If Some is returned, only the specified modes are valid.
            pub fn get_allowed_frame_change_handling(&self) -> Option<&'static [FrameChangeHandling]> { // TODO delete me!
                match self {
                    $(
                        SensoryCorticalUnit::$variant_name => {
                            $crate::get_allowed_frame_change_handling_impl!($($($allowed_frame),*)?)
                        }
                    )*
                }
            }

            pub fn get_cortical_id_vector_from_index_and_serde_io_configuration_flags(&self, cortical_unit_index: CorticalUnitIndex, map: Map<String, Value>) -> Result<Vec<CorticalID>, crate::FeagiDataError> {
                match self {
                    $(
                        SensoryCorticalUnit::$variant_name => {
                            paste::paste! {
                                let array = SensoryCorticalUnit::[<get_cortical_ids_array_for_ $variant_name:snake _with_parameters >](
                                    $($param_type::try_from_serde_map(&map)?,)*
                                    cortical_unit_index);
                                return Ok(array.to_vec());
                            }
                        }
                    )*
                }
            }



        }

        impl Display for SensoryCorticalUnit {
            fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
                match self {
                    $(
                        SensoryCorticalUnit::$variant_name => write!(f, $friendly_name),
                    )*
                }
    }
        }
    };

}
// Generate the SensoryCorticalUnit enum and all helper methods from the template
sensor_cortical_units!(define_sensory_cortical_units_enum);

/// Cortical area property key for the per-IPU sensor-cadence hold option.
///
/// When `true`, the burst engine re-injects the previous burst's frame for this input
/// area on a burst where no new frame arrived (exactly one extra burst). Areas without
/// the property use [`SensoryCorticalUnit::get_default_sensory_hold_last_frame`].
pub const SENSORY_HOLD_LAST_FRAME_PROPERTY: &str = "sensory_hold_last_frame";

/// Effective `sensory_hold_last_frame` for a cortical area: the explicit property when
/// present and boolean, otherwise the template default of its sensory unit. Non-input
/// areas and unknown input units resolve to `false`.
pub fn resolve_sensory_hold_last_frame(
    cortical_id: &CorticalID,
    properties: &HashMap<String, Value>,
) -> bool {
    if let Some(value) = properties
        .get(SENSORY_HOLD_LAST_FRAME_PROPERTY)
        .and_then(Value::as_bool)
    {
        return value;
    }
    SensoryCorticalUnit::try_from_cortical_id(cortical_id)
        .map(|unit| unit.get_default_sensory_hold_last_frame())
        .unwrap_or(false)
}

impl SensoryCorticalUnit {
    /// Template default for the `sensory_hold_last_frame` cortical area property:
    /// `true` for scalar sensor units (IMU, encoder, proximity, battery, analog, ...),
    /// `false` for frame-based (vision, audio), boolean/digital and opaque (`MiscData`) inputs.
    /// Derived from the unit's accepted wrapped IO data type.
    pub fn get_default_sensory_hold_last_frame(&self) -> bool {
        matches!(
            self.get_accepted_wrapped_io_data_type(),
            "Percentage" | "Percentage_3D" | "SignedPercentage_4D" | "RawIMU"
        )
    }

    /// Resolve the sensory unit of an input (`i...`) cortical ID from its 3-byte unit reference.
    /// Returns `None` for non-input IDs and unknown unit references.
    pub fn try_from_cortical_id(cortical_id: &CorticalID) -> Option<SensoryCorticalUnit> {
        let bytes = cortical_id.as_bytes();
        if bytes[0] != b'i' {
            return None;
        }
        let unit_ref = [bytes[1], bytes[2], bytes[3]];
        Self::list_all()
            .iter()
            .copied()
            .find(|unit| unit.get_cortical_id_unit_reference() == unit_ref)
    }

    /// Check if a legacy 3-char subtype matches a supported IPU type and return its default CorticalID.
    /// Used by genome migrator to avoid converting unsupported legacy IPU areas to MiscData.
    pub fn try_from_legacy_subtype(subtype: &str) -> Option<CorticalID> {
        let subtype_bytes = subtype.as_bytes();
        if subtype_bytes.len() != 3 {
            return None;
        }
        let subtype_arr = [subtype_bytes[0], subtype_bytes[1], subtype_bytes[2]];
        for unit in Self::list_all() {
            if unit.get_cortical_id_unit_reference() == subtype_arr {
                return Some(unit.get_default_cortical_id_for_group(CorticalUnitIndex::from(0u16)));
            }
        }
        None
    }

    /// Get the default CorticalID for this unit with group index 0 (Absolute frame handling, Linear positioning).
    pub fn get_default_cortical_id_for_group(&self, group_index: CorticalUnitIndex) -> CorticalID {
        use crate::genomic::cortical_area::io_cortical_area_configuration_flag::{
            FrameChangeHandling, PercentageNeuronPositioning,
        };
        let fh = FrameChangeHandling::Absolute;
        let pos = PercentageNeuronPositioning::Linear;
        match self {
            SensoryCorticalUnit::Infrared => {
                Self::get_cortical_ids_array_for_infrared_with_parameters(fh, pos, group_index)[0]
            }
            SensoryCorticalUnit::Proximity => {
                Self::get_cortical_ids_array_for_proximity_with_parameters(fh, pos, group_index)[0]
            }
            SensoryCorticalUnit::Shock => {
                Self::get_cortical_ids_array_for_shock_with_parameters(fh, pos, group_index)[0]
            }
            SensoryCorticalUnit::Battery => {
                Self::get_cortical_ids_array_for_battery_with_parameters(fh, pos, group_index)[0]
            }
            SensoryCorticalUnit::Servo => {
                Self::get_cortical_ids_array_for_servo_with_parameters(fh, pos, group_index)[0]
            }
            SensoryCorticalUnit::AnalogGPIO => {
                Self::get_cortical_ids_array_for_analog_g_p_i_o_with_parameters(
                    fh,
                    pos,
                    group_index,
                )[0]
            }
            SensoryCorticalUnit::DigitalGPIO => {
                Self::get_cortical_ids_array_for_digital_g_p_i_o_with_parameters(group_index)[0]
            }
            SensoryCorticalUnit::MiscData => {
                Self::get_cortical_ids_array_for_misc_data_with_parameters(fh, group_index)[0]
            }
            SensoryCorticalUnit::TextEnglishInput => {
                Self::get_cortical_ids_array_for_text_english_input_with_parameters(fh, group_index)
                    [0]
            }
            SensoryCorticalUnit::CountInput => {
                Self::get_cortical_ids_array_for_count_input_with_parameters(fh, pos, group_index)
                    [0]
            }
            SensoryCorticalUnit::Vision => {
                Self::get_cortical_ids_array_for_vision_with_parameters(fh, group_index)[0]
            }
            SensoryCorticalUnit::DepthMap => {
                Self::get_cortical_ids_array_for_depth_map_with_parameters(fh, group_index)[0]
            }
            SensoryCorticalUnit::ObjectSegmentationInput => {
                Self::get_cortical_ids_array_for_object_segmentation_input_with_parameters(
                    fh,
                    group_index,
                )[0]
            }
            SensoryCorticalUnit::SegmentedVision => {
                Self::get_cortical_ids_array_for_segmented_vision_with_parameters(fh, group_index)
                    [0]
            }
            SensoryCorticalUnit::RawIMU => {
                // Default group index returns the accelerometer (sub-area 0) as
                // the canonical "primary" cortical id of a Raw IMU unit.
                Self::get_cortical_ids_array_for_raw_i_m_u_with_parameters(fh, pos, group_index)[0]
            }
            SensoryCorticalUnit::SmartIMU => {
                Self::get_cortical_ids_array_for_smart_i_m_u_with_parameters(fh, pos, group_index)
                    [0]
            }
            SensoryCorticalUnit::CartesianPosition => {
                Self::get_cortical_ids_array_for_cartesian_position_with_parameters(
                    fh,
                    pos,
                    group_index,
                )[0]
            }
            SensoryCorticalUnit::AudioInput => {
                Self::get_cortical_ids_array_for_audio_input_with_parameters(fh, group_index)[0]
            }
        }
    }
}

#[cfg(test)]
mod sensory_hold_default_tests {
    use super::*;

    #[test]
    fn scalar_units_hold_by_default_frame_units_do_not() {
        for unit in [
            SensoryCorticalUnit::RawIMU,
            SensoryCorticalUnit::SmartIMU,
            SensoryCorticalUnit::Servo,
            SensoryCorticalUnit::Proximity,
            SensoryCorticalUnit::Battery,
            SensoryCorticalUnit::AnalogGPIO,
            SensoryCorticalUnit::CartesianPosition,
        ] {
            assert!(unit.get_default_sensory_hold_last_frame(), "{unit:?}");
        }
        for unit in [
            SensoryCorticalUnit::Vision,
            SensoryCorticalUnit::SegmentedVision,
            SensoryCorticalUnit::AudioInput,
            SensoryCorticalUnit::DigitalGPIO,
            SensoryCorticalUnit::MiscData,
            SensoryCorticalUnit::DepthMap,
        ] {
            assert!(!unit.get_default_sensory_hold_last_frame(), "{unit:?}");
        }
    }

    #[test]
    fn resolve_uses_property_then_template_default() {
        let imu = SensoryCorticalUnit::RawIMU
            .get_default_cortical_id_for_group(CorticalUnitIndex::from(0u16));
        let vision = SensoryCorticalUnit::Vision
            .get_default_cortical_id_for_group(CorticalUnitIndex::from(0u16));
        assert_eq!(
            SensoryCorticalUnit::try_from_cortical_id(&imu),
            Some(SensoryCorticalUnit::RawIMU)
        );
        let empty = HashMap::new();
        assert!(resolve_sensory_hold_last_frame(&imu, &empty));
        assert!(!resolve_sensory_hold_last_frame(&vision, &empty));
        let off = HashMap::from([(
            SENSORY_HOLD_LAST_FRAME_PROPERTY.to_string(),
            Value::Bool(false),
        )]);
        assert!(!resolve_sensory_hold_last_frame(&imu, &off));
        let on = HashMap::from([(
            SENSORY_HOLD_LAST_FRAME_PROPERTY.to_string(),
            Value::Bool(true),
        )]);
        assert!(resolve_sensory_hold_last_frame(&vision, &on));
        let custom = CorticalID::try_from_bytes(b"cHOLD001").unwrap();
        assert!(!resolve_sensory_hold_last_frame(&custom, &empty));
    }
}