feagi_brain_development/connectivity/rules/
patterns.rs

1// Copyright 2025 Neuraville Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4/*!
5Pattern-based connectivity - wildcard matching and transformations.
6*/
7
8use crate::types::Position;
9
10type Dimensions = (usize, usize, usize);
11
12/// Pattern element types
13#[derive(Debug, Clone, PartialEq)]
14pub enum PatternElement {
15    Wildcard,   // "*" - matches any coordinate
16    Skip,       // "?" - pass through source coordinate
17    Exclude,    // "!" - exclude source coordinate
18    Exact(i32), // Exact integer match
19}
20
21impl PatternElement {
22    pub fn from_value(value: &str) -> Self {
23        match value {
24            "*" => PatternElement::Wildcard,
25            "?" => PatternElement::Skip,
26            "!" => PatternElement::Exclude,
27            _ => {
28                if let Ok(num) = value.parse::<i32>() {
29                    PatternElement::Exact(num)
30                } else {
31                    PatternElement::Wildcard // Default to wildcard for invalid
32                }
33            }
34        }
35    }
36
37    pub fn from_int(value: i32) -> Self {
38        if value == -1 {
39            PatternElement::Wildcard
40        } else if value == -2 {
41            PatternElement::Skip
42        } else if value == -3 {
43            PatternElement::Exclude
44        } else {
45            PatternElement::Exact(value)
46        }
47    }
48}
49
50/// 3D pattern (x, y, z)
51pub type Pattern3D = (PatternElement, PatternElement, PatternElement);
52
53/// Match a coordinate against a pattern element
54pub fn match_pattern_element(element: &PatternElement, coordinate: i32, src_coord: i32) -> bool {
55    match element {
56        PatternElement::Wildcard => true,
57        PatternElement::Skip => coordinate == src_coord,
58        PatternElement::Exclude => coordinate != src_coord,
59        PatternElement::Exact(val) => coordinate == *val,
60    }
61}
62
63/// Generate destination coordinates from pattern matching
64pub fn find_destination_coordinates(
65    dst_dimensions: Dimensions,
66    src_coordinate: Position,
67    _src_pattern: &Pattern3D,
68    dst_pattern: &Pattern3D,
69) -> Vec<Position> {
70    let mut results = Vec::new();
71
72    let (dst_width, dst_height, dst_depth) = dst_dimensions;
73    let (src_x, src_y, src_z) = src_coordinate;
74
75    // Generate ranges based on destination pattern
76    let x_range: Vec<u32> = match &dst_pattern.0 {
77        PatternElement::Wildcard => (0..dst_width as u32).collect(),
78        PatternElement::Skip => {
79            if (src_x as usize) < dst_width {
80                vec![src_x]
81            } else {
82                vec![]
83            }
84        }
85        PatternElement::Exclude => (0..dst_width as u32).filter(|&x| x != src_x).collect(),
86        PatternElement::Exact(val) => {
87            if *val >= 0 && (*val as usize) < dst_width {
88                vec![*val as u32]
89            } else {
90                vec![]
91            }
92        }
93    };
94
95    let y_range: Vec<u32> = match &dst_pattern.1 {
96        PatternElement::Wildcard => (0..dst_height as u32).collect(),
97        PatternElement::Skip => {
98            if (src_y as usize) < dst_height {
99                vec![src_y]
100            } else {
101                vec![]
102            }
103        }
104        PatternElement::Exclude => (0..dst_height as u32).filter(|&y| y != src_y).collect(),
105        PatternElement::Exact(val) => {
106            if *val >= 0 && (*val as usize) < dst_height {
107                vec![*val as u32]
108            } else {
109                vec![]
110            }
111        }
112    };
113
114    let z_range: Vec<u32> = match &dst_pattern.2 {
115        PatternElement::Wildcard => (0..dst_depth as u32).collect(),
116        PatternElement::Skip => {
117            if (src_z as usize) < dst_depth {
118                vec![src_z]
119            } else {
120                vec![]
121            }
122        }
123        PatternElement::Exclude => (0..dst_depth as u32).filter(|&z| z != src_z).collect(),
124        PatternElement::Exact(val) => {
125            if *val >= 0 && (*val as usize) < dst_depth {
126                vec![*val as u32]
127            } else {
128                vec![]
129            }
130        }
131    };
132
133    // Generate all combinations
134    for x in &x_range {
135        for y in &y_range {
136            for z in &z_range {
137                results.push((*x, *y, *z));
138            }
139        }
140    }
141
142    results
143}
144
145/// Find source coordinates that match a pattern
146pub fn find_source_coordinates(
147    src_pattern: &Pattern3D,
148    src_dimensions: Dimensions,
149) -> Vec<Position> {
150    let mut results = Vec::new();
151
152    let (src_width, src_height, src_depth) = src_dimensions;
153
154    // Generate ranges based on source pattern
155    let x_range: Vec<u32> = match &src_pattern.0 {
156        PatternElement::Wildcard => (0..src_width as u32).collect(),
157        PatternElement::Exact(val) => {
158            if *val >= 0 && (*val as usize) < src_width {
159                vec![*val as u32]
160            } else {
161                vec![]
162            }
163        }
164        _ => (0..src_width as u32).collect(), // Skip/Exclude treated as wildcard for source
165    };
166
167    let y_range: Vec<u32> = match &src_pattern.1 {
168        PatternElement::Wildcard => (0..src_height as u32).collect(),
169        PatternElement::Exact(val) => {
170            if *val >= 0 && (*val as usize) < src_height {
171                vec![*val as u32]
172            } else {
173                vec![]
174            }
175        }
176        _ => (0..src_height as u32).collect(),
177    };
178
179    let z_range: Vec<u32> = match &src_pattern.2 {
180        PatternElement::Wildcard => (0..src_depth as u32).collect(),
181        PatternElement::Exact(val) => {
182            if *val >= 0 && (*val as usize) < src_depth {
183                vec![*val as u32]
184            } else {
185                vec![]
186            }
187        }
188        _ => (0..src_depth as u32).collect(),
189    };
190
191    // Generate all combinations
192    for x in &x_range {
193        for y in &y_range {
194            for z in &z_range {
195                results.push((*x, *y, *z));
196            }
197        }
198    }
199
200    results
201}
202
203/// Batch process pattern matching for multiple patterns
204pub fn match_patterns_batch(
205    src_coordinate: Position,
206    patterns: &[(Pattern3D, Pattern3D)], // (src_pattern, dst_pattern) pairs
207    _src_dimensions: Dimensions,
208    dst_dimensions: Dimensions,
209) -> Vec<Position> {
210    let mut all_results = Vec::new();
211
212    for (src_pattern, dst_pattern) in patterns {
213        // Check if source coordinate matches source pattern
214        let (src_x, src_y, src_z) = src_coordinate;
215
216        let x_match = match &src_pattern.0 {
217            PatternElement::Wildcard => true,
218            PatternElement::Exact(val) => src_x == (*val as u32),
219            _ => true, // Skip/Exclude don't filter source
220        };
221
222        let y_match = match &src_pattern.1 {
223            PatternElement::Wildcard => true,
224            PatternElement::Exact(val) => src_y == (*val as u32),
225            _ => true,
226        };
227
228        let z_match = match &src_pattern.2 {
229            PatternElement::Wildcard => true,
230            PatternElement::Exact(val) => src_z == (*val as u32),
231            _ => true,
232        };
233
234        if x_match && y_match && z_match {
235            let mut results = find_destination_coordinates(
236                dst_dimensions,
237                src_coordinate,
238                src_pattern,
239                dst_pattern,
240            );
241            all_results.append(&mut results);
242        }
243    }
244
245    // Remove duplicates
246    all_results.sort_unstable();
247    all_results.dedup();
248
249    all_results
250}
251
252#[cfg(test)]
253mod tests {
254    use super::*;
255
256    #[test]
257    fn test_wildcard_pattern() {
258        let src_pattern = (
259            PatternElement::Wildcard,
260            PatternElement::Wildcard,
261            PatternElement::Exact(0),
262        );
263        let dst_pattern = (
264            PatternElement::Skip,
265            PatternElement::Skip,
266            PatternElement::Exact(1),
267        );
268
269        let results =
270            find_destination_coordinates((10, 10, 10), (5, 5, 0), &src_pattern, &dst_pattern);
271
272        assert_eq!(results.len(), 1);
273        assert_eq!(results[0], (5, 5, 1)); // Pass through x,y, set z=1
274    }
275
276    #[test]
277    fn test_exact_pattern() {
278        let src_pattern = (
279            PatternElement::Exact(0),
280            PatternElement::Exact(0),
281            PatternElement::Exact(0),
282        );
283        let dst_pattern = (
284            PatternElement::Exact(1),
285            PatternElement::Exact(2),
286            PatternElement::Exact(3),
287        );
288
289        let results =
290            find_destination_coordinates((10, 10, 10), (0, 0, 0), &src_pattern, &dst_pattern);
291
292        assert_eq!(results.len(), 1);
293        assert_eq!(results[0], (1, 2, 3));
294    }
295
296    #[test]
297    fn test_exclude_pattern() {
298        let src_pattern = (
299            PatternElement::Wildcard,
300            PatternElement::Wildcard,
301            PatternElement::Wildcard,
302        );
303        let dst_pattern = (
304            PatternElement::Exclude,
305            PatternElement::Exact(0),
306            PatternElement::Exact(0),
307        );
308
309        let results =
310            find_destination_coordinates((3, 1, 1), (1, 0, 0), &src_pattern, &dst_pattern);
311
312        // Should match x=0 and x=2 (excluding x=1)
313        assert_eq!(results.len(), 2);
314        assert!(results.contains(&(0, 0, 0)));
315        assert!(results.contains(&(2, 0, 0)));
316    }
317}