1use anyhow::{anyhow, Result};
17use serde::{Deserialize, Serialize};
18use std::collections::{HashSet, VecDeque};
19use std::sync::Arc;
20use std::time::{Duration, SystemTime};
21use tokio::sync::RwLock;
22use tracing::info;
23
24use scirs2_core::ndarray_ext::{Array1, Array2};
26use scirs2_core::random::Random;
27use scirs2_core::sampling::random_normal;
28
29use crate::ast::{Document, OperationType, Selection, SelectionSet};
30use crate::optimizer::{QueryComplexity, QueryOptimizer};
31use crate::performance::{OperationMetrics, PerformanceTracker};
32use crate::system_monitor;
33
34#[derive(Debug, Clone)]
36pub struct MLOptimizerConfig {
37 pub learning_enabled: bool,
38 pub min_samples_for_learning: usize,
39 pub feature_extraction_enabled: bool,
40 pub prediction_threshold: f64,
41 pub model_update_interval: Duration,
42 pub max_training_samples: usize,
43 pub performance_history_window: Duration,
44 pub use_neural_network: bool,
45 pub neural_network_layers: Vec<usize>,
46 pub neural_learning_rate: f64,
47 pub enable_reinforcement_learning: bool,
48 pub enable_semantic_analysis: bool,
49 pub adaptive_optimization: bool,
50}
51
52impl Default for MLOptimizerConfig {
53 fn default() -> Self {
54 Self {
55 learning_enabled: true,
56 min_samples_for_learning: 100,
57 feature_extraction_enabled: true,
58 prediction_threshold: 0.7,
59 model_update_interval: Duration::from_secs(3600), max_training_samples: 10000,
61 performance_history_window: Duration::from_secs(86400), use_neural_network: false,
63 neural_network_layers: vec![64, 32, 16],
64 neural_learning_rate: 0.001,
65 enable_reinforcement_learning: false,
66 enable_semantic_analysis: true,
67 adaptive_optimization: true,
68 }
69 }
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct QueryFeatures {
75 pub field_count: f64,
76 pub max_depth: f64,
77 pub complexity_score: f64,
78 pub selection_count: f64,
79 pub has_fragments: f64,
80 pub has_variables: f64,
81 pub operation_type: f64, pub unique_field_types: f64,
83 pub nested_list_count: f64,
84 pub argument_count: f64,
85 pub directive_count: f64,
86 pub estimated_result_size: f64,
87}
88
89impl QueryFeatures {
90 pub fn to_vector(&self) -> Vec<f64> {
92 vec![
93 self.field_count,
94 self.max_depth,
95 self.complexity_score,
96 self.selection_count,
97 self.has_fragments,
98 self.has_variables,
99 self.operation_type,
100 self.unique_field_types,
101 self.nested_list_count,
102 self.argument_count,
103 self.directive_count,
104 self.estimated_result_size,
105 ]
106 }
107
108 pub fn from_vector(vector: &[f64]) -> Result<Self> {
110 if vector.len() != 12 {
111 return Err(anyhow!(
112 "Invalid feature vector length: expected 12, got {}",
113 vector.len()
114 ));
115 }
116
117 Ok(Self {
118 field_count: vector[0],
119 max_depth: vector[1],
120 complexity_score: vector[2],
121 selection_count: vector[3],
122 has_fragments: vector[4],
123 has_variables: vector[5],
124 operation_type: vector[6],
125 unique_field_types: vector[7],
126 nested_list_count: vector[8],
127 argument_count: vector[9],
128 directive_count: vector[10],
129 estimated_result_size: vector[11],
130 })
131 }
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct TrainingSample {
137 pub features: QueryFeatures,
138 pub execution_time_ms: f64,
139 pub memory_usage_mb: f64,
140 pub cache_hit: bool,
141 pub error_occurred: bool,
142 pub timestamp: SystemTime,
143}
144
145#[derive(Debug, Clone)]
147pub struct PerformancePrediction {
148 pub predicted_execution_time: Duration,
149 pub predicted_memory_usage: f64,
150 pub cache_hit_probability: f64,
151 pub error_probability: f64,
152 pub confidence_score: f64,
153}
154
155#[derive(Debug, Clone)]
157pub struct OptimizationRecommendation {
158 pub recommendation_type: RecommendationType,
159 pub description: String,
160 pub estimated_improvement: f64, pub confidence: f64,
162}
163
164#[derive(Debug, Clone)]
165pub enum RecommendationType {
166 ReduceDepth,
167 AddCaching,
168 BreakIntoSubqueries,
169 AddIndexes,
170 OptimizeFragments,
171 ParallelizeFields,
172 ReduceComplexity,
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
177pub enum ActivationFunction {
178 ReLU,
179 Sigmoid,
180 Tanh,
181 Linear,
182 Softmax,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct LinearRegressionModel {
189 pub weights: Vec<f64>,
190 pub bias: f64,
191 pub training_samples: usize,
192 pub last_updated: SystemTime,
193 pub regularization: f64,
195}
196
197impl LinearRegressionModel {
198 pub fn new(feature_count: usize) -> Self {
199 let mut rng = Random::seed(42);
201 let weights = (0..feature_count)
202 .map(|_| random_normal(&mut rng, 0.0, 0.01))
203 .collect();
204
205 Self {
206 weights,
207 bias: 0.0,
208 training_samples: 0,
209 last_updated: SystemTime::now(),
210 regularization: 0.01,
211 }
212 }
213
214 pub fn with_regularization(feature_count: usize, regularization: f64) -> Self {
216 let mut model = Self::new(feature_count);
217 model.regularization = regularization;
218 model
219 }
220
221 pub fn predict(&self, features: &[f64]) -> f64 {
223 if features.len() != self.weights.len() {
224 return 1000.0; }
226
227 let prediction = self.bias
228 + features
229 .iter()
230 .zip(&self.weights)
231 .map(|(f, w)| f * w)
232 .sum::<f64>();
233
234 prediction.max(0.0) }
236
237 pub fn train(&mut self, samples: &[TrainingSample], learning_rate: f64, iterations: usize) {
240 if samples.is_empty() {
241 return;
242 }
243
244 let start_time = SystemTime::now();
245
246 let valid_samples: Vec<_> = samples
248 .iter()
249 .filter(|s| s.features.to_vector().len() == self.weights.len())
250 .collect();
251
252 if valid_samples.is_empty() {
253 return;
254 }
255
256 let n_samples = valid_samples.len();
258 let n_features = self.weights.len();
259
260 let mut feature_matrix = Vec::with_capacity(n_samples * n_features);
261 let mut targets = Vec::with_capacity(n_samples);
262
263 for sample in &valid_samples {
264 feature_matrix.extend_from_slice(&sample.features.to_vector());
265 targets.push(sample.execution_time_ms);
266 }
267
268 let x_matrix = Array2::from_shape_vec((n_samples, n_features), feature_matrix)
269 .unwrap_or_else(|_| Array2::zeros((n_samples, n_features)));
270 let y_vector = Array1::from_vec(targets);
271 let weight_array = Array1::from_vec(self.weights.clone());
272
273 let mut weights = weight_array;
275 let mut bias = self.bias;
276
277 for _ in 0..iterations {
278 let predictions = x_matrix.dot(&weights) + bias;
280
281 let errors = &predictions - &y_vector;
283
284 let gradient_w =
287 x_matrix.t().dot(&errors) / (n_samples as f64) + &weights * self.regularization;
288
289 let gradient_b = errors.sum() / (n_samples as f64);
291
292 weights = &weights - &gradient_w * learning_rate;
294 bias -= learning_rate * gradient_b;
295 }
296
297 self.weights = weights.to_vec();
299 self.bias = bias;
300 self.training_samples += samples.len();
301 self.last_updated = SystemTime::now();
302
303 let training_time = start_time.elapsed().unwrap_or(Duration::from_secs(0));
304 info!(
305 "ML training completed in {:?} with {} samples (vectorized with SciRS2)",
306 training_time,
307 valid_samples.len()
308 );
309 }
310
311 pub fn get_confidence(&self) -> f64 {
313 if self.training_samples < 10 {
314 0.1
315 } else if self.training_samples < 100 {
316 0.3
317 } else if self.training_samples < 1000 {
318 0.6
319 } else {
320 0.8
321 }
322 }
323
324 pub fn sample_count(&self) -> usize {
326 self.training_samples
327 }
328}
329
330pub struct MLQueryOptimizer {
332 config: MLOptimizerConfig,
333 base_optimizer: QueryOptimizer,
334 #[allow(dead_code)]
335 performance_tracker: Arc<PerformanceTracker>,
336 execution_time_model: Arc<RwLock<LinearRegressionModel>>,
337 memory_model: Arc<RwLock<LinearRegressionModel>>,
338 training_samples: Arc<RwLock<VecDeque<TrainingSample>>>,
339 feature_stats: Arc<RwLock<FeatureStatistics>>,
340}
341
342#[derive(Debug, Clone, Default)]
344pub struct FeatureStatistics {
345 pub feature_means: Vec<f64>,
346 pub feature_stds: Vec<f64>,
347 pub sample_count: usize,
348}
349
350impl FeatureStatistics {
351 pub fn update(&mut self, samples: &[TrainingSample]) {
353 if samples.is_empty() {
354 return;
355 }
356
357 let feature_count = samples[0].features.to_vector().len();
358
359 if self.feature_means.is_empty() {
360 self.feature_means = vec![0.0; feature_count];
361 self.feature_stds = vec![1.0; feature_count];
362 }
363
364 let mut sums = vec![0.0; feature_count];
366 for sample in samples {
367 let features = sample.features.to_vector();
368 for (i, &feature) in features.iter().enumerate() {
369 sums[i] += feature;
370 }
371 }
372
373 let total_samples = self.sample_count + samples.len();
374 #[allow(clippy::needless_range_loop)]
375 for i in 0..feature_count {
376 let new_mean = sums[i] / samples.len() as f64;
377 self.feature_means[i] = (self.feature_means[i] * self.sample_count as f64
378 + new_mean * samples.len() as f64)
379 / total_samples as f64;
380 }
381
382 let mut var_sums = vec![0.0; feature_count];
384 for sample in samples {
385 let features = sample.features.to_vector();
386 for (i, &feature) in features.iter().enumerate() {
387 let diff = feature - self.feature_means[i];
388 var_sums[i] += diff * diff;
389 }
390 }
391
392 #[allow(clippy::needless_range_loop)]
393 for i in 0..feature_count {
394 self.feature_stds[i] = (var_sums[i] / samples.len() as f64).sqrt().max(1e-6);
395 }
396
397 self.sample_count = total_samples;
398 }
399
400 pub fn normalize(&self, features: &[f64]) -> Vec<f64> {
403 if self.feature_means.is_empty() || features.len() != self.feature_means.len() {
404 return features.to_vec();
405 }
406
407 let features_arr = Array1::from_vec(features.to_vec());
409 let means_arr = Array1::from_vec(self.feature_means.clone());
410 let stds_arr = Array1::from_vec(self.feature_stds.clone());
411
412 let normalized = (&features_arr - &means_arr) / &stds_arr;
414
415 normalized.to_vec()
416 }
417}
418
419pub struct NeuralNetworkModel {
422 weights: Vec<Array2<f64>>,
424 biases: Vec<Array1<f64>>,
426 layer_sizes: Vec<usize>,
428 learning_rate: f64,
430 l2_lambda: f64,
432 training_iterations: usize,
434 loss_history: Vec<f64>,
436}
437
438impl NeuralNetworkModel {
439 pub fn new(layer_sizes: Vec<usize>, learning_rate: f64) -> Self {
441 let mut weights = Vec::new();
442 let mut biases = Vec::new();
443 let mut rng = Random::seed(42);
444
445 for i in 0..layer_sizes.len() - 1 {
447 let input_size = layer_sizes[i];
448 let output_size = layer_sizes[i + 1];
449
450 let scale = (2.0 / (input_size + output_size) as f64).sqrt();
452
453 let weight_data: Vec<f64> = (0..input_size * output_size)
455 .map(|_| rng.gen_range(-1.0..1.0) * scale)
456 .collect();
457 let weight_matrix = Array2::from_shape_vec((input_size, output_size), weight_data)
458 .expect("weight data should match the specified shape dimensions");
459 weights.push(weight_matrix);
460
461 let bias_data: Vec<f64> = vec![0.0; output_size];
463 biases.push(Array1::from_vec(bias_data));
464 }
465
466 Self {
467 weights,
468 biases,
469 layer_sizes,
470 learning_rate,
471 l2_lambda: 0.001, training_iterations: 0,
473 loss_history: Vec::new(),
474 }
475 }
476
477 fn relu(x: f64) -> f64 {
479 x.max(0.0)
480 }
481
482 fn relu_derivative(x: f64) -> f64 {
484 if x > 0.0 {
485 1.0
486 } else {
487 0.0
488 }
489 }
490
491 pub fn forward(&self, input: &[f64]) -> (Vec<Array1<f64>>, f64) {
493 let mut activations = vec![Array1::from_vec(input.to_vec())];
494
495 for i in 0..self.weights.len() {
496 let current = &activations[i];
497
498 let z = current.dot(&self.weights[i]) + &self.biases[i];
500
501 let activation = if i < self.weights.len() - 1 {
503 z.mapv(Self::relu)
504 } else {
505 z };
507
508 activations.push(activation);
509 }
510
511 let output = activations
513 .last()
514 .expect("activations should not be empty after forward pass")[0];
515 (activations, output)
516 }
517
518 pub fn train(&mut self, samples: &[TrainingSample], epochs: usize) {
520 for _epoch in 0..epochs {
521 let mut total_loss = 0.0;
522
523 for sample in samples {
524 let features = sample.features.to_vector();
525 let target = sample.execution_time_ms;
526
527 let (activations, prediction) = self.forward(&features);
529
530 let error = prediction - target;
532 total_loss += error * error;
533
534 self.backward(&activations, error);
536 }
537
538 let avg_loss = total_loss / samples.len() as f64;
540 self.loss_history.push(avg_loss);
541 self.training_iterations += 1;
542 }
543 }
544
545 fn backward(&mut self, activations: &[Array1<f64>], error: f64) {
547 let mut delta = Array1::from_vec(vec![error]);
549
550 for i in (0..self.weights.len()).rev() {
552 let input_activation = &activations[i];
553
554 let grad_w = input_activation
556 .view()
557 .insert_axis(scirs2_core::ndarray_ext::Axis(1))
558 .dot(&delta.view().insert_axis(scirs2_core::ndarray_ext::Axis(0)));
559
560 let regularization = &self.weights[i] * self.l2_lambda;
562 self.weights[i] = &self.weights[i] - &(&grad_w * self.learning_rate + ®ularization);
563
564 self.biases[i] = &self.biases[i] - &(&delta * self.learning_rate);
566
567 if i > 0 {
569 let pre_activation = activations[i]
570 .iter()
571 .map(|&x| Self::relu_derivative(x))
572 .collect::<Vec<_>>();
573
574 delta = self.weights[i]
575 .dot(&delta)
576 .iter()
577 .zip(pre_activation.iter())
578 .map(|(d, &r)| d * r)
579 .collect::<Vec<_>>()
580 .into();
581 }
582 }
583 }
584
585 pub fn predict_with_confidence(&self, features: &[f64]) -> (f64, f64) {
587 let (_, prediction) = self.forward(features);
588
589 let confidence = if self.training_iterations < 100 {
591 0.3 } else if self.training_iterations < 1000 {
593 0.6
594 } else {
595 let recent_loss = self.loss_history.iter().rev().take(10).sum::<f64>() / 10.0;
597 (1.0 / (1.0 + recent_loss.sqrt())).min(0.95)
598 };
599
600 (prediction, confidence)
601 }
602
603 pub fn get_feature_importance(&self) -> Vec<f64> {
605 if self.weights.is_empty() {
606 return Vec::new();
607 }
608
609 let first_layer = &self.weights[0];
611 let mut importance: Vec<f64> = Vec::with_capacity(first_layer.nrows());
612
613 for i in 0..first_layer.nrows() {
614 let row_sum: f64 = first_layer.row(i).iter().map(|w| w.abs()).sum();
615 importance.push(row_sum);
616 }
617
618 let total: f64 = importance.iter().sum();
620 if total > 0.0 {
621 importance.iter_mut().for_each(|x| *x /= total);
622 }
623
624 importance
625 }
626
627 pub fn get_stats(&self) -> ModelStats {
629 ModelStats {
630 training_iterations: self.training_iterations,
631 final_loss: self.loss_history.last().copied().unwrap_or(0.0),
632 layer_sizes: self.layer_sizes.clone(),
633 total_parameters: self.weights.iter().map(|w| w.len()).sum(),
634 }
635 }
636}
637
638#[derive(Debug, Clone, Serialize, Deserialize)]
640pub struct ModelStats {
641 pub training_iterations: usize,
642 pub final_loss: f64,
643 pub layer_sizes: Vec<usize>,
644 pub total_parameters: usize,
645}
646
647pub struct EnsembleModel {
649 linear_model: LinearRegressionModel,
650 neural_model: Option<NeuralNetworkModel>,
651 linear_weight: f64,
652 neural_weight: f64,
653}
654
655impl EnsembleModel {
656 pub fn new(feature_count: usize, layer_sizes: Option<Vec<usize>>, learning_rate: f64) -> Self {
658 let neural_model = layer_sizes.map(|sizes| {
659 let mut full_sizes = vec![feature_count];
660 full_sizes.extend(sizes);
661 full_sizes.push(1); NeuralNetworkModel::new(full_sizes, learning_rate)
663 });
664
665 Self {
666 linear_model: LinearRegressionModel::new(feature_count),
667 neural_model,
668 linear_weight: 0.5,
669 neural_weight: 0.5,
670 }
671 }
672
673 pub fn train(&mut self, samples: &[TrainingSample], epochs: usize) {
675 self.linear_model.train(samples, 0.01, 100);
677
678 if let Some(ref mut neural) = self.neural_model {
680 neural.train(samples, epochs);
681 }
682
683 self.adjust_weights(samples);
685 }
686
687 fn adjust_weights(&mut self, samples: &[TrainingSample]) {
689 if samples.is_empty() {
690 return;
691 }
692
693 if self.neural_model.is_none() {
695 return;
696 }
697
698 let mut linear_error = 0.0;
699 let mut neural_error = 0.0;
700
701 for sample in samples {
702 let features = sample.features.to_vector();
703 let target = sample.execution_time_ms;
704
705 let linear_pred = self.linear_model.predict(&features);
706 linear_error += (linear_pred - target).abs();
707
708 if let Some(ref neural) = self.neural_model {
709 let (neural_pred, _) = neural.predict_with_confidence(&features);
710 neural_error += (neural_pred - target).abs();
711 }
712 }
713
714 let epsilon = 1e-6;
717 let linear_inv = 1.0 / (linear_error + epsilon);
718 let neural_inv = 1.0 / (neural_error + epsilon);
719 let total_inv = linear_inv + neural_inv;
720
721 self.linear_weight = linear_inv / total_inv;
723 self.neural_weight = neural_inv / total_inv;
724 }
725
726 pub fn predict(&self, features: &[f64]) -> f64 {
728 let linear_pred = self.linear_model.predict(features);
729
730 if let Some(ref neural) = self.neural_model {
731 let (neural_pred, _) = neural.predict_with_confidence(features);
732 linear_pred * self.linear_weight + neural_pred * self.neural_weight
733 } else {
734 linear_pred
735 }
736 }
737
738 pub fn predict_with_confidence(&self, features: &[f64]) -> (f64, f64) {
740 let prediction = self.predict(features);
741
742 let confidence = if let Some(ref neural) = self.neural_model {
743 let (_, neural_conf) = neural.predict_with_confidence(features);
744 let linear_conf = self.linear_model.get_confidence();
745 linear_conf * self.linear_weight + neural_conf * self.neural_weight
746 } else {
747 self.linear_model.get_confidence()
748 };
749
750 (prediction, confidence)
751 }
752
753 pub fn get_stats(&self) -> EnsembleStats {
755 EnsembleStats {
756 linear_weight: self.linear_weight,
757 neural_weight: self.neural_weight,
758 linear_sample_count: self.linear_model.sample_count(),
759 neural_stats: self.neural_model.as_ref().map(|n| n.get_stats()),
760 }
761 }
762}
763
764#[derive(Debug, Clone, Serialize, Deserialize)]
766pub struct EnsembleStats {
767 pub linear_weight: f64,
768 pub neural_weight: f64,
769 pub linear_sample_count: usize,
770 pub neural_stats: Option<ModelStats>,
771}
772
773impl MLQueryOptimizer {
774 pub fn new(config: MLOptimizerConfig, performance_tracker: Arc<PerformanceTracker>) -> Self {
776 let feature_count = 12; Self {
779 config,
780 base_optimizer: QueryOptimizer::new(crate::optimizer::OptimizationConfig::default()),
781 performance_tracker,
782 execution_time_model: Arc::new(RwLock::new(LinearRegressionModel::new(feature_count))),
783 memory_model: Arc::new(RwLock::new(LinearRegressionModel::new(feature_count))),
784 training_samples: Arc::new(RwLock::new(VecDeque::new())),
785 feature_stats: Arc::new(RwLock::new(FeatureStatistics::default())),
786 }
787 }
788
789 pub fn extract_features(&self, document: &Document) -> Result<QueryFeatures> {
791 let mut field_count = 0;
792 let mut max_depth = 0;
793 let mut selection_count = 0;
794 let mut has_fragments = false;
795 let mut has_variables = false;
796 let mut operation_type = 0.0;
797 let mut unique_field_types = HashSet::new();
798 let mut nested_list_count = 0;
799 let mut argument_count = 0;
800 let mut directive_count = 0;
801
802 use std::collections::HashSet;
803
804 for definition in &document.definitions {
806 match definition {
807 crate::ast::Definition::Operation(operation) => {
808 operation_type = match operation.operation_type {
809 OperationType::Query => 0.0,
810 OperationType::Mutation => 1.0,
811 OperationType::Subscription => 2.0,
812 };
813
814 if !operation.variable_definitions.is_empty() {
815 has_variables = true;
816 }
817
818 let (fc, md, sc, uft, nlc, ac, dc) =
820 self.analyze_selection_set(&operation.selection_set, 1)?;
821 field_count += fc;
822 max_depth = max_depth.max(md);
823 selection_count += sc;
824 unique_field_types.extend(uft);
825 nested_list_count += nlc;
826 argument_count += ac;
827 directive_count += dc;
828 }
829 crate::ast::Definition::Fragment(_) => {
830 has_fragments = true;
831 }
832 crate::ast::Definition::Schema(_) => {
833 }
835 crate::ast::Definition::Type(_) => {
836 }
838 crate::ast::Definition::Directive(_) => {
839 directive_count += 1;
840 }
841 crate::ast::Definition::SchemaExtension(_) => {
842 }
844 crate::ast::Definition::TypeExtension(_) => {
845 }
847 }
848 }
849
850 let complexity = self.base_optimizer.analyze_complexity(document)?;
851
852 Ok(QueryFeatures {
853 field_count: field_count as f64,
854 max_depth: max_depth as f64,
855 complexity_score: complexity.complexity_score as f64,
856 selection_count: selection_count as f64,
857 has_fragments: if has_fragments { 1.0 } else { 0.0 },
858 has_variables: if has_variables { 1.0 } else { 0.0 },
859 operation_type,
860 unique_field_types: unique_field_types.len() as f64,
861 nested_list_count: nested_list_count as f64,
862 argument_count: argument_count as f64,
863 directive_count: directive_count as f64,
864 estimated_result_size: self.estimate_result_size(&complexity),
865 })
866 }
867
868 pub async fn predict_performance(&self, document: &Document) -> Result<PerformancePrediction> {
870 let features = self.extract_features(document)?;
871 let feature_vector = features.to_vector();
872
873 let stats = self.feature_stats.read().await;
875 let normalized_features = stats.normalize(&feature_vector);
876 drop(stats);
877
878 let execution_time_model = self.execution_time_model.read().await;
880 let memory_model = self.memory_model.read().await;
881
882 let predicted_execution_ms = execution_time_model.predict(&normalized_features);
883 let predicted_memory_mb = memory_model.predict(&normalized_features);
884
885 let confidence = self
887 .calculate_confidence(&execution_time_model, &memory_model)
888 .await;
889
890 let cache_hit_probability = self.estimate_cache_hit_probability(&features);
892 let error_probability = self.estimate_error_probability(&features);
893
894 Ok(PerformancePrediction {
895 predicted_execution_time: Duration::from_millis(predicted_execution_ms as u64),
896 predicted_memory_usage: predicted_memory_mb,
897 cache_hit_probability,
898 error_probability,
899 confidence_score: confidence,
900 })
901 }
902
903 pub async fn recommend_optimizations(
905 &self,
906 document: &Document,
907 ) -> Result<Vec<OptimizationRecommendation>> {
908 let features = self.extract_features(document)?;
909 let prediction = self.predict_performance(document).await?;
910 let mut recommendations = Vec::new();
911
912 if prediction.predicted_execution_time > Duration::from_millis(1000) {
914 if features.max_depth > 5.0 {
915 recommendations.push(OptimizationRecommendation {
916 recommendation_type: RecommendationType::ReduceDepth,
917 description: "Consider reducing query depth to improve performance".to_string(),
918 estimated_improvement: 15.0,
919 confidence: 0.8,
920 });
921 }
922
923 if features.complexity_score > 100.0 {
924 recommendations.push(OptimizationRecommendation {
925 recommendation_type: RecommendationType::ReduceComplexity,
926 description: "Query complexity is high, consider simplifying".to_string(),
927 estimated_improvement: 20.0,
928 confidence: 0.7,
929 });
930 }
931 }
932
933 if prediction.cache_hit_probability < 0.3 {
934 recommendations.push(OptimizationRecommendation {
935 recommendation_type: RecommendationType::AddCaching,
936 description: "Low cache hit probability, consider adding caching strategy"
937 .to_string(),
938 estimated_improvement: 30.0,
939 confidence: 0.6,
940 });
941 }
942
943 if features.field_count > 20.0 {
944 recommendations.push(OptimizationRecommendation {
945 recommendation_type: RecommendationType::ParallelizeFields,
946 description: "Many fields requested, consider parallelizing field resolution"
947 .to_string(),
948 estimated_improvement: 25.0,
949 confidence: 0.7,
950 });
951 }
952
953 Ok(recommendations)
954 }
955
956 pub async fn record_execution(
958 &self,
959 document: &Document,
960 metrics: &OperationMetrics,
961 ) -> Result<()> {
962 if !self.config.learning_enabled {
963 return Ok(());
964 }
965
966 let features = self.extract_features(document)?;
967
968 let memory_usage_mb = system_monitor::get_current_memory_usage_mb().await;
970
971 let sample = TrainingSample {
972 features,
973 execution_time_ms: metrics.execution_time.as_millis() as f64,
974 memory_usage_mb,
975 cache_hit: metrics.cache_hit,
976 error_occurred: metrics.error_count > 0,
977 timestamp: metrics.timestamp,
978 };
979
980 let mut samples = self.training_samples.write().await;
982 samples.push_back(sample);
983
984 while samples.len() > self.config.max_training_samples {
986 samples.pop_front();
987 }
988
989 if samples.len() >= self.config.min_samples_for_learning {
991 self.update_models().await?;
992 }
993
994 Ok(())
995 }
996
997 pub async fn add_training_sample(&self, sample: TrainingSample) -> Result<()> {
999 if !self.config.learning_enabled {
1000 return Ok(());
1001 }
1002 let mut samples = self.training_samples.write().await;
1003 samples.push_back(sample);
1004 while samples.len() > self.config.max_training_samples {
1005 samples.pop_front();
1006 }
1007 let sample_count = samples.len();
1008 drop(samples);
1009 if sample_count >= self.config.min_samples_for_learning {
1010 self.update_models().await?;
1011 }
1012 Ok(())
1013 }
1014
1015 pub async fn update_models(&self) -> Result<()> {
1017 let samples = self.training_samples.read().await;
1018 if samples.len() < self.config.min_samples_for_learning {
1019 return Ok(());
1020 }
1021
1022 let recent_samples: Vec<_> = samples
1023 .iter()
1024 .filter(|sample| {
1025 sample.timestamp.elapsed().unwrap_or(Duration::from_secs(0))
1026 < self.config.performance_history_window
1027 })
1028 .cloned()
1029 .collect();
1030
1031 if recent_samples.is_empty() {
1032 return Ok(());
1033 }
1034
1035 drop(samples);
1036
1037 {
1039 let mut stats = self.feature_stats.write().await;
1040 stats.update(&recent_samples);
1041 }
1042
1043 {
1045 let mut model = self.execution_time_model.write().await;
1046 model.train(&recent_samples, 0.01, 100); }
1048
1049 {
1051 let mut model = self.memory_model.write().await;
1052 model.train(&recent_samples, 0.01, 100);
1053 }
1054
1055 info!("ML models updated with {} samples", recent_samples.len());
1056 Ok(())
1057 }
1058
1059 #[allow(clippy::only_used_in_recursion)]
1061 #[allow(clippy::type_complexity)]
1062 fn analyze_selection_set(
1063 &self,
1064 selection_set: &SelectionSet,
1065 depth: usize,
1066 ) -> Result<(usize, usize, usize, HashSet<String>, usize, usize, usize)> {
1067 let mut field_count = 0;
1068 let mut max_depth = depth;
1069 let mut selection_count = selection_set.selections.len();
1070 let mut unique_field_types = HashSet::new();
1071 let mut nested_list_count = 0;
1072 let mut argument_count = 0;
1073 let mut directive_count = 0;
1074
1075 for selection in &selection_set.selections {
1076 match selection {
1077 Selection::Field(field) => {
1078 field_count += 1;
1079 unique_field_types.insert(field.name.clone());
1080 argument_count += field.arguments.len();
1081 directive_count += field.directives.len();
1082
1083 if let Some(ref sub_selection_set) = field.selection_set {
1084 let (fc, md, sc, uft, nlc, ac, dc) =
1085 self.analyze_selection_set(sub_selection_set, depth + 1)?;
1086 field_count += fc;
1087 max_depth = max_depth.max(md);
1088 selection_count += sc;
1089 unique_field_types.extend(uft);
1090 nested_list_count += nlc;
1091 argument_count += ac;
1092 directive_count += dc;
1093
1094 if field.name.ends_with("s") || field.name.contains("list") {
1096 nested_list_count += 1;
1097 }
1098 }
1099 }
1100 Selection::InlineFragment(fragment) => {
1101 directive_count += fragment.directives.len();
1102 let (fc, md, sc, uft, nlc, ac, dc) =
1103 self.analyze_selection_set(&fragment.selection_set, depth)?;
1104 field_count += fc;
1105 max_depth = max_depth.max(md);
1106 selection_count += sc;
1107 unique_field_types.extend(uft);
1108 nested_list_count += nlc;
1109 argument_count += ac;
1110 directive_count += dc;
1111 }
1112 Selection::FragmentSpread(spread) => {
1113 directive_count += spread.directives.len();
1114 }
1116 }
1117 }
1118
1119 Ok((
1120 field_count,
1121 max_depth,
1122 selection_count,
1123 unique_field_types,
1124 nested_list_count,
1125 argument_count,
1126 directive_count,
1127 ))
1128 }
1129
1130 fn estimate_result_size(&self, complexity: &QueryComplexity) -> f64 {
1132 (complexity.field_count as f64 * complexity.depth as f64)
1134 .log10()
1135 .max(1.0)
1136 }
1137
1138 fn estimate_cache_hit_probability(&self, features: &QueryFeatures) -> f64 {
1140 let complexity_factor = (features.complexity_score / 100.0).min(1.0);
1142 (1.0 - complexity_factor).max(0.1)
1143 }
1144
1145 fn estimate_error_probability(&self, features: &QueryFeatures) -> f64 {
1147 let complexity_factor = (features.complexity_score / 200.0).min(1.0);
1149 complexity_factor.max(0.01)
1150 }
1151
1152 async fn calculate_confidence(
1154 &self,
1155 execution_model: &LinearRegressionModel,
1156 memory_model: &LinearRegressionModel,
1157 ) -> f64 {
1158 let min_samples = execution_model
1159 .training_samples
1160 .min(memory_model.training_samples);
1161 let confidence =
1162 (min_samples as f64 / self.config.min_samples_for_learning as f64).min(1.0);
1163 confidence.max(0.1)
1164 }
1165}
1166
1167#[cfg(test)]
1168mod tests {
1169 use super::*;
1170 use crate::ast::*;
1171 use crate::performance::PerformanceTracker;
1172
1173 #[test]
1174 fn test_linear_regression_model() {
1175 let feature_count = 12; let mut model = LinearRegressionModel::new(feature_count);
1177
1178 let samples = vec![
1180 TrainingSample {
1181 features: QueryFeatures::from_vector(&[
1182 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0,
1183 ])
1184 .expect("should succeed"),
1185 execution_time_ms: 100.0,
1186 memory_usage_mb: 0.0,
1187 cache_hit: false,
1188 error_occurred: false,
1189 timestamp: SystemTime::now(),
1190 },
1191 TrainingSample {
1192 features: QueryFeatures::from_vector(&[
1193 2.0, 4.0, 6.0, 8.0, 10.0, 12.0, 14.0, 16.0, 18.0, 20.0, 22.0, 24.0,
1194 ])
1195 .expect("should succeed"),
1196 execution_time_ms: 200.0,
1197 memory_usage_mb: 0.0,
1198 cache_hit: false,
1199 error_occurred: false,
1200 timestamp: SystemTime::now(),
1201 },
1202 ];
1203
1204 model.train(&samples, 0.01, 100);
1205
1206 let test_features = [
1208 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5, 15.0, 16.5, 18.0,
1209 ];
1210 let prediction = model.predict(&test_features);
1211
1212 assert!(prediction >= 0.0, "Prediction should be non-negative");
1214 assert!(prediction < 1000.0, "Prediction should be reasonable");
1215 }
1216
1217 #[test]
1218 fn test_feature_statistics() {
1219 let mut stats = FeatureStatistics::default();
1220
1221 let samples = vec![
1222 TrainingSample {
1223 features: QueryFeatures::from_vector(&[
1224 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0,
1225 ])
1226 .expect("should succeed"),
1227 execution_time_ms: 100.0,
1228 memory_usage_mb: 50.0, cache_hit: false,
1230 error_occurred: false,
1231 timestamp: SystemTime::now(),
1232 },
1233 TrainingSample {
1234 features: QueryFeatures::from_vector(&[
1235 2.0, 4.0, 6.0, 8.0, 10.0, 12.0, 14.0, 16.0, 18.0, 20.0, 22.0, 24.0,
1236 ])
1237 .expect("should succeed"),
1238 execution_time_ms: 200.0,
1239 memory_usage_mb: 20.0,
1240 cache_hit: true,
1241 error_occurred: false,
1242 timestamp: SystemTime::now(),
1243 },
1244 ];
1245
1246 stats.update(&samples);
1247
1248 assert_eq!(stats.feature_means.len(), 12);
1249 assert_eq!(stats.feature_stds.len(), 12);
1250 assert_eq!(stats.sample_count, 2);
1251
1252 let normalized = stats.normalize(&[
1254 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5, 15.0, 16.5, 18.0,
1255 ]);
1256 assert_eq!(normalized.len(), 12);
1257 }
1258
1259 #[tokio::test]
1260 async fn test_ml_optimizer_creation() {
1261 let config = MLOptimizerConfig::default();
1262 let performance_tracker = Arc::new(PerformanceTracker::new());
1263
1264 let optimizer = MLQueryOptimizer::new(config, performance_tracker);
1265
1266 let document = Document {
1268 definitions: vec![Definition::Operation(OperationDefinition {
1269 operation_type: OperationType::Query,
1270 name: Some("TestQuery".to_string()),
1271 variable_definitions: vec![],
1272 directives: vec![],
1273 selection_set: SelectionSet {
1274 selections: vec![Selection::Field(Field {
1275 alias: None,
1276 name: "user".to_string(),
1277 arguments: vec![],
1278 directives: vec![],
1279 selection_set: Some(SelectionSet {
1280 selections: vec![Selection::Field(Field {
1281 alias: None,
1282 name: "id".to_string(),
1283 arguments: vec![],
1284 directives: vec![],
1285 selection_set: None,
1286 })],
1287 }),
1288 })],
1289 },
1290 })],
1291 };
1292
1293 let features = optimizer
1294 .extract_features(&document)
1295 .expect("should succeed");
1296 assert!(features.field_count > 0.0);
1297 assert!(features.max_depth > 0.0);
1298 }
1299
1300 #[test]
1301 fn test_neural_network_model_creation() {
1302 let layer_sizes = vec![12, 8, 4, 1];
1303 let model = NeuralNetworkModel::new(layer_sizes.clone(), 0.01);
1304
1305 assert_eq!(model.layer_sizes, layer_sizes);
1306 assert_eq!(model.weights.len(), 3); assert_eq!(model.biases.len(), 3);
1308 assert_eq!(model.training_iterations, 0);
1309 }
1310
1311 #[test]
1312 fn test_neural_network_forward_pass() {
1313 let layer_sizes = vec![3, 2, 1];
1314 let model = NeuralNetworkModel::new(layer_sizes, 0.01);
1315
1316 let input = vec![1.0, 2.0, 3.0];
1317 let (activations, output) = model.forward(&input);
1318
1319 assert_eq!(activations.len(), 3); assert_eq!(activations[0].len(), 3); assert!(!output.is_nan());
1322 }
1323
1324 #[test]
1325 fn test_neural_network_training() {
1326 let layer_sizes = vec![12, 4, 1];
1327 let mut model = NeuralNetworkModel::new(layer_sizes, 0.01);
1328
1329 let samples: Vec<TrainingSample> = (0..10)
1331 .map(|i| {
1332 let features = QueryFeatures {
1333 field_count: i as f64,
1334 max_depth: (i % 5) as f64,
1335 complexity_score: i as f64 * 2.0,
1336 selection_count: i as f64,
1337 has_fragments: 0.0,
1338 has_variables: 1.0,
1339 operation_type: 0.0,
1340 unique_field_types: 3.0,
1341 nested_list_count: 0.0,
1342 argument_count: i as f64,
1343 directive_count: 0.0,
1344 estimated_result_size: 100.0,
1345 };
1346 TrainingSample {
1347 features,
1348 execution_time_ms: (i as f64 * 10.0).max(1.0),
1349 memory_usage_mb: 1.0,
1350 cache_hit: false,
1351 error_occurred: false,
1352 timestamp: std::time::SystemTime::now(),
1353 }
1354 })
1355 .collect();
1356
1357 model.train(&samples, 10);
1358
1359 assert_eq!(model.training_iterations, 10);
1360 assert_eq!(model.loss_history.len(), 10);
1361 }
1362
1363 #[test]
1364 fn test_neural_network_predict_with_confidence() {
1365 let layer_sizes = vec![12, 4, 1];
1366 let model = NeuralNetworkModel::new(layer_sizes, 0.01);
1367
1368 let features = vec![1.0; 12];
1369 let (prediction, confidence) = model.predict_with_confidence(&features);
1370
1371 assert!(!prediction.is_nan());
1372 assert!((0.0..=1.0).contains(&confidence));
1373 }
1374
1375 #[test]
1376 fn test_neural_network_feature_importance() {
1377 let layer_sizes = vec![12, 4, 1];
1378 let model = NeuralNetworkModel::new(layer_sizes, 0.01);
1379
1380 let importance = model.get_feature_importance();
1381
1382 assert_eq!(importance.len(), 12);
1383 let total: f64 = importance.iter().sum();
1385 assert!((total - 1.0).abs() < 0.001);
1386 }
1387
1388 #[test]
1389 fn test_neural_network_stats() {
1390 let layer_sizes = vec![12, 8, 4, 1];
1391 let model = NeuralNetworkModel::new(layer_sizes.clone(), 0.01);
1392
1393 let stats = model.get_stats();
1394
1395 assert_eq!(stats.training_iterations, 0);
1396 assert_eq!(stats.final_loss, 0.0);
1397 assert_eq!(stats.layer_sizes, layer_sizes);
1398 assert!(stats.total_parameters > 0);
1399 }
1400
1401 #[test]
1402 fn test_ensemble_model_creation() {
1403 let model = EnsembleModel::new(12, Some(vec![8, 4]), 0.01);
1404
1405 assert_eq!(model.linear_weight, 0.5);
1406 assert_eq!(model.neural_weight, 0.5);
1407 }
1408
1409 #[test]
1410 fn test_ensemble_model_prediction() {
1411 let model = EnsembleModel::new(12, Some(vec![4]), 0.01);
1412
1413 let features = vec![1.0; 12];
1414 let prediction = model.predict(&features);
1415
1416 assert!(!prediction.is_nan());
1417 }
1418
1419 #[test]
1420 fn test_ensemble_model_predict_with_confidence() {
1421 let model = EnsembleModel::new(12, Some(vec![4]), 0.01);
1422
1423 let features = vec![1.0; 12];
1424 let (prediction, confidence) = model.predict_with_confidence(&features);
1425
1426 assert!(!prediction.is_nan());
1427 assert!((0.0..=1.0).contains(&confidence));
1428 }
1429
1430 #[test]
1431 fn test_ensemble_model_training() {
1432 let mut model = EnsembleModel::new(12, None, 0.01);
1434
1435 let samples: Vec<TrainingSample> = (0..5)
1437 .map(|i| {
1438 let features = QueryFeatures {
1439 field_count: (i + 1) as f64,
1440 max_depth: 2.0,
1441 complexity_score: (i + 1) as f64 * 2.0,
1442 selection_count: (i + 1) as f64,
1443 has_fragments: 0.0,
1444 has_variables: 1.0,
1445 operation_type: 0.0,
1446 unique_field_types: 3.0,
1447 nested_list_count: 0.0,
1448 argument_count: (i + 1) as f64,
1449 directive_count: 0.0,
1450 estimated_result_size: 100.0,
1451 };
1452 TrainingSample {
1453 features,
1454 execution_time_ms: ((i + 1) as f64 * 10.0),
1455 memory_usage_mb: 1.0,
1456 cache_hit: false,
1457 error_occurred: false,
1458 timestamp: std::time::SystemTime::now(),
1459 }
1460 })
1461 .collect();
1462
1463 model.train(&samples, 5);
1464
1465 let stats = model.get_stats();
1467 assert_eq!(stats.linear_weight, 0.5);
1468 assert_eq!(stats.neural_weight, 0.5);
1469 assert!(stats.neural_stats.is_none());
1470 }
1471
1472 #[test]
1473 fn test_ensemble_stats() {
1474 let model = EnsembleModel::new(12, Some(vec![8, 4]), 0.01);
1475
1476 let stats = model.get_stats();
1477
1478 assert_eq!(stats.linear_weight, 0.5);
1479 assert_eq!(stats.neural_weight, 0.5);
1480 assert_eq!(stats.linear_sample_count, 0);
1481 assert!(stats.neural_stats.is_some());
1482 }
1483
1484 #[test]
1485 fn test_linear_regression_confidence() {
1486 let mut model = LinearRegressionModel::new(12);
1487
1488 assert!(model.get_confidence() < 0.3);
1490
1491 model.training_samples = 500;
1493 assert!(model.get_confidence() > 0.5);
1494
1495 model.training_samples = 2000;
1496 assert!(model.get_confidence() >= 0.8);
1497 }
1498}