feagi_brain_development/connectivity/core_morphologies/
bitmask.rs1use crate::types::BduResult;
13use feagi_npu_neural::types::{NeuronId, SynapticPsp, SynapticWeight};
14use feagi_npu_neural::SynapseType;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum BitmaskAxis {
18 X,
19 Y,
20 Z,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum BitmaskMode {
25 Encoder,
26 Decoder,
27}
28
29#[allow(clippy::too_many_arguments)]
30pub fn apply_bitmask_morphology_with_dimensions(
31 npu: &mut feagi_npu_burst_engine::DynamicNPU,
32 src_area_id: u32,
33 dst_area_id: u32,
34 src_dimensions: (usize, usize, usize),
35 dst_dimensions: (usize, usize, usize),
36 axis: BitmaskAxis,
37 mode: BitmaskMode,
38 weight: f32,
39 psp: f32,
40 synapse_attractivity: u8,
41 synapse_type: SynapseType,
42) -> BduResult<u32> {
43 use crate::rng::get_rng;
44 use rand::Rng;
45
46 let mut rng = get_rng();
47
48 let src_neurons = npu.get_neurons_in_cortical_area(src_area_id);
49 if src_neurons.is_empty() {
50 return Ok(0);
51 }
52
53 if dst_dimensions.0 == 0 || dst_dimensions.1 == 0 || dst_dimensions.2 == 0 {
54 return Ok(0);
55 }
56
57 let mut dst_pos_map = std::collections::HashMap::new();
58 for dst_nid in npu.get_neurons_in_cortical_area(dst_area_id) {
59 if let Some(coords) = npu.get_neuron_coordinates(dst_nid) {
60 dst_pos_map.insert(coords, dst_nid);
61 }
62 }
63
64 let mut synapse_count = 0u32;
65
66 for src_nid in src_neurons {
67 let Some(src_pos) = npu.get_neuron_coordinates(src_nid) else {
68 continue;
69 };
70
71 let dst_positions =
72 map_positions_for_bitmask(src_pos, src_dimensions, dst_dimensions, axis, mode);
73
74 for dst_pos in dst_positions {
75 #[allow(clippy::collapsible_if)]
77 if let Some(&dst_nid) = dst_pos_map.get(&dst_pos) {
78 if rng.gen_range(0..100) < synapse_attractivity
79 && npu
80 .add_synapse(
81 NeuronId(src_nid),
82 NeuronId(dst_nid),
83 SynapticWeight(weight),
84 SynapticPsp(psp),
85 synapse_type,
86 0,
87 )
88 .is_ok()
89 {
90 synapse_count += 1;
91 }
92 }
93 }
94 }
95
96 Ok(synapse_count)
97}
98
99fn map_positions_for_bitmask(
100 src_pos: (u32, u32, u32),
101 src_dimensions: (usize, usize, usize),
102 dst_dimensions: (usize, usize, usize),
103 axis: BitmaskAxis,
104 mode: BitmaskMode,
105) -> Vec<(u32, u32, u32)> {
106 let src_axis_len = axis_len(src_dimensions, axis);
107 let dst_axis_len = axis_len(dst_dimensions, axis);
108 if src_axis_len == 0 || dst_axis_len == 0 {
109 return Vec::new();
110 }
111
112 let clamped = (
113 clamp_to_dim(src_pos.0, dst_dimensions.0),
114 clamp_to_dim(src_pos.1, dst_dimensions.1),
115 clamp_to_dim(src_pos.2, dst_dimensions.2),
116 );
117
118 match mode {
119 BitmaskMode::Encoder => {
120 let src_bit_index = axis_value(src_pos, axis) as usize;
121 if src_bit_index >= src_axis_len {
122 return Vec::new();
123 }
124
125 let mut out = Vec::new();
126 for dst_axis_index in 0..dst_axis_len {
127 if bit_is_set_msb(dst_axis_index as u32, src_bit_index, src_axis_len) {
128 out.push(compose_pos(axis, dst_axis_index as u32, clamped));
129 }
130 }
131 out
132 }
133 BitmaskMode::Decoder => {
134 let encoded_axis_value = axis_value(src_pos, axis);
135 let mut out = Vec::new();
136 for dst_bit_index in 0..dst_axis_len {
137 if bit_is_set_msb(encoded_axis_value, dst_bit_index, dst_axis_len) {
138 out.push(compose_pos(axis, dst_bit_index as u32, clamped));
139 }
140 }
141 out
142 }
143 }
144}
145
146#[inline]
147fn axis_len(dim: (usize, usize, usize), axis: BitmaskAxis) -> usize {
148 match axis {
149 BitmaskAxis::X => dim.0,
150 BitmaskAxis::Y => dim.1,
151 BitmaskAxis::Z => dim.2,
152 }
153}
154
155#[inline]
156fn axis_value(pos: (u32, u32, u32), axis: BitmaskAxis) -> u32 {
157 match axis {
158 BitmaskAxis::X => pos.0,
159 BitmaskAxis::Y => pos.1,
160 BitmaskAxis::Z => pos.2,
161 }
162}
163
164#[inline]
165fn compose_pos(
166 axis: BitmaskAxis,
167 axis_value: u32,
168 clamped_src_pos: (u32, u32, u32),
169) -> (u32, u32, u32) {
170 match axis {
171 BitmaskAxis::X => (axis_value, clamped_src_pos.1, clamped_src_pos.2),
172 BitmaskAxis::Y => (clamped_src_pos.0, axis_value, clamped_src_pos.2),
173 BitmaskAxis::Z => (clamped_src_pos.0, clamped_src_pos.1, axis_value),
174 }
175}
176
177#[inline]
178fn clamp_to_dim(value: u32, dim_len: usize) -> u32 {
179 if dim_len == 0 {
180 return 0;
181 }
182 value.min((dim_len - 1) as u32)
183}
184
185#[inline]
186fn bit_is_set_msb(value: u32, bit_index_from_msb: usize, bit_width: usize) -> bool {
187 if bit_index_from_msb >= bit_width {
188 return false;
189 }
190 let lsb_index = bit_width - 1 - bit_index_from_msb;
191 if lsb_index >= u32::BITS as usize {
192 return false;
193 }
194 (value & (1u32 << lsb_index)) != 0
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 #[test]
202 fn test_encoder_x_uses_msb_convention() {
203 let out = map_positions_for_bitmask(
205 (1, 2, 3),
206 (3, 10, 10),
207 (8, 10, 10),
208 BitmaskAxis::X,
209 BitmaskMode::Encoder,
210 );
211
212 assert_eq!(
213 out,
214 vec![(2, 2, 3), (3, 2, 3), (6, 2, 3), (7, 2, 3)],
215 "Expected X positions where middle bit is set for 3-bit values"
216 );
217 }
218
219 #[test]
220 fn test_decoder_x_uses_msb_convention() {
221 let out = map_positions_for_bitmask(
223 (5, 1, 1),
224 (16, 10, 10),
225 (4, 10, 10),
226 BitmaskAxis::X,
227 BitmaskMode::Decoder,
228 );
229
230 assert_eq!(out, vec![(1, 1, 1), (3, 1, 1)]);
231 }
232}