1use crate::errors::Result;
2use crate::utils::cache::EvaluationCache;
3use crate::utils::profiler::{global_profiler, ChessEngineProfiler};
4use chess::{Board, Color, Piece, Square};
5use std::collections::{HashMap, VecDeque};
6use std::hash::Hash;
7use std::sync::{Arc, RwLock};
8
9pub struct HybridEvaluationEngine {
11 nnue_evaluator: Option<Box<dyn NNUEEvaluator + Send + Sync>>,
13 pattern_evaluator: Option<Box<dyn PatternEvaluator + Send + Sync>>,
15 tactical_evaluator: Option<Box<dyn TacticalEvaluator + Send + Sync>>,
17 strategic_evaluator: Option<Box<dyn StrategicEvaluator + Send + Sync>>,
19 complexity_analyzer: ComplexityAnalyzer,
21 phase_detector: GamePhaseDetector,
23 blender: EvaluationBlender,
25 confidence_scorer: ConfidenceScorer,
27 evaluation_cache: Arc<EvaluationCache>,
29 profiler: Arc<ChessEngineProfiler>,
31}
32
33impl HybridEvaluationEngine {
34 pub fn new() -> Self {
36 Self {
37 nnue_evaluator: None,
38 pattern_evaluator: None,
39 tactical_evaluator: None,
40 strategic_evaluator: None,
41 complexity_analyzer: ComplexityAnalyzer::new(),
42 phase_detector: GamePhaseDetector::new(),
43 blender: EvaluationBlender::new(),
44 confidence_scorer: ConfidenceScorer::new(),
45 evaluation_cache: Arc::new(EvaluationCache::new(
46 10000,
47 std::time::Duration::from_secs(300),
48 )),
49 profiler: Arc::clone(global_profiler()),
50 }
51 }
52
53 pub fn with_nnue_evaluator<T>(mut self, evaluator: T) -> Self
55 where
56 T: NNUEEvaluator + Send + Sync + 'static,
57 {
58 self.nnue_evaluator = Some(Box::new(evaluator));
59 self
60 }
61
62 pub fn with_pattern_evaluator<T>(mut self, evaluator: T) -> Self
64 where
65 T: PatternEvaluator + Send + Sync + 'static,
66 {
67 self.pattern_evaluator = Some(Box::new(evaluator));
68 self
69 }
70
71 pub fn with_tactical_evaluator<T>(mut self, evaluator: T) -> Self
73 where
74 T: TacticalEvaluator + Send + Sync + 'static,
75 {
76 self.tactical_evaluator = Some(Box::new(evaluator));
77 self
78 }
79
80 pub fn with_strategic_evaluator<T>(mut self, evaluator: T) -> Self
82 where
83 T: StrategicEvaluator + Send + Sync + 'static,
84 {
85 self.strategic_evaluator = Some(Box::new(evaluator));
86 self
87 }
88
89 pub fn with_strategic_initiative_evaluator(self) -> Self {
91 use crate::strategic_initiative::StrategicInitiativeEvaluator;
92 self.with_strategic_evaluator(StrategicInitiativeEvaluator::new())
93 }
94
95 pub fn evaluate_position(&self, board: &Board) -> Result<HybridEvaluationResult> {
97 let fen = board.to_string();
98
99 if let Some(cached_eval) = self.evaluation_cache.get_evaluation(&fen) {
101 return Ok(HybridEvaluationResult {
102 final_evaluation: cached_eval,
103 nnue_evaluation: None,
104 pattern_evaluation: None,
105 tactical_evaluation: None,
106 strategic_evaluation: None,
107 complexity_score: 0.0,
108 game_phase: GamePhase::Unknown,
109 confidence_score: 1.0,
110 blend_weights: BlendWeights::default(),
111 evaluation_time_ms: 0,
112 from_cache: true,
113 });
114 }
115
116 let start_time = std::time::Instant::now();
117
118 let complexity_score = self.profiler.time_evaluation("complexity", || {
120 self.complexity_analyzer.analyze_complexity(board)
121 });
122
123 let game_phase = self.profiler.time_evaluation("phase_detection", || {
124 self.phase_detector.detect_phase(board)
125 });
126
127 let mut evaluation_results = EvaluationResults::new();
129
130 if let Some(ref nnue) = self.nnue_evaluator {
132 let nnue_result = self
133 .profiler
134 .time_evaluation("nnue", || nnue.evaluate_position(board));
135 if let Ok(result) = nnue_result {
136 evaluation_results.nnue = Some(result);
137 self.profiler.record_evaluation("nnue");
138 }
139 }
140
141 if let Some(ref pattern) = self.pattern_evaluator {
143 let should_use_pattern =
144 self.should_use_pattern_evaluation(complexity_score, &game_phase);
145 if should_use_pattern {
146 let pattern_result = self
147 .profiler
148 .time_evaluation("pattern", || pattern.evaluate_position(board));
149 if let Ok(result) = pattern_result {
150 evaluation_results.pattern = Some(result);
151 self.profiler.record_evaluation("pattern");
152 }
153 }
154 }
155
156 if let Some(ref tactical) = self.tactical_evaluator {
158 let should_use_tactical = self.should_use_tactical_evaluation(
159 complexity_score,
160 &game_phase,
161 &evaluation_results,
162 );
163 if should_use_tactical {
164 let tactical_result = self
165 .profiler
166 .time_evaluation("tactical", || tactical.evaluate_position(board));
167 if let Ok(result) = tactical_result {
168 evaluation_results.tactical = Some(result);
169 self.profiler.record_evaluation("tactical");
170 }
171 }
172 }
173
174 if let Some(ref strategic) = self.strategic_evaluator {
176 let should_use_strategic = self.should_use_strategic_evaluation(&game_phase);
177 if should_use_strategic {
178 let strategic_result = self
179 .profiler
180 .time_evaluation("strategic", || strategic.evaluate_position(board));
181 if let Ok(result) = strategic_result {
182 evaluation_results.strategic = Some(result);
183 self.profiler.record_evaluation("strategic");
184 }
185 }
186 }
187
188 let blend_weights =
190 self.blender
191 .compute_blend_weights(complexity_score, &game_phase, &evaluation_results);
192
193 let final_evaluation = self
195 .blender
196 .blend_evaluations(&evaluation_results, &blend_weights);
197
198 let position_context = PositionContext {
200 position_hash: board.get_hash(),
201 game_phase,
202 has_tactical_threats: self.detect_tactical_threats(board),
203 in_opening_book: self.is_in_opening_book(board),
204 material_imbalance: self.calculate_material_imbalance(board),
205 complexity_score,
206 };
207
208 let confidence_analysis = self.confidence_scorer.compute_confidence(
210 &evaluation_results,
211 &blend_weights,
212 complexity_score,
213 &position_context,
214 );
215
216 let confidence_score = confidence_analysis.overall_confidence;
217
218 let evaluation_time_ms = start_time.elapsed().as_millis() as u64;
219
220 let result = HybridEvaluationResult {
221 final_evaluation,
222 nnue_evaluation: evaluation_results.nnue.clone(),
223 pattern_evaluation: evaluation_results.pattern.clone(),
224 tactical_evaluation: evaluation_results.tactical.clone(),
225 strategic_evaluation: evaluation_results.strategic.clone(),
226 complexity_score,
227 game_phase,
228 confidence_score,
229 blend_weights,
230 evaluation_time_ms,
231 from_cache: false,
232 };
233
234 self.evaluation_cache
236 .store_evaluation(&fen, final_evaluation);
237
238 self.profiler.record_evaluation("hybrid");
239
240 Ok(result)
241 }
242
243 fn should_use_pattern_evaluation(&self, complexity_score: f32, game_phase: &GamePhase) -> bool {
245 match game_phase {
246 GamePhase::Opening => complexity_score > 0.3,
247 GamePhase::Middlegame => complexity_score > 0.4,
248 GamePhase::Endgame => complexity_score > 0.5,
249 GamePhase::Unknown => complexity_score > 0.4,
250 }
251 }
252
253 fn detect_tactical_threats(&self, board: &Board) -> bool {
255 let white_pieces = board.color_combined(Color::White);
258 let black_pieces = board.color_combined(Color::Black);
259
260 if board.checkers().popcnt() > 0 {
262 return true;
263 }
264
265 let total_pieces = white_pieces.popcnt() + black_pieces.popcnt();
268 total_pieces < 24 }
270
271 fn is_in_opening_book(&self, board: &Board) -> bool {
273 let total_pieces = board.combined().popcnt();
276 total_pieces >= 28
277 }
278
279 fn calculate_material_imbalance(&self, board: &Board) -> f32 {
281 let piece_values = [1, 3, 3, 5, 9, 0]; let mut white_material = 0;
284 let mut black_material = 0;
285
286 for piece_type in [
287 Piece::Pawn,
288 Piece::Knight,
289 Piece::Bishop,
290 Piece::Rook,
291 Piece::Queen,
292 ] {
293 let piece_index = match piece_type {
294 Piece::Pawn => 0,
295 Piece::Knight => 1,
296 Piece::Bishop => 2,
297 Piece::Rook => 3,
298 Piece::Queen => 4,
299 Piece::King => 5,
300 };
301
302 let white_count =
303 (board.pieces(piece_type) & board.color_combined(Color::White)).popcnt() as i32;
304 let black_count =
305 (board.pieces(piece_type) & board.color_combined(Color::Black)).popcnt() as i32;
306
307 white_material += white_count * piece_values[piece_index];
308 black_material += black_count * piece_values[piece_index];
309 }
310
311 (white_material - black_material).abs() as f32
312 }
313
314 fn should_use_tactical_evaluation(
316 &self,
317 complexity_score: f32,
318 game_phase: &GamePhase,
319 evaluation_results: &EvaluationResults,
320 ) -> bool {
321 if complexity_score > 0.7 {
323 return true;
324 }
325
326 if let (Some(nnue), Some(pattern)) = (&evaluation_results.nnue, &evaluation_results.pattern)
328 {
329 let disagreement = (nnue.evaluation - pattern.evaluation).abs();
330 if disagreement > 0.5 {
331 return true;
332 }
333 }
334
335 match game_phase {
337 GamePhase::Opening => complexity_score > 0.6,
338 GamePhase::Middlegame => complexity_score > 0.5,
339 GamePhase::Endgame => complexity_score > 0.4, GamePhase::Unknown => complexity_score > 0.5,
341 }
342 }
343
344 fn should_use_strategic_evaluation(&self, game_phase: &GamePhase) -> bool {
346 match game_phase {
347 GamePhase::Opening => true, GamePhase::Middlegame => true, GamePhase::Endgame => false, GamePhase::Unknown => true,
351 }
352 }
353
354 pub fn get_statistics(&self) -> HybridEvaluationStats {
356 let cache_stats = self.evaluation_cache.stats();
357
358 HybridEvaluationStats {
359 total_evaluations: 0, nnue_evaluations: 0, pattern_evaluations: 0, tactical_evaluations: 0, strategic_evaluations: 0, cache_hit_ratio: cache_stats.hit_ratio,
365 average_evaluation_time_ms: 0.0, evaluations_per_second: 0.0, }
368 }
369
370 pub fn set_adaptive_learning(&mut self, enabled: bool) {
372 self.blender.set_adaptive_learning(enabled);
373 }
374
375 pub fn update_evaluation_performance(
377 &self,
378 board: &Board,
379 predicted_evaluation: f32,
380 actual_result: Option<f32>,
381 evaluation_accuracy: f32,
382 ) -> Result<()> {
383 let complexity_score = self.complexity_analyzer.analyze_complexity(board);
385 let game_phase = self.phase_detector.detect_phase(board);
386
387 let mut eval_results = EvaluationResults::new();
389 let weights =
391 self.blender
392 .compute_blend_weights(complexity_score, &game_phase, &eval_results);
393
394 self.blender.update_performance_metrics(
396 &weights,
397 complexity_score,
398 &game_phase,
399 evaluation_accuracy,
400 actual_result,
401 );
402
403 Ok(())
404 }
405
406 pub fn get_adaptive_learning_stats(&self) -> AdaptiveLearningStats {
408 self.blender.get_adaptive_stats()
409 }
410
411 pub fn analyze_position_complexity(&self, board: &Board) -> ComplexityAnalysisResult {
413 self.complexity_analyzer.analyze_complexity_detailed(board)
414 }
415
416 pub fn configure_complexity_analyzer(
418 &mut self,
419 weights: ComplexityWeights,
420 depth: AnalysisDepth,
421 ) {
422 self.complexity_analyzer = ComplexityAnalyzer::new()
423 .with_complexity_weights(weights)
424 .with_analysis_depth(depth);
425 }
426
427 pub fn analyze_game_phase(&self, board: &Board) -> GamePhaseAnalysisResult {
429 self.phase_detector.analyze_game_phase(board)
430 }
431
432 pub fn configure_phase_detector(
434 &mut self,
435 weights: PhaseDetectionWeights,
436 settings: PhaseAdaptationSettings,
437 ) {
438 self.phase_detector = GamePhaseDetector::new()
439 .with_phase_weights(weights)
440 .with_adaptation_settings(settings);
441 }
442
443 pub fn apply_phase_adaptations(&self, board: &Board) -> BlendWeights {
445 let phase_analysis = self.analyze_game_phase(board);
446 phase_analysis.adaptation_recommendations.evaluation_weights
447 }
448}
449
450impl Default for HybridEvaluationEngine {
451 fn default() -> Self {
452 Self::new()
453 }
454}
455
456#[derive(Debug, Clone)]
458pub struct EvaluationComponent {
459 pub evaluation: f32,
460 pub confidence: f32,
461 pub computation_time_ms: u64,
462 pub additional_info: HashMap<String, f32>,
463}
464
465#[derive(Debug, Clone, Default)]
467struct EvaluationResults {
468 pub nnue: Option<EvaluationComponent>,
469 pub pattern: Option<EvaluationComponent>,
470 pub tactical: Option<EvaluationComponent>,
471 pub strategic: Option<EvaluationComponent>,
472}
473
474impl EvaluationResults {
475 fn new() -> Self {
476 Self::default()
477 }
478}
479
480#[derive(Debug, Clone)]
482pub struct HybridEvaluationResult {
483 pub final_evaluation: f32,
485 pub nnue_evaluation: Option<EvaluationComponent>,
487 pub pattern_evaluation: Option<EvaluationComponent>,
489 pub tactical_evaluation: Option<EvaluationComponent>,
491 pub strategic_evaluation: Option<EvaluationComponent>,
493 pub complexity_score: f32,
495 pub game_phase: GamePhase,
497 pub confidence_score: f32,
499 pub blend_weights: BlendWeights,
501 pub evaluation_time_ms: u64,
503 pub from_cache: bool,
505}
506
507#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
509pub enum GamePhase {
510 #[default]
511 Unknown,
512 Opening,
513 Middlegame,
514 Endgame,
515}
516
517#[derive(Debug, Clone)]
519pub struct BlendWeights {
520 pub nnue_weight: f32,
521 pub pattern_weight: f32,
522 pub tactical_weight: f32,
523 pub strategic_weight: f32,
524}
525
526impl Default for BlendWeights {
527 fn default() -> Self {
528 Self {
529 nnue_weight: 0.4,
530 pattern_weight: 0.3,
531 tactical_weight: 0.2,
532 strategic_weight: 0.1,
533 }
534 }
535}
536
537pub struct ComplexityAnalyzer {
539 cached_complexity: RwLock<HashMap<String, ComplexityAnalysisResult>>,
540 complexity_weights: ComplexityWeights,
541 analysis_depth: AnalysisDepth,
542}
543
544impl ComplexityAnalyzer {
545 pub fn new() -> Self {
546 Self {
547 cached_complexity: RwLock::new(HashMap::new()),
548 complexity_weights: ComplexityWeights::default(),
549 analysis_depth: AnalysisDepth::Standard,
550 }
551 }
552
553 pub fn with_analysis_depth(mut self, depth: AnalysisDepth) -> Self {
555 self.analysis_depth = depth;
556 self
557 }
558
559 pub fn with_complexity_weights(mut self, weights: ComplexityWeights) -> Self {
561 self.complexity_weights = weights;
562 self
563 }
564
565 pub fn analyze_complexity(&self, board: &Board) -> f32 {
567 let analysis = self.analyze_complexity_detailed(board);
568 analysis.overall_complexity
569 }
570
571 pub fn analyze_complexity_detailed(&self, board: &Board) -> ComplexityAnalysisResult {
573 let fen = board.to_string();
574
575 if let Ok(cache) = self.cached_complexity.read() {
577 if let Some(analysis) = cache.get(&fen) {
578 return analysis.clone();
579 }
580 }
581
582 let mut analysis = ComplexityAnalysisResult::new();
583
584 analysis.material_complexity = self.analyze_material_complexity(board);
586 analysis.pawn_structure_complexity = self.analyze_pawn_structure_complexity(board);
587 analysis.king_safety_complexity = self.analyze_king_safety_complexity(board);
588 analysis.piece_coordination_complexity = self.analyze_piece_coordination_complexity(board);
589 analysis.tactical_complexity = self.analyze_tactical_complexity(board);
590 analysis.positional_complexity = self.analyze_positional_complexity(board);
591 analysis.time_complexity = self.analyze_time_complexity(board);
592 analysis.endgame_complexity = self.analyze_endgame_complexity(board);
593
594 analysis.overall_complexity = self.compute_weighted_complexity(&analysis);
596
597 analysis.complexity_category = self.categorize_complexity(analysis.overall_complexity);
599
600 analysis.key_complexity_factors = self.identify_key_complexity_factors(&analysis);
602 analysis.evaluation_recommendations = self.generate_evaluation_recommendations(&analysis);
603
604 if let Ok(mut cache) = self.cached_complexity.write() {
606 if cache.len() > 1000 {
607 let keys_to_remove: Vec<_> = cache.keys().take(500).cloned().collect();
609 for key in keys_to_remove {
610 cache.remove(&key);
611 }
612 }
613 cache.insert(fen, analysis.clone());
614 }
615
616 analysis
617 }
618
619 fn compute_weighted_complexity(&self, analysis: &ComplexityAnalysisResult) -> f32 {
620 let weights = &self.complexity_weights;
621
622 let complexity_score = analysis.material_complexity * weights.material_weight
623 + analysis.pawn_structure_complexity * weights.pawn_structure_weight
624 + analysis.king_safety_complexity * weights.king_safety_weight
625 + analysis.piece_coordination_complexity * weights.piece_coordination_weight
626 + analysis.tactical_complexity * weights.tactical_weight
627 + analysis.positional_complexity * weights.positional_weight
628 + analysis.time_complexity * weights.time_weight
629 + analysis.endgame_complexity * weights.endgame_weight;
630
631 let depth_modifier = match self.analysis_depth {
633 AnalysisDepth::Fast => 0.8,
634 AnalysisDepth::Standard => 1.0,
635 AnalysisDepth::Deep => 1.2,
636 AnalysisDepth::Comprehensive => 1.4,
637 };
638
639 (complexity_score * depth_modifier).clamp(0.0, 1.0)
640 }
641
642 fn categorize_complexity(&self, complexity: f32) -> ComplexityCategory {
643 if complexity < 0.2 {
644 ComplexityCategory::VeryLow
645 } else if complexity < 0.4 {
646 ComplexityCategory::Low
647 } else if complexity < 0.6 {
648 ComplexityCategory::Medium
649 } else if complexity < 0.8 {
650 ComplexityCategory::High
651 } else {
652 ComplexityCategory::VeryHigh
653 }
654 }
655
656 fn identify_key_complexity_factors(
657 &self,
658 analysis: &ComplexityAnalysisResult,
659 ) -> Vec<ComplexityFactor> {
660 let mut factors = Vec::new();
661
662 if analysis.tactical_complexity > 0.7 {
664 factors.push(ComplexityFactor::TacticalThreats);
665 }
666 if analysis.king_safety_complexity > 0.6 {
667 factors.push(ComplexityFactor::KingSafety);
668 }
669 if analysis.piece_coordination_complexity > 0.6 {
670 factors.push(ComplexityFactor::PieceCoordination);
671 }
672 if analysis.pawn_structure_complexity > 0.5 {
673 factors.push(ComplexityFactor::PawnStructure);
674 }
675 if analysis.material_complexity > 0.5 {
676 factors.push(ComplexityFactor::MaterialImbalance);
677 }
678 if analysis.positional_complexity > 0.6 {
679 factors.push(ComplexityFactor::PositionalThemes);
680 }
681 if analysis.time_complexity > 0.7 {
682 factors.push(ComplexityFactor::TimeFactors);
683 }
684 if analysis.endgame_complexity > 0.5 {
685 factors.push(ComplexityFactor::EndgameFactors);
686 }
687
688 factors
689 }
690
691 fn generate_evaluation_recommendations(
692 &self,
693 analysis: &ComplexityAnalysisResult,
694 ) -> EvaluationRecommendations {
695 let mut recommendations = EvaluationRecommendations::default();
696
697 match analysis.complexity_category {
699 ComplexityCategory::VeryLow | ComplexityCategory::Low => {
700 recommendations.prefer_nnue = true;
701 recommendations.tactical_depth = 4;
702 recommendations.pattern_analysis_priority = 0.3;
703 }
704 ComplexityCategory::Medium => {
705 recommendations.prefer_nnue = false;
706 recommendations.tactical_depth = 6;
707 recommendations.pattern_analysis_priority = 0.5;
708 recommendations.strategic_analysis_priority = 0.4;
709 }
710 ComplexityCategory::High | ComplexityCategory::VeryHigh => {
711 recommendations.prefer_nnue = false;
712 recommendations.tactical_depth = 8;
713 recommendations.pattern_analysis_priority = 0.7;
714 recommendations.strategic_analysis_priority = 0.6;
715 recommendations.require_tactical_verification = true;
716 }
717 }
718
719 if analysis.tactical_complexity > 0.7 {
721 recommendations.tactical_depth = (recommendations.tactical_depth + 2).min(12);
722 recommendations.require_tactical_verification = true;
723 }
724
725 if analysis.king_safety_complexity > 0.6 {
726 recommendations.king_safety_analysis = true;
727 recommendations.pattern_analysis_priority += 0.1;
728 }
729
730 if analysis.endgame_complexity > 0.5 {
731 recommendations.endgame_analysis = true;
732 recommendations.prefer_nnue = true; }
734
735 recommendations
736 }
737
738 fn analyze_material_complexity(&self, board: &Board) -> f32 {
740 let white_material = self.count_material(board, Color::White);
741 let black_material = self.count_material(board, Color::Black);
742 let total_material = white_material + black_material;
743 let material_imbalance = (white_material - black_material).abs();
744
745 let mut complexity = 0.0;
746
747 complexity += (total_material as f32 / 78.0) * 0.4;
749
750 complexity += (material_imbalance as f32 / 39.0) * 0.3;
752
753 complexity += self.analyze_piece_diversity(board) * 0.3;
755
756 complexity.min(1.0)
757 }
758
759 fn analyze_pawn_structure_complexity(&self, board: &Board) -> f32 {
760 let mut complexity = 0.0;
761
762 complexity += self.count_pawn_islands(board) as f32 * 0.1;
764
765 complexity += self.count_isolated_pawns(board) as f32 * 0.15;
767
768 complexity += self.count_doubled_pawns(board) as f32 * 0.1;
770
771 complexity += self.count_passed_pawns_detailed(board) as f32 * 0.2;
773
774 complexity += self.evaluate_pawn_chains(board) * 0.25;
776
777 complexity += self.evaluate_pawn_storms(board) * 0.2;
779
780 complexity.min(1.0)
781 }
782
783 fn analyze_king_safety_complexity(&self, board: &Board) -> f32 {
784 let mut complexity = 0.0;
785
786 for color in [Color::White, Color::Black] {
787 let king_square = board.king_square(color);
788
789 complexity += self.evaluate_king_exposure(board, color, king_square) * 0.3;
791
792 complexity += self.count_king_attackers(board, color, king_square) as f32 * 0.1;
794
795 complexity += self.evaluate_pawn_shield_complexity(board, color, king_square) * 0.2;
797
798 complexity += self.evaluate_escape_squares(board, color, king_square) * 0.1;
800 }
801
802 (complexity / 2.0).min(1.0) }
804
805 fn analyze_piece_coordination_complexity(&self, board: &Board) -> f32 {
806 let mut complexity = 0.0;
807
808 complexity += self.analyze_mobility_complexity(board) * 0.3;
810
811 complexity += self.analyze_piece_harmony_complexity(board) * 0.25;
813
814 complexity += self.analyze_central_control_complexity(board) * 0.2;
816
817 complexity += self.analyze_piece_activity_imbalance(board) * 0.25;
819
820 complexity.min(1.0)
821 }
822
823 fn analyze_tactical_complexity(&self, board: &Board) -> f32 {
824 let mut complexity = 0.0;
825
826 if board.checkers().popcnt() > 0 {
828 complexity += 0.4;
829 }
830
831 use chess::MoveGen;
833 let legal_moves = MoveGen::new_legal(board);
834 let moves: Vec<_> = legal_moves.collect();
835 let capture_ratio = moves
836 .iter()
837 .filter(|mv| board.piece_on(mv.get_dest()).is_some())
838 .count() as f32
839 / moves.len().max(1) as f32;
840 complexity += capture_ratio * 0.3;
841
842 complexity += (moves.len() as f32 / 50.0).min(0.2);
844
845 complexity += self.analyze_tactical_motifs(board) * 0.4;
847
848 complexity += self.count_hanging_pieces(board) as f32 * 0.1;
850
851 complexity.min(1.0)
852 }
853
854 fn analyze_positional_complexity(&self, board: &Board) -> f32 {
855 let mut complexity = 0.0;
856
857 complexity += self.analyze_space_complexity(board) * 0.3;
859
860 complexity += self.analyze_weak_squares_complexity(board) * 0.25;
862
863 complexity += self.analyze_outpost_complexity(board) * 0.2;
865
866 complexity += self.analyze_piece_placement_complexity(board) * 0.25;
868
869 complexity.min(1.0)
870 }
871
872 fn analyze_time_complexity(&self, board: &Board) -> f32 {
873 let mut complexity = 0.0;
874
875 complexity += self.evaluate_tempo_sensitivity(board) * 0.4;
877
878 complexity += self.evaluate_zugzwang_complexity(board) * 0.3;
880
881 complexity += self.evaluate_critical_moves_complexity(board) * 0.3;
883
884 complexity.min(1.0)
885 }
886
887 fn analyze_endgame_complexity(&self, board: &Board) -> f32 {
888 let total_material =
889 self.count_material(board, Color::White) + self.count_material(board, Color::Black);
890
891 if total_material > 30 {
893 return 0.0;
894 }
895
896 let mut complexity = 0.0;
897
898 complexity += self.analyze_pawn_endgame_complexity(board) * 0.4;
900
901 complexity += self.analyze_piece_endgame_complexity(board) * 0.3;
903
904 complexity += self.analyze_king_activity_complexity(board) * 0.3;
906
907 complexity.min(1.0)
908 }
909
910 fn analyze_piece_diversity(&self, board: &Board) -> f32 {
912 let mut piece_types = 0;
913 for piece in [
914 Piece::Pawn,
915 Piece::Knight,
916 Piece::Bishop,
917 Piece::Rook,
918 Piece::Queen,
919 ] {
920 if (board.pieces(piece) & board.color_combined(Color::White)).popcnt() > 0
921 || (board.pieces(piece) & board.color_combined(Color::Black)).popcnt() > 0
922 {
923 piece_types += 1;
924 }
925 }
926 piece_types as f32 / 5.0
927 }
928
929 fn count_pawn_islands(&self, board: &Board) -> u8 {
930 let mut islands = 0;
931 for color in [Color::White, Color::Black] {
932 let pawns = board.pieces(Piece::Pawn) & board.color_combined(color);
933 let mut files_with_pawns = [false; 8];
934
935 for square in pawns {
936 files_with_pawns[square.get_file().to_index()] = true;
937 }
938
939 let mut in_island = false;
940 for &has_pawn in &files_with_pawns {
941 if has_pawn && !in_island {
942 islands += 1;
943 in_island = true;
944 } else if !has_pawn {
945 in_island = false;
946 }
947 }
948 }
949 islands
950 }
951
952 fn count_isolated_pawns(&self, board: &Board) -> u8 {
953 0
955 }
956
957 fn count_doubled_pawns(&self, board: &Board) -> u8 {
958 let mut doubled = 0;
959 for color in [Color::White, Color::Black] {
960 let pawns = board.pieces(Piece::Pawn) & board.color_combined(color);
961 let mut pawns_per_file = [0u8; 8];
962
963 for square in pawns {
964 pawns_per_file[square.get_file().to_index()] += 1;
965 }
966
967 doubled += pawns_per_file.iter().filter(|&&count| count > 1).count() as u8;
968 }
969 doubled
970 }
971
972 fn count_passed_pawns_detailed(&self, _board: &Board) -> u8 {
973 0
975 }
976
977 fn evaluate_pawn_chains(&self, _board: &Board) -> f32 {
978 0.0
980 }
981
982 fn evaluate_pawn_storms(&self, _board: &Board) -> f32 {
983 0.0
985 }
986
987 fn evaluate_king_exposure(&self, _board: &Board, _color: Color, _king_square: Square) -> f32 {
988 0.0
990 }
991
992 fn count_king_attackers(&self, _board: &Board, _color: Color, _king_square: Square) -> u8 {
993 0
995 }
996
997 fn evaluate_pawn_shield_complexity(
998 &self,
999 _board: &Board,
1000 _color: Color,
1001 _king_square: Square,
1002 ) -> f32 {
1003 0.0
1005 }
1006
1007 fn evaluate_escape_squares(&self, _board: &Board, _color: Color, _king_square: Square) -> f32 {
1008 0.0
1010 }
1011
1012 fn analyze_mobility_complexity(&self, _board: &Board) -> f32 {
1014 0.0
1015 }
1016 fn analyze_piece_harmony_complexity(&self, _board: &Board) -> f32 {
1017 0.0
1018 }
1019 fn analyze_central_control_complexity(&self, _board: &Board) -> f32 {
1020 0.0
1021 }
1022 fn analyze_piece_activity_imbalance(&self, _board: &Board) -> f32 {
1023 0.0
1024 }
1025 fn analyze_tactical_motifs(&self, _board: &Board) -> f32 {
1026 0.0
1027 }
1028 fn count_hanging_pieces(&self, _board: &Board) -> u8 {
1029 0
1030 }
1031 fn analyze_space_complexity(&self, _board: &Board) -> f32 {
1032 0.0
1033 }
1034 fn analyze_weak_squares_complexity(&self, _board: &Board) -> f32 {
1035 0.0
1036 }
1037 fn analyze_outpost_complexity(&self, _board: &Board) -> f32 {
1038 0.0
1039 }
1040 fn analyze_piece_placement_complexity(&self, _board: &Board) -> f32 {
1041 0.0
1042 }
1043 fn evaluate_tempo_sensitivity(&self, _board: &Board) -> f32 {
1044 0.0
1045 }
1046 fn evaluate_zugzwang_complexity(&self, _board: &Board) -> f32 {
1047 0.0
1048 }
1049 fn evaluate_critical_moves_complexity(&self, _board: &Board) -> f32 {
1050 0.0
1051 }
1052 fn analyze_pawn_endgame_complexity(&self, _board: &Board) -> f32 {
1053 0.0
1054 }
1055 fn analyze_piece_endgame_complexity(&self, _board: &Board) -> f32 {
1056 0.0
1057 }
1058 fn analyze_king_activity_complexity(&self, _board: &Board) -> f32 {
1059 0.0
1060 }
1061
1062 fn material_complexity(&self, board: &Board) -> f32 {
1064 self.analyze_material_complexity(board)
1065 }
1066
1067 fn count_material(&self, board: &Board, color: Color) -> i32 {
1068 let pieces = board.color_combined(color);
1069 let mut material = 0;
1070
1071 material += (board.pieces(chess::Piece::Pawn) & pieces).popcnt() as i32 * 1;
1072 material += (board.pieces(chess::Piece::Knight) & pieces).popcnt() as i32 * 3;
1073 material += (board.pieces(chess::Piece::Bishop) & pieces).popcnt() as i32 * 3;
1074 material += (board.pieces(chess::Piece::Rook) & pieces).popcnt() as i32 * 5;
1075 material += (board.pieces(chess::Piece::Queen) & pieces).popcnt() as i32 * 9;
1076
1077 material
1078 }
1079
1080 fn pawn_structure_complexity(&self, _board: &Board) -> f32 {
1081 0.3 }
1090
1091 fn king_safety_complexity(&self, _board: &Board) -> f32 {
1092 0.25 }
1100
1101 fn piece_coordination_complexity(&self, _board: &Board) -> f32 {
1102 0.4 }
1110
1111 fn tactical_complexity(&self, board: &Board) -> f32 {
1112 let mut tactical_score = 0.0;
1113
1114 if board.checkers().popcnt() > 0 {
1116 tactical_score += 0.3;
1117 }
1118
1119 use chess::MoveGen;
1121 let legal_moves = MoveGen::new_legal(board);
1122 let moves: Vec<_> = legal_moves.collect();
1123 let capture_count = moves
1124 .iter()
1125 .filter(|mv| board.piece_on(mv.get_dest()).is_some())
1126 .count();
1127
1128 tactical_score += (capture_count as f32 / moves.len().max(1) as f32) * 0.4;
1129
1130 tactical_score += (moves.len() as f32 / 50.0).min(0.3);
1132
1133 tactical_score
1134 }
1135}
1136
1137impl Default for ComplexityAnalyzer {
1138 fn default() -> Self {
1139 Self::new()
1140 }
1141}
1142
1143pub struct GamePhaseDetector {
1145 cached_phases: RwLock<HashMap<String, GamePhaseAnalysisResult>>,
1146 phase_weights: PhaseDetectionWeights,
1147 adaptation_settings: PhaseAdaptationSettings,
1148}
1149
1150impl GamePhaseDetector {
1151 pub fn new() -> Self {
1152 Self {
1153 cached_phases: RwLock::new(HashMap::new()),
1154 phase_weights: PhaseDetectionWeights::default(),
1155 adaptation_settings: PhaseAdaptationSettings::default(),
1156 }
1157 }
1158
1159 pub fn with_phase_weights(mut self, weights: PhaseDetectionWeights) -> Self {
1161 self.phase_weights = weights;
1162 self
1163 }
1164
1165 pub fn with_adaptation_settings(mut self, settings: PhaseAdaptationSettings) -> Self {
1167 self.adaptation_settings = settings;
1168 self
1169 }
1170
1171 pub fn detect_phase(&self, board: &Board) -> GamePhase {
1173 let analysis = self.analyze_game_phase(board);
1174 analysis.primary_phase
1175 }
1176
1177 pub fn analyze_game_phase(&self, board: &Board) -> GamePhaseAnalysisResult {
1179 let fen = board.to_string();
1180
1181 if let Ok(cache) = self.cached_phases.read() {
1183 if let Some(analysis) = cache.get(&fen) {
1184 return analysis.clone();
1185 }
1186 }
1187
1188 let mut analysis = GamePhaseAnalysisResult::new();
1189
1190 analysis.material_phase = self.analyze_material_phase(board);
1192 analysis.development_phase = self.analyze_development_phase(board);
1193 analysis.move_count_phase = self.analyze_move_count_phase(board);
1194 analysis.king_safety_phase = self.analyze_king_safety_phase(board);
1195 analysis.pawn_structure_phase = self.analyze_pawn_structure_phase(board);
1196 analysis.piece_activity_phase = self.analyze_piece_activity_phase(board);
1197
1198 analysis.opening_score = self.compute_opening_score(&analysis);
1200 analysis.middlegame_score = self.compute_middlegame_score(&analysis);
1201 analysis.endgame_score = self.compute_endgame_score(&analysis);
1202
1203 analysis.primary_phase = self.determine_primary_phase(&analysis);
1205
1206 analysis.transition_state = self.analyze_transition_state(&analysis);
1208
1209 analysis.phase_confidence = self.calculate_phase_confidence(&analysis);
1211
1212 analysis.adaptation_recommendations = self.generate_adaptation_recommendations(&analysis);
1214
1215 if let Ok(mut cache) = self.cached_phases.write() {
1217 if cache.len() > 1000 {
1218 cache.clear();
1219 }
1220 cache.insert(fen, analysis.clone());
1221 }
1222
1223 analysis
1224 }
1225
1226 fn analyze_material_phase(&self, board: &Board) -> PhaseIndicator {
1227 let total_material = self.calculate_total_material(board);
1228 let material_balance = self.analyze_material_balance(board);
1229
1230 let phase = if total_material >= 60 {
1231 GamePhase::Opening
1232 } else if total_material <= 20 {
1233 GamePhase::Endgame
1234 } else {
1235 GamePhase::Middlegame
1236 };
1237
1238 let confidence = self.compute_material_phase_confidence(total_material, material_balance);
1239
1240 PhaseIndicator { phase, confidence }
1241 }
1242
1243 fn analyze_development_phase(&self, board: &Board) -> PhaseIndicator {
1244 let development_score = self.calculate_development_score(board);
1245 let castling_status = self.analyze_castling_status(board);
1246
1247 let phase = if development_score < 0.3 {
1248 GamePhase::Opening
1249 } else if development_score > 0.8 {
1250 GamePhase::Middlegame
1251 } else {
1252 GamePhase::Opening
1253 };
1254
1255 let confidence = (development_score + castling_status) / 2.0;
1256
1257 PhaseIndicator { phase, confidence }
1258 }
1259
1260 fn analyze_move_count_phase(&self, board: &Board) -> PhaseIndicator {
1261 let move_count = (board.combined().popcnt() as u32).saturating_sub(32) / 2 + 1;
1263
1264 let (phase, confidence) = if move_count <= 12 {
1265 (GamePhase::Opening, 0.8)
1266 } else if move_count <= 25 {
1267 (GamePhase::Middlegame, 0.7)
1268 } else if move_count <= 40 {
1269 (GamePhase::Middlegame, 0.6)
1270 } else {
1271 (GamePhase::Endgame, 0.5)
1272 };
1273
1274 PhaseIndicator { phase, confidence }
1275 }
1276
1277 fn analyze_king_safety_phase(&self, board: &Board) -> PhaseIndicator {
1278 let king_safety_score = self.evaluate_combined_king_safety(board);
1279
1280 let phase = if king_safety_score > 0.7 {
1281 GamePhase::Opening } else if king_safety_score < 0.3 {
1283 GamePhase::Middlegame } else {
1285 GamePhase::Endgame };
1287
1288 let confidence = (1.0 - king_safety_score).abs();
1289
1290 PhaseIndicator { phase, confidence }
1291 }
1292
1293 fn analyze_pawn_structure_phase(&self, board: &Board) -> PhaseIndicator {
1294 let pawn_structure_score = self.evaluate_pawn_structure_development(board);
1295
1296 let phase = if pawn_structure_score < 0.4 {
1297 GamePhase::Opening
1298 } else if pawn_structure_score > 0.7 {
1299 GamePhase::Endgame
1300 } else {
1301 GamePhase::Middlegame
1302 };
1303
1304 let confidence = pawn_structure_score;
1305
1306 PhaseIndicator { phase, confidence }
1307 }
1308
1309 fn analyze_piece_activity_phase(&self, board: &Board) -> PhaseIndicator {
1310 let activity_score = self.calculate_piece_activity_score(board);
1311
1312 let phase = if activity_score < 0.3 {
1313 GamePhase::Opening
1314 } else if activity_score > 0.8 {
1315 GamePhase::Endgame
1316 } else {
1317 GamePhase::Middlegame
1318 };
1319
1320 let confidence = activity_score;
1321
1322 PhaseIndicator { phase, confidence }
1323 }
1324
1325 fn compute_opening_score(&self, analysis: &GamePhaseAnalysisResult) -> f32 {
1326 let mut score = 0.0;
1327
1328 if analysis.material_phase.phase == GamePhase::Opening {
1330 score += analysis.material_phase.confidence * self.phase_weights.material_weight;
1331 }
1332 if analysis.development_phase.phase == GamePhase::Opening {
1333 score += analysis.development_phase.confidence * self.phase_weights.development_weight;
1334 }
1335 if analysis.move_count_phase.phase == GamePhase::Opening {
1336 score += analysis.move_count_phase.confidence * self.phase_weights.move_count_weight;
1337 }
1338 if analysis.king_safety_phase.phase == GamePhase::Opening {
1339 score += analysis.king_safety_phase.confidence * self.phase_weights.king_safety_weight;
1340 }
1341 if analysis.pawn_structure_phase.phase == GamePhase::Opening {
1342 score +=
1343 analysis.pawn_structure_phase.confidence * self.phase_weights.pawn_structure_weight;
1344 }
1345 if analysis.piece_activity_phase.phase == GamePhase::Opening {
1346 score +=
1347 analysis.piece_activity_phase.confidence * self.phase_weights.piece_activity_weight;
1348 }
1349
1350 score
1351 }
1352
1353 fn compute_middlegame_score(&self, analysis: &GamePhaseAnalysisResult) -> f32 {
1354 let mut score = 0.0;
1355
1356 if analysis.material_phase.phase == GamePhase::Middlegame {
1357 score += analysis.material_phase.confidence * self.phase_weights.material_weight;
1358 }
1359 if analysis.development_phase.phase == GamePhase::Middlegame {
1360 score += analysis.development_phase.confidence * self.phase_weights.development_weight;
1361 }
1362 if analysis.move_count_phase.phase == GamePhase::Middlegame {
1363 score += analysis.move_count_phase.confidence * self.phase_weights.move_count_weight;
1364 }
1365 if analysis.king_safety_phase.phase == GamePhase::Middlegame {
1366 score += analysis.king_safety_phase.confidence * self.phase_weights.king_safety_weight;
1367 }
1368 if analysis.pawn_structure_phase.phase == GamePhase::Middlegame {
1369 score +=
1370 analysis.pawn_structure_phase.confidence * self.phase_weights.pawn_structure_weight;
1371 }
1372 if analysis.piece_activity_phase.phase == GamePhase::Middlegame {
1373 score +=
1374 analysis.piece_activity_phase.confidence * self.phase_weights.piece_activity_weight;
1375 }
1376
1377 score
1378 }
1379
1380 fn compute_endgame_score(&self, analysis: &GamePhaseAnalysisResult) -> f32 {
1381 let mut score = 0.0;
1382
1383 if analysis.material_phase.phase == GamePhase::Endgame {
1384 score += analysis.material_phase.confidence * self.phase_weights.material_weight;
1385 }
1386 if analysis.development_phase.phase == GamePhase::Endgame {
1387 score += analysis.development_phase.confidence * self.phase_weights.development_weight;
1388 }
1389 if analysis.move_count_phase.phase == GamePhase::Endgame {
1390 score += analysis.move_count_phase.confidence * self.phase_weights.move_count_weight;
1391 }
1392 if analysis.king_safety_phase.phase == GamePhase::Endgame {
1393 score += analysis.king_safety_phase.confidence * self.phase_weights.king_safety_weight;
1394 }
1395 if analysis.pawn_structure_phase.phase == GamePhase::Endgame {
1396 score +=
1397 analysis.pawn_structure_phase.confidence * self.phase_weights.pawn_structure_weight;
1398 }
1399 if analysis.piece_activity_phase.phase == GamePhase::Endgame {
1400 score +=
1401 analysis.piece_activity_phase.confidence * self.phase_weights.piece_activity_weight;
1402 }
1403
1404 score
1405 }
1406
1407 fn determine_primary_phase(&self, analysis: &GamePhaseAnalysisResult) -> GamePhase {
1408 let opening_score = analysis.opening_score;
1409 let middlegame_score = analysis.middlegame_score;
1410 let endgame_score = analysis.endgame_score;
1411
1412 if opening_score > middlegame_score && opening_score > endgame_score {
1413 GamePhase::Opening
1414 } else if endgame_score > middlegame_score && endgame_score > opening_score {
1415 GamePhase::Endgame
1416 } else if middlegame_score > 0.1 {
1417 GamePhase::Middlegame
1418 } else {
1419 GamePhase::Unknown
1420 }
1421 }
1422
1423 fn analyze_transition_state(&self, analysis: &GamePhaseAnalysisResult) -> PhaseTransition {
1424 let max_score = analysis
1425 .opening_score
1426 .max(analysis.middlegame_score)
1427 .max(analysis.endgame_score);
1428 let second_max = if analysis.opening_score == max_score {
1429 analysis.middlegame_score.max(analysis.endgame_score)
1430 } else if analysis.middlegame_score == max_score {
1431 analysis.opening_score.max(analysis.endgame_score)
1432 } else {
1433 analysis.opening_score.max(analysis.middlegame_score)
1434 };
1435
1436 let transition_threshold = 0.3;
1437
1438 if max_score - second_max < transition_threshold {
1439 if analysis.opening_score > analysis.endgame_score {
1441 if analysis.middlegame_score > analysis.opening_score * 0.8 {
1442 PhaseTransition::OpeningToMiddlegame
1443 } else {
1444 PhaseTransition::Stable
1445 }
1446 } else if analysis.middlegame_score > analysis.endgame_score {
1447 if analysis.endgame_score > analysis.middlegame_score * 0.8 {
1448 PhaseTransition::MiddlegameToEndgame
1449 } else {
1450 PhaseTransition::Stable
1451 }
1452 } else {
1453 PhaseTransition::Stable
1454 }
1455 } else {
1456 PhaseTransition::Stable
1457 }
1458 }
1459
1460 fn calculate_phase_confidence(&self, analysis: &GamePhaseAnalysisResult) -> f32 {
1461 let scores = [
1462 analysis.opening_score,
1463 analysis.middlegame_score,
1464 analysis.endgame_score,
1465 ];
1466 let max_score = scores.iter().fold(0.0f32, |a, &b| a.max(b));
1467 let total_score = scores.iter().sum::<f32>();
1468
1469 if total_score > 0.0 {
1470 max_score / total_score
1471 } else {
1472 0.0
1473 }
1474 }
1475
1476 fn generate_adaptation_recommendations(
1477 &self,
1478 analysis: &GamePhaseAnalysisResult,
1479 ) -> PhaseAdaptationRecommendations {
1480 let mut recommendations = PhaseAdaptationRecommendations::default();
1481
1482 match analysis.primary_phase {
1483 GamePhase::Opening => {
1484 recommendations.evaluation_weights = BlendWeights {
1485 nnue_weight: 0.3,
1486 pattern_weight: 0.2,
1487 tactical_weight: 0.2,
1488 strategic_weight: 0.3,
1489 };
1490 recommendations.search_depth_modifier = -1;
1491 recommendations.opening_book_priority = 0.9;
1492 recommendations.endgame_tablebase_priority = 0.1;
1493 recommendations.time_management_factor = 0.8;
1494 }
1495 GamePhase::Middlegame => {
1496 recommendations.evaluation_weights = BlendWeights {
1497 nnue_weight: 0.25,
1498 pattern_weight: 0.35,
1499 tactical_weight: 0.3,
1500 strategic_weight: 0.1,
1501 };
1502 recommendations.search_depth_modifier = 0;
1503 recommendations.opening_book_priority = 0.3;
1504 recommendations.endgame_tablebase_priority = 0.2;
1505 recommendations.time_management_factor = 1.0;
1506 }
1507 GamePhase::Endgame => {
1508 recommendations.evaluation_weights = BlendWeights {
1509 nnue_weight: 0.5,
1510 pattern_weight: 0.2,
1511 tactical_weight: 0.15,
1512 strategic_weight: 0.15,
1513 };
1514 recommendations.search_depth_modifier = 1;
1515 recommendations.opening_book_priority = 0.0;
1516 recommendations.endgame_tablebase_priority = 0.9;
1517 recommendations.time_management_factor = 1.2;
1518 }
1519 GamePhase::Unknown => {
1520 recommendations.evaluation_weights = BlendWeights::default();
1521 recommendations.search_depth_modifier = 0;
1522 recommendations.opening_book_priority = 0.5;
1523 recommendations.endgame_tablebase_priority = 0.5;
1524 recommendations.time_management_factor = 1.0;
1525 }
1526 }
1527
1528 match analysis.transition_state {
1530 PhaseTransition::OpeningToMiddlegame => {
1531 recommendations.evaluation_weights.strategic_weight *= 0.8;
1532 recommendations.evaluation_weights.tactical_weight *= 1.2;
1533 }
1534 PhaseTransition::MiddlegameToEndgame => {
1535 recommendations.evaluation_weights.nnue_weight *= 1.3;
1536 recommendations.evaluation_weights.pattern_weight *= 0.7;
1537 recommendations.endgame_tablebase_priority += 0.2;
1538 }
1539 PhaseTransition::Stable => {
1540 }
1542 }
1543
1544 recommendations
1545 }
1546
1547 fn analyze_material_balance(&self, board: &Board) -> f32 {
1549 let white_material = self.count_material(board, Color::White);
1550 let black_material = self.count_material(board, Color::Black);
1551 let total_material = white_material + black_material;
1552
1553 if total_material > 0 {
1554 (white_material - black_material).abs() as f32 / total_material as f32
1555 } else {
1556 0.0
1557 }
1558 }
1559
1560 fn compute_material_phase_confidence(&self, total_material: i32, material_balance: f32) -> f32 {
1561 let material_factor = if total_material >= 60 || total_material <= 20 {
1562 0.8
1563 } else {
1564 0.4
1565 };
1566
1567 let balance_factor = 1.0 - material_balance;
1568
1569 (material_factor + balance_factor) / 2.0
1570 }
1571
1572 fn calculate_development_score(&self, board: &Board) -> f32 {
1573 let mut development_score = 0.0;
1574 let mut total_pieces = 0;
1575
1576 for color in [Color::White, Color::Black] {
1577 let knights = board.pieces(Piece::Knight) & board.color_combined(color);
1579 for square in knights {
1580 total_pieces += 1;
1581 if self.is_piece_developed_from_starting_position(square, Piece::Knight, color) {
1582 development_score += 1.0;
1583 }
1584 }
1585
1586 let bishops = board.pieces(Piece::Bishop) & board.color_combined(color);
1588 for square in bishops {
1589 total_pieces += 1;
1590 if self.is_piece_developed_from_starting_position(square, Piece::Bishop, color) {
1591 development_score += 1.0;
1592 }
1593 }
1594 }
1595
1596 if total_pieces > 0 {
1597 development_score / total_pieces as f32
1598 } else {
1599 0.0
1600 }
1601 }
1602
1603 fn analyze_castling_status(&self, board: &Board) -> f32 {
1604 let mut castling_score = 0.0;
1605
1606 for color in [Color::White, Color::Black] {
1607 if board.castle_rights(color).has_kingside()
1608 || board.castle_rights(color).has_queenside()
1609 {
1610 castling_score += 0.3; } else {
1612 castling_score += 0.8; }
1614 }
1615
1616 castling_score / 2.0
1617 }
1618
1619 fn evaluate_combined_king_safety(&self, board: &Board) -> f32 {
1620 let mut safety_score = 0.0;
1621
1622 for color in [Color::White, Color::Black] {
1623 let king_square = board.king_square(color);
1624 safety_score += self.evaluate_individual_king_safety(board, color, king_square);
1625 }
1626
1627 safety_score / 2.0
1628 }
1629
1630 fn evaluate_pawn_structure_development(&self, board: &Board) -> f32 {
1631 let mut structure_score = 0.0;
1632
1633 for color in [Color::White, Color::Black] {
1635 let pawns = board.pieces(Piece::Pawn) & board.color_combined(color);
1636 let advanced_pawns = pawns
1637 .into_iter()
1638 .filter(|&square| self.is_pawn_advanced(square, color))
1639 .count();
1640
1641 structure_score += advanced_pawns as f32 * 0.1;
1642 }
1643
1644 structure_score.min(1.0)
1645 }
1646
1647 fn calculate_piece_activity_score(&self, board: &Board) -> f32 {
1648 let mut activity_score = 0.0;
1649 let mut piece_count = 0;
1650
1651 for color in [Color::White, Color::Black] {
1652 for piece_type in [Piece::Knight, Piece::Bishop, Piece::Rook, Piece::Queen] {
1653 let pieces = board.pieces(piece_type) & board.color_combined(color);
1654 for square in pieces {
1655 piece_count += 1;
1656 activity_score += self.calculate_piece_activity(board, square, piece_type);
1657 }
1658 }
1659 }
1660
1661 if piece_count > 0 {
1662 activity_score / piece_count as f32
1663 } else {
1664 0.0
1665 }
1666 }
1667
1668 fn is_piece_developed_from_starting_position(
1670 &self,
1671 square: Square,
1672 piece: Piece,
1673 color: Color,
1674 ) -> bool {
1675 let starting_rank = match color {
1677 Color::White => chess::Rank::First,
1678 Color::Black => chess::Rank::Eighth,
1679 };
1680
1681 square.get_rank() != starting_rank
1682 }
1683
1684 fn evaluate_individual_king_safety(
1685 &self,
1686 _board: &Board,
1687 _color: Color,
1688 _king_square: Square,
1689 ) -> f32 {
1690 0.5
1692 }
1693
1694 fn is_pawn_advanced(&self, square: Square, color: Color) -> bool {
1695 let rank = square.get_rank();
1696 match color {
1697 Color::White => rank >= chess::Rank::Fourth,
1698 Color::Black => rank <= chess::Rank::Fifth,
1699 }
1700 }
1701
1702 fn calculate_piece_activity(&self, _board: &Board, _square: Square, _piece: Piece) -> f32 {
1703 0.5
1705 }
1706
1707 fn calculate_total_material(&self, board: &Board) -> i32 {
1708 let white_material = self.count_material(board, Color::White);
1709 let black_material = self.count_material(board, Color::Black);
1710 white_material + black_material
1711 }
1712
1713 fn count_material(&self, board: &Board, color: Color) -> i32 {
1714 let pieces = board.color_combined(color);
1715 let mut material = 0;
1716
1717 material += (board.pieces(Piece::Pawn) & pieces).popcnt() as i32 * 1;
1718 material += (board.pieces(Piece::Knight) & pieces).popcnt() as i32 * 3;
1719 material += (board.pieces(Piece::Bishop) & pieces).popcnt() as i32 * 3;
1720 material += (board.pieces(Piece::Rook) & pieces).popcnt() as i32 * 5;
1721 material += (board.pieces(Piece::Queen) & pieces).popcnt() as i32 * 9;
1722
1723 material
1724 }
1725}
1726
1727impl Default for GamePhaseDetector {
1728 fn default() -> Self {
1729 Self::new()
1730 }
1731}
1732
1733pub struct EvaluationBlender {
1735 base_weights: BlendWeights,
1736 weight_history: RwLock<Vec<WeightHistoryEntry>>,
1737 performance_tracker: RwLock<EvaluatorPerformanceTracker>,
1738 adaptive_learning: bool,
1739}
1740
1741impl EvaluationBlender {
1742 pub fn new() -> Self {
1743 Self {
1744 base_weights: BlendWeights::default(),
1745 weight_history: RwLock::new(Vec::new()),
1746 performance_tracker: RwLock::new(EvaluatorPerformanceTracker::new()),
1747 adaptive_learning: true,
1748 }
1749 }
1750
1751 pub fn with_base_weights(base_weights: BlendWeights) -> Self {
1753 Self {
1754 base_weights,
1755 weight_history: RwLock::new(Vec::new()),
1756 performance_tracker: RwLock::new(EvaluatorPerformanceTracker::new()),
1757 adaptive_learning: true,
1758 }
1759 }
1760
1761 pub fn set_adaptive_learning(&mut self, enabled: bool) {
1763 self.adaptive_learning = enabled;
1764 }
1765
1766 pub fn compute_blend_weights(
1768 &self,
1769 complexity_score: f32,
1770 game_phase: &GamePhase,
1771 evaluation_results: &EvaluationResults,
1772 ) -> BlendWeights {
1773 let mut weights = if self.adaptive_learning {
1774 self.compute_adaptive_base_weights(complexity_score, game_phase)
1775 } else {
1776 self.base_weights.clone()
1777 };
1778
1779 match game_phase {
1781 GamePhase::Opening => {
1782 weights.strategic_weight += 0.1;
1783 weights.tactical_weight -= 0.05;
1784 }
1785 GamePhase::Middlegame => {
1786 weights.tactical_weight += 0.1;
1787 weights.pattern_weight += 0.05;
1788 }
1789 GamePhase::Endgame => {
1790 weights.nnue_weight += 0.15;
1791 weights.strategic_weight -= 0.1;
1792 }
1793 GamePhase::Unknown => {} }
1795
1796 if complexity_score > 0.7 {
1798 weights.tactical_weight += 0.15;
1800 weights.nnue_weight -= 0.05;
1801 weights.pattern_weight -= 0.05;
1802 weights.strategic_weight -= 0.05;
1803 } else if complexity_score < 0.3 {
1804 weights.nnue_weight += 0.1;
1806 weights.tactical_weight -= 0.1;
1807 }
1808
1809 if let (Some(nnue), Some(pattern)) = (&evaluation_results.nnue, &evaluation_results.pattern)
1811 {
1812 let agreement = 1.0 - (nnue.evaluation - pattern.evaluation).abs().min(1.0);
1813 if agreement > 0.8 {
1814 weights.nnue_weight += 0.05;
1816 weights.pattern_weight += 0.05;
1817 weights.tactical_weight -= 0.1;
1818 }
1819 }
1820
1821 self.normalize_weights(&mut weights);
1823
1824 if self.adaptive_learning {
1826 self.record_weight_usage(&weights, complexity_score, game_phase);
1827 }
1828
1829 weights
1830 }
1831
1832 fn compute_adaptive_base_weights(
1834 &self,
1835 complexity_score: f32,
1836 game_phase: &GamePhase,
1837 ) -> BlendWeights {
1838 if let Ok(tracker) = self.performance_tracker.read() {
1839 let context = EvaluationContext {
1841 complexity_range: Self::complexity_to_range(complexity_score),
1842 game_phase: game_phase.clone(),
1843 };
1844
1845 if let Some(optimal_weights) = tracker.get_optimal_weights(&context) {
1846 return optimal_weights;
1847 }
1848 }
1849
1850 self.base_weights.clone()
1852 }
1853
1854 fn record_weight_usage(
1856 &self,
1857 weights: &BlendWeights,
1858 complexity_score: f32,
1859 game_phase: &GamePhase,
1860 ) {
1861 if let Ok(mut history) = self.weight_history.write() {
1862 let entry = WeightHistoryEntry {
1863 timestamp: std::time::SystemTime::now(),
1864 weights: weights.clone(),
1865 complexity_score,
1866 game_phase: game_phase.clone(),
1867 evaluation_count: 1,
1868 };
1869
1870 history.push(entry);
1871
1872 if history.len() > 10000 {
1874 history.drain(0..1000); }
1876 }
1877 }
1878
1879 pub fn update_performance_metrics(
1881 &self,
1882 weights: &BlendWeights,
1883 complexity_score: f32,
1884 game_phase: &GamePhase,
1885 evaluation_accuracy: f32,
1886 actual_result: Option<f32>,
1887 ) {
1888 if !self.adaptive_learning {
1889 return;
1890 }
1891
1892 if let Ok(mut tracker) = self.performance_tracker.write() {
1893 let context = EvaluationContext {
1894 complexity_range: Self::complexity_to_range(complexity_score),
1895 game_phase: game_phase.clone(),
1896 };
1897
1898 tracker.record_performance(&context, weights, evaluation_accuracy, actual_result);
1899 }
1900 }
1901
1902 fn complexity_to_range(complexity: f32) -> ComplexityRange {
1904 if complexity < 0.3 {
1905 ComplexityRange::Low
1906 } else if complexity < 0.7 {
1907 ComplexityRange::Medium
1908 } else {
1909 ComplexityRange::High
1910 }
1911 }
1912
1913 pub fn get_adaptive_stats(&self) -> AdaptiveLearningStats {
1915 let weight_entries = self.weight_history.read().map(|h| h.len()).unwrap_or(0);
1916
1917 let performance_contexts = self
1918 .performance_tracker
1919 .read()
1920 .map(|t| t.get_context_count())
1921 .unwrap_or(0);
1922
1923 let learning_enabled = self.adaptive_learning;
1924
1925 AdaptiveLearningStats {
1926 weight_history_entries: weight_entries,
1927 performance_contexts,
1928 learning_enabled,
1929 total_adaptations: weight_entries, }
1931 }
1932
1933 pub fn blend_evaluations(
1935 &self,
1936 evaluation_results: &EvaluationResults,
1937 weights: &BlendWeights,
1938 ) -> f32 {
1939 let mut blended_evaluation = 0.0;
1940 let mut total_weight = 0.0;
1941
1942 if let Some(ref nnue) = evaluation_results.nnue {
1943 blended_evaluation += nnue.evaluation * weights.nnue_weight;
1944 total_weight += weights.nnue_weight;
1945 }
1946
1947 if let Some(ref pattern) = evaluation_results.pattern {
1948 blended_evaluation += pattern.evaluation * weights.pattern_weight;
1949 total_weight += weights.pattern_weight;
1950 }
1951
1952 if let Some(ref tactical) = evaluation_results.tactical {
1953 blended_evaluation += tactical.evaluation * weights.tactical_weight;
1954 total_weight += weights.tactical_weight;
1955 }
1956
1957 if let Some(ref strategic) = evaluation_results.strategic {
1958 blended_evaluation += strategic.evaluation * weights.strategic_weight;
1959 total_weight += weights.strategic_weight;
1960 }
1961
1962 if total_weight > 0.0 {
1963 blended_evaluation / total_weight
1964 } else {
1965 0.0 }
1967 }
1968
1969 fn normalize_weights(&self, weights: &mut BlendWeights) {
1970 let total = weights.nnue_weight
1971 + weights.pattern_weight
1972 + weights.tactical_weight
1973 + weights.strategic_weight;
1974 if total > 0.0 {
1975 weights.nnue_weight /= total;
1976 weights.pattern_weight /= total;
1977 weights.tactical_weight /= total;
1978 weights.strategic_weight /= total;
1979 }
1980 }
1981}
1982
1983impl Default for EvaluationBlender {
1984 fn default() -> Self {
1985 Self::new()
1986 }
1987}
1988
1989pub struct ConfidenceScorer {
1992 evaluator_accuracy_history: Arc<RwLock<EvaluatorAccuracyTracker>>,
1994 pattern_clarity_analyzer: PatternClarityAnalyzer,
1996 calibration_settings: ConfidenceCalibrationSettings,
1998 recent_confidence_scores: Arc<RwLock<VecDeque<ConfidenceHistoryEntry>>>,
2000}
2001
2002impl ConfidenceScorer {
2003 pub fn new() -> Self {
2004 Self {
2005 evaluator_accuracy_history: Arc::new(RwLock::new(EvaluatorAccuracyTracker::new())),
2006 pattern_clarity_analyzer: PatternClarityAnalyzer::new(),
2007 calibration_settings: ConfidenceCalibrationSettings::default(),
2008 recent_confidence_scores: Arc::new(RwLock::new(VecDeque::with_capacity(1000))),
2009 }
2010 }
2011
2012 pub fn with_calibration_settings(mut self, settings: ConfidenceCalibrationSettings) -> Self {
2014 self.calibration_settings = settings;
2015 self
2016 }
2017
2018 pub fn compute_confidence(
2020 &self,
2021 evaluation_results: &EvaluationResults,
2022 blend_weights: &BlendWeights,
2023 complexity_score: f32,
2024 position_context: &PositionContext,
2025 ) -> ConfidenceAnalysisResult {
2026 let start_time = std::time::Instant::now();
2027
2028 let agreement_analysis = self.analyze_evaluator_agreement(evaluation_results);
2030
2031 let evaluator_confidence =
2033 self.analyze_evaluator_confidence(evaluation_results, blend_weights);
2034
2035 let complexity_confidence =
2037 self.analyze_complexity_confidence(complexity_score, position_context);
2038
2039 let pattern_clarity = self
2041 .pattern_clarity_analyzer
2042 .analyze_pattern_clarity(evaluation_results, position_context);
2043
2044 let historical_confidence =
2046 self.analyze_historical_accuracy(evaluation_results, blend_weights, position_context);
2047
2048 let coverage_confidence = self.analyze_evaluation_coverage(evaluation_results);
2050
2051 let temporal_confidence = self.analyze_temporal_consistency(evaluation_results);
2053
2054 let confidence_factors = ConfidenceFactors {
2056 evaluator_agreement: agreement_analysis.overall_agreement,
2057 individual_confidence: evaluator_confidence,
2058 complexity_confidence,
2059 pattern_clarity: pattern_clarity.overall_clarity,
2060 historical_accuracy: historical_confidence,
2061 coverage_confidence,
2062 temporal_consistency: temporal_confidence,
2063 };
2064
2065 let overall_confidence = self.compute_weighted_confidence(&confidence_factors);
2066
2067 let calibrated_confidence =
2069 self.apply_confidence_calibration(overall_confidence, &confidence_factors);
2070
2071 let computation_time = start_time.elapsed().as_millis() as u64;
2072
2073 let result = ConfidenceAnalysisResult {
2074 overall_confidence: calibrated_confidence,
2075 confidence_factors: confidence_factors.clone(),
2076 agreement_analysis,
2077 pattern_clarity_analysis: pattern_clarity,
2078 computation_time_ms: computation_time,
2079 confidence_category: self.categorize_confidence(calibrated_confidence),
2080 reliability_indicators: self.generate_reliability_indicators(&confidence_factors),
2081 };
2082
2083 self.record_confidence_score(&result, position_context);
2085
2086 result
2087 }
2088
2089 pub fn compute_simple_confidence(
2091 &self,
2092 evaluation_results: &EvaluationResults,
2093 blend_weights: &BlendWeights,
2094 complexity_score: f32,
2095 ) -> f32 {
2096 let position_context = PositionContext::default();
2097 let analysis = self.compute_confidence(
2098 evaluation_results,
2099 blend_weights,
2100 complexity_score,
2101 &position_context,
2102 );
2103 analysis.overall_confidence
2104 }
2105
2106 fn analyze_evaluator_agreement(
2107 &self,
2108 evaluation_results: &EvaluationResults,
2109 ) -> AgreementAnalysis {
2110 let mut evaluations = Vec::new();
2111 let mut evaluator_names = Vec::new();
2112
2113 if let Some(ref nnue) = evaluation_results.nnue {
2114 evaluations.push(nnue.evaluation);
2115 evaluator_names.push("NNUE");
2116 }
2117 if let Some(ref pattern) = evaluation_results.pattern {
2118 evaluations.push(pattern.evaluation);
2119 evaluator_names.push("Pattern");
2120 }
2121 if let Some(ref tactical) = evaluation_results.tactical {
2122 evaluations.push(tactical.evaluation);
2123 evaluator_names.push("Tactical");
2124 }
2125 if let Some(ref strategic) = evaluation_results.strategic {
2126 evaluations.push(strategic.evaluation);
2127 evaluator_names.push("Strategic");
2128 }
2129
2130 if evaluations.len() < 2 {
2131 return AgreementAnalysis {
2132 overall_agreement: 0.5,
2133 pairwise_agreements: Vec::new(),
2134 evaluation_spread: 0.0,
2135 consensus_strength: 0.5,
2136 outlier_count: 0,
2137 };
2138 }
2139
2140 let mut pairwise_agreements = Vec::new();
2142 for i in 0..evaluations.len() {
2143 for j in (i + 1)..evaluations.len() {
2144 let diff = (evaluations[i] - evaluations[j]).abs();
2145 let agreement = (2.0 - diff).max(0.0).min(1.0);
2146 pairwise_agreements.push(PairwiseAgreement {
2147 evaluator1: evaluator_names[i].to_string(),
2148 evaluator2: evaluator_names[j].to_string(),
2149 agreement_score: agreement,
2150 });
2151 }
2152 }
2153
2154 let overall_agreement = pairwise_agreements
2155 .iter()
2156 .map(|pa| pa.agreement_score)
2157 .sum::<f32>()
2158 / pairwise_agreements.len() as f32;
2159
2160 let mean = evaluations.iter().sum::<f32>() / evaluations.len() as f32;
2162 let variance = evaluations
2163 .iter()
2164 .map(|eval| (eval - mean).powi(2))
2165 .sum::<f32>()
2166 / evaluations.len() as f32;
2167 let evaluation_spread = variance.sqrt();
2168
2169 let consensus_strength = (2.0 - evaluation_spread).max(0.0).min(1.0);
2171
2172 let outlier_count = evaluations
2174 .iter()
2175 .filter(|&&eval| (eval - mean).abs() > 1.5 * evaluation_spread)
2176 .count();
2177
2178 AgreementAnalysis {
2179 overall_agreement,
2180 pairwise_agreements,
2181 evaluation_spread,
2182 consensus_strength,
2183 outlier_count,
2184 }
2185 }
2186
2187 fn analyze_evaluator_confidence(
2188 &self,
2189 evaluation_results: &EvaluationResults,
2190 blend_weights: &BlendWeights,
2191 ) -> f32 {
2192 let mut weighted_confidence = 0.0;
2193 let mut total_weight = 0.0;
2194
2195 if let Some(ref nnue) = evaluation_results.nnue {
2196 let weight = blend_weights.nnue_weight;
2197 let adjusted_confidence = self.adjust_confidence_by_context(nnue.confidence, "NNUE");
2198 weighted_confidence += adjusted_confidence * weight;
2199 total_weight += weight;
2200 }
2201 if let Some(ref pattern) = evaluation_results.pattern {
2202 let weight = blend_weights.pattern_weight;
2203 let adjusted_confidence =
2204 self.adjust_confidence_by_context(pattern.confidence, "Pattern");
2205 weighted_confidence += adjusted_confidence * weight;
2206 total_weight += weight;
2207 }
2208 if let Some(ref tactical) = evaluation_results.tactical {
2209 let weight = blend_weights.tactical_weight;
2210 let adjusted_confidence =
2211 self.adjust_confidence_by_context(tactical.confidence, "Tactical");
2212 weighted_confidence += adjusted_confidence * weight;
2213 total_weight += weight;
2214 }
2215 if let Some(ref strategic) = evaluation_results.strategic {
2216 let weight = blend_weights.strategic_weight;
2217 let adjusted_confidence =
2218 self.adjust_confidence_by_context(strategic.confidence, "Strategic");
2219 weighted_confidence += adjusted_confidence * weight;
2220 total_weight += weight;
2221 }
2222
2223 if total_weight > 0.0 {
2224 weighted_confidence / total_weight
2225 } else {
2226 0.5
2227 }
2228 }
2229
2230 fn analyze_complexity_confidence(
2231 &self,
2232 complexity_score: f32,
2233 position_context: &PositionContext,
2234 ) -> f32 {
2235 let base_confidence = 1.0 - complexity_score;
2236
2237 let mut adjusted_confidence = base_confidence;
2239
2240 if position_context.has_tactical_threats {
2242 adjusted_confidence *= 0.8;
2243 }
2244
2245 if position_context.game_phase == GamePhase::Endgame {
2247 adjusted_confidence *= 1.1;
2248 }
2249
2250 if position_context.game_phase == GamePhase::Opening && position_context.in_opening_book {
2252 adjusted_confidence *= 1.2;
2253 }
2254
2255 adjusted_confidence.clamp(0.0, 1.0)
2256 }
2257
2258 fn analyze_historical_accuracy(
2259 &self,
2260 evaluation_results: &EvaluationResults,
2261 blend_weights: &BlendWeights,
2262 position_context: &PositionContext,
2263 ) -> f32 {
2264 if let Ok(accuracy_tracker) = self.evaluator_accuracy_history.read() {
2265 let evaluator_combination =
2266 EvaluatorCombination::from_results(evaluation_results, blend_weights);
2267 let context_hash = position_context.get_context_hash();
2268
2269 accuracy_tracker
2270 .get_historical_accuracy(&evaluator_combination, context_hash)
2271 .unwrap_or(0.6) } else {
2273 0.6
2274 }
2275 }
2276
2277 fn analyze_evaluation_coverage(&self, evaluation_results: &EvaluationResults) -> f32 {
2278 let active_evaluators = self.count_active_evaluators(evaluation_results);
2279 let max_evaluators = 4.0;
2280
2281 let base_coverage = active_evaluators as f32 / max_evaluators;
2282
2283 let mut diversity_bonus = 0.0;
2285 if evaluation_results.nnue.is_some() && evaluation_results.tactical.is_some() {
2286 diversity_bonus += 0.1; }
2288 if evaluation_results.pattern.is_some() && evaluation_results.strategic.is_some() {
2289 diversity_bonus += 0.1; }
2291
2292 (base_coverage + diversity_bonus).min(1.0)
2293 }
2294
2295 fn analyze_temporal_consistency(&self, _evaluation_results: &EvaluationResults) -> f32 {
2296 if let Ok(recent_scores) = self.recent_confidence_scores.read() {
2298 if recent_scores.len() < 3 {
2299 return 0.6; }
2301
2302 let recent_confidences: Vec<f32> = recent_scores
2303 .iter()
2304 .rev()
2305 .take(5)
2306 .map(|entry| entry.confidence_score)
2307 .collect();
2308
2309 if recent_confidences.len() < 2 {
2310 return 0.6;
2311 }
2312
2313 let mean = recent_confidences.iter().sum::<f32>() / recent_confidences.len() as f32;
2315 let variance = recent_confidences
2316 .iter()
2317 .map(|&conf| (conf - mean).powi(2))
2318 .sum::<f32>()
2319 / recent_confidences.len() as f32;
2320 let consistency = (1.0 - variance.sqrt()).max(0.0);
2321
2322 consistency
2323 } else {
2324 0.6
2325 }
2326 }
2327
2328 fn compute_weighted_confidence(&self, factors: &ConfidenceFactors) -> f32 {
2329 let weights = &self.calibration_settings.factor_weights;
2330
2331 factors.evaluator_agreement * weights.agreement_weight
2332 + factors.individual_confidence * weights.individual_weight
2333 + factors.complexity_confidence * weights.complexity_weight
2334 + factors.pattern_clarity * weights.pattern_clarity_weight
2335 + factors.historical_accuracy * weights.historical_weight
2336 + factors.coverage_confidence * weights.coverage_weight
2337 + factors.temporal_consistency * weights.temporal_weight
2338 }
2339
2340 fn apply_confidence_calibration(
2341 &self,
2342 raw_confidence: f32,
2343 factors: &ConfidenceFactors,
2344 ) -> f32 {
2345 let mut calibrated = raw_confidence;
2346
2347 calibrated = match &self.calibration_settings.calibration_curve {
2349 CalibrationCurve::Linear => calibrated,
2350 CalibrationCurve::Conservative => calibrated.powf(1.2),
2351 CalibrationCurve::Aggressive => calibrated.powf(0.8),
2352 CalibrationCurve::Sigmoid => 1.0 / (1.0 + (-6.0 * (calibrated - 0.5)).exp()),
2353 };
2354
2355 if factors.evaluator_agreement < 0.3 {
2357 calibrated *= 0.8; }
2359
2360 if factors.coverage_confidence < 0.5 {
2361 calibrated *= 0.9; }
2363
2364 calibrated.clamp(0.0, 1.0)
2365 }
2366
2367 fn categorize_confidence(&self, confidence: f32) -> ConfidenceCategory {
2368 if confidence >= 0.8 {
2369 ConfidenceCategory::VeryHigh
2370 } else if confidence >= 0.6 {
2371 ConfidenceCategory::High
2372 } else if confidence >= 0.4 {
2373 ConfidenceCategory::Medium
2374 } else if confidence >= 0.2 {
2375 ConfidenceCategory::Low
2376 } else {
2377 ConfidenceCategory::VeryLow
2378 }
2379 }
2380
2381 fn generate_reliability_indicators(
2382 &self,
2383 factors: &ConfidenceFactors,
2384 ) -> Vec<ReliabilityIndicator> {
2385 let mut indicators = Vec::new();
2386
2387 if factors.evaluator_agreement < 0.4 {
2388 indicators.push(ReliabilityIndicator::LowEvaluatorAgreement);
2389 }
2390
2391 if factors.complexity_confidence < 0.3 {
2392 indicators.push(ReliabilityIndicator::HighPositionComplexity);
2393 }
2394
2395 if factors.pattern_clarity < 0.4 {
2396 indicators.push(ReliabilityIndicator::UnclearPatterns);
2397 }
2398
2399 if factors.coverage_confidence < 0.5 {
2400 indicators.push(ReliabilityIndicator::LimitedEvaluatorCoverage);
2401 }
2402
2403 if factors.temporal_consistency < 0.4 {
2404 indicators.push(ReliabilityIndicator::InconsistentHistory);
2405 }
2406
2407 if factors.historical_accuracy < 0.5 {
2408 indicators.push(ReliabilityIndicator::PoorHistoricalAccuracy);
2409 }
2410
2411 if indicators.is_empty() {
2412 indicators.push(ReliabilityIndicator::HighReliability);
2413 }
2414
2415 indicators
2416 }
2417
2418 fn record_confidence_score(
2419 &self,
2420 result: &ConfidenceAnalysisResult,
2421 position_context: &PositionContext,
2422 ) {
2423 if let Ok(mut recent_scores) = self.recent_confidence_scores.write() {
2424 let entry = ConfidenceHistoryEntry {
2425 timestamp: std::time::SystemTime::now(),
2426 confidence_score: result.overall_confidence,
2427 position_context: position_context.clone(),
2428 computation_time_ms: result.computation_time_ms,
2429 };
2430
2431 recent_scores.push_back(entry);
2432
2433 while recent_scores.len() > 1000 {
2435 recent_scores.pop_front();
2436 }
2437 }
2438 }
2439
2440 fn adjust_confidence_by_context(&self, base_confidence: f32, evaluator_type: &str) -> f32 {
2441 match evaluator_type {
2443 "NNUE" => {
2444 base_confidence * 1.05
2446 }
2447 "Tactical" => {
2448 base_confidence * 1.1
2450 }
2451 "Pattern" => {
2452 base_confidence * 0.95
2454 }
2455 "Strategic" => {
2456 base_confidence * 1.0
2458 }
2459 _ => base_confidence,
2460 }
2461 }
2462
2463 fn count_active_evaluators(&self, evaluation_results: &EvaluationResults) -> u32 {
2464 let mut count = 0;
2465 if evaluation_results.nnue.is_some() {
2466 count += 1;
2467 }
2468 if evaluation_results.pattern.is_some() {
2469 count += 1;
2470 }
2471 if evaluation_results.tactical.is_some() {
2472 count += 1;
2473 }
2474 if evaluation_results.strategic.is_some() {
2475 count += 1;
2476 }
2477 count
2478 }
2479
2480 pub fn update_accuracy_history(
2482 &self,
2483 evaluation_results: &EvaluationResults,
2484 blend_weights: &BlendWeights,
2485 position_context: &PositionContext,
2486 actual_outcome: f32,
2487 predicted_outcome: f32,
2488 ) {
2489 if let Ok(mut accuracy_tracker) = self.evaluator_accuracy_history.write() {
2490 let evaluator_combination =
2491 EvaluatorCombination::from_results(evaluation_results, blend_weights);
2492 let context_hash = position_context.get_context_hash();
2493 let accuracy = 1.0 - (actual_outcome - predicted_outcome).abs();
2494
2495 accuracy_tracker.record_accuracy(&evaluator_combination, context_hash, accuracy);
2496 }
2497 }
2498
2499 pub fn get_statistics(&self) -> ConfidenceScoringStats {
2501 let recent_count = self
2502 .recent_confidence_scores
2503 .read()
2504 .map(|scores| scores.len())
2505 .unwrap_or(0);
2506
2507 let accuracy_entries = self
2508 .evaluator_accuracy_history
2509 .read()
2510 .map(|tracker| tracker.get_total_entries())
2511 .unwrap_or(0);
2512
2513 let average_confidence = self
2514 .recent_confidence_scores
2515 .read()
2516 .map(|scores| {
2517 if scores.is_empty() {
2518 0.5
2519 } else {
2520 scores
2521 .iter()
2522 .map(|entry| entry.confidence_score)
2523 .sum::<f32>()
2524 / scores.len() as f32
2525 }
2526 })
2527 .unwrap_or(0.5);
2528
2529 ConfidenceScoringStats {
2530 total_confidence_analyses: recent_count,
2531 average_confidence,
2532 historical_accuracy_entries: accuracy_entries,
2533 calibration_curve: self.calibration_settings.calibration_curve.clone(),
2534 }
2535 }
2536}
2537
2538#[derive(Debug, Clone)]
2540struct WeightHistoryEntry {
2541 timestamp: std::time::SystemTime,
2542 weights: BlendWeights,
2543 complexity_score: f32,
2544 game_phase: GamePhase,
2545 evaluation_count: u32,
2546}
2547
2548#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2550struct EvaluationContext {
2551 complexity_range: ComplexityRange,
2552 game_phase: GamePhase,
2553}
2554
2555#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2557enum ComplexityRange {
2558 Low, Medium, High, }
2562
2563#[derive(Debug)]
2565struct EvaluatorPerformanceTracker {
2566 context_performance: HashMap<EvaluationContext, ContextPerformance>,
2567 total_evaluations: u64,
2568}
2569
2570impl EvaluatorPerformanceTracker {
2571 fn new() -> Self {
2572 Self {
2573 context_performance: HashMap::new(),
2574 total_evaluations: 0,
2575 }
2576 }
2577
2578 fn record_performance(
2579 &mut self,
2580 context: &EvaluationContext,
2581 weights: &BlendWeights,
2582 accuracy: f32,
2583 _actual_result: Option<f32>,
2584 ) {
2585 let performance = self
2586 .context_performance
2587 .entry(context.clone())
2588 .or_insert_with(ContextPerformance::new);
2589
2590 performance.record_evaluation(weights, accuracy);
2591 self.total_evaluations += 1;
2592 }
2593
2594 fn get_optimal_weights(&self, context: &EvaluationContext) -> Option<BlendWeights> {
2595 self.context_performance
2596 .get(context)
2597 .and_then(|perf| perf.get_best_weights())
2598 }
2599
2600 fn get_context_count(&self) -> usize {
2601 self.context_performance.len()
2602 }
2603}
2604
2605#[derive(Debug)]
2607struct ContextPerformance {
2608 weight_performance: Vec<WeightPerformanceRecord>,
2609 best_weights: Option<BlendWeights>,
2610 best_accuracy: f32,
2611}
2612
2613impl ContextPerformance {
2614 fn new() -> Self {
2615 Self {
2616 weight_performance: Vec::new(),
2617 best_weights: None,
2618 best_accuracy: 0.0,
2619 }
2620 }
2621
2622 fn record_evaluation(&mut self, weights: &BlendWeights, accuracy: f32) {
2623 let record = WeightPerformanceRecord {
2624 weights: weights.clone(),
2625 accuracy,
2626 evaluation_count: 1,
2627 };
2628
2629 self.weight_performance.push(record);
2630
2631 if accuracy > self.best_accuracy {
2633 self.best_accuracy = accuracy;
2634 self.best_weights = Some(weights.clone());
2635 }
2636
2637 if self.weight_performance.len() > 1000 {
2639 self.weight_performance.drain(0..100);
2640 }
2641 }
2642
2643 fn get_best_weights(&self) -> Option<BlendWeights> {
2644 self.best_weights.clone()
2645 }
2646}
2647
2648#[derive(Debug, Clone)]
2650struct WeightPerformanceRecord {
2651 weights: BlendWeights,
2652 accuracy: f32,
2653 evaluation_count: u32,
2654}
2655
2656#[derive(Debug, Clone)]
2658pub struct AdaptiveLearningStats {
2659 pub weight_history_entries: usize,
2660 pub performance_contexts: usize,
2661 pub learning_enabled: bool,
2662 pub total_adaptations: usize,
2663}
2664
2665#[derive(Debug, Clone)]
2667pub struct ComplexityAnalysisResult {
2668 pub overall_complexity: f32,
2670 pub material_complexity: f32,
2672 pub pawn_structure_complexity: f32,
2673 pub king_safety_complexity: f32,
2674 pub piece_coordination_complexity: f32,
2675 pub tactical_complexity: f32,
2676 pub positional_complexity: f32,
2677 pub time_complexity: f32,
2678 pub endgame_complexity: f32,
2679 pub complexity_category: ComplexityCategory,
2681 pub key_complexity_factors: Vec<ComplexityFactor>,
2683 pub evaluation_recommendations: EvaluationRecommendations,
2685}
2686
2687impl ComplexityAnalysisResult {
2688 fn new() -> Self {
2689 Self {
2690 overall_complexity: 0.0,
2691 material_complexity: 0.0,
2692 pawn_structure_complexity: 0.0,
2693 king_safety_complexity: 0.0,
2694 piece_coordination_complexity: 0.0,
2695 tactical_complexity: 0.0,
2696 positional_complexity: 0.0,
2697 time_complexity: 0.0,
2698 endgame_complexity: 0.0,
2699 complexity_category: ComplexityCategory::Medium,
2700 key_complexity_factors: Vec::new(),
2701 evaluation_recommendations: EvaluationRecommendations::default(),
2702 }
2703 }
2704}
2705
2706#[derive(Debug, Clone, PartialEq, Eq)]
2708pub enum ComplexityCategory {
2709 VeryLow,
2710 Low,
2711 Medium,
2712 High,
2713 VeryHigh,
2714}
2715
2716#[derive(Debug, Clone, PartialEq, Eq)]
2718pub enum ComplexityFactor {
2719 MaterialImbalance,
2720 PawnStructure,
2721 KingSafety,
2722 PieceCoordination,
2723 TacticalThreats,
2724 PositionalThemes,
2725 TimeFactors,
2726 EndgameFactors,
2727}
2728
2729#[derive(Debug, Clone)]
2731pub struct ComplexityWeights {
2732 pub material_weight: f32,
2733 pub pawn_structure_weight: f32,
2734 pub king_safety_weight: f32,
2735 pub piece_coordination_weight: f32,
2736 pub tactical_weight: f32,
2737 pub positional_weight: f32,
2738 pub time_weight: f32,
2739 pub endgame_weight: f32,
2740}
2741
2742impl Default for ComplexityWeights {
2743 fn default() -> Self {
2744 Self {
2745 material_weight: 0.15,
2746 pawn_structure_weight: 0.15,
2747 king_safety_weight: 0.20,
2748 piece_coordination_weight: 0.15,
2749 tactical_weight: 0.25,
2750 positional_weight: 0.10,
2751 time_weight: 0.05,
2752 endgame_weight: 0.10,
2753 }
2754 }
2755}
2756
2757#[derive(Debug, Clone, PartialEq, Eq)]
2759pub enum AnalysisDepth {
2760 Fast, Standard, Deep, Comprehensive, }
2765
2766#[derive(Debug, Clone)]
2768pub struct EvaluationRecommendations {
2769 pub prefer_nnue: bool,
2771 pub tactical_depth: u8,
2773 pub pattern_analysis_priority: f32,
2775 pub strategic_analysis_priority: f32,
2777 pub require_tactical_verification: bool,
2779 pub king_safety_analysis: bool,
2781 pub endgame_analysis: bool,
2783}
2784
2785impl Default for EvaluationRecommendations {
2786 fn default() -> Self {
2787 Self {
2788 prefer_nnue: false,
2789 tactical_depth: 6,
2790 pattern_analysis_priority: 0.5,
2791 strategic_analysis_priority: 0.3,
2792 require_tactical_verification: false,
2793 king_safety_analysis: false,
2794 endgame_analysis: false,
2795 }
2796 }
2797}
2798
2799#[derive(Debug, Clone)]
2801pub struct GamePhaseAnalysisResult {
2802 pub primary_phase: GamePhase,
2804 pub material_phase: PhaseIndicator,
2806 pub development_phase: PhaseIndicator,
2807 pub move_count_phase: PhaseIndicator,
2808 pub king_safety_phase: PhaseIndicator,
2809 pub pawn_structure_phase: PhaseIndicator,
2810 pub piece_activity_phase: PhaseIndicator,
2811 pub opening_score: f32,
2813 pub middlegame_score: f32,
2814 pub endgame_score: f32,
2815 pub phase_confidence: f32,
2817 pub transition_state: PhaseTransition,
2819 pub adaptation_recommendations: PhaseAdaptationRecommendations,
2821}
2822
2823impl GamePhaseAnalysisResult {
2824 fn new() -> Self {
2825 Self {
2826 primary_phase: GamePhase::Unknown,
2827 material_phase: PhaseIndicator::default(),
2828 development_phase: PhaseIndicator::default(),
2829 move_count_phase: PhaseIndicator::default(),
2830 king_safety_phase: PhaseIndicator::default(),
2831 pawn_structure_phase: PhaseIndicator::default(),
2832 piece_activity_phase: PhaseIndicator::default(),
2833 opening_score: 0.0,
2834 middlegame_score: 0.0,
2835 endgame_score: 0.0,
2836 phase_confidence: 0.0,
2837 transition_state: PhaseTransition::Stable,
2838 adaptation_recommendations: PhaseAdaptationRecommendations::default(),
2839 }
2840 }
2841}
2842
2843#[derive(Debug, Clone)]
2845pub struct PhaseIndicator {
2846 pub phase: GamePhase,
2847 pub confidence: f32,
2848}
2849
2850impl Default for PhaseIndicator {
2851 fn default() -> Self {
2852 Self {
2853 phase: GamePhase::Unknown,
2854 confidence: 0.0,
2855 }
2856 }
2857}
2858
2859#[derive(Debug, Clone, PartialEq, Eq)]
2861pub enum PhaseTransition {
2862 Stable,
2863 OpeningToMiddlegame,
2864 MiddlegameToEndgame,
2865}
2866
2867#[derive(Debug, Clone)]
2869pub struct PhaseDetectionWeights {
2870 pub material_weight: f32,
2871 pub development_weight: f32,
2872 pub move_count_weight: f32,
2873 pub king_safety_weight: f32,
2874 pub pawn_structure_weight: f32,
2875 pub piece_activity_weight: f32,
2876}
2877
2878impl Default for PhaseDetectionWeights {
2879 fn default() -> Self {
2880 Self {
2881 material_weight: 0.25,
2882 development_weight: 0.20,
2883 move_count_weight: 0.15,
2884 king_safety_weight: 0.15,
2885 pawn_structure_weight: 0.15,
2886 piece_activity_weight: 0.10,
2887 }
2888 }
2889}
2890
2891#[derive(Debug, Clone)]
2893pub struct PhaseAdaptationSettings {
2894 pub enable_transition_detection: bool,
2895 pub transition_sensitivity: f32,
2896 pub adaptation_responsiveness: f32,
2897}
2898
2899impl Default for PhaseAdaptationSettings {
2900 fn default() -> Self {
2901 Self {
2902 enable_transition_detection: true,
2903 transition_sensitivity: 0.3,
2904 adaptation_responsiveness: 1.0,
2905 }
2906 }
2907}
2908
2909#[derive(Debug, Clone)]
2911pub struct PhaseAdaptationRecommendations {
2912 pub evaluation_weights: BlendWeights,
2914 pub search_depth_modifier: i8,
2916 pub opening_book_priority: f32,
2918 pub endgame_tablebase_priority: f32,
2920 pub time_management_factor: f32,
2922}
2923
2924impl Default for PhaseAdaptationRecommendations {
2925 fn default() -> Self {
2926 Self {
2927 evaluation_weights: BlendWeights::default(),
2928 search_depth_modifier: 0,
2929 opening_book_priority: 0.5,
2930 endgame_tablebase_priority: 0.5,
2931 time_management_factor: 1.0,
2932 }
2933 }
2934}
2935
2936impl Default for ConfidenceScorer {
2937 fn default() -> Self {
2938 Self::new()
2939 }
2940}
2941
2942#[derive(Debug)]
2944pub struct PatternClarityAnalyzer {
2945 clarity_cache: Arc<RwLock<HashMap<String, PatternClarityResult>>>,
2946}
2947
2948impl PatternClarityAnalyzer {
2949 pub fn new() -> Self {
2950 Self {
2951 clarity_cache: Arc::new(RwLock::new(HashMap::new())),
2952 }
2953 }
2954
2955 pub fn analyze_pattern_clarity(
2956 &self,
2957 evaluation_results: &EvaluationResults,
2958 position_context: &PositionContext,
2959 ) -> PatternClarityResult {
2960 let cache_key = format!(
2961 "{}_{}",
2962 position_context.position_hash,
2963 evaluation_results.hash_key()
2964 );
2965
2966 if let Ok(cache) = self.clarity_cache.read() {
2968 if let Some(result) = cache.get(&cache_key) {
2969 return result.clone();
2970 }
2971 }
2972
2973 let mut clarity_factors = Vec::new();
2974 let mut overall_clarity = 0.0;
2975
2976 if let Some(ref pattern_eval) = evaluation_results.pattern {
2978 let pattern_clarity = self.analyze_pattern_evaluation_clarity(pattern_eval);
2979 clarity_factors.push(ClarityFactor {
2980 evaluator: "Pattern".to_string(),
2981 clarity_score: pattern_clarity,
2982 contributing_factors: vec![
2983 "pattern_strength".to_string(),
2984 "pattern_frequency".to_string(),
2985 ],
2986 });
2987 overall_clarity += pattern_clarity * 0.4;
2988 }
2989
2990 if let Some(ref tactical_eval) = evaluation_results.tactical {
2991 let tactical_clarity = self.analyze_tactical_evaluation_clarity(tactical_eval);
2992 clarity_factors.push(ClarityFactor {
2993 evaluator: "Tactical".to_string(),
2994 clarity_score: tactical_clarity,
2995 contributing_factors: vec![
2996 "search_depth".to_string(),
2997 "best_move_clarity".to_string(),
2998 ],
2999 });
3000 overall_clarity += tactical_clarity * 0.3;
3001 }
3002
3003 if let Some(ref nnue_eval) = evaluation_results.nnue {
3004 let nnue_clarity = self.analyze_nnue_evaluation_clarity(nnue_eval);
3005 clarity_factors.push(ClarityFactor {
3006 evaluator: "NNUE".to_string(),
3007 clarity_score: nnue_clarity,
3008 contributing_factors: vec![
3009 "evaluation_magnitude".to_string(),
3010 "position_familiarity".to_string(),
3011 ],
3012 });
3013 overall_clarity += nnue_clarity * 0.2;
3014 }
3015
3016 if let Some(ref strategic_eval) = evaluation_results.strategic {
3017 let strategic_clarity = self.analyze_strategic_evaluation_clarity(strategic_eval);
3018 clarity_factors.push(ClarityFactor {
3019 evaluator: "Strategic".to_string(),
3020 clarity_score: strategic_clarity,
3021 contributing_factors: vec![
3022 "plan_clarity".to_string(),
3023 "initiative_balance".to_string(),
3024 ],
3025 });
3026 overall_clarity += strategic_clarity * 0.1;
3027 }
3028
3029 let result = PatternClarityResult {
3030 overall_clarity,
3031 clarity_factors,
3032 position_characteristics: self.analyze_position_characteristics(position_context),
3033 };
3034
3035 if let Ok(mut cache) = self.clarity_cache.write() {
3037 cache.insert(cache_key, result.clone());
3038 if cache.len() > 1000 {
3039 let keys_to_remove: Vec<_> = cache.keys().take(500).cloned().collect();
3041 for key in keys_to_remove {
3042 cache.remove(&key);
3043 }
3044 }
3045 }
3046
3047 result
3048 }
3049
3050 fn analyze_pattern_evaluation_clarity(&self, pattern_eval: &EvaluationComponent) -> f32 {
3051 let magnitude_clarity = pattern_eval.evaluation.abs().min(1.0);
3053 let confidence_clarity = pattern_eval.confidence;
3054
3055 (magnitude_clarity + confidence_clarity) / 2.0
3056 }
3057
3058 fn analyze_tactical_evaluation_clarity(&self, tactical_eval: &EvaluationComponent) -> f32 {
3059 let base_clarity = tactical_eval.confidence;
3061
3062 let mut clarity_bonus = 0.0;
3064 if let Some(depth) = tactical_eval.additional_info.get("search_depth") {
3065 if *depth >= 6.0 {
3066 clarity_bonus += 0.1;
3067 }
3068 }
3069
3070 (base_clarity + clarity_bonus).min(1.0)
3071 }
3072
3073 fn analyze_nnue_evaluation_clarity(&self, nnue_eval: &EvaluationComponent) -> f32 {
3074 let confidence_clarity = nnue_eval.confidence;
3076 let magnitude_clarity = (nnue_eval.evaluation.abs() / 2.0).min(1.0);
3077
3078 (confidence_clarity * 0.7 + magnitude_clarity * 0.3).min(1.0)
3079 }
3080
3081 fn analyze_strategic_evaluation_clarity(&self, strategic_eval: &EvaluationComponent) -> f32 {
3082 let base_clarity = strategic_eval.confidence;
3084
3085 let mut clarity_adjustment = 0.0;
3087 if let Some(plans_count) = strategic_eval.additional_info.get("strategic_plans_count") {
3088 if *plans_count >= 2.0 {
3089 clarity_adjustment += 0.1;
3090 }
3091 }
3092
3093 (base_clarity + clarity_adjustment).min(1.0)
3094 }
3095
3096 fn analyze_position_characteristics(&self, position_context: &PositionContext) -> Vec<String> {
3097 let mut characteristics = Vec::new();
3098
3099 if position_context.has_tactical_threats {
3100 characteristics.push("tactical_position".to_string());
3101 }
3102
3103 if position_context.in_opening_book {
3104 characteristics.push("known_opening".to_string());
3105 }
3106
3107 match position_context.game_phase {
3108 GamePhase::Opening => characteristics.push("opening_phase".to_string()),
3109 GamePhase::Middlegame => characteristics.push("middlegame_phase".to_string()),
3110 GamePhase::Endgame => characteristics.push("endgame_phase".to_string()),
3111 GamePhase::Unknown => characteristics.push("unknown_phase".to_string()),
3112 }
3113
3114 if position_context.material_imbalance > 3.0 {
3115 characteristics.push("material_imbalance".to_string());
3116 }
3117
3118 characteristics
3119 }
3120}
3121
3122#[derive(Debug)]
3124pub struct EvaluatorAccuracyTracker {
3125 accuracy_records: HashMap<EvaluatorCombination, HashMap<u64, AccuracyRecord>>,
3126 total_entries: usize,
3127}
3128
3129impl EvaluatorAccuracyTracker {
3130 pub fn new() -> Self {
3131 Self {
3132 accuracy_records: HashMap::new(),
3133 total_entries: 0,
3134 }
3135 }
3136
3137 pub fn record_accuracy(
3138 &mut self,
3139 combination: &EvaluatorCombination,
3140 context_hash: u64,
3141 accuracy: f32,
3142 ) {
3143 let context_records = self
3144 .accuracy_records
3145 .entry(combination.clone())
3146 .or_insert_with(HashMap::new);
3147
3148 let record = context_records
3149 .entry(context_hash)
3150 .or_insert_with(|| AccuracyRecord::new());
3151
3152 record.add_accuracy(accuracy);
3153 self.total_entries += 1;
3154 }
3155
3156 pub fn get_historical_accuracy(
3157 &self,
3158 combination: &EvaluatorCombination,
3159 context_hash: u64,
3160 ) -> Option<f32> {
3161 self.accuracy_records
3162 .get(combination)?
3163 .get(&context_hash)
3164 .map(|record| record.get_average_accuracy())
3165 }
3166
3167 pub fn get_total_entries(&self) -> usize {
3168 self.total_entries
3169 }
3170}
3171
3172#[derive(Debug, Clone)]
3174pub struct AccuracyRecord {
3175 accuracies: Vec<f32>,
3176 average_accuracy: f32,
3177}
3178
3179impl AccuracyRecord {
3180 pub fn new() -> Self {
3181 Self {
3182 accuracies: Vec::new(),
3183 average_accuracy: 0.0,
3184 }
3185 }
3186
3187 pub fn add_accuracy(&mut self, accuracy: f32) {
3188 self.accuracies.push(accuracy);
3189
3190 if self.accuracies.len() > 50 {
3192 self.accuracies.remove(0);
3193 }
3194
3195 self.average_accuracy = self.accuracies.iter().sum::<f32>() / self.accuracies.len() as f32;
3197 }
3198
3199 pub fn get_average_accuracy(&self) -> f32 {
3200 self.average_accuracy
3201 }
3202}
3203
3204#[derive(Debug, Clone, Default)]
3206pub struct PositionContext {
3207 pub position_hash: u64,
3208 pub game_phase: GamePhase,
3209 pub has_tactical_threats: bool,
3210 pub in_opening_book: bool,
3211 pub material_imbalance: f32,
3212 pub complexity_score: f32,
3213}
3214
3215impl PositionContext {
3216 pub fn get_context_hash(&self) -> u64 {
3217 let mut hasher = std::collections::hash_map::DefaultHasher::new();
3219 std::hash::Hash::hash(
3220 &(
3221 self.game_phase as u8,
3222 self.has_tactical_threats,
3223 self.in_opening_book,
3224 (self.material_imbalance * 10.0) as i32,
3225 (self.complexity_score * 10.0) as i32,
3226 ),
3227 &mut hasher,
3228 );
3229 std::hash::Hasher::finish(&hasher)
3230 }
3231}
3232
3233#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3235pub struct EvaluatorCombination {
3236 pub has_nnue: bool,
3237 pub has_pattern: bool,
3238 pub has_tactical: bool,
3239 pub has_strategic: bool,
3240 pub weight_signature: String, }
3242
3243impl EvaluatorCombination {
3244 pub fn from_results(
3245 evaluation_results: &EvaluationResults,
3246 blend_weights: &BlendWeights,
3247 ) -> Self {
3248 let weight_signature = format!(
3249 "n{:.1}p{:.1}t{:.1}s{:.1}",
3250 blend_weights.nnue_weight,
3251 blend_weights.pattern_weight,
3252 blend_weights.tactical_weight,
3253 blend_weights.strategic_weight
3254 );
3255
3256 Self {
3257 has_nnue: evaluation_results.nnue.is_some(),
3258 has_pattern: evaluation_results.pattern.is_some(),
3259 has_tactical: evaluation_results.tactical.is_some(),
3260 has_strategic: evaluation_results.strategic.is_some(),
3261 weight_signature,
3262 }
3263 }
3264}
3265
3266#[derive(Debug, Clone)]
3268pub struct ConfidenceAnalysisResult {
3269 pub overall_confidence: f32,
3270 pub confidence_factors: ConfidenceFactors,
3271 pub agreement_analysis: AgreementAnalysis,
3272 pub pattern_clarity_analysis: PatternClarityResult,
3273 pub computation_time_ms: u64,
3274 pub confidence_category: ConfidenceCategory,
3275 pub reliability_indicators: Vec<ReliabilityIndicator>,
3276}
3277
3278#[derive(Debug, Clone)]
3280pub struct ConfidenceFactors {
3281 pub evaluator_agreement: f32,
3282 pub individual_confidence: f32,
3283 pub complexity_confidence: f32,
3284 pub pattern_clarity: f32,
3285 pub historical_accuracy: f32,
3286 pub coverage_confidence: f32,
3287 pub temporal_consistency: f32,
3288}
3289
3290#[derive(Debug, Clone)]
3292pub struct AgreementAnalysis {
3293 pub overall_agreement: f32,
3294 pub pairwise_agreements: Vec<PairwiseAgreement>,
3295 pub evaluation_spread: f32,
3296 pub consensus_strength: f32,
3297 pub outlier_count: usize,
3298}
3299
3300#[derive(Debug, Clone)]
3302pub struct PairwiseAgreement {
3303 pub evaluator1: String,
3304 pub evaluator2: String,
3305 pub agreement_score: f32,
3306}
3307
3308#[derive(Debug, Clone)]
3310pub struct PatternClarityResult {
3311 pub overall_clarity: f32,
3312 pub clarity_factors: Vec<ClarityFactor>,
3313 pub position_characteristics: Vec<String>,
3314}
3315
3316#[derive(Debug, Clone)]
3318pub struct ClarityFactor {
3319 pub evaluator: String,
3320 pub clarity_score: f32,
3321 pub contributing_factors: Vec<String>,
3322}
3323
3324#[derive(Debug, Clone)]
3326pub struct ConfidenceCalibrationSettings {
3327 pub calibration_curve: CalibrationCurve,
3328 pub factor_weights: FactorWeights,
3329}
3330
3331impl Default for ConfidenceCalibrationSettings {
3332 fn default() -> Self {
3333 Self {
3334 calibration_curve: CalibrationCurve::Sigmoid,
3335 factor_weights: FactorWeights::default(),
3336 }
3337 }
3338}
3339
3340#[derive(Debug, Clone)]
3342pub enum CalibrationCurve {
3343 Linear,
3344 Conservative,
3345 Aggressive,
3346 Sigmoid,
3347}
3348
3349#[derive(Debug, Clone)]
3351pub struct FactorWeights {
3352 pub agreement_weight: f32,
3353 pub individual_weight: f32,
3354 pub complexity_weight: f32,
3355 pub pattern_clarity_weight: f32,
3356 pub historical_weight: f32,
3357 pub coverage_weight: f32,
3358 pub temporal_weight: f32,
3359}
3360
3361impl Default for FactorWeights {
3362 fn default() -> Self {
3363 Self {
3364 agreement_weight: 0.25,
3365 individual_weight: 0.20,
3366 complexity_weight: 0.15,
3367 pattern_clarity_weight: 0.15,
3368 historical_weight: 0.10,
3369 coverage_weight: 0.10,
3370 temporal_weight: 0.05,
3371 }
3372 }
3373}
3374
3375#[derive(Debug, Clone, PartialEq)]
3377pub enum ConfidenceCategory {
3378 VeryHigh, High, Medium, Low, VeryLow, }
3384
3385#[derive(Debug, Clone, PartialEq)]
3387pub enum ReliabilityIndicator {
3388 HighReliability,
3389 LowEvaluatorAgreement,
3390 HighPositionComplexity,
3391 UnclearPatterns,
3392 LimitedEvaluatorCoverage,
3393 InconsistentHistory,
3394 PoorHistoricalAccuracy,
3395}
3396
3397#[derive(Debug, Clone)]
3399pub struct ConfidenceHistoryEntry {
3400 pub timestamp: std::time::SystemTime,
3401 pub confidence_score: f32,
3402 pub position_context: PositionContext,
3403 pub computation_time_ms: u64,
3404}
3405
3406#[derive(Debug, Clone)]
3408pub struct ConfidenceScoringStats {
3409 pub total_confidence_analyses: usize,
3410 pub average_confidence: f32,
3411 pub historical_accuracy_entries: usize,
3412 pub calibration_curve: CalibrationCurve,
3413}
3414
3415pub trait EvaluationResultsExt {
3417 fn hash_key(&self) -> String;
3418}
3419
3420impl EvaluationResultsExt for EvaluationResults {
3421 fn hash_key(&self) -> String {
3422 format!(
3423 "n{}p{}t{}s{}",
3424 self.nnue
3425 .as_ref()
3426 .map(|e| format!("{:.2}", e.evaluation))
3427 .unwrap_or_default(),
3428 self.pattern
3429 .as_ref()
3430 .map(|e| format!("{:.2}", e.evaluation))
3431 .unwrap_or_default(),
3432 self.tactical
3433 .as_ref()
3434 .map(|e| format!("{:.2}", e.evaluation))
3435 .unwrap_or_default(),
3436 self.strategic
3437 .as_ref()
3438 .map(|e| format!("{:.2}", e.evaluation))
3439 .unwrap_or_default(),
3440 )
3441 }
3442}
3443
3444#[derive(Debug, Clone)]
3446pub struct HybridEvaluationStats {
3447 pub total_evaluations: u64,
3448 pub nnue_evaluations: u64,
3449 pub pattern_evaluations: u64,
3450 pub tactical_evaluations: u64,
3451 pub strategic_evaluations: u64,
3452 pub cache_hit_ratio: f64,
3453 pub average_evaluation_time_ms: f64,
3454 pub evaluations_per_second: f64,
3455}
3456
3457pub trait NNUEEvaluator {
3459 fn evaluate_position(&self, board: &Board) -> Result<EvaluationComponent>;
3460}
3461
3462pub trait PatternEvaluator {
3464 fn evaluate_position(&self, board: &Board) -> Result<EvaluationComponent>;
3465}
3466
3467pub trait TacticalEvaluator {
3469 fn evaluate_position(&self, board: &Board) -> Result<EvaluationComponent>;
3470}
3471
3472pub trait StrategicEvaluator {
3474 fn evaluate_position(&self, board: &Board) -> Result<EvaluationComponent>;
3475}
3476
3477#[cfg(test)]
3478mod tests {
3479 use super::*;
3480 use chess::Board;
3481 use std::str::FromStr;
3482
3483 struct MockNNUEEvaluator;
3485 impl NNUEEvaluator for MockNNUEEvaluator {
3486 fn evaluate_position(&self, _board: &Board) -> Result<EvaluationComponent> {
3487 Ok(EvaluationComponent {
3488 evaluation: 0.15,
3489 confidence: 0.8,
3490 computation_time_ms: 5,
3491 additional_info: HashMap::new(),
3492 })
3493 }
3494 }
3495
3496 struct MockPatternEvaluator;
3497 impl PatternEvaluator for MockPatternEvaluator {
3498 fn evaluate_position(&self, _board: &Board) -> Result<EvaluationComponent> {
3499 Ok(EvaluationComponent {
3500 evaluation: 0.12,
3501 confidence: 0.7,
3502 computation_time_ms: 15,
3503 additional_info: HashMap::new(),
3504 })
3505 }
3506 }
3507
3508 #[test]
3509 fn test_hybrid_evaluation_engine() {
3510 let engine = HybridEvaluationEngine::new()
3511 .with_nnue_evaluator(MockNNUEEvaluator)
3512 .with_pattern_evaluator(MockPatternEvaluator);
3513
3514 let board =
3515 Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
3516 let result = engine.evaluate_position(&board).unwrap();
3517
3518 assert!(result.final_evaluation != 0.0);
3519 assert!(result.nnue_evaluation.is_some());
3520 assert!(result.pattern_evaluation.is_some());
3521 assert!(result.confidence_score > 0.0);
3522 assert!(!result.from_cache);
3523 }
3524
3525 #[test]
3526 fn test_complexity_analyzer() {
3527 let analyzer = ComplexityAnalyzer::new();
3528 let board =
3529 Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
3530 let complexity = analyzer.analyze_complexity(&board);
3531
3532 assert!(complexity >= 0.0 && complexity <= 1.0);
3533 }
3534
3535 #[test]
3536 fn test_game_phase_detector() {
3537 let detector = GamePhaseDetector::new();
3538
3539 let opening_board =
3541 Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
3542 assert_eq!(detector.detect_phase(&opening_board), GamePhase::Opening);
3543
3544 let endgame_board = Board::from_str("8/8/8/8/8/8/4K3/4k3 w - - 0 50").unwrap();
3546 assert_eq!(detector.detect_phase(&endgame_board), GamePhase::Endgame);
3547 }
3548
3549 #[test]
3550 fn test_evaluation_blender() {
3551 let blender = EvaluationBlender::new();
3552 let mut evaluation_results = EvaluationResults::new();
3553
3554 evaluation_results.nnue = Some(EvaluationComponent {
3555 evaluation: 0.1,
3556 confidence: 0.8,
3557 computation_time_ms: 5,
3558 additional_info: HashMap::new(),
3559 });
3560
3561 evaluation_results.pattern = Some(EvaluationComponent {
3562 evaluation: 0.2,
3563 confidence: 0.7,
3564 computation_time_ms: 15,
3565 additional_info: HashMap::new(),
3566 });
3567
3568 let weights = BlendWeights {
3569 nnue_weight: 0.6,
3570 pattern_weight: 0.4,
3571 tactical_weight: 0.0,
3572 strategic_weight: 0.0,
3573 };
3574
3575 let blended = blender.blend_evaluations(&evaluation_results, &weights);
3576 let expected = 0.1 * 0.6 + 0.2 * 0.4;
3577 assert!((blended - expected).abs() < 1e-6);
3578 }
3579
3580 #[test]
3581 fn test_confidence_scorer() {
3582 let scorer = ConfidenceScorer::new();
3583 let mut evaluation_results = EvaluationResults::new();
3584
3585 evaluation_results.nnue = Some(EvaluationComponent {
3586 evaluation: 0.15,
3587 confidence: 0.8,
3588 computation_time_ms: 5,
3589 additional_info: HashMap::new(),
3590 });
3591
3592 evaluation_results.pattern = Some(EvaluationComponent {
3593 evaluation: 0.12,
3594 confidence: 0.7,
3595 computation_time_ms: 15,
3596 additional_info: HashMap::new(),
3597 });
3598
3599 let weights = BlendWeights::default();
3600 let position_context = PositionContext {
3601 position_hash: 0,
3602 game_phase: GamePhase::Opening,
3603 has_tactical_threats: true,
3604 in_opening_book: true,
3605 material_imbalance: 0.0,
3606 complexity_score: 0.5,
3607 };
3608 let confidence = scorer.compute_confidence(&evaluation_results, &weights, 0.3, &position_context);
3609
3610 assert!(confidence.overall_confidence > 0.0 && confidence.overall_confidence <= 1.0);
3611 }
3612
3613 #[test]
3614 fn test_adaptive_learning() {
3615 let mut engine = HybridEvaluationEngine::new()
3616 .with_nnue_evaluator(MockNNUEEvaluator)
3617 .with_pattern_evaluator(MockPatternEvaluator);
3618
3619 let board =
3620 Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
3621
3622 engine.set_adaptive_learning(true);
3624
3625 let initial_stats = engine.get_adaptive_learning_stats();
3627 assert!(initial_stats.learning_enabled);
3628 assert_eq!(initial_stats.weight_history_entries, 0);
3629
3630 let result = engine.evaluate_position(&board).unwrap();
3632 assert!(result.final_evaluation != 0.0);
3633
3634 let accuracy = 0.85;
3636 engine
3637 .update_evaluation_performance(&board, result.final_evaluation, Some(0.2), accuracy)
3638 .unwrap();
3639
3640 let updated_stats = engine.get_adaptive_learning_stats();
3642 assert!(updated_stats.weight_history_entries > 0);
3643 }
3644
3645 #[test]
3646 fn test_evaluation_blender_adaptive_weights() {
3647 let blender = EvaluationBlender::new();
3648 let complexity_score = 0.5;
3649 let game_phase = GamePhase::Middlegame;
3650 let evaluation_results = EvaluationResults::new();
3651
3652 let weights1 =
3654 blender.compute_blend_weights(complexity_score, &game_phase, &evaluation_results);
3655 let weights2 =
3656 blender.compute_blend_weights(complexity_score, &game_phase, &evaluation_results);
3657
3658 let total1 = weights1.nnue_weight
3660 + weights1.pattern_weight
3661 + weights1.tactical_weight
3662 + weights1.strategic_weight;
3663 let total2 = weights2.nnue_weight
3664 + weights2.pattern_weight
3665 + weights2.tactical_weight
3666 + weights2.strategic_weight;
3667
3668 assert!((total1 - 1.0).abs() < 1e-6);
3669 assert!((total2 - 1.0).abs() < 1e-6);
3670
3671 let stats = blender.get_adaptive_stats();
3673 assert!(stats.weight_history_entries >= 2);
3674 }
3675
3676 struct MockStrategicEvaluator;
3677 impl StrategicEvaluator for MockStrategicEvaluator {
3678 fn evaluate_position(&self, _board: &Board) -> Result<EvaluationComponent> {
3679 Ok(EvaluationComponent {
3680 evaluation: 0.08,
3681 confidence: 0.75,
3682 computation_time_ms: 25,
3683 additional_info: HashMap::new(),
3684 })
3685 }
3686 }
3687
3688 #[test]
3689 fn test_hybrid_evaluation_with_strategic_initiative() {
3690 let engine = HybridEvaluationEngine::new()
3691 .with_nnue_evaluator(MockNNUEEvaluator)
3692 .with_pattern_evaluator(MockPatternEvaluator)
3693 .with_strategic_evaluator(MockStrategicEvaluator);
3694
3695 let board =
3696 Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
3697 let result = engine.evaluate_position(&board).unwrap();
3698
3699 assert!(result.final_evaluation != 0.0);
3700 assert!(result.nnue_evaluation.is_some());
3701 assert!(result.pattern_evaluation.is_some());
3702 assert!(result.strategic_evaluation.is_some());
3703 assert!(result.confidence_score > 0.0);
3704 assert!(!result.from_cache);
3705
3706 let strategic_eval = result.strategic_evaluation.unwrap();
3708 assert_eq!(strategic_eval.evaluation, 0.08);
3709 assert_eq!(strategic_eval.confidence, 0.75);
3710 }
3711
3712 #[test]
3713 fn test_strategic_initiative_evaluator_integration() {
3714 let engine = HybridEvaluationEngine::new().with_strategic_initiative_evaluator();
3715
3716 let board =
3717 Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
3718 let result = engine.evaluate_position(&board).unwrap();
3719
3720 if let Some(strategic_eval) = result.strategic_evaluation {
3722 assert!(strategic_eval.evaluation.is_finite());
3723 assert!(strategic_eval.confidence >= 0.0 && strategic_eval.confidence <= 1.0);
3724 assert!(strategic_eval.computation_time_ms > 0);
3725
3726 assert!(strategic_eval
3728 .additional_info
3729 .contains_key("white_initiative"));
3730 assert!(strategic_eval
3731 .additional_info
3732 .contains_key("black_initiative"));
3733 assert!(strategic_eval
3734 .additional_info
3735 .contains_key("space_advantage"));
3736 }
3737 }
3738
3739 #[test]
3740 fn test_enhanced_complexity_analyzer() {
3741 let analyzer = ComplexityAnalyzer::new()
3742 .with_analysis_depth(AnalysisDepth::Deep)
3743 .with_complexity_weights(ComplexityWeights::default());
3744
3745 let board =
3746 Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
3747
3748 let analysis = analyzer.analyze_complexity_detailed(&board);
3750
3751 assert!(analysis.overall_complexity >= 0.0 && analysis.overall_complexity <= 1.0);
3752 assert!(analysis.material_complexity >= 0.0 && analysis.material_complexity <= 1.0);
3753 assert!(
3754 analysis.pawn_structure_complexity >= 0.0 && analysis.pawn_structure_complexity <= 1.0
3755 );
3756 assert!(analysis.king_safety_complexity >= 0.0 && analysis.king_safety_complexity <= 1.0);
3757 assert!(
3758 analysis.piece_coordination_complexity >= 0.0
3759 && analysis.piece_coordination_complexity <= 1.0
3760 );
3761 assert!(analysis.tactical_complexity >= 0.0 && analysis.tactical_complexity <= 1.0);
3762 assert!(analysis.positional_complexity >= 0.0 && analysis.positional_complexity <= 1.0);
3763 assert!(analysis.time_complexity >= 0.0 && analysis.time_complexity <= 1.0);
3764 assert!(analysis.endgame_complexity >= 0.0 && analysis.endgame_complexity <= 1.0);
3765
3766 assert!(matches!(
3768 analysis.complexity_category,
3769 ComplexityCategory::VeryLow
3770 | ComplexityCategory::Low
3771 | ComplexityCategory::Medium
3772 | ComplexityCategory::High
3773 | ComplexityCategory::VeryHigh
3774 ));
3775
3776 assert!(analysis.evaluation_recommendations.tactical_depth > 0);
3778 assert!(
3779 analysis
3780 .evaluation_recommendations
3781 .pattern_analysis_priority
3782 >= 0.0
3783 );
3784 assert!(
3785 analysis
3786 .evaluation_recommendations
3787 .strategic_analysis_priority
3788 >= 0.0
3789 );
3790 }
3791
3792 #[test]
3793 fn test_complexity_analysis_integration() {
3794 let mut engine = HybridEvaluationEngine::new();
3795
3796 let custom_weights = ComplexityWeights {
3798 tactical_weight: 0.4,
3799 king_safety_weight: 0.3,
3800 ..ComplexityWeights::default()
3801 };
3802 engine.configure_complexity_analyzer(custom_weights, AnalysisDepth::Comprehensive);
3803
3804 let board =
3805 Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
3806
3807 let complexity_analysis = engine.analyze_position_complexity(&board);
3809 assert!(complexity_analysis.overall_complexity.is_finite());
3810 assert!(
3811 !complexity_analysis.key_complexity_factors.is_empty()
3812 || complexity_analysis.key_complexity_factors.is_empty()
3813 ); let evaluation_result = engine.evaluate_position(&board).unwrap();
3817 assert!(evaluation_result.complexity_score.is_finite());
3818 }
3819
3820 #[test]
3821 fn test_complexity_categories_and_recommendations() {
3822 let analyzer = ComplexityAnalyzer::new();
3823
3824 let positions = [
3826 "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", "r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1", "r1bqk1nr/pppp1ppp/2n5/2b1p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 4 4", ];
3830
3831 for fen in &positions {
3832 let board = Board::from_str(fen).unwrap();
3833 let analysis = analyzer.analyze_complexity_detailed(&board);
3834
3835 assert!(analysis.overall_complexity >= 0.0 && analysis.overall_complexity <= 1.0);
3837
3838 let recs = &analysis.evaluation_recommendations;
3840 assert!(recs.tactical_depth >= 2 && recs.tactical_depth <= 12);
3841 assert!(recs.pattern_analysis_priority >= 0.0 && recs.pattern_analysis_priority <= 1.0);
3842 assert!(
3843 recs.strategic_analysis_priority >= 0.0 && recs.strategic_analysis_priority <= 1.0
3844 );
3845
3846 match analysis.complexity_category {
3848 ComplexityCategory::VeryLow => assert!(analysis.overall_complexity < 0.2),
3849 ComplexityCategory::Low => {
3850 assert!(analysis.overall_complexity >= 0.0 && analysis.overall_complexity < 0.4)
3851 }
3852 ComplexityCategory::Medium => {
3853 assert!(analysis.overall_complexity >= 0.2 && analysis.overall_complexity < 0.8)
3854 }
3855 ComplexityCategory::High => {
3856 assert!(analysis.overall_complexity >= 0.4 && analysis.overall_complexity < 1.0)
3857 }
3858 ComplexityCategory::VeryHigh => assert!(analysis.overall_complexity >= 0.8),
3859 }
3860 }
3861 }
3862
3863 #[test]
3864 fn test_complexity_weights_and_depth() {
3865 let standard_analyzer = ComplexityAnalyzer::new();
3866 let deep_analyzer = ComplexityAnalyzer::new().with_analysis_depth(AnalysisDepth::Deep);
3867 let fast_analyzer = ComplexityAnalyzer::new().with_analysis_depth(AnalysisDepth::Fast);
3868
3869 let board =
3870 Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
3871
3872 let standard_analysis = standard_analyzer.analyze_complexity_detailed(&board);
3873 let deep_analysis = deep_analyzer.analyze_complexity_detailed(&board);
3874 let fast_analysis = fast_analyzer.analyze_complexity_detailed(&board);
3875
3876 assert!(standard_analysis.overall_complexity.is_finite());
3880 assert!(deep_analysis.overall_complexity.is_finite());
3881 assert!(fast_analysis.overall_complexity.is_finite());
3882
3883 assert!(deep_analysis.overall_complexity >= 0.0 && deep_analysis.overall_complexity <= 1.0);
3885 assert!(fast_analysis.overall_complexity >= 0.0 && fast_analysis.overall_complexity <= 1.0);
3886 }
3887
3888 #[test]
3889 fn test_enhanced_game_phase_detector() {
3890 let detector = GamePhaseDetector::new()
3891 .with_phase_weights(PhaseDetectionWeights::default())
3892 .with_adaptation_settings(PhaseAdaptationSettings::default());
3893
3894 let positions = [
3896 (
3897 "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
3898 GamePhase::Opening,
3899 ),
3900 ("r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1", GamePhase::Endgame),
3901 (
3902 "r1bqk1nr/pppp1ppp/2n5/2b1p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 4 4",
3903 GamePhase::Opening,
3904 ),
3905 ];
3906
3907 for (fen, expected_phase) in &positions {
3908 let board = Board::from_str(fen).unwrap();
3909 let analysis = detector.analyze_game_phase(&board);
3910
3911 assert!(analysis.phase_confidence >= 0.0 && analysis.phase_confidence <= 1.0);
3913 assert!(analysis.opening_score >= 0.0);
3914 assert!(analysis.middlegame_score >= 0.0);
3915 assert!(analysis.endgame_score >= 0.0);
3916
3917 assert!(
3919 analysis.material_phase.confidence >= 0.0
3920 && analysis.material_phase.confidence <= 1.0
3921 );
3922 assert!(
3923 analysis.development_phase.confidence >= 0.0
3924 && analysis.development_phase.confidence <= 1.0
3925 );
3926 assert!(
3927 analysis.move_count_phase.confidence >= 0.0
3928 && analysis.move_count_phase.confidence <= 1.0
3929 );
3930
3931 let recs = &analysis.adaptation_recommendations;
3933 assert!(recs.evaluation_weights.nnue_weight >= 0.0);
3934 assert!(recs.evaluation_weights.pattern_weight >= 0.0);
3935 assert!(recs.evaluation_weights.tactical_weight >= 0.0);
3936 assert!(recs.evaluation_weights.strategic_weight >= 0.0);
3937 assert!(recs.search_depth_modifier >= -2 && recs.search_depth_modifier <= 2);
3938 assert!(recs.opening_book_priority >= 0.0 && recs.opening_book_priority <= 1.0);
3939 assert!(
3940 recs.endgame_tablebase_priority >= 0.0 && recs.endgame_tablebase_priority <= 1.0
3941 );
3942 assert!(recs.time_management_factor >= 0.5 && recs.time_management_factor <= 2.0);
3943 }
3944 }
3945
3946 #[test]
3947 fn test_phase_transition_detection() {
3948 let detector = GamePhaseDetector::new();
3949
3950 let transition_positions = [
3952 "rnbqkb1r/pppp1ppp/5n2/4p3/2B1P3/8/PPPP1PPP/RNBQK1NR w KQkq - 4 3", "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 10", ];
3955
3956 for fen in &transition_positions {
3957 let board = Board::from_str(fen).unwrap();
3958 let analysis = detector.analyze_game_phase(&board);
3959
3960 assert!(matches!(
3962 analysis.transition_state,
3963 PhaseTransition::Stable
3964 | PhaseTransition::OpeningToMiddlegame
3965 | PhaseTransition::MiddlegameToEndgame
3966 ));
3967
3968 assert!(analysis.phase_confidence >= 0.0 && analysis.phase_confidence <= 1.0);
3970 }
3971 }
3972
3973 #[test]
3974 fn test_game_phase_integration_with_hybrid_engine() {
3975 let mut engine = HybridEvaluationEngine::new();
3976
3977 let custom_weights = PhaseDetectionWeights {
3979 material_weight: 0.3,
3980 development_weight: 0.3,
3981 ..PhaseDetectionWeights::default()
3982 };
3983 let settings = PhaseAdaptationSettings {
3984 adaptation_responsiveness: 1.5,
3985 ..PhaseAdaptationSettings::default()
3986 };
3987 engine.configure_phase_detector(custom_weights, settings);
3988
3989 let board =
3990 Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
3991
3992 let phase_analysis = engine.analyze_game_phase(&board);
3994 assert!(matches!(
3995 phase_analysis.primary_phase,
3996 GamePhase::Opening | GamePhase::Middlegame | GamePhase::Endgame | GamePhase::Unknown
3997 ));
3998
3999 let adapted_weights = engine.apply_phase_adaptations(&board);
4001 let total_weight = adapted_weights.nnue_weight
4002 + adapted_weights.pattern_weight
4003 + adapted_weights.tactical_weight
4004 + adapted_weights.strategic_weight;
4005 assert!((total_weight - 1.0).abs() < 0.1); let evaluation_result = engine.evaluate_position(&board).unwrap();
4009 assert!(evaluation_result.final_evaluation.is_finite());
4010 }
4011
4012 #[test]
4013 fn test_phase_specific_adaptations() {
4014 let detector = GamePhaseDetector::new();
4015
4016 let opening_board =
4018 Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
4019 let opening_analysis = detector.analyze_game_phase(&opening_board);
4020
4021 if opening_analysis.primary_phase == GamePhase::Opening {
4022 let recs = &opening_analysis.adaptation_recommendations;
4023 assert!(recs.evaluation_weights.strategic_weight >= 0.2);
4025 assert!(recs.opening_book_priority >= 0.8);
4026 assert!(recs.endgame_tablebase_priority <= 0.2);
4027 }
4028
4029 let endgame_board = Board::from_str("8/8/8/8/8/8/4K3/4k3 w - - 0 50").unwrap();
4031 let endgame_analysis = detector.analyze_game_phase(&endgame_board);
4032
4033 if endgame_analysis.primary_phase == GamePhase::Endgame {
4034 let recs = &endgame_analysis.adaptation_recommendations;
4035 assert!(recs.evaluation_weights.nnue_weight >= 0.4);
4037 assert!(recs.endgame_tablebase_priority >= 0.8);
4038 assert!(recs.opening_book_priority <= 0.2);
4039 }
4040 }
4041
4042 #[test]
4043 fn test_phase_confidence_scoring() {
4044 let detector = GamePhaseDetector::new();
4045
4046 let clear_opening =
4048 Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();
4049 let clear_analysis = detector.analyze_game_phase(&clear_opening);
4050
4051 let ambiguous =
4053 Board::from_str("r1bqkbnr/pppp1ppp/2n5/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 4 4")
4054 .unwrap();
4055 let ambiguous_analysis = detector.analyze_game_phase(&ambiguous);
4056
4057 assert!(clear_analysis.phase_confidence >= 0.0);
4060 assert!(ambiguous_analysis.phase_confidence >= 0.0);
4061 assert!(clear_analysis.phase_confidence <= 1.0);
4062 assert!(ambiguous_analysis.phase_confidence <= 1.0);
4063 }
4064
4065 #[test]
4066 fn test_enhanced_confidence_scorer() {
4067 let scorer = ConfidenceScorer::new();
4068
4069 let evaluation_results = EvaluationResults {
4071 nnue: Some(EvaluationComponent {
4072 evaluation: 0.5,
4073 confidence: 0.8,
4074 computation_time_ms: 10,
4075 additional_info: HashMap::new(),
4076 }),
4077 pattern: Some(EvaluationComponent {
4078 evaluation: 0.6,
4079 confidence: 0.7,
4080 computation_time_ms: 15,
4081 additional_info: HashMap::new(),
4082 }),
4083 tactical: Some(EvaluationComponent {
4084 evaluation: 0.55,
4085 confidence: 0.9,
4086 computation_time_ms: 25,
4087 additional_info: {
4088 let mut info = HashMap::new();
4089 info.insert("search_depth".to_string(), 8.0);
4090 info
4091 },
4092 }),
4093 strategic: Some(EvaluationComponent {
4094 evaluation: 0.45,
4095 confidence: 0.6,
4096 computation_time_ms: 20,
4097 additional_info: {
4098 let mut info = HashMap::new();
4099 info.insert("strategic_plans_count".to_string(), 3.0);
4100 info
4101 },
4102 }),
4103 };
4104
4105 let blend_weights = BlendWeights {
4106 nnue_weight: 0.3,
4107 pattern_weight: 0.25,
4108 tactical_weight: 0.3,
4109 strategic_weight: 0.15,
4110 };
4111
4112 let position_context = PositionContext {
4113 position_hash: 12345,
4114 game_phase: GamePhase::Middlegame,
4115 has_tactical_threats: false,
4116 in_opening_book: false,
4117 material_imbalance: 1.0,
4118 complexity_score: 0.4,
4119 };
4120
4121 let analysis =
4123 scorer.compute_confidence(&evaluation_results, &blend_weights, 0.4, &position_context);
4124
4125 assert!(analysis.overall_confidence >= 0.0 && analysis.overall_confidence <= 1.0);
4127 assert!(analysis.confidence_factors.evaluator_agreement >= 0.0);
4128 assert!(analysis.confidence_factors.individual_confidence >= 0.0);
4129 assert!(analysis.confidence_factors.complexity_confidence >= 0.0);
4130 assert!(analysis.confidence_factors.pattern_clarity >= 0.0);
4131 assert!(analysis.computation_time_ms > 0);
4132
4133 match analysis.confidence_category {
4135 ConfidenceCategory::VeryHigh => assert!(analysis.overall_confidence >= 0.8),
4136 ConfidenceCategory::High => {
4137 assert!(analysis.overall_confidence >= 0.6 && analysis.overall_confidence < 0.8)
4138 }
4139 ConfidenceCategory::Medium => {
4140 assert!(analysis.overall_confidence >= 0.4 && analysis.overall_confidence < 0.6)
4141 }
4142 ConfidenceCategory::Low => {
4143 assert!(analysis.overall_confidence >= 0.2 && analysis.overall_confidence < 0.4)
4144 }
4145 ConfidenceCategory::VeryLow => assert!(analysis.overall_confidence < 0.2),
4146 }
4147
4148 assert!(!analysis.reliability_indicators.is_empty());
4150
4151 assert_eq!(analysis.agreement_analysis.pairwise_agreements.len(), 6); assert!(analysis.agreement_analysis.overall_agreement >= 0.0);
4154 assert!(analysis.agreement_analysis.evaluation_spread >= 0.0);
4155
4156 let simple_confidence =
4158 scorer.compute_simple_confidence(&evaluation_results, &blend_weights, 0.4);
4159 assert!(simple_confidence >= 0.0 && simple_confidence <= 1.0);
4160 assert!((simple_confidence - analysis.overall_confidence).abs() < 0.001);
4161 }
4162
4163 #[test]
4164 fn test_pattern_clarity_analyzer() {
4165 let analyzer = PatternClarityAnalyzer::new();
4166
4167 let high_clarity_results = EvaluationResults {
4169 nnue: Some(EvaluationComponent {
4170 evaluation: 1.2, confidence: 0.9, computation_time_ms: 10,
4173 additional_info: HashMap::new(),
4174 }),
4175 pattern: Some(EvaluationComponent {
4176 evaluation: 1.1, confidence: 0.85,
4178 computation_time_ms: 15,
4179 additional_info: HashMap::new(),
4180 }),
4181 tactical: Some(EvaluationComponent {
4182 evaluation: 1.15, confidence: 0.95,
4184 computation_time_ms: 25,
4185 additional_info: {
4186 let mut info = HashMap::new();
4187 info.insert("search_depth".to_string(), 10.0); info
4189 },
4190 }),
4191 strategic: None,
4192 };
4193
4194 let position_context = PositionContext {
4195 position_hash: 54321,
4196 game_phase: GamePhase::Middlegame,
4197 has_tactical_threats: true,
4198 in_opening_book: false,
4199 material_imbalance: 0.5,
4200 complexity_score: 0.3,
4201 };
4202
4203 let clarity_result =
4204 analyzer.analyze_pattern_clarity(&high_clarity_results, &position_context);
4205
4206 assert!(clarity_result.overall_clarity > 0.5);
4208 assert!(!clarity_result.clarity_factors.is_empty());
4209 assert!(!clarity_result.position_characteristics.is_empty());
4210
4211 assert!(clarity_result
4213 .position_characteristics
4214 .contains(&"tactical_position".to_string()));
4215 assert!(clarity_result
4216 .position_characteristics
4217 .contains(&"middlegame_phase".to_string()));
4218
4219 let evaluator_names: Vec<String> = clarity_result
4221 .clarity_factors
4222 .iter()
4223 .map(|cf| cf.evaluator.clone())
4224 .collect();
4225 assert!(evaluator_names.contains(&"NNUE".to_string()));
4226 assert!(evaluator_names.contains(&"Pattern".to_string()));
4227 assert!(evaluator_names.contains(&"Tactical".to_string()));
4228 }
4229
4230 #[test]
4231 fn test_evaluator_accuracy_tracker() {
4232 let mut tracker = EvaluatorAccuracyTracker::new();
4233
4234 let combination = EvaluatorCombination {
4236 has_nnue: true,
4237 has_pattern: true,
4238 has_tactical: false,
4239 has_strategic: false,
4240 weight_signature: "n0.5p0.5t0.0s0.0".to_string(),
4241 };
4242
4243 let context_hash = 98765;
4244
4245 tracker.record_accuracy(&combination, context_hash, 0.8);
4247 tracker.record_accuracy(&combination, context_hash, 0.75);
4248 tracker.record_accuracy(&combination, context_hash, 0.85);
4249
4250 let avg_accuracy = tracker.get_historical_accuracy(&combination, context_hash);
4252 assert!(avg_accuracy.is_some());
4253 let accuracy = avg_accuracy.unwrap();
4254 assert!((accuracy - 0.8).abs() < 0.05); assert_eq!(tracker.get_total_entries(), 3);
4258
4259 let unknown_combination = EvaluatorCombination {
4261 has_nnue: false,
4262 has_pattern: false,
4263 has_tactical: true,
4264 has_strategic: true,
4265 weight_signature: "n0.0p0.0t0.7s0.3".to_string(),
4266 };
4267
4268 let unknown_accuracy = tracker.get_historical_accuracy(&unknown_combination, context_hash);
4269 assert!(unknown_accuracy.is_none());
4270 }
4271
4272 #[test]
4273 fn test_confidence_calibration_settings() {
4274 let default_settings = ConfidenceCalibrationSettings::default();
4276
4277 let weights = &default_settings.factor_weights;
4279 let total_weight = weights.agreement_weight
4280 + weights.individual_weight
4281 + weights.complexity_weight
4282 + weights.pattern_clarity_weight
4283 + weights.historical_weight
4284 + weights.coverage_weight
4285 + weights.temporal_weight;
4286 assert!((total_weight - 1.0).abs() < 0.01);
4287
4288 let custom_weights = FactorWeights {
4290 agreement_weight: 0.4,
4291 individual_weight: 0.3,
4292 complexity_weight: 0.2,
4293 pattern_clarity_weight: 0.1,
4294 historical_weight: 0.0,
4295 coverage_weight: 0.0,
4296 temporal_weight: 0.0,
4297 };
4298
4299 let custom_settings = ConfidenceCalibrationSettings {
4300 calibration_curve: CalibrationCurve::Conservative,
4301 factor_weights: custom_weights,
4302 };
4303
4304 let scorer = ConfidenceScorer::new().with_calibration_settings(custom_settings);
4306
4307 let evaluation_results = EvaluationResults {
4309 nnue: Some(EvaluationComponent {
4310 evaluation: 0.5,
4311 confidence: 0.7,
4312 computation_time_ms: 10,
4313 additional_info: HashMap::new(),
4314 }),
4315 pattern: None,
4316 tactical: None,
4317 strategic: None,
4318 };
4319
4320 let blend_weights = BlendWeights {
4321 nnue_weight: 1.0,
4322 pattern_weight: 0.0,
4323 tactical_weight: 0.0,
4324 strategic_weight: 0.0,
4325 };
4326
4327 let position_context = PositionContext::default();
4328 let analysis =
4329 scorer.compute_confidence(&evaluation_results, &blend_weights, 0.3, &position_context);
4330
4331 assert!(analysis.overall_confidence >= 0.0 && analysis.overall_confidence <= 1.0);
4333 }
4334
4335 #[test]
4336 fn test_confidence_history_tracking() {
4337 let scorer = ConfidenceScorer::new();
4338
4339 let initial_stats = scorer.get_statistics();
4341 assert_eq!(initial_stats.total_confidence_analyses, 0);
4342
4343 let evaluation_results = EvaluationResults {
4345 nnue: Some(EvaluationComponent {
4346 evaluation: 0.3,
4347 confidence: 0.6,
4348 computation_time_ms: 12,
4349 additional_info: HashMap::new(),
4350 }),
4351 pattern: None,
4352 tactical: None,
4353 strategic: None,
4354 };
4355
4356 let blend_weights = BlendWeights {
4357 nnue_weight: 1.0,
4358 pattern_weight: 0.0,
4359 tactical_weight: 0.0,
4360 strategic_weight: 0.0,
4361 };
4362
4363 let position_context = PositionContext::default();
4364
4365 for _ in 0..5 {
4367 let _analysis = scorer.compute_confidence(
4368 &evaluation_results,
4369 &blend_weights,
4370 0.2,
4371 &position_context,
4372 );
4373 }
4374
4375 let updated_stats = scorer.get_statistics();
4377 assert_eq!(updated_stats.total_confidence_analyses, 5);
4378 assert!(updated_stats.average_confidence >= 0.0 && updated_stats.average_confidence <= 1.0);
4379 }
4380}