Skip to main content

oxirs_arq/advanced_optimizer/
ml_predictor_training.rs

1//! ML predictor: core `MLPredictor` orchestration, training, and prediction logic.
2//!
3//! This module owns the `MLPredictor` struct that ties feature extraction,
4//! the regression model, online learning, prediction caching, and histogram-
5//! based cardinality estimation together. It also defines `TrainingExample`,
6//! which is shared with `ml_predictor_features` for histogram construction.
7//!
8//! Split from `ml_predictor.rs` to keep the file below the workspace
9//! 2000-line refactor threshold.
10
11use std::collections::HashMap;
12use std::path::Path;
13use std::sync::Arc;
14use std::time::SystemTime;
15
16use anyhow::{Context, Result};
17use serde::{Deserialize, Serialize};
18
19// SciRS2 imports for ML and statistics
20use scirs2_core::metrics::{Counter, Histogram, MetricsRegistry, Timer};
21use scirs2_core::ndarray_ext::{Array1, Array2};
22use scirs2_stats::regression::{linear_regression, ridge_regression, RegressionResults};
23
24use crate::algebra::Algebra;
25
26use super::ml_predictor_features::{
27    FeatureExtractor, HistogramCardinalityEstimator, HistogramConfig, HistogramStatistics,
28    NormalizationParams, QueryCharacteristics,
29};
30use super::ml_predictor_model::{
31    AccuracyMetrics, MLConfig, MLModel, MLModelType, MLPrediction, OptimizationRecommendation,
32    SerializableRegressionResults,
33};
34
35// ---------------------------------------------------------------------------
36// Training example
37// ---------------------------------------------------------------------------
38
39/// Training example for ML model
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct TrainingExample {
42    pub features: Vec<f64>,
43    pub target_cost: f64,
44    pub actual_cost: f64,
45    pub query_characteristics: QueryCharacteristics,
46    pub timestamp: SystemTime,
47}
48
49// ---------------------------------------------------------------------------
50// Machine learning predictor
51// ---------------------------------------------------------------------------
52
53/// Machine learning predictor for optimization decisions
54pub struct MLPredictor {
55    pub(super) model: MLModel,
56    pub(super) training_data: Vec<TrainingExample>,
57    pub(super) feature_extractor: FeatureExtractor,
58    pub(super) prediction_cache: HashMap<u64, MLPrediction>,
59    pub(super) config: MLConfig,
60    pub(super) metrics_collector: Arc<MetricsRegistry>,
61    pub(super) last_training: Option<SystemTime>,
62    pub(super) histogram_estimator: HistogramCardinalityEstimator,
63
64    // Metrics
65    pub(super) prediction_counter: Counter,
66    pub(super) prediction_timer: Timer,
67    pub(super) prediction_histogram: Histogram,
68}
69
70impl MLPredictor {
71    /// Create a new ML predictor with configuration
72    pub fn new(config: MLConfig) -> Result<Self> {
73        let metrics_collector = Arc::new(MetricsRegistry::new());
74
75        let prediction_counter = Counter::new("ml_predictor_predictions_total".to_string());
76        let prediction_timer = Timer::new("ml_predictor_prediction_duration_seconds".to_string());
77        let prediction_histogram =
78            Histogram::new("ml_predictor_prediction_distribution".to_string());
79
80        Ok(Self {
81            model: MLModel {
82                regression_results: None,
83                model_type: config.model_type.clone(),
84                accuracy_metrics: AccuracyMetrics {
85                    mean_absolute_error: 0.0,
86                    root_mean_square_error: 0.0,
87                    r_squared: 0.0,
88                    confidence_interval: (0.0, 0.0),
89                },
90                normalization_params: None,
91            },
92            training_data: Vec::new(),
93            feature_extractor: FeatureExtractor::new(),
94            prediction_cache: HashMap::new(),
95            config,
96            metrics_collector,
97            last_training: None,
98            histogram_estimator: HistogramCardinalityEstimator::new(HistogramConfig::default()),
99            prediction_counter,
100            prediction_timer,
101            prediction_histogram,
102        })
103    }
104
105    /// Create predictor from model type (backward compatibility)
106    pub fn from_model_type(model_type: MLModelType) -> Result<Self> {
107        let config = MLConfig {
108            model_type,
109            ..Default::default()
110        };
111        Self::new(config)
112    }
113
114    /// Load model from disk
115    pub fn load_model(path: &Path) -> Result<Self> {
116        let contents = std::fs::read_to_string(path)
117            .with_context(|| format!("Failed to read model from {:?}", path))?;
118
119        let predictor: MLPredictor = serde_json::from_str(&contents)
120            .with_context(|| format!("Failed to deserialize model from {:?}", path))?;
121
122        Ok(predictor)
123    }
124
125    /// Save model to disk
126    pub fn save_model(&self, path: &Path) -> Result<()> {
127        // Create parent directory if it doesn't exist
128        if let Some(parent) = path.parent() {
129            std::fs::create_dir_all(parent)
130                .with_context(|| format!("Failed to create directory {:?}", parent))?;
131        }
132
133        let contents = serde_json::to_string_pretty(self).context("Failed to serialize model")?;
134
135        std::fs::write(path, contents)
136            .with_context(|| format!("Failed to write model to {:?}", path))?;
137
138        Ok(())
139    }
140
141    /// Get model confidence score (0.0-1.0)
142    pub fn confidence(&self) -> f64 {
143        // Confidence based on R² score
144        let r_squared = self.model.accuracy_metrics.r_squared;
145        r_squared.clamp(0.0, 1.0)
146    }
147
148    /// Check if ML model should be used
149    pub fn should_use_ml(&self) -> bool {
150        self.confidence() >= self.config.confidence_threshold
151            && self.model.regression_results.is_some()
152    }
153
154    /// Extract 13 features from query plan
155    pub fn extract_features(&self, query: &Algebra) -> Vec<f64> {
156        let mut features = Vec::with_capacity(13);
157
158        // Extract structural features
159        let characteristics = self.analyze_query_structure(query);
160
161        // 1. Number of triple patterns
162        features.push(characteristics.triple_pattern_count as f64);
163
164        // 2. Number of joins
165        features.push(characteristics.join_count as f64);
166
167        // 3. Number of filters
168        features.push(characteristics.filter_count as f64);
169
170        // 4. Number of optional patterns
171        features.push(characteristics.optional_count as f64);
172
173        // 5. Has aggregation
174        features.push(if characteristics.has_aggregation {
175            1.0
176        } else {
177            0.0
178        });
179
180        // 6. Has sorting
181        features.push(if characteristics.has_sorting {
182            1.0
183        } else {
184            0.0
185        });
186
187        // 7. Estimated cardinality
188        features.push(characteristics.estimated_cardinality as f64);
189
190        // 8. Query graph diameter
191        features.push(characteristics.query_graph_diameter as f64);
192
193        // 9. Average degree
194        features.push(characteristics.avg_degree);
195
196        // 10. Max degree
197        features.push(characteristics.max_degree as f64);
198
199        // 11. Has cross product (detected from join pattern)
200        features.push(self.calculate_cross_product_likelihood(query));
201
202        // 12. Subquery depth
203        features.push(self.calculate_subquery_depth(query));
204
205        // 13. Aggregation complexity
206        features.push(self.calculate_aggregation_complexity(query));
207
208        // Normalize features if enabled
209        if self.config.feature_normalization {
210            self.normalize_features(features)
211        } else {
212            features
213        }
214    }
215
216    /// Analyze query structure to extract characteristics
217    fn analyze_query_structure(&self, query: &Algebra) -> QueryCharacteristics {
218        let mut characteristics = QueryCharacteristics {
219            triple_pattern_count: 0,
220            join_count: 0,
221            filter_count: 0,
222            optional_count: 0,
223            has_aggregation: false,
224            has_sorting: false,
225            estimated_cardinality: 1000,
226            complexity_score: 0.0,
227            query_graph_diameter: 1,
228            avg_degree: 1.0,
229            max_degree: 1,
230        };
231
232        self.traverse_algebra(query, &mut characteristics);
233
234        // Calculate derived metrics
235        characteristics.complexity_score = self.calculate_complexity_score(&characteristics);
236        characteristics.query_graph_diameter = self.calculate_graph_diameter(&characteristics);
237        let (avg_deg, max_deg) = self.calculate_degree_metrics(&characteristics);
238        characteristics.avg_degree = avg_deg;
239        characteristics.max_degree = max_deg;
240
241        characteristics
242    }
243
244    /// Traverse algebra tree to extract features
245    fn traverse_algebra(&self, algebra: &Algebra, characteristics: &mut QueryCharacteristics) {
246        use crate::algebra::Algebra;
247
248        match algebra {
249            Algebra::Service { .. } => characteristics.triple_pattern_count += 1,
250            Algebra::PropertyPath { .. } => characteristics.triple_pattern_count += 1,
251            Algebra::Join { left, right, .. } => {
252                characteristics.join_count += 1;
253                self.traverse_algebra(left, characteristics);
254                self.traverse_algebra(right, characteristics);
255            }
256            Algebra::LeftJoin { left, right, .. } => {
257                characteristics.join_count += 1;
258                characteristics.optional_count += 1;
259                self.traverse_algebra(left, characteristics);
260                self.traverse_algebra(right, characteristics);
261            }
262            Algebra::Filter { pattern, .. } => {
263                characteristics.filter_count += 1;
264                self.traverse_algebra(pattern, characteristics);
265            }
266            Algebra::Union { left, right } => {
267                self.traverse_algebra(left, characteristics);
268                self.traverse_algebra(right, characteristics);
269            }
270            Algebra::Extend { pattern, .. } => {
271                self.traverse_algebra(pattern, characteristics);
272            }
273            Algebra::OrderBy { pattern, .. } => {
274                characteristics.has_sorting = true;
275                self.traverse_algebra(pattern, characteristics);
276            }
277            Algebra::Project { pattern, .. } => {
278                self.traverse_algebra(pattern, characteristics);
279            }
280            Algebra::Distinct { pattern } => {
281                self.traverse_algebra(pattern, characteristics);
282            }
283            Algebra::Reduced { pattern } => {
284                self.traverse_algebra(pattern, characteristics);
285            }
286            Algebra::Slice { pattern, .. } => {
287                self.traverse_algebra(pattern, characteristics);
288            }
289            Algebra::Group { pattern, .. } => {
290                characteristics.has_aggregation = true;
291                self.traverse_algebra(pattern, characteristics);
292            }
293            _ => {}
294        }
295    }
296
297    /// Calculate complexity score from characteristics
298    fn calculate_complexity_score(&self, characteristics: &QueryCharacteristics) -> f64 {
299        let mut score = 0.0;
300
301        // Exponential growth for joins (most expensive operation)
302        score += (characteristics.join_count as f64).powi(2) * 3.0;
303
304        // Linear growth for triple patterns
305        score += characteristics.triple_pattern_count as f64 * 1.0;
306
307        // Filter complexity
308        score += characteristics.filter_count as f64 * 0.5;
309
310        // Optional patterns
311        score += characteristics.optional_count as f64 * 2.0;
312
313        // Aggregation penalty
314        if characteristics.has_aggregation {
315            score += 5.0;
316        }
317
318        // Sorting penalty
319        if characteristics.has_sorting {
320            score += 2.0;
321        }
322
323        // Cardinality factor
324        let cardinality_log = (characteristics.estimated_cardinality as f64)
325            .log10()
326            .max(1.0);
327        score *= cardinality_log;
328
329        score
330    }
331
332    /// Calculate query graph diameter
333    fn calculate_graph_diameter(&self, characteristics: &QueryCharacteristics) -> usize {
334        // Simplified diameter calculation based on structure
335        if characteristics.join_count == 0 {
336            1
337        } else {
338            (characteristics.join_count as f64).sqrt().ceil() as usize + 1
339        }
340    }
341
342    /// Calculate degree metrics (average, max)
343    fn calculate_degree_metrics(&self, characteristics: &QueryCharacteristics) -> (f64, usize) {
344        if characteristics.triple_pattern_count == 0 {
345            return (0.0, 0);
346        }
347
348        let avg_degree = if characteristics.triple_pattern_count > 0 {
349            (characteristics.join_count as f64 * 2.0) / characteristics.triple_pattern_count as f64
350        } else {
351            0.0
352        };
353
354        let max_degree = characteristics
355            .join_count
356            .min(characteristics.triple_pattern_count);
357
358        (avg_degree, max_degree)
359    }
360
361    /// Calculate cross product likelihood
362    fn calculate_cross_product_likelihood(&self, _query: &Algebra) -> f64 {
363        // Simplified heuristic - would need join graph analysis for accuracy
364        0.0
365    }
366
367    /// Calculate subquery nesting depth
368    fn calculate_subquery_depth(&self, algebra: &Algebra) -> f64 {
369        self.calculate_depth_recursive(algebra, 0) as f64
370    }
371
372    fn calculate_depth_recursive(&self, algebra: &Algebra, current_depth: usize) -> usize {
373        use crate::algebra::Algebra;
374
375        match algebra {
376            Algebra::Join { left, right, .. }
377            | Algebra::LeftJoin { left, right, .. }
378            | Algebra::Union { left, right } => {
379                let left_depth = self.calculate_depth_recursive(left, current_depth + 1);
380                let right_depth = self.calculate_depth_recursive(right, current_depth + 1);
381                left_depth.max(right_depth)
382            }
383            Algebra::Filter { pattern, .. }
384            | Algebra::Extend { pattern, .. }
385            | Algebra::OrderBy { pattern, .. }
386            | Algebra::Project { pattern, .. }
387            | Algebra::Distinct { pattern }
388            | Algebra::Reduced { pattern }
389            | Algebra::Slice { pattern, .. }
390            | Algebra::Group { pattern, .. } => {
391                self.calculate_depth_recursive(pattern, current_depth + 1)
392            }
393            _ => current_depth,
394        }
395    }
396
397    /// Calculate aggregation complexity
398    fn calculate_aggregation_complexity(&self, algebra: &Algebra) -> f64 {
399        self.count_aggregations(algebra) as f64
400    }
401
402    fn count_aggregations(&self, algebra: &Algebra) -> usize {
403        use crate::algebra::Algebra;
404
405        match algebra {
406            Algebra::Group { .. } => 1,
407            Algebra::Join { left, right, .. }
408            | Algebra::LeftJoin { left, right, .. }
409            | Algebra::Union { left, right } => {
410                self.count_aggregations(left) + self.count_aggregations(right)
411            }
412            Algebra::Filter { pattern, .. }
413            | Algebra::Extend { pattern, .. }
414            | Algebra::OrderBy { pattern, .. }
415            | Algebra::Project { pattern, .. }
416            | Algebra::Distinct { pattern }
417            | Algebra::Reduced { pattern }
418            | Algebra::Slice { pattern, .. } => self.count_aggregations(pattern),
419            _ => 0,
420        }
421    }
422
423    /// Normalize features using z-score normalization
424    fn normalize_features(&self, features: Vec<f64>) -> Vec<f64> {
425        if let Some(params) = self.feature_extractor.normalization_params.as_ref() {
426            features
427                .iter()
428                .enumerate()
429                .map(|(i, &value)| {
430                    let mean_slice = params.mean();
431                    let std_slice = params.std_dev();
432                    if i < mean_slice.len() && i < std_slice.len() {
433                        let mean = mean_slice[i];
434                        let std_dev = std_slice[i];
435                        if std_dev > 1e-10 {
436                            (value - mean) / std_dev
437                        } else {
438                            value
439                        }
440                    } else {
441                        value
442                    }
443                })
444                .collect()
445        } else {
446            features
447        }
448    }
449
450    /// Make cost prediction
451    pub fn predict_cost(&mut self, query: &Algebra) -> Result<MLPrediction> {
452        let _guard = self.prediction_timer.start();
453        self.prediction_counter.inc();
454
455        let features = self.extract_features(query);
456        let query_hash = self.hash_query(query);
457
458        // Check cache
459        if let Some(cached) = self.prediction_cache.get(&query_hash) {
460            return Ok(cached.clone());
461        }
462
463        // Make prediction
464        let (predicted_cost, confidence) = if self.should_use_ml() {
465            self.predict_with_model(&features)?
466        } else {
467            self.heuristic_prediction(&features)?
468        };
469
470        self.prediction_histogram.observe(predicted_cost);
471
472        // Generate recommendations and feature importance
473        let recommendation = self.generate_recommendation(&features, predicted_cost);
474        let feature_importance = self.calculate_feature_importance(&features);
475
476        let prediction = MLPrediction {
477            predicted_cost,
478            confidence,
479            recommendation,
480            feature_importance,
481        };
482
483        // Cache prediction
484        self.prediction_cache.insert(query_hash, prediction.clone());
485
486        Ok(prediction)
487    }
488
489    /// Predict using trained model
490    fn predict_with_model(&self, features: &[f64]) -> Result<(f64, f64)> {
491        if let Some(ref results) = self.model.regression_results {
492            // Linear prediction: y = b0 + b1*x1 + b2*x2 + ... + bn*xn
493            let mut prediction = 0.0;
494
495            for (i, &coef) in results.coefficients.iter().enumerate() {
496                if i < features.len() {
497                    prediction += coef * features[i];
498                } else if i == features.len() {
499                    // Intercept (if present as last coefficient)
500                    prediction += coef;
501                }
502            }
503
504            // Ensure non-negative prediction
505            prediction = prediction.max(0.1);
506
507            // Confidence from R²
508            let confidence = self.confidence();
509
510            Ok((prediction, confidence))
511        } else {
512            // Fall back to heuristic if model not trained
513            self.heuristic_prediction(features)
514        }
515    }
516
517    /// Heuristic-based prediction when ML model is not available
518    fn heuristic_prediction(&self, features: &[f64]) -> Result<(f64, f64)> {
519        let mut cost = 0.0;
520
521        if features.len() >= 13 {
522            let triple_patterns = features[0];
523            let joins = features[1];
524            let filters = features[2];
525            let optional = features[3];
526            let has_aggregation = features[4];
527            let has_sorting = features[5];
528            let cardinality = features[6];
529
530            // Heuristic cost calculation
531            cost += triple_patterns * 10.0;
532            cost += joins * joins * 50.0;
533            cost += filters * 5.0;
534            cost += optional * 15.0;
535            cost += has_aggregation * 100.0;
536            cost += has_sorting * 20.0;
537            cost += (cardinality / 1000.0) * 2.0;
538        }
539
540        cost = cost.max(1.0);
541        let confidence = 0.5; // Lower confidence for heuristic
542
543        Ok((cost, confidence))
544    }
545
546    /// Generate optimization recommendations
547    fn generate_recommendation(
548        &self,
549        features: &[f64],
550        predicted_cost: f64,
551    ) -> OptimizationRecommendation {
552        if features.len() < 7 {
553            return OptimizationRecommendation::NoChange;
554        }
555
556        let joins = features[1];
557        let has_aggregation = features[4];
558        let cardinality = features[6];
559
560        if predicted_cost > 1000.0 {
561            if joins > 3.0 {
562                return OptimizationRecommendation::ReorderJoins(vec![0, 1, 2]);
563            }
564            if cardinality > 10000.0 {
565                return OptimizationRecommendation::EnableParallelism(4);
566            }
567            if has_aggregation > 0.0 {
568                return OptimizationRecommendation::MaterializeSubquery;
569            }
570        }
571
572        if predicted_cost > 100.0 && cardinality > 5000.0 {
573            return OptimizationRecommendation::ApplyStreaming;
574        }
575
576        OptimizationRecommendation::NoChange
577    }
578
579    /// Calculate feature importance
580    fn calculate_feature_importance(&self, features: &[f64]) -> Vec<(String, f64)> {
581        let feature_names = vec![
582            "triple_patterns",
583            "joins",
584            "filters",
585            "optional",
586            "has_aggregation",
587            "has_sorting",
588            "cardinality",
589            "graph_diameter",
590            "avg_degree",
591            "max_degree",
592            "cross_product",
593            "subquery_depth",
594            "aggregation_complexity",
595        ];
596
597        let total: f64 = features.iter().map(|x| x.abs()).sum();
598
599        let mut importance: Vec<(String, f64)> = feature_names
600            .iter()
601            .zip(features.iter())
602            .map(|(name, &value)| {
603                let normalized = if total > 1e-10 {
604                    (value.abs() / total).min(1.0)
605                } else {
606                    0.0
607                };
608                (name.to_string(), normalized)
609            })
610            .collect();
611
612        importance.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
613
614        importance
615    }
616
617    /// Add training example for online learning
618    pub fn add_training_example(&mut self, example: TrainingExample) {
619        self.training_data.push(example);
620
621        // Limit training data size
622        if self.training_data.len() > self.config.max_training_examples {
623            self.training_data.remove(0);
624        }
625
626        // Clear prediction cache when new data arrives
627        self.prediction_cache.clear();
628    }
629
630    /// Update from actual execution results (online learning)
631    pub fn update_from_execution(&mut self, query: &Algebra, actual_cost: f64) -> Result<()> {
632        let features = self.extract_features(query);
633        let characteristics = self.analyze_query_structure(query);
634
635        let example = TrainingExample {
636            features: features.clone(),
637            target_cost: actual_cost,
638            actual_cost,
639            query_characteristics: characteristics,
640            timestamp: SystemTime::now(),
641        };
642
643        self.add_training_example(example);
644
645        // Check if we should retrain
646        if self.should_retrain() {
647            self.train_model()?;
648        }
649
650        Ok(())
651    }
652
653    /// Check if model should be retrained
654    fn should_retrain(&self) -> bool {
655        if !self.config.auto_retraining {
656            return false;
657        }
658
659        // Need minimum examples
660        if self.training_data.len() < self.config.min_examples_for_training {
661            return false;
662        }
663
664        // Check time since last training
665        if let Some(last_training) = self.last_training {
666            if let Ok(elapsed) = SystemTime::now().duration_since(last_training) {
667                let hours_elapsed = elapsed.as_secs() / 3600;
668                if hours_elapsed < self.config.training_interval_hours {
669                    return false;
670                }
671            }
672        }
673
674        true
675    }
676
677    /// Train the model with collected data
678    pub fn train_model(&mut self) -> Result<()> {
679        if self.training_data.len() < self.config.min_examples_for_training {
680            return Err(anyhow::anyhow!(
681                "Insufficient training data: {} < {}",
682                self.training_data.len(),
683                self.config.min_examples_for_training
684            ));
685        }
686
687        // Extract features and targets
688        let n_samples = self.training_data.len();
689        let n_features = self.training_data[0].features.len();
690
691        // Build feature matrix
692        let mut x_data = Vec::with_capacity(n_samples * n_features);
693        let mut y_data = Vec::with_capacity(n_samples);
694
695        for example in &self.training_data {
696            x_data.extend_from_slice(&example.features);
697            y_data.push(example.actual_cost);
698        }
699
700        let x = Array2::from_shape_vec((n_samples, n_features), x_data)
701            .map_err(|e| anyhow::anyhow!("Failed to create feature matrix: {}", e))?;
702        let y = Array1::from_vec(y_data);
703
704        // Calculate normalization parameters
705        if self.config.feature_normalization {
706            self.calculate_normalization_params(&x);
707        }
708
709        // Train model based on type
710        let results = match self.model.model_type {
711            MLModelType::LinearRegression => linear_regression(&x.view(), &y.view(), None)
712                .map_err(|e| anyhow::anyhow!("Linear regression failed: {:?}", e))?,
713            MLModelType::Ridge => {
714                let alpha = Some(1.0); // Regularization parameter
715                ridge_regression(
716                    &x.view(),
717                    &y.view(),
718                    alpha,
719                    None, // fit_intercept
720                    None, // normalize
721                    None, // tol
722                    None, // max_iter
723                    None, // conf_level
724                )
725                .map_err(|e| anyhow::anyhow!("Ridge regression failed: {:?}", e))?
726            }
727            _ => {
728                // Fall back to linear regression for unsupported types
729                linear_regression(&x.view(), &y.view(), None)
730                    .map_err(|e| anyhow::anyhow!("Linear regression failed: {:?}", e))?
731            }
732        };
733
734        // Store serializable results
735        self.model.regression_results = Some(SerializableRegressionResults::from(&results));
736
737        // Update accuracy metrics
738        self.update_accuracy_metrics(&results, &x, &y)?;
739
740        // Build histograms for improved cardinality estimation
741        self.histogram_estimator
742            .build_from_training_data(&self.training_data);
743
744        // Update last training time
745        self.last_training = Some(SystemTime::now());
746
747        // Save model if persistence enabled
748        if let Some(ref path) = self.config.model_persistence_path {
749            self.save_model(path)?;
750        }
751
752        Ok(())
753    }
754
755    /// Calculate normalization parameters from training data
756    fn calculate_normalization_params(&mut self, x: &Array2<f64>) {
757        let n_features = x.ncols();
758
759        let mut means = vec![0.0; n_features];
760        let mut std_devs = vec![0.0; n_features];
761        let mut mins = vec![f64::MAX; n_features];
762        let mut maxs = vec![f64::MIN; n_features];
763
764        // Calculate statistics for each feature
765        for j in 0..n_features {
766            let column = x.column(j);
767            let n = column.len() as f64;
768
769            // Mean
770            let mean: f64 = column.iter().sum::<f64>() / n;
771            means[j] = mean;
772
773            // Std dev
774            let variance: f64 = column.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / n;
775            std_devs[j] = variance.sqrt();
776
777            // Min/Max
778            for &val in column.iter() {
779                if val < mins[j] {
780                    mins[j] = val;
781                }
782                if val > maxs[j] {
783                    maxs[j] = val;
784                }
785            }
786        }
787
788        let params = NormalizationParams::new(means, std_devs, mins, maxs);
789
790        self.feature_extractor.normalization_params = Some(params.clone());
791        self.model.normalization_params = Some(params);
792    }
793
794    /// Update accuracy metrics after training
795    fn update_accuracy_metrics(
796        &mut self,
797        results: &RegressionResults<f64>,
798        _x: &Array2<f64>,
799        _y: &Array1<f64>,
800    ) -> Result<()> {
801        // Use metrics from RegressionResults
802        let r_squared = results.r_squared;
803        let residual_std_error = results.residual_std_error;
804
805        // Calculate MAE and RMSE from residuals
806        let residuals = &results.residuals;
807        let n = residuals.len() as f64;
808
809        let mae = residuals.iter().map(|&r| r.abs()).sum::<f64>() / n;
810        let rmse = (residuals.iter().map(|&r| r * r).sum::<f64>() / n).sqrt();
811
812        // Calculate confidence interval (simplified)
813        let confidence_interval = (
814            r_squared - 1.96 * residual_std_error / n.sqrt(),
815            r_squared + 1.96 * residual_std_error / n.sqrt(),
816        );
817
818        self.model.accuracy_metrics = AccuracyMetrics {
819            mean_absolute_error: mae,
820            root_mean_square_error: rmse,
821            r_squared,
822            confidence_interval,
823        };
824
825        Ok(())
826    }
827
828    /// Get model accuracy metrics
829    pub fn accuracy_metrics(&self) -> &AccuracyMetrics {
830        &self.model.accuracy_metrics
831    }
832
833    /// Get the number of predictions made
834    pub fn predictions_count(&self) -> usize {
835        self.prediction_counter.get() as usize
836    }
837
838    /// Get training data count
839    pub fn training_data_count(&self) -> usize {
840        self.training_data.len()
841    }
842
843    /// Get improved cardinality estimate using histogram statistics
844    pub fn estimate_cardinality_with_histogram(&self, features: &[f64]) -> Option<f64> {
845        self.histogram_estimator
846            .estimate_cardinality_with_histogram(features)
847    }
848
849    /// Get histogram statistics
850    pub fn get_histogram_statistics(&self) -> HistogramStatistics {
851        self.histogram_estimator.get_statistics()
852    }
853
854    /// Predict with enhanced histogram-based cardinality estimation
855    pub fn predict_cost_with_histogram(&mut self, query: &Algebra) -> Result<MLPrediction> {
856        let _guard = self.prediction_timer.start();
857        self.prediction_counter.inc();
858
859        let features = self.extract_features(query);
860        let query_hash = self.hash_query(query);
861
862        // Check cache
863        if let Some(cached) = self.prediction_cache.get(&query_hash) {
864            return Ok(cached.clone());
865        }
866
867        // Make prediction with histogram enhancement
868        let (mut predicted_cost, mut confidence) = if self.should_use_ml() {
869            self.predict_with_model(&features)?
870        } else {
871            self.heuristic_prediction(&features)?
872        };
873
874        // Enhance with histogram-based cardinality if available
875        if let Some(histogram_cardinality) = self.estimate_cardinality_with_histogram(&features) {
876            // Blend ML prediction with histogram-based estimate
877            let blend_weight = 0.3; // 30% histogram, 70% ML
878            predicted_cost =
879                predicted_cost * (1.0 - blend_weight) + histogram_cardinality * blend_weight;
880
881            // Increase confidence if histogram estimate agrees
882            let agreement =
883                1.0 - ((predicted_cost - histogram_cardinality).abs() / (predicted_cost + 1.0));
884            confidence = (confidence + agreement * 0.2).min(1.0);
885        }
886
887        self.prediction_histogram.observe(predicted_cost);
888
889        // Generate recommendations and feature importance
890        let recommendation = self.generate_recommendation(&features, predicted_cost);
891        let feature_importance = self.calculate_feature_importance(&features);
892
893        let prediction = MLPrediction {
894            predicted_cost,
895            confidence,
896            recommendation,
897            feature_importance,
898        };
899
900        // Cache prediction
901        self.prediction_cache.insert(query_hash, prediction.clone());
902
903        Ok(prediction)
904    }
905
906    /// Hash query for caching
907    fn hash_query(&self, query: &Algebra) -> u64 {
908        use std::collections::hash_map::DefaultHasher;
909        use std::hash::{Hash, Hasher};
910
911        let mut hasher = DefaultHasher::new();
912        let query_string = self.algebra_to_string(query);
913        query_string.hash(&mut hasher);
914        hasher.finish()
915    }
916
917    /// Convert algebra to string for hashing
918    fn algebra_to_string(&self, algebra: &Algebra) -> String {
919        use crate::algebra::Algebra;
920
921        match algebra {
922            Algebra::Service { .. } => "Service".to_string(),
923            Algebra::PropertyPath { .. } => "PropertyPath".to_string(),
924            Algebra::Join { left, right, .. } => {
925                format!(
926                    "Join({},{})",
927                    self.algebra_to_string(left),
928                    self.algebra_to_string(right)
929                )
930            }
931            Algebra::LeftJoin { left, right, .. } => {
932                format!(
933                    "LeftJoin({},{})",
934                    self.algebra_to_string(left),
935                    self.algebra_to_string(right)
936                )
937            }
938            Algebra::Filter { pattern, .. } => {
939                format!("Filter({})", self.algebra_to_string(pattern))
940            }
941            Algebra::Union { left, right } => {
942                format!(
943                    "Union({},{})",
944                    self.algebra_to_string(left),
945                    self.algebra_to_string(right)
946                )
947            }
948            Algebra::Extend { pattern, .. } => {
949                format!("Extend({})", self.algebra_to_string(pattern))
950            }
951            Algebra::OrderBy { pattern, .. } => {
952                format!("OrderBy({})", self.algebra_to_string(pattern))
953            }
954            Algebra::Project { pattern, .. } => {
955                format!("Project({})", self.algebra_to_string(pattern))
956            }
957            Algebra::Distinct { pattern } => {
958                format!("Distinct({})", self.algebra_to_string(pattern))
959            }
960            Algebra::Reduced { pattern } => {
961                format!("Reduced({})", self.algebra_to_string(pattern))
962            }
963            Algebra::Slice { pattern, .. } => {
964                format!("Slice({})", self.algebra_to_string(pattern))
965            }
966            Algebra::Group { pattern, .. } => {
967                format!("Group({})", self.algebra_to_string(pattern))
968            }
969            _ => "Unknown".to_string(),
970        }
971    }
972}
973
974// ---------------------------------------------------------------------------
975// Manual Serialize / Deserialize / Clone for MLPredictor
976// ---------------------------------------------------------------------------
977
978impl Serialize for MLPredictor {
979    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
980    where
981        S: serde::Serializer,
982    {
983        use serde::ser::SerializeStruct;
984
985        let mut state = serializer.serialize_struct("MLPredictor", 5)?;
986        state.serialize_field("model", &self.model)?;
987        state.serialize_field("training_data", &self.training_data)?;
988        state.serialize_field("feature_extractor", &self.feature_extractor)?;
989        state.serialize_field("config", &self.config)?;
990        state.serialize_field("histogram_estimator", &self.histogram_estimator)?;
991        state.end()
992    }
993}
994
995impl<'de> Deserialize<'de> for MLPredictor {
996    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
997    where
998        D: serde::Deserializer<'de>,
999    {
1000        #[derive(Deserialize)]
1001        struct MLPredictorData {
1002            model: MLModel,
1003            training_data: Vec<TrainingExample>,
1004            feature_extractor: FeatureExtractor,
1005            config: MLConfig,
1006            #[serde(default)]
1007            histogram_estimator: Option<HistogramCardinalityEstimator>,
1008        }
1009
1010        let data = MLPredictorData::deserialize(deserializer)?;
1011
1012        let metrics_collector = Arc::new(MetricsRegistry::new());
1013        let prediction_counter = Counter::new("ml_predictor_predictions_total".to_string());
1014        let prediction_timer = Timer::new("ml_predictor_prediction_duration_seconds".to_string());
1015        let prediction_histogram =
1016            Histogram::new("ml_predictor_prediction_distribution".to_string());
1017
1018        Ok(MLPredictor {
1019            model: data.model,
1020            training_data: data.training_data,
1021            feature_extractor: data.feature_extractor,
1022            prediction_cache: HashMap::new(),
1023            config: data.config,
1024            metrics_collector,
1025            last_training: None,
1026            histogram_estimator: data
1027                .histogram_estimator
1028                .unwrap_or_else(|| HistogramCardinalityEstimator::new(HistogramConfig::default())),
1029            prediction_counter,
1030            prediction_timer,
1031            prediction_histogram,
1032        })
1033    }
1034}
1035
1036impl Clone for MLPredictor {
1037    fn clone(&self) -> Self {
1038        MLPredictor {
1039            model: self.model.clone(),
1040            training_data: self.training_data.clone(),
1041            feature_extractor: self.feature_extractor.clone(),
1042            prediction_cache: HashMap::new(), // Don't clone cache
1043            config: self.config.clone(),
1044            metrics_collector: Arc::clone(&self.metrics_collector),
1045            last_training: self.last_training,
1046            histogram_estimator: self.histogram_estimator.clone(),
1047            prediction_counter: Counter::new("ml_predictor_predictions_total".to_string()),
1048            prediction_timer: Timer::new("ml_predictor_prediction_duration_seconds".to_string()),
1049            prediction_histogram: Histogram::new(
1050                "ml_predictor_prediction_distribution".to_string(),
1051            ),
1052        }
1053    }
1054}