chess_vector_engine/
strategic_evaluator_lazy.rs

1//! Enhanced strategic evaluator with lazy-loaded motif database
2//! 
3//! This module extends the strategic evaluator to use lazy-loaded strategic motifs,
4//! providing master-level strategic insights while minimizing memory usage through
5//! on-demand pattern loading.
6
7use crate::strategic_evaluator::{StrategicEvaluator, StrategicConfig, StrategicEvaluation};
8use crate::utils::lazy_motifs::{LazyStrategicDatabase, LazyLoadConfig, LazyLoadStats};
9use crate::strategic_motifs::MotifMatch;
10use crate::strategic_motifs::{GamePhase, MotifType};
11use chess::{Board, ChessMove};
12use std::path::Path;
13use std::sync::Arc;
14
15/// Enhanced strategic evaluator with lazy-loaded strategic motifs
16pub struct LazyStrategicEvaluator {
17    /// Core strategic evaluator for basic evaluation
18    core_evaluator: StrategicEvaluator,
19    /// Lazy-loading motif database
20    motif_database: Arc<LazyStrategicDatabase>,
21    /// Configuration for lazy loading
22    lazy_config: LazyLoadConfig,
23    /// Enable/disable motif-based evaluation
24    use_motifs: bool,
25}
26
27/// Enhanced strategic evaluation with motif insights
28#[derive(Debug, Clone)]
29pub struct EnhancedStrategicEvaluation {
30    /// Core strategic evaluation
31    pub core_evaluation: StrategicEvaluation,
32    /// Matched strategic motifs
33    pub motif_matches: Vec<MotifMatch>,
34    /// Strategic adjustment from motifs
35    pub motif_adjustment: f32,
36    /// Confidence in motif-based recommendations
37    pub motif_confidence: f32,
38    /// Game phase detected
39    pub detected_phase: GamePhase,
40}
41
42impl LazyStrategicEvaluator {
43    /// Create new lazy strategic evaluator
44    pub fn new<P: AsRef<Path>>(
45        config: StrategicConfig,
46        motif_database_path: P,
47        lazy_config: LazyLoadConfig,
48    ) -> Result<Self, Box<dyn std::error::Error>> {
49        let core_evaluator = StrategicEvaluator::new(config);
50        
51        let motif_database = match LazyStrategicDatabase::new(motif_database_path, lazy_config.clone()) {
52            Ok(db) => Arc::new(db),
53            Err(e) => {
54                // If motif database fails to load, continue without it
55                eprintln!("Warning: Failed to load motif database: {}", e);
56                return Ok(Self {
57                    core_evaluator,
58                    motif_database: Arc::new(Self::create_empty_database()?),
59                    lazy_config,
60                    use_motifs: false,
61                });
62            }
63        };
64
65        Ok(Self {
66            core_evaluator,
67            motif_database,
68            lazy_config,
69            use_motifs: true,
70        })
71    }
72
73    /// Create empty database as fallback
74    fn create_empty_database() -> Result<LazyStrategicDatabase, Box<dyn std::error::Error>> {
75        use std::env;
76        use crate::utils::lazy_motifs::MotifSegmentBuilder;
77        
78        // Use a temporary directory in system temp
79        let temp_dir_path = env::temp_dir().join("chess_empty_motifs");
80        std::fs::create_dir_all(&temp_dir_path)?;
81        
82        let config = LazyLoadConfig::default();
83        
84        // Create empty index
85        let builder = MotifSegmentBuilder::new(&temp_dir_path, config.clone());
86        let _index = builder.create_segments(Vec::new(), 100)?;
87        
88        LazyStrategicDatabase::new(&temp_dir_path, config)
89    }
90
91    /// Evaluate position with enhanced strategic analysis including motifs
92    pub fn evaluate_position(&self, board: &Board) -> Result<EnhancedStrategicEvaluation, Box<dyn std::error::Error>> {
93        // Core strategic evaluation
94        let core_evaluation = self.core_evaluator.evaluate_strategic(board);
95        
96        if !self.use_motifs {
97            return Ok(EnhancedStrategicEvaluation {
98                core_evaluation,
99                motif_matches: Vec::new(),
100                motif_adjustment: 0.0,
101                motif_confidence: 0.0,
102                detected_phase: GamePhase::Any,
103            });
104        }
105
106        // Detect game phase for targeted motif loading
107        let detected_phase = self.detect_game_phase(board);
108        
109        // Find relevant strategic motifs
110        let motif_matches = self.motif_database.evaluate_position(board)?;
111        
112        // Calculate motif-based adjustments
113        let (motif_adjustment, motif_confidence) = self.calculate_motif_adjustments(&motif_matches);
114        
115        Ok(EnhancedStrategicEvaluation {
116            core_evaluation,
117            motif_matches,
118            motif_adjustment,
119            motif_confidence,
120            detected_phase,
121        })
122    }
123
124    /// Evaluate a specific move with strategic motifs
125    pub fn evaluate_move(&self, board: &Board, chess_move: &ChessMove) -> Result<f32, Box<dyn std::error::Error>> {
126        // Make the move to create the resulting position
127        let mut new_board = *board;
128        new_board = new_board.make_move_new(*chess_move);
129        
130        // Evaluate the resulting position
131        let evaluation = self.evaluate_position(&new_board)?;
132        
133        // Combine core and motif evaluations
134        let total_adjustment = evaluation.core_evaluation.total_evaluation + evaluation.motif_adjustment;
135        
136        Ok(total_adjustment)
137    }
138
139    /// Get strategic motifs for a specific game phase (preloading optimization)
140    pub fn preload_phase_motifs(&self, phase: GamePhase) -> Result<usize, Box<dyn std::error::Error>> {
141        if !self.use_motifs {
142            return Ok(0);
143        }
144        
145        self.motif_database.preload_phase(phase)
146    }
147
148    /// Find strategic motifs matching specific criteria
149    pub fn find_motifs_by_type(&self, _motif_type: &MotifType) -> Result<Vec<MotifMatch>, Box<dyn std::error::Error>> {
150        if !self.use_motifs {
151            return Ok(Vec::new());
152        }
153
154        // This would require extending the motif database with type-based queries
155        // For now, return empty - would be implemented with more sophisticated indexing
156        Ok(Vec::new())
157    }
158
159    /// Analyze position for specific strategic themes
160    pub fn analyze_strategic_themes(&self, board: &Board) -> Result<StrategicThemeAnalysis, Box<dyn std::error::Error>> {
161        let evaluation = self.evaluate_position(board)?;
162        
163        let mut themes = StrategicThemeAnalysis::new();
164        
165        // Analyze motifs by type
166        for motif_match in &evaluation.motif_matches {
167            match &motif_match.motif.motif_type {
168                MotifType::PawnStructure(_) => {
169                    themes.pawn_themes.push(motif_match.clone());
170                }
171                MotifType::PieceCoordination(_) => {
172                    themes.piece_coordination_themes.push(motif_match.clone());
173                }
174                MotifType::KingSafety(_) => {
175                    themes.king_safety_themes.push(motif_match.clone());
176                }
177                MotifType::Initiative(_) => {
178                    themes.initiative_themes.push(motif_match.clone());
179                }
180                MotifType::Endgame(_) => {
181                    themes.endgame_themes.push(motif_match.clone());
182                }
183                MotifType::Opening(_) => {
184                    themes.opening_themes.push(motif_match.clone());
185                }
186            }
187        }
188        
189        Ok(themes)
190    }
191
192    /// Get performance statistics for the lazy loading system
193    pub fn get_lazy_load_stats(&self) -> LazyLoadStats {
194        self.motif_database.get_stats()
195    }
196
197    /// Clear motif caches (for memory management)
198    pub fn clear_motif_caches(&self) {
199        self.motif_database.clear_caches();
200    }
201
202    /// Get total number of available motifs
203    pub fn total_available_motifs(&self) -> usize {
204        self.motif_database.total_motifs()
205    }
206
207    /// Get number of currently cached motifs
208    pub fn cached_motifs_count(&self) -> usize {
209        self.motif_database.cached_motifs()
210    }
211
212    /// Detect the current game phase for targeted motif loading
213    fn detect_game_phase(&self, board: &Board) -> GamePhase {
214        let piece_count = board.combined().popcnt();
215        
216        if piece_count > 28 {
217            GamePhase::Opening
218        } else if piece_count > 12 {
219            GamePhase::Middlegame
220        } else {
221            GamePhase::Endgame
222        }
223    }
224
225    /// Calculate strategic adjustments based on matched motifs
226    fn calculate_motif_adjustments(&self, motif_matches: &[MotifMatch]) -> (f32, f32) {
227        if motif_matches.is_empty() {
228            return (0.0, 0.0);
229        }
230
231        let mut total_adjustment = 0.0;
232        let mut total_confidence = 0.0;
233        let mut weight_sum = 0.0;
234
235        for motif_match in motif_matches {
236            let weight = motif_match.relevance * motif_match.motif.confidence;
237            total_adjustment += motif_match.motif.evaluation * weight;
238            total_confidence += motif_match.motif.confidence * weight;
239            weight_sum += weight;
240        }
241
242        if weight_sum > 0.0 {
243            total_adjustment /= weight_sum;
244            total_confidence /= weight_sum;
245        }
246
247        // Cap adjustments to reasonable bounds
248        total_adjustment = total_adjustment.clamp(-1.0, 1.0);
249        total_confidence = total_confidence.clamp(0.0, 1.0);
250
251        (total_adjustment, total_confidence)
252    }
253}
254
255/// Analysis of strategic themes found in a position
256#[derive(Debug, Clone, Default)]
257pub struct StrategicThemeAnalysis {
258    pub pawn_themes: Vec<MotifMatch>,
259    pub piece_coordination_themes: Vec<MotifMatch>,
260    pub king_safety_themes: Vec<MotifMatch>,
261    pub initiative_themes: Vec<MotifMatch>,
262    pub endgame_themes: Vec<MotifMatch>,
263    pub opening_themes: Vec<MotifMatch>,
264}
265
266impl StrategicThemeAnalysis {
267    pub fn new() -> Self {
268        Self::default()
269    }
270
271    /// Get the most relevant theme in this position
272    pub fn primary_theme(&self) -> Option<&MotifMatch> {
273        let all_themes: Vec<&MotifMatch> = [
274            &self.pawn_themes,
275            &self.piece_coordination_themes,
276            &self.king_safety_themes,
277            &self.initiative_themes,
278            &self.endgame_themes,
279            &self.opening_themes,
280        ]
281        .iter()
282        .flat_map(|themes| themes.iter())
283        .collect();
284
285        all_themes.into_iter().max_by(|a, b| {
286            a.relevance.partial_cmp(&b.relevance).unwrap_or(std::cmp::Ordering::Equal)
287        })
288    }
289
290    /// Get total number of strategic themes identified
291    pub fn theme_count(&self) -> usize {
292        self.pawn_themes.len()
293            + self.piece_coordination_themes.len()
294            + self.king_safety_themes.len()
295            + self.initiative_themes.len()
296            + self.endgame_themes.len()
297            + self.opening_themes.len()
298    }
299
300    /// Get strategic recommendations based on identified themes
301    pub fn get_recommendations(&self) -> Vec<String> {
302        let mut recommendations = Vec::new();
303
304        if let Some(primary) = self.primary_theme() {
305            recommendations.push(primary.motif.description.clone());
306        }
307
308        // Add specific recommendations based on theme types
309        if !self.pawn_themes.is_empty() {
310            recommendations.push("Consider pawn structure improvements".to_string());
311        }
312
313        if !self.king_safety_themes.is_empty() {
314            recommendations.push("Focus on king safety measures".to_string());
315        }
316
317        if !self.initiative_themes.is_empty() {
318            recommendations.push("Maintain initiative and create pressure".to_string());
319        }
320
321        recommendations
322    }
323}
324
325#[cfg(test)]
326mod tests {
327    use super::*;
328    use chess::Board;
329    use std::str::FromStr;
330    use std::env;
331
332    #[test]
333    fn test_lazy_strategic_evaluator_creation() {
334        let temp_dir_path = env::temp_dir().join("chess_test_lazy");
335        std::fs::create_dir_all(&temp_dir_path).unwrap();
336        let config = StrategicConfig::default();
337        let lazy_config = LazyLoadConfig::default();
338        
339        let evaluator = LazyStrategicEvaluator::new(config, &temp_dir_path, lazy_config);
340        
341        // Should succeed even with empty database
342        assert!(evaluator.is_ok());
343    }
344
345    #[test]
346    fn test_game_phase_detection() {
347        let temp_dir_path = env::temp_dir().join("chess_test_phase");
348        std::fs::create_dir_all(&temp_dir_path).unwrap();
349        let config = StrategicConfig::default();
350        let lazy_config = LazyLoadConfig::default();
351        
352        let evaluator = LazyStrategicEvaluator::new(config, &temp_dir_path, lazy_config).unwrap();
353        
354        // Test starting position (should be opening)
355        let board = Board::default();
356        let phase = evaluator.detect_game_phase(&board);
357        matches!(phase, GamePhase::Opening);
358        
359        // Test endgame position
360        let endgame_fen = "8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 1";
361        let endgame_board = Board::from_str(endgame_fen).unwrap();
362        let endgame_phase = evaluator.detect_game_phase(&endgame_board);
363        matches!(endgame_phase, GamePhase::Endgame);
364    }
365
366    #[test]
367    fn test_strategic_theme_analysis() {
368        let analysis = StrategicThemeAnalysis::new();
369        assert_eq!(analysis.theme_count(), 0);
370        
371        let recommendations = analysis.get_recommendations();
372        assert!(recommendations.is_empty());
373    }
374}