1use 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
19use 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#[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
49pub 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 pub(super) prediction_counter: Counter,
66 pub(super) prediction_timer: Timer,
67 pub(super) prediction_histogram: Histogram,
68}
69
70impl MLPredictor {
71 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 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 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 pub fn save_model(&self, path: &Path) -> Result<()> {
127 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 pub fn confidence(&self) -> f64 {
143 let r_squared = self.model.accuracy_metrics.r_squared;
145 r_squared.clamp(0.0, 1.0)
146 }
147
148 pub fn should_use_ml(&self) -> bool {
150 self.confidence() >= self.config.confidence_threshold
151 && self.model.regression_results.is_some()
152 }
153
154 pub fn extract_features(&self, query: &Algebra) -> Vec<f64> {
156 let mut features = Vec::with_capacity(13);
157
158 let characteristics = self.analyze_query_structure(query);
160
161 features.push(characteristics.triple_pattern_count as f64);
163
164 features.push(characteristics.join_count as f64);
166
167 features.push(characteristics.filter_count as f64);
169
170 features.push(characteristics.optional_count as f64);
172
173 features.push(if characteristics.has_aggregation {
175 1.0
176 } else {
177 0.0
178 });
179
180 features.push(if characteristics.has_sorting {
182 1.0
183 } else {
184 0.0
185 });
186
187 features.push(characteristics.estimated_cardinality as f64);
189
190 features.push(characteristics.query_graph_diameter as f64);
192
193 features.push(characteristics.avg_degree);
195
196 features.push(characteristics.max_degree as f64);
198
199 features.push(self.calculate_cross_product_likelihood(query));
201
202 features.push(self.calculate_subquery_depth(query));
204
205 features.push(self.calculate_aggregation_complexity(query));
207
208 if self.config.feature_normalization {
210 self.normalize_features(features)
211 } else {
212 features
213 }
214 }
215
216 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 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 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 fn calculate_complexity_score(&self, characteristics: &QueryCharacteristics) -> f64 {
299 let mut score = 0.0;
300
301 score += (characteristics.join_count as f64).powi(2) * 3.0;
303
304 score += characteristics.triple_pattern_count as f64 * 1.0;
306
307 score += characteristics.filter_count as f64 * 0.5;
309
310 score += characteristics.optional_count as f64 * 2.0;
312
313 if characteristics.has_aggregation {
315 score += 5.0;
316 }
317
318 if characteristics.has_sorting {
320 score += 2.0;
321 }
322
323 let cardinality_log = (characteristics.estimated_cardinality as f64)
325 .log10()
326 .max(1.0);
327 score *= cardinality_log;
328
329 score
330 }
331
332 fn calculate_graph_diameter(&self, characteristics: &QueryCharacteristics) -> usize {
334 if characteristics.join_count == 0 {
336 1
337 } else {
338 (characteristics.join_count as f64).sqrt().ceil() as usize + 1
339 }
340 }
341
342 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 fn calculate_cross_product_likelihood(&self, _query: &Algebra) -> f64 {
363 0.0
365 }
366
367 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 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 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 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 if let Some(cached) = self.prediction_cache.get(&query_hash) {
460 return Ok(cached.clone());
461 }
462
463 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 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 self.prediction_cache.insert(query_hash, prediction.clone());
485
486 Ok(prediction)
487 }
488
489 fn predict_with_model(&self, features: &[f64]) -> Result<(f64, f64)> {
491 if let Some(ref results) = self.model.regression_results {
492 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 prediction += coef;
501 }
502 }
503
504 prediction = prediction.max(0.1);
506
507 let confidence = self.confidence();
509
510 Ok((prediction, confidence))
511 } else {
512 self.heuristic_prediction(features)
514 }
515 }
516
517 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 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; Ok((cost, confidence))
544 }
545
546 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 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 pub fn add_training_example(&mut self, example: TrainingExample) {
619 self.training_data.push(example);
620
621 if self.training_data.len() > self.config.max_training_examples {
623 self.training_data.remove(0);
624 }
625
626 self.prediction_cache.clear();
628 }
629
630 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 if self.should_retrain() {
647 self.train_model()?;
648 }
649
650 Ok(())
651 }
652
653 fn should_retrain(&self) -> bool {
655 if !self.config.auto_retraining {
656 return false;
657 }
658
659 if self.training_data.len() < self.config.min_examples_for_training {
661 return false;
662 }
663
664 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 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 let n_samples = self.training_data.len();
689 let n_features = self.training_data[0].features.len();
690
691 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 if self.config.feature_normalization {
706 self.calculate_normalization_params(&x);
707 }
708
709 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); ridge_regression(
716 &x.view(),
717 &y.view(),
718 alpha,
719 None, None, None, None, None, )
725 .map_err(|e| anyhow::anyhow!("Ridge regression failed: {:?}", e))?
726 }
727 _ => {
728 linear_regression(&x.view(), &y.view(), None)
730 .map_err(|e| anyhow::anyhow!("Linear regression failed: {:?}", e))?
731 }
732 };
733
734 self.model.regression_results = Some(SerializableRegressionResults::from(&results));
736
737 self.update_accuracy_metrics(&results, &x, &y)?;
739
740 self.histogram_estimator
742 .build_from_training_data(&self.training_data);
743
744 self.last_training = Some(SystemTime::now());
746
747 if let Some(ref path) = self.config.model_persistence_path {
749 self.save_model(path)?;
750 }
751
752 Ok(())
753 }
754
755 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 for j in 0..n_features {
766 let column = x.column(j);
767 let n = column.len() as f64;
768
769 let mean: f64 = column.iter().sum::<f64>() / n;
771 means[j] = mean;
772
773 let variance: f64 = column.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / n;
775 std_devs[j] = variance.sqrt();
776
777 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 fn update_accuracy_metrics(
796 &mut self,
797 results: &RegressionResults<f64>,
798 _x: &Array2<f64>,
799 _y: &Array1<f64>,
800 ) -> Result<()> {
801 let r_squared = results.r_squared;
803 let residual_std_error = results.residual_std_error;
804
805 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 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 pub fn accuracy_metrics(&self) -> &AccuracyMetrics {
830 &self.model.accuracy_metrics
831 }
832
833 pub fn predictions_count(&self) -> usize {
835 self.prediction_counter.get() as usize
836 }
837
838 pub fn training_data_count(&self) -> usize {
840 self.training_data.len()
841 }
842
843 pub fn estimate_cardinality_with_histogram(&self, features: &[f64]) -> Option<f64> {
845 self.histogram_estimator
846 .estimate_cardinality_with_histogram(features)
847 }
848
849 pub fn get_histogram_statistics(&self) -> HistogramStatistics {
851 self.histogram_estimator.get_statistics()
852 }
853
854 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 if let Some(cached) = self.prediction_cache.get(&query_hash) {
864 return Ok(cached.clone());
865 }
866
867 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 if let Some(histogram_cardinality) = self.estimate_cardinality_with_histogram(&features) {
876 let blend_weight = 0.3; predicted_cost =
879 predicted_cost * (1.0 - blend_weight) + histogram_cardinality * blend_weight;
880
881 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 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 self.prediction_cache.insert(query_hash, prediction.clone());
902
903 Ok(prediction)
904 }
905
906 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 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
974impl 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(), 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}