Skip to main content

oxirs_gql/
ml_optimizer.rs

1//! Machine Learning-Enhanced Query Optimizer
2//!
3//! This module provides ML-powered query optimization capabilities that learn from
4//! historical query performance to make intelligent optimization decisions.
5//!
6//! ## SciRS2-Core Integration
7//!
8//! This module leverages SciRS2-Core for high-performance ML capabilities:
9//! - **Array Operations**: `scirs2_core::ndarray_ext` for vectorized feature matrices
10//! - **Random Number Generation**: `scirs2_core::random` for model initialization
11//! - **SIMD Operations**: Vectorized computations for faster training
12//! - **Parallel Processing**: Batch operations for improved throughput
13//! - **Memory Efficiency**: Optimized memory usage for large training datasets
14//! - **Statistics**: Advanced statistical operations for model evaluation
15
16use 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
24// SciRS2-Core integration for comprehensive ML operations
25use 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/// ML optimizer configuration
35#[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), // 1 hour
60            max_training_samples: 10000,
61            performance_history_window: Duration::from_secs(86400), // 24 hours
62            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/// Query features extracted for ML
73#[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, // 0 = Query, 1 = Mutation, 2 = Subscription
82    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    /// Convert features to a vector for ML algorithms
91    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    /// Create features from a vector
109    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/// Training sample for the ML model
135#[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/// Performance prediction from the ML model
146#[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/// Optimization recommendation
156#[derive(Debug, Clone)]
157pub struct OptimizationRecommendation {
158    pub recommendation_type: RecommendationType,
159    pub description: String,
160    pub estimated_improvement: f64, // Percentage improvement
161    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/// Activation functions for neural network
176#[derive(Debug, Clone, Serialize, Deserialize)]
177pub enum ActivationFunction {
178    ReLU,
179    Sigmoid,
180    Tanh,
181    Linear,
182    Softmax,
183}
184
185/// Advanced linear regression model for performance prediction
186/// Enhanced with SciRS2-Core for vectorized matrix operations and efficient training
187#[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    /// L2 regularization parameter to prevent overfitting
194    pub regularization: f64,
195}
196
197impl LinearRegressionModel {
198    pub fn new(feature_count: usize) -> Self {
199        // Enhanced initialization with SciRS2 Random for better convergence
200        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    /// Create model with custom regularization
215    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    /// Predict execution time based on features
222    pub fn predict(&self, features: &[f64]) -> f64 {
223        if features.len() != self.weights.len() {
224            return 1000.0; // Default prediction
225        }
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) // Ensure non-negative prediction
235    }
236
237    /// Train the model using vectorized gradient descent with SciRS2-Core array operations
238    /// This implementation uses matrix operations for efficient batch processing
239    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        // Filter valid samples
247        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        // Convert to feature matrix using SciRS2 Array2 for vectorized operations
257        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        // Gradient descent with vectorized operations
274        let mut weights = weight_array;
275        let mut bias = self.bias;
276
277        for _ in 0..iterations {
278            // Vectorized prediction: X @ w + b
279            let predictions = x_matrix.dot(&weights) + bias;
280
281            // Compute errors: predictions - y
282            let errors = &predictions - &y_vector;
283
284            // Vectorized gradient computation with L2 regularization
285            // gradient_w = (1/n) * X^T @ errors + lambda * w
286            let gradient_w =
287                x_matrix.t().dot(&errors) / (n_samples as f64) + &weights * self.regularization;
288
289            // gradient_b = (1/n) * sum(errors)
290            let gradient_b = errors.sum() / (n_samples as f64);
291
292            // Update parameters
293            weights = &weights - &gradient_w * learning_rate;
294            bias -= learning_rate * gradient_b;
295        }
296
297        // Update model parameters
298        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    /// Get confidence score based on training samples
312    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    /// Get the number of training samples (alias for compatibility)
325    pub fn sample_count(&self) -> usize {
326        self.training_samples
327    }
328}
329
330/// ML-enhanced query optimizer
331pub 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/// Statistics for feature normalization
343#[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    /// Update statistics with new samples
352    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        // Calculate means
365        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        // Calculate standard deviations
383        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    /// Normalize features using z-score normalization
401    /// Enhanced with SciRS2-Core array operations for vectorized processing
402    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        // Use SciRS2 array operations for efficient vectorized normalization
408        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        // Vectorized z-score: (x - mean) / std
413        let normalized = (&features_arr - &means_arr) / &stds_arr;
414
415        normalized.to_vec()
416    }
417}
418
419/// Advanced Neural Network ML Model using SciRS2-Core
420/// Provides improved prediction accuracy through multi-layer perceptron architecture
421pub struct NeuralNetworkModel {
422    /// Weights for each layer
423    weights: Vec<Array2<f64>>,
424    /// Biases for each layer
425    biases: Vec<Array1<f64>>,
426    /// Layer sizes
427    layer_sizes: Vec<usize>,
428    /// Learning rate
429    learning_rate: f64,
430    /// L2 regularization strength
431    l2_lambda: f64,
432    /// Number of training iterations
433    training_iterations: usize,
434    /// Training loss history
435    loss_history: Vec<f64>,
436}
437
438impl NeuralNetworkModel {
439    /// Create a new neural network model
440    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        // Initialize weights and biases for each layer
446        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            // Xavier/Glorot initialization: scale by sqrt(2 / (input + output))
451            let scale = (2.0 / (input_size + output_size) as f64).sqrt();
452
453            // Initialize weights with scaled normal distribution
454            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            // Initialize biases to zero
462            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, // Default L2 regularization
472            training_iterations: 0,
473            loss_history: Vec::new(),
474        }
475    }
476
477    /// ReLU activation function
478    fn relu(x: f64) -> f64 {
479        x.max(0.0)
480    }
481
482    /// ReLU derivative
483    fn relu_derivative(x: f64) -> f64 {
484        if x > 0.0 {
485            1.0
486        } else {
487            0.0
488        }
489    }
490
491    /// Forward pass through the network
492    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            // Linear transformation: z = Wx + b
499            let z = current.dot(&self.weights[i]) + &self.biases[i];
500
501            // Apply activation (ReLU for hidden layers, linear for output)
502            let activation = if i < self.weights.len() - 1 {
503                z.mapv(Self::relu)
504            } else {
505                z // Linear output for regression
506            };
507
508            activations.push(activation);
509        }
510
511        // Return activations and final output
512        let output = activations
513            .last()
514            .expect("activations should not be empty after forward pass")[0];
515        (activations, output)
516    }
517
518    /// Train the model using gradient descent
519    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                // Forward pass
528                let (activations, prediction) = self.forward(&features);
529
530                // Compute loss (MSE)
531                let error = prediction - target;
532                total_loss += error * error;
533
534                // Backward pass (gradient descent)
535                self.backward(&activations, error);
536            }
537
538            // Record average loss
539            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    /// Backward pass for gradient computation and weight updates
546    fn backward(&mut self, activations: &[Array1<f64>], error: f64) {
547        // Output layer gradient
548        let mut delta = Array1::from_vec(vec![error]);
549
550        // Backpropagate through layers
551        for i in (0..self.weights.len()).rev() {
552            let input_activation = &activations[i];
553
554            // Compute gradients
555            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            // Update weights with L2 regularization
561            let regularization = &self.weights[i] * self.l2_lambda;
562            self.weights[i] = &self.weights[i] - &(&grad_w * self.learning_rate + &regularization);
563
564            // Update biases
565            self.biases[i] = &self.biases[i] - &(&delta * self.learning_rate);
566
567            // Compute delta for previous layer
568            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    /// Predict with confidence interval
586    pub fn predict_with_confidence(&self, features: &[f64]) -> (f64, f64) {
587        let (_, prediction) = self.forward(features);
588
589        // Estimate confidence based on training history
590        let confidence = if self.training_iterations < 100 {
591            0.3 // Low confidence with few iterations
592        } else if self.training_iterations < 1000 {
593            0.6
594        } else {
595            // Use recent loss trend to estimate confidence
596            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    /// Get feature importance scores
604    pub fn get_feature_importance(&self) -> Vec<f64> {
605        if self.weights.is_empty() {
606            return Vec::new();
607        }
608
609        // Use first layer weights to compute importance
610        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        // Normalize
619        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    /// Get model statistics
628    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/// Model statistics
639#[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
647/// Ensemble model combining linear regression and neural network
648pub struct EnsembleModel {
649    linear_model: LinearRegressionModel,
650    neural_model: Option<NeuralNetworkModel>,
651    linear_weight: f64,
652    neural_weight: f64,
653}
654
655impl EnsembleModel {
656    /// Create a new ensemble model
657    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); // Single output
662            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    /// Train the ensemble
674    pub fn train(&mut self, samples: &[TrainingSample], epochs: usize) {
675        // Train linear model with default learning rate and iterations
676        self.linear_model.train(samples, 0.01, 100);
677
678        // Train neural model if available
679        if let Some(ref mut neural) = self.neural_model {
680            neural.train(samples, epochs);
681        }
682
683        // Adjust weights based on performance
684        self.adjust_weights(samples);
685    }
686
687    /// Adjust ensemble weights based on validation performance
688    fn adjust_weights(&mut self, samples: &[TrainingSample]) {
689        if samples.is_empty() {
690            return;
691        }
692
693        // If no neural model, keep default weights
694        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        // Inverse error weighting - models with lower error get higher weight
715        // Add small epsilon to avoid division by zero
716        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        // Normalize to get weights that sum to 1.0
722        self.linear_weight = linear_inv / total_inv;
723        self.neural_weight = neural_inv / total_inv;
724    }
725
726    /// Predict using ensemble
727    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    /// Predict with confidence
739    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    /// Get ensemble statistics
754    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/// Ensemble model statistics
765#[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    /// Create a new ML-enhanced query optimizer
775    pub fn new(config: MLOptimizerConfig, performance_tracker: Arc<PerformanceTracker>) -> Self {
776        let feature_count = 12; // Number of features in QueryFeatures
777
778        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    /// Extract features from a GraphQL document
790    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        // Analyze each definition in the document
805        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                    // Analyze selection set
819                    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                    // Schema definitions don't affect query complexity
834                }
835                crate::ast::Definition::Type(_) => {
836                    // Type definitions don't affect query complexity
837                }
838                crate::ast::Definition::Directive(_) => {
839                    directive_count += 1;
840                }
841                crate::ast::Definition::SchemaExtension(_) => {
842                    // Schema extensions don't affect query complexity
843                }
844                crate::ast::Definition::TypeExtension(_) => {
845                    // Type extensions don't affect query complexity
846                }
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    /// Predict query performance using ML models
869    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        // Normalize features
874        let stats = self.feature_stats.read().await;
875        let normalized_features = stats.normalize(&feature_vector);
876        drop(stats);
877
878        // Get predictions from models
879        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        // Calculate confidence based on training samples
886        let confidence = self
887            .calculate_confidence(&execution_time_model, &memory_model)
888            .await;
889
890        // Estimate cache hit probability and error probability based on complexity
891        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    /// Generate optimization recommendations based on ML predictions
904    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        // Recommend based on predicted performance
913        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    /// Record query execution for learning
957    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        // Get real memory usage measurement
969        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        // Add to training samples
981        let mut samples = self.training_samples.write().await;
982        samples.push_back(sample);
983
984        // Limit sample size
985        while samples.len() > self.config.max_training_samples {
986            samples.pop_front();
987        }
988
989        // Trigger model update if we have enough samples
990        if samples.len() >= self.config.min_samples_for_learning {
991            self.update_models().await?;
992        }
993
994        Ok(())
995    }
996
997    /// Add a pre-built training sample directly (used by external callers who don't have a Document)
998    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    /// Update ML models with new training data
1016    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        // Update feature statistics
1038        {
1039            let mut stats = self.feature_stats.write().await;
1040            stats.update(&recent_samples);
1041        }
1042
1043        // Train execution time model
1044        {
1045            let mut model = self.execution_time_model.write().await;
1046            model.train(&recent_samples, 0.01, 100); // learning_rate=0.01, iterations=100
1047        }
1048
1049        // Train memory model
1050        {
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    /// Analyze a selection set recursively
1060    #[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                        // Count nested lists (simplified heuristic)
1095                        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                    // Fragment spread doesn't directly contribute to depth analysis
1115                }
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    /// Estimate result size based on query complexity
1131    fn estimate_result_size(&self, complexity: &QueryComplexity) -> f64 {
1132        // Simple heuristic based on field count and depth
1133        (complexity.field_count as f64 * complexity.depth as f64)
1134            .log10()
1135            .max(1.0)
1136    }
1137
1138    /// Estimate cache hit probability based on query features
1139    fn estimate_cache_hit_probability(&self, features: &QueryFeatures) -> f64 {
1140        // Simple heuristic: less complex queries are more likely to be cached
1141        let complexity_factor = (features.complexity_score / 100.0).min(1.0);
1142        (1.0 - complexity_factor).max(0.1)
1143    }
1144
1145    /// Estimate error probability based on query features
1146    fn estimate_error_probability(&self, features: &QueryFeatures) -> f64 {
1147        // Simple heuristic: more complex queries are more likely to have errors
1148        let complexity_factor = (features.complexity_score / 200.0).min(1.0);
1149        complexity_factor.max(0.01)
1150    }
1151
1152    /// Calculate confidence score based on model training
1153    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; // Match QueryFeatures vector length
1176        let mut model = LinearRegressionModel::new(feature_count);
1177
1178        // Create simple training data
1179        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        // Test prediction with a feature vector
1207        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        // Since we're training with limited data, just ensure we get a reasonable prediction
1213        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, // Realistic test value
1229                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        // Test normalization
1253        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        // Test with a simple document
1267        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); // 3 weight matrices for 4 layers
1307        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); // Input + 2 hidden layers
1320        assert_eq!(activations[0].len(), 3); // Input size
1321        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        // Create synthetic training samples
1330        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        // Importance should sum to approximately 1.0
1384        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        // Test ensemble without neural network component (simpler case)
1433        let mut model = EnsembleModel::new(12, None, 0.01);
1434
1435        // Create synthetic training samples
1436        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        // Weights should remain at default when no neural network
1466        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        // Initial confidence should be low
1489        assert!(model.get_confidence() < 0.3);
1490
1491        // Simulate training samples
1492        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}