chess_vector_engine/
pattern_recognition.rs

1use crate::errors::Result;
2use crate::hybrid_evaluation::{EvaluationComponent, PatternEvaluator};
3use crate::utils::cache::PatternCache;
4use chess::{Board, Color, Piece};
5use ndarray::{s, Array1};
6use std::collections::HashMap;
7use std::sync::{Arc, RwLock};
8
9/// Advanced pattern recognition system for chess positions
10pub struct AdvancedPatternRecognizer {
11    /// Individual pattern recognizers
12    pawn_structure_recognizer: PawnStructureRecognizer,
13    piece_coordination_recognizer: PieceCoordinationRecognizer,
14    king_safety_recognizer: KingSafetyRecognizer,
15    tactical_pattern_recognizer: TacticalPatternRecognizer,
16    endgame_pattern_recognizer: EndgamePatternRecognizer,
17
18    /// Pattern weights for different game phases
19    pattern_weights: PatternWeights,
20
21    /// Pattern cache
22    pattern_cache: Arc<PatternCache<String, PatternAnalysisResult>>,
23
24    /// Learned pattern database
25    learned_patterns: Arc<RwLock<LearnedPatternDatabase>>,
26}
27
28impl AdvancedPatternRecognizer {
29    /// Create a new pattern recognizer
30    pub fn new() -> Self {
31        Self {
32            pawn_structure_recognizer: PawnStructureRecognizer::new(),
33            piece_coordination_recognizer: PieceCoordinationRecognizer::new(),
34            king_safety_recognizer: KingSafetyRecognizer::new(),
35            tactical_pattern_recognizer: TacticalPatternRecognizer::new(),
36            endgame_pattern_recognizer: EndgamePatternRecognizer::new(),
37            pattern_weights: PatternWeights::default(),
38            pattern_cache: Arc::new(PatternCache::new(10000)),
39            learned_patterns: Arc::new(RwLock::new(LearnedPatternDatabase::new())),
40        }
41    }
42
43    /// Analyze all patterns in a position
44    pub fn analyze_patterns(&self, board: &Board) -> Result<PatternAnalysisResult> {
45        let fen = board.to_string();
46
47        // Check cache first
48        if let Some(cached_result) = self.pattern_cache.get(&fen) {
49            return Ok(cached_result);
50        }
51
52        let mut result = PatternAnalysisResult::new();
53
54        // Pawn structure analysis
55        result.pawn_structure = self.pawn_structure_recognizer.analyze(board)?;
56
57        // Piece coordination analysis
58        result.piece_coordination = self.piece_coordination_recognizer.analyze(board)?;
59
60        // King safety analysis
61        result.king_safety = self.king_safety_recognizer.analyze(board)?;
62
63        // Tactical pattern analysis
64        result.tactical_patterns = self.tactical_pattern_recognizer.analyze(board)?;
65
66        // Endgame pattern analysis
67        result.endgame_patterns = self.endgame_pattern_recognizer.analyze(board)?;
68
69        // Compute overall pattern score
70        result.overall_score = self.compute_overall_score(&result);
71
72        // Check for learned patterns
73        result.learned_patterns = self.check_learned_patterns(board)?;
74
75        // Cache the result
76        self.pattern_cache.insert(fen, result.clone());
77
78        Ok(result)
79    }
80
81    /// Compute vector representation of patterns
82    pub fn encode_patterns(&self, board: &Board) -> Result<Array1<f32>> {
83        let analysis = self.analyze_patterns(board)?;
84
85        // Create a 256-dimensional pattern vector
86        let mut pattern_vector = Array1::zeros(256);
87
88        // Encode pawn structure features (64 dimensions)
89        self.encode_pawn_structure(
90            &analysis.pawn_structure,
91            pattern_vector.slice_mut(s![0..64]),
92        );
93
94        // Encode piece coordination features (64 dimensions)
95        self.encode_piece_coordination(
96            &analysis.piece_coordination,
97            pattern_vector.slice_mut(s![64..128]),
98        );
99
100        // Encode king safety features (32 dimensions)
101        self.encode_king_safety(
102            &analysis.king_safety,
103            pattern_vector.slice_mut(s![128..160]),
104        );
105
106        // Encode tactical patterns (32 dimensions)
107        self.encode_tactical_patterns(
108            &analysis.tactical_patterns,
109            pattern_vector.slice_mut(s![160..192]),
110        );
111
112        // Encode endgame patterns (32 dimensions)
113        self.encode_endgame_patterns(
114            &analysis.endgame_patterns,
115            pattern_vector.slice_mut(s![192..224]),
116        );
117
118        // Encode learned patterns (32 dimensions)
119        self.encode_learned_patterns(
120            &analysis.learned_patterns,
121            pattern_vector.slice_mut(s![224..256]),
122        );
123
124        Ok(pattern_vector)
125    }
126
127    fn compute_overall_score(&self, analysis: &PatternAnalysisResult) -> f32 {
128        let mut score = 0.0;
129
130        score += analysis.pawn_structure.score * self.pattern_weights.pawn_structure;
131        score += analysis.piece_coordination.score * self.pattern_weights.piece_coordination;
132        score += analysis.king_safety.score * self.pattern_weights.king_safety;
133        score += analysis.tactical_patterns.score * self.pattern_weights.tactical_patterns;
134        score += analysis.endgame_patterns.score * self.pattern_weights.endgame_patterns;
135
136        // Add learned pattern contributions
137        for pattern_match in &analysis.learned_patterns {
138            score += pattern_match.strength * pattern_match.confidence * 0.1;
139        }
140
141        score
142    }
143
144    fn check_learned_patterns(&self, board: &Board) -> Result<Vec<LearnedPatternMatch>> {
145        if let Ok(database) = self.learned_patterns.read() {
146            database.find_matching_patterns(board)
147        } else {
148            Ok(Vec::new())
149        }
150    }
151
152    fn encode_pawn_structure(
153        &self,
154        pawn_analysis: &PawnStructureAnalysis,
155        mut slice: ndarray::ArrayViewMut1<f32>,
156    ) {
157        slice[0] = pawn_analysis.isolated_pawns as f32 / 8.0;
158        slice[1] = pawn_analysis.doubled_pawns as f32 / 8.0;
159        slice[2] = pawn_analysis.passed_pawns as f32 / 8.0;
160        slice[3] = pawn_analysis.backward_pawns as f32 / 8.0;
161        slice[4] = pawn_analysis.pawn_islands as f32 / 8.0;
162        slice[5] = pawn_analysis.pawn_chains as f32 / 8.0;
163        slice[6] = pawn_analysis.pawn_storm_potential;
164        slice[7] = pawn_analysis.pawn_shield_quality;
165
166        // Encode pawn structure patterns (remaining 56 dimensions)
167        for i in 8..64 {
168            slice[i] = 0.0; // Placeholder for additional pawn features
169        }
170    }
171
172    fn encode_piece_coordination(
173        &self,
174        coord_analysis: &PieceCoordinationAnalysis,
175        mut slice: ndarray::ArrayViewMut1<f32>,
176    ) {
177        slice[0] = coord_analysis.piece_activity;
178        slice[1] = coord_analysis.piece_harmony;
179        slice[2] = coord_analysis.central_control;
180        slice[3] = coord_analysis.outpost_strength;
181        slice[4] = coord_analysis.piece_mobility;
182        slice[5] = coord_analysis.piece_safety;
183
184        // Encode coordination patterns (remaining 58 dimensions)
185        for i in 6..64 {
186            slice[i] = 0.0; // Placeholder for additional coordination features
187        }
188    }
189
190    fn encode_king_safety(
191        &self,
192        safety_analysis: &KingSafetyAnalysis,
193        mut slice: ndarray::ArrayViewMut1<f32>,
194    ) {
195        slice[0] = safety_analysis.king_exposure;
196        slice[1] = safety_analysis.attacking_pieces;
197        slice[2] = safety_analysis.escape_squares;
198        slice[3] = safety_analysis.pawn_shield;
199        slice[4] = safety_analysis.king_tropism;
200
201        // Encode additional king safety features (remaining 27 dimensions)
202        for i in 5..32 {
203            slice[i] = 0.0; // Placeholder
204        }
205    }
206
207    fn encode_tactical_patterns(
208        &self,
209        tactical_analysis: &TacticalPatternAnalysis,
210        mut slice: ndarray::ArrayViewMut1<f32>,
211    ) {
212        slice[0] = if tactical_analysis.pins > 0 { 1.0 } else { 0.0 };
213        slice[1] = if tactical_analysis.forks > 0 {
214            1.0
215        } else {
216            0.0
217        };
218        slice[2] = if tactical_analysis.skewers > 0 {
219            1.0
220        } else {
221            0.0
222        };
223        slice[3] = if tactical_analysis.discovered_attacks > 0 {
224            1.0
225        } else {
226            0.0
227        };
228        slice[4] = tactical_analysis.hanging_pieces as f32 / 16.0;
229        slice[5] = tactical_analysis.undefended_pieces as f32 / 16.0;
230
231        // Encode additional tactical features (remaining 26 dimensions)
232        for i in 6..32 {
233            slice[i] = 0.0; // Placeholder
234        }
235    }
236
237    fn encode_endgame_patterns(
238        &self,
239        endgame_analysis: &EndgamePatternAnalysis,
240        mut slice: ndarray::ArrayViewMut1<f32>,
241    ) {
242        slice[0] = if endgame_analysis.opposition {
243            1.0
244        } else {
245            0.0
246        };
247        slice[1] = if endgame_analysis.zugzwang_potential {
248            1.0
249        } else {
250            0.0
251        };
252        slice[2] = endgame_analysis.king_activity;
253        slice[3] = endgame_analysis.pawn_majority_value;
254        slice[4] = endgame_analysis.piece_vs_pawns_evaluation;
255
256        // Encode additional endgame features (remaining 27 dimensions)
257        for i in 5..32 {
258            slice[i] = 0.0; // Placeholder
259        }
260    }
261
262    fn encode_learned_patterns(
263        &self,
264        learned_matches: &[LearnedPatternMatch],
265        mut slice: ndarray::ArrayViewMut1<f32>,
266    ) {
267        // Encode up to 16 learned pattern matches (2 dimensions each)
268        for (i, pattern_match) in learned_matches.iter().take(16).enumerate() {
269            slice[i * 2] = pattern_match.strength;
270            slice[i * 2 + 1] = pattern_match.confidence;
271        }
272
273        // Fill remaining dimensions with zeros
274        for i in (learned_matches.len().min(16) * 2)..32 {
275            slice[i] = 0.0;
276        }
277    }
278
279    /// Train the pattern recognizer with position-evaluation pairs
280    pub fn train_patterns(&self, training_data: &[(Board, f32)]) -> Result<()> {
281        if let Ok(mut database) = self.learned_patterns.write() {
282            database.learn_from_data(training_data)?;
283        }
284        Ok(())
285    }
286
287    /// Get pattern recognition statistics
288    pub fn get_statistics(&self) -> PatternRecognitionStats {
289        let cache_stats = self.pattern_cache.stats();
290        let learned_count = self
291            .learned_patterns
292            .read()
293            .map(|db| db.pattern_count())
294            .unwrap_or(0);
295
296        PatternRecognitionStats {
297            cached_analyses: cache_stats.cache_size,
298            learned_patterns: learned_count,
299            cache_hit_ratio: cache_stats.cache_hit_ratio,
300        }
301    }
302}
303
304impl Default for AdvancedPatternRecognizer {
305    fn default() -> Self {
306        Self::new()
307    }
308}
309
310impl PatternEvaluator for AdvancedPatternRecognizer {
311    fn evaluate_position(&self, board: &Board) -> Result<EvaluationComponent> {
312        let start_time = std::time::Instant::now();
313        let analysis = self.analyze_patterns(board)?;
314        let computation_time = start_time.elapsed().as_millis() as u64;
315
316        let mut additional_info = HashMap::new();
317        additional_info.insert(
318            "pawn_structure_score".to_string(),
319            analysis.pawn_structure.score,
320        );
321        additional_info.insert(
322            "piece_coordination_score".to_string(),
323            analysis.piece_coordination.score,
324        );
325        additional_info.insert("king_safety_score".to_string(), analysis.king_safety.score);
326        additional_info.insert(
327            "tactical_score".to_string(),
328            analysis.tactical_patterns.score,
329        );
330        additional_info.insert("endgame_score".to_string(), analysis.endgame_patterns.score);
331        additional_info.insert(
332            "learned_patterns_count".to_string(),
333            analysis.learned_patterns.len() as f32,
334        );
335
336        // Compute confidence based on pattern clarity and agreement
337        let confidence = self.compute_pattern_confidence(&analysis);
338
339        Ok(EvaluationComponent {
340            evaluation: analysis.overall_score,
341            confidence,
342            computation_time_ms: computation_time,
343            additional_info,
344        })
345    }
346}
347
348impl AdvancedPatternRecognizer {
349    fn compute_pattern_confidence(&self, analysis: &PatternAnalysisResult) -> f32 {
350        let mut confidence_factors = Vec::new();
351
352        // Confidence based on pattern strength
353        confidence_factors.push(analysis.pawn_structure.score.abs().min(1.0));
354        confidence_factors.push(analysis.piece_coordination.score.abs().min(1.0));
355        confidence_factors.push(analysis.king_safety.score.abs().min(1.0));
356
357        // Confidence based on tactical clarity
358        if analysis.tactical_patterns.pins > 0 || analysis.tactical_patterns.forks > 0 {
359            confidence_factors.push(0.8);
360        }
361
362        // Confidence based on learned patterns
363        let learned_confidence = analysis
364            .learned_patterns
365            .iter()
366            .map(|p| p.confidence)
367            .fold(0.0, f32::max);
368        confidence_factors.push(learned_confidence);
369
370        // Average confidence
371        if confidence_factors.is_empty() {
372            0.5
373        } else {
374            confidence_factors.iter().sum::<f32>() / confidence_factors.len() as f32
375        }
376    }
377}
378
379/// Comprehensive pattern analysis result
380#[derive(Debug, Clone)]
381pub struct PatternAnalysisResult {
382    pub pawn_structure: PawnStructureAnalysis,
383    pub piece_coordination: PieceCoordinationAnalysis,
384    pub king_safety: KingSafetyAnalysis,
385    pub tactical_patterns: TacticalPatternAnalysis,
386    pub endgame_patterns: EndgamePatternAnalysis,
387    pub learned_patterns: Vec<LearnedPatternMatch>,
388    pub overall_score: f32,
389}
390
391impl PatternAnalysisResult {
392    fn new() -> Self {
393        Self {
394            pawn_structure: PawnStructureAnalysis::default(),
395            piece_coordination: PieceCoordinationAnalysis::default(),
396            king_safety: KingSafetyAnalysis::default(),
397            tactical_patterns: TacticalPatternAnalysis::default(),
398            endgame_patterns: EndgamePatternAnalysis::default(),
399            learned_patterns: Vec::new(),
400            overall_score: 0.0,
401        }
402    }
403}
404
405/// Pawn structure recognizer
406pub struct PawnStructureRecognizer;
407
408impl PawnStructureRecognizer {
409    pub fn new() -> Self {
410        Self
411    }
412
413    pub fn analyze(&self, board: &Board) -> Result<PawnStructureAnalysis> {
414        let mut analysis = PawnStructureAnalysis::default();
415
416        // Analyze for both colors
417        let white_analysis = self.analyze_color(board, Color::White);
418        let black_analysis = self.analyze_color(board, Color::Black);
419
420        // Compute relative scores (positive = good for white)
421        analysis.isolated_pawns =
422            (black_analysis.isolated_pawns - white_analysis.isolated_pawns) as i8;
423        analysis.doubled_pawns =
424            (black_analysis.doubled_pawns - white_analysis.doubled_pawns) as i8;
425        analysis.passed_pawns = (white_analysis.passed_pawns - black_analysis.passed_pawns) as i8;
426        analysis.backward_pawns =
427            (black_analysis.backward_pawns - white_analysis.backward_pawns) as i8;
428        analysis.pawn_islands = (black_analysis.pawn_islands - white_analysis.pawn_islands) as i8;
429        analysis.pawn_chains = (white_analysis.pawn_chains - black_analysis.pawn_chains) as i8;
430
431        // Compute overall pawn structure score
432        analysis.score = self.compute_pawn_score(&analysis);
433
434        // Additional pawn structure evaluations
435        analysis.pawn_storm_potential = self.evaluate_pawn_storm(board);
436        analysis.pawn_shield_quality = self.evaluate_pawn_shield(board);
437
438        Ok(analysis)
439    }
440
441    fn analyze_color(&self, board: &Board, color: Color) -> PawnColorAnalysis {
442        let pawns = board.pieces(Piece::Pawn) & board.color_combined(color);
443        let mut analysis = PawnColorAnalysis::default();
444
445        // Count pawns on each file
446        let mut pawns_per_file = [0u8; 8];
447        for square in pawns {
448            pawns_per_file[square.get_file().to_index()] += 1;
449        }
450
451        // Count doubled pawns
452        analysis.doubled_pawns = pawns_per_file.iter().filter(|&&count| count > 1).count();
453
454        // Count isolated pawns
455        for (file_idx, &pawn_count) in pawns_per_file.iter().enumerate() {
456            if pawn_count > 0 {
457                let has_neighbor = (file_idx > 0 && pawns_per_file[file_idx - 1] > 0)
458                    || (file_idx < 7 && pawns_per_file[file_idx + 1] > 0);
459                if !has_neighbor {
460                    analysis.isolated_pawns += pawn_count as usize;
461                }
462            }
463        }
464
465        // Count pawn islands
466        let mut in_island = false;
467        for &pawn_count in &pawns_per_file {
468            if pawn_count > 0 {
469                if !in_island {
470                    analysis.pawn_islands += 1;
471                    in_island = true;
472                }
473            } else {
474                in_island = false;
475            }
476        }
477
478        // Count passed pawns (simplified)
479        analysis.passed_pawns = self.count_passed_pawns(board, color);
480
481        // Count backward pawns (simplified)
482        analysis.backward_pawns = self.count_backward_pawns(board, color);
483
484        // Count pawn chains (simplified)
485        analysis.pawn_chains = self.count_pawn_chains(board, color);
486
487        analysis
488    }
489
490    fn count_passed_pawns(&self, _board: &Board, _color: Color) -> usize {
491        // Simplified implementation
492        0
493    }
494
495    fn count_backward_pawns(&self, _board: &Board, _color: Color) -> usize {
496        // Simplified implementation
497        0
498    }
499
500    fn count_pawn_chains(&self, _board: &Board, _color: Color) -> usize {
501        // Simplified implementation
502        0
503    }
504
505    fn compute_pawn_score(&self, analysis: &PawnStructureAnalysis) -> f32 {
506        let mut score = 0.0;
507
508        // Isolated pawns are bad
509        score -= analysis.isolated_pawns as f32 * 0.2;
510
511        // Doubled pawns are bad
512        score -= analysis.doubled_pawns as f32 * 0.15;
513
514        // Passed pawns are good
515        score += analysis.passed_pawns as f32 * 0.5;
516
517        // Backward pawns are bad
518        score -= analysis.backward_pawns as f32 * 0.1;
519
520        // Too many pawn islands are bad
521        score -= analysis.pawn_islands as f32 * 0.1;
522
523        // Pawn chains are good
524        score += analysis.pawn_chains as f32 * 0.15;
525
526        score
527    }
528
529    fn evaluate_pawn_storm(&self, _board: &Board) -> f32 {
530        // Simplified implementation
531        0.0
532    }
533
534    fn evaluate_pawn_shield(&self, _board: &Board) -> f32 {
535        // Simplified implementation
536        0.0
537    }
538}
539
540impl Default for PawnStructureRecognizer {
541    fn default() -> Self {
542        Self::new()
543    }
544}
545
546/// Other recognizers (simplified implementations for now)
547pub struct PieceCoordinationRecognizer;
548pub struct KingSafetyRecognizer;
549pub struct TacticalPatternRecognizer;
550pub struct EndgamePatternRecognizer;
551
552impl PieceCoordinationRecognizer {
553    pub fn new() -> Self {
554        Self
555    }
556    pub fn analyze(&self, _board: &Board) -> Result<PieceCoordinationAnalysis> {
557        Ok(PieceCoordinationAnalysis::default())
558    }
559}
560
561impl KingSafetyRecognizer {
562    pub fn new() -> Self {
563        Self
564    }
565    pub fn analyze(&self, _board: &Board) -> Result<KingSafetyAnalysis> {
566        Ok(KingSafetyAnalysis::default())
567    }
568}
569
570impl TacticalPatternRecognizer {
571    pub fn new() -> Self {
572        Self
573    }
574    pub fn analyze(&self, _board: &Board) -> Result<TacticalPatternAnalysis> {
575        Ok(TacticalPatternAnalysis::default())
576    }
577}
578
579impl EndgamePatternRecognizer {
580    pub fn new() -> Self {
581        Self
582    }
583    pub fn analyze(&self, _board: &Board) -> Result<EndgamePatternAnalysis> {
584        Ok(EndgamePatternAnalysis::default())
585    }
586}
587
588/// Pattern analysis structures
589#[derive(Debug, Clone, Default)]
590pub struct PawnStructureAnalysis {
591    pub isolated_pawns: i8,
592    pub doubled_pawns: i8,
593    pub passed_pawns: i8,
594    pub backward_pawns: i8,
595    pub pawn_islands: i8,
596    pub pawn_chains: i8,
597    pub pawn_storm_potential: f32,
598    pub pawn_shield_quality: f32,
599    pub score: f32,
600}
601
602#[derive(Debug, Clone, Default)]
603struct PawnColorAnalysis {
604    isolated_pawns: usize,
605    doubled_pawns: usize,
606    passed_pawns: usize,
607    backward_pawns: usize,
608    pawn_islands: usize,
609    pawn_chains: usize,
610}
611
612#[derive(Debug, Clone, Default)]
613pub struct PieceCoordinationAnalysis {
614    pub piece_activity: f32,
615    pub piece_harmony: f32,
616    pub central_control: f32,
617    pub outpost_strength: f32,
618    pub piece_mobility: f32,
619    pub piece_safety: f32,
620    pub score: f32,
621}
622
623#[derive(Debug, Clone, Default)]
624pub struct KingSafetyAnalysis {
625    pub king_exposure: f32,
626    pub attacking_pieces: f32,
627    pub escape_squares: f32,
628    pub pawn_shield: f32,
629    pub king_tropism: f32,
630    pub score: f32,
631}
632
633#[derive(Debug, Clone, Default)]
634pub struct TacticalPatternAnalysis {
635    pub pins: u8,
636    pub forks: u8,
637    pub skewers: u8,
638    pub discovered_attacks: u8,
639    pub hanging_pieces: u8,
640    pub undefended_pieces: u8,
641    pub score: f32,
642}
643
644#[derive(Debug, Clone, Default)]
645pub struct EndgamePatternAnalysis {
646    pub opposition: bool,
647    pub zugzwang_potential: bool,
648    pub king_activity: f32,
649    pub pawn_majority_value: f32,
650    pub piece_vs_pawns_evaluation: f32,
651    pub score: f32,
652}
653
654/// Learned pattern database
655pub struct LearnedPatternDatabase {
656    patterns: Vec<LearnedPattern>,
657    pattern_cache: HashMap<u64, Vec<usize>>, // Position hash -> pattern indices
658}
659
660impl LearnedPatternDatabase {
661    pub fn new() -> Self {
662        Self {
663            patterns: Vec::new(),
664            pattern_cache: HashMap::new(),
665        }
666    }
667
668    pub fn learn_from_data(&mut self, training_data: &[(Board, f32)]) -> Result<()> {
669        // Simplified pattern learning
670        for (board, evaluation) in training_data {
671            if evaluation.abs() > 0.5 {
672                // Only learn from decisive positions
673                let pattern = LearnedPattern {
674                    position_hash: board.get_hash(),
675                    evaluation: *evaluation,
676                    frequency: 1,
677                    strength: evaluation.abs(),
678                };
679                self.patterns.push(pattern);
680            }
681        }
682        Ok(())
683    }
684
685    pub fn find_matching_patterns(&self, board: &Board) -> Result<Vec<LearnedPatternMatch>> {
686        let position_hash = board.get_hash();
687        let mut matches = Vec::new();
688
689        // Sophisticated pattern matching using both exact hash and similarity
690        for (pattern_id, pattern) in self.patterns.iter().enumerate() {
691            let mut match_confidence = 0.0;
692            
693            // Exact hash match gets full confidence
694            if pattern.position_hash == position_hash {
695                match_confidence = 1.0;
696            } else {
697                // Check for similar positions using Hamming distance
698                let hash_diff = (pattern.position_hash ^ position_hash).count_ones();
699                if hash_diff <= 3 {  // Allow small differences (piece moves, captures)
700                    match_confidence = 1.0 - (hash_diff as f32 / 64.0);
701                }
702            }
703            
704            if match_confidence > 0.1 {  // Only include patterns with reasonable confidence
705                matches.push(LearnedPatternMatch {
706                    pattern_id: pattern_id as u32,
707                    strength: pattern.strength * match_confidence,  // Scale strength by confidence
708                    confidence: (pattern.frequency as f32 / 100.0).min(1.0) * match_confidence,
709                });
710            }
711        }
712
713        Ok(matches)
714    }
715
716    pub fn pattern_count(&self) -> usize {
717        self.patterns.len()
718    }
719}
720
721#[derive(Debug, Clone)]
722pub struct LearnedPattern {
723    pub position_hash: u64,
724    pub evaluation: f32,
725    pub frequency: u32,
726    pub strength: f32,
727}
728
729#[derive(Debug, Clone)]
730pub struct LearnedPatternMatch {
731    pub pattern_id: u32,
732    pub strength: f32,
733    pub confidence: f32,
734}
735
736/// Pattern weights for different aspects
737#[derive(Debug, Clone)]
738pub struct PatternWeights {
739    pub pawn_structure: f32,
740    pub piece_coordination: f32,
741    pub king_safety: f32,
742    pub tactical_patterns: f32,
743    pub endgame_patterns: f32,
744}
745
746impl Default for PatternWeights {
747    fn default() -> Self {
748        Self {
749            pawn_structure: 0.25,
750            piece_coordination: 0.25,
751            king_safety: 0.20,
752            tactical_patterns: 0.20,
753            endgame_patterns: 0.10,
754        }
755    }
756}
757
758/// Pattern recognition statistics
759#[derive(Debug, Clone)]
760pub struct PatternRecognitionStats {
761    pub cached_analyses: usize,
762    pub learned_patterns: usize,
763    pub cache_hit_ratio: f64,
764}
765
766#[cfg(test)]
767mod tests {
768    use super::*;
769    use chess::Board;
770    use std::str::FromStr;
771
772    #[test]
773    fn test_pattern_recognizer() {
774        let recognizer = AdvancedPatternRecognizer::new();
775        let board =
776            Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
777
778        let analysis = recognizer.analyze_patterns(&board).unwrap();
779        assert!(analysis.overall_score.is_finite());
780    }
781
782    #[test]
783    fn test_pattern_encoding() {
784        let recognizer = AdvancedPatternRecognizer::new();
785        let board =
786            Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
787
788        let pattern_vector = recognizer.encode_patterns(&board).unwrap();
789        assert_eq!(pattern_vector.len(), 256);
790        assert!(pattern_vector.iter().all(|&x| x.is_finite()));
791    }
792
793    #[test]
794    fn test_pawn_structure_recognizer() {
795        let recognizer = PawnStructureRecognizer::new();
796        let board =
797            Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
798
799        let analysis = recognizer.analyze(&board).unwrap();
800        assert!(analysis.score.is_finite());
801    }
802
803    #[test]
804    fn test_pattern_evaluator_trait() {
805        let recognizer = AdvancedPatternRecognizer::new();
806        let board =
807            Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
808
809        let evaluation = recognizer.evaluate_position(&board).unwrap();
810        assert!(evaluation.evaluation.is_finite());
811        assert!(evaluation.confidence >= 0.0 && evaluation.confidence <= 1.0);
812        assert!(evaluation.computation_time_ms > 0);
813        assert!(!evaluation.additional_info.is_empty());
814    }
815}