1use super::{config::*, models::TimeSeriesModelTrait};
4use crate::error::{MLError, Result};
5use ndarray::{s, Array1, Array2};
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::f64::consts::PI;
9
10#[derive(Debug, Clone)]
12pub struct QuantumEnsembleManager {
13 config: EnsembleConfig,
15
16 models: Vec<Box<dyn TimeSeriesModelTrait>>,
18
19 model_weights: Array1<f64>,
21
22 voting_circuits: Vec<Array1<f64>>,
24
25 performance_history: Vec<ModelPerformanceHistory>,
27
28 diversity_metrics: DiversityMetrics,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct ModelPerformanceHistory {
35 pub model_id: usize,
37
38 pub accuracies: Vec<f64>,
40
41 pub losses: Vec<f64>,
43
44 pub confidence_scores: Vec<f64>,
46
47 pub quantum_fidelities: Vec<f64>,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct DiversityMetrics {
54 pub prediction_correlations: Array2<f64>,
56
57 pub disagreement_scores: Array1<f64>,
59
60 pub quantum_entanglement: Array2<f64>,
62
63 pub overall_diversity: f64,
65}
66
67#[derive(Debug, Clone)]
69pub struct QuantumVotingMechanism {
70 strategy: VotingStrategy,
72
73 voting_circuit: VotingCircuit,
75
76 confidence_aggregation: ConfidenceAggregation,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
82pub enum VotingStrategy {
83 Majority,
85
86 Weighted,
88
89 QuantumSuperposition,
91
92 BayesianAveraging,
94
95 Adaptive,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct VotingCircuit {
102 num_qubits: usize,
104
105 parameters: Array1<f64>,
107
108 entanglement_patterns: Vec<EntanglementPattern>,
110
111 measurement_strategy: MeasurementStrategy,
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct EntanglementPattern {
118 pub qubits: Vec<usize>,
120
121 pub strength: f64,
123
124 pub pattern_type: EntanglementType,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130pub enum EntanglementType {
131 Bell,
132 GHZ,
133 Cluster,
134 Custom(String),
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
139pub enum ConfidenceAggregation {
140 Average,
141 WeightedAverage,
142 QuantumCoherence,
143 BayesianFusion,
144}
145
146#[derive(Debug, Clone)]
148pub struct BootstrapAggregator {
149 num_samples: usize,
151
152 sample_fraction: f64,
154
155 bootstrap_models: Vec<Box<dyn TimeSeriesModelTrait>>,
157
158 quantum_sampling: bool,
160}
161
162#[derive(Debug, Clone)]
164pub struct StackingEnsemble {
165 base_models: Vec<Box<dyn TimeSeriesModelTrait>>,
167
168 meta_learner: Box<dyn TimeSeriesModelTrait>,
170
171 cv_folds: usize,
173
174 quantum_meta_learning: bool,
176}
177
178impl QuantumEnsembleManager {
179 pub fn new(config: EnsembleConfig) -> Self {
181 let num_models = config.num_models;
182 let model_weights = Array1::from_elem(num_models, 1.0 / num_models as f64);
183
184 let mut voting_circuits = Vec::new();
186 for model_idx in 0..num_models {
187 let circuit_params = Array1::from_shape_fn(10, |i| {
188 PI * (model_idx + i) as f64 / (num_models + 10) as f64
189 });
190 voting_circuits.push(circuit_params);
191 }
192
193 let performance_history = (0..num_models)
194 .map(|i| ModelPerformanceHistory::new(i))
195 .collect();
196 let diversity_metrics = DiversityMetrics::new(num_models);
197
198 Self {
199 config,
200 models: Vec::new(),
201 model_weights,
202 voting_circuits,
203 performance_history,
204 diversity_metrics,
205 }
206 }
207
208 pub fn add_model(&mut self, model: Box<dyn TimeSeriesModelTrait>) {
210 self.models.push(model);
211
212 if self.models.len() > self.model_weights.len() {
214 let new_size = self.models.len();
215 self.model_weights = Array1::from_elem(new_size, 1.0 / new_size as f64);
216 }
217 }
218
219 pub fn set_models(&mut self, models: Vec<Box<dyn TimeSeriesModelTrait>>) {
221 self.models = models;
222 let num_models = self.models.len();
223 self.model_weights = Array1::from_elem(num_models, 1.0 / num_models as f64);
224
225 self.performance_history = (0..num_models)
227 .map(|i| ModelPerformanceHistory::new(i))
228 .collect();
229 self.diversity_metrics = DiversityMetrics::new(num_models);
230 }
231
232 pub fn fit_ensemble(&mut self, data: &Array2<f64>, targets: &Array2<f64>) -> Result<()> {
234 let diversity_strategy = self.config.diversity_strategy.clone();
236 let voting_circuits = self.voting_circuits.clone();
237
238 for (model_idx, model) in self.models.iter_mut().enumerate() {
239 let (diverse_data, diverse_targets) = Self::apply_diversity_strategy_static(
241 &diversity_strategy,
242 data,
243 targets,
244 model_idx,
245 &voting_circuits,
246 )?;
247
248 model.fit(&diverse_data, &diverse_targets)?;
250
251 let predictions = model.predict(&diverse_data, diverse_targets.ncols())?;
253 let performance =
254 Self::calculate_model_performance_static(&predictions, &diverse_targets)?;
255 self.performance_history[model_idx].update_performance(performance);
256 }
257
258 self.update_diversity_metrics(data, targets)?;
260
261 self.optimize_ensemble_weights(data, targets)?;
263
264 Ok(())
265 }
266
267 pub fn predict_ensemble(&self, data: &Array2<f64>, horizon: usize) -> Result<Array2<f64>> {
269 if self.models.is_empty() {
270 return Err(MLError::MLOperationError(
271 "No models in ensemble".to_string(),
272 ));
273 }
274
275 let mut model_predictions = Vec::new();
277 for model in &self.models {
278 let predictions = model.predict(data, horizon)?;
279 model_predictions.push(predictions);
280 }
281
282 let ensemble_prediction = match &self.config.method {
284 EnsembleMethod::Average => self.average_predictions(&model_predictions)?,
285 EnsembleMethod::Weighted(weights) => {
286 self.weighted_average_predictions(&model_predictions, weights)?
287 }
288 EnsembleMethod::QuantumSuperposition => {
289 self.quantum_superposition_predictions(&model_predictions)?
290 }
291 EnsembleMethod::Stacking => self.stacking_predictions(&model_predictions, data)?,
292 EnsembleMethod::BayesianAverage => {
293 self.bayesian_average_predictions(&model_predictions)?
294 }
295 };
296
297 Ok(ensemble_prediction)
298 }
299
300 fn apply_diversity_strategy(
302 &self,
303 data: &Array2<f64>,
304 targets: &Array2<f64>,
305 model_idx: usize,
306 ) -> Result<(Array2<f64>, Array2<f64>)> {
307 Self::apply_diversity_strategy_static(
308 &self.config.diversity_strategy,
309 data,
310 targets,
311 model_idx,
312 &self.voting_circuits,
313 )
314 }
315
316 fn apply_diversity_strategy_static(
318 strategy: &DiversityStrategy,
319 data: &Array2<f64>,
320 targets: &Array2<f64>,
321 model_idx: usize,
322 voting_circuits: &[Array1<f64>],
323 ) -> Result<(Array2<f64>, Array2<f64>)> {
324 match strategy {
325 DiversityStrategy::RandomInit => {
326 Ok((data.clone(), targets.clone()))
328 }
329 DiversityStrategy::Bootstrap => Self::bootstrap_sample_static(data, targets),
330 DiversityStrategy::FeatureBagging => {
331 Self::feature_bagging_static(data, targets, model_idx)
332 }
333 DiversityStrategy::QuantumDiversity => {
334 Self::quantum_diversity_transform_static(data, targets, model_idx, voting_circuits)
335 }
336 }
337 }
338
339 fn bootstrap_sample(
341 &self,
342 data: &Array2<f64>,
343 targets: &Array2<f64>,
344 ) -> Result<(Array2<f64>, Array2<f64>)> {
345 Self::bootstrap_sample_static(data, targets)
346 }
347
348 fn bootstrap_sample_static(
350 data: &Array2<f64>,
351 targets: &Array2<f64>,
352 ) -> Result<(Array2<f64>, Array2<f64>)> {
353 let n_samples = data.nrows();
354 let mut sampled_data = Array2::zeros(data.dim());
355 let mut sampled_targets = Array2::zeros(targets.dim());
356
357 for i in 0..n_samples {
358 let sample_idx = fastrand::usize(0..n_samples);
359 sampled_data.row_mut(i).assign(&data.row(sample_idx));
360 sampled_targets.row_mut(i).assign(&targets.row(sample_idx));
361 }
362
363 Ok((sampled_data, sampled_targets))
364 }
365
366 fn feature_bagging(
368 &self,
369 data: &Array2<f64>,
370 targets: &Array2<f64>,
371 model_idx: usize,
372 ) -> Result<(Array2<f64>, Array2<f64>)> {
373 Self::feature_bagging_static(data, targets, model_idx)
374 }
375
376 fn feature_bagging_static(
378 data: &Array2<f64>,
379 targets: &Array2<f64>,
380 _model_idx: usize,
381 ) -> Result<(Array2<f64>, Array2<f64>)> {
382 let n_features = data.ncols();
383 let feature_fraction = 0.7; let n_selected = ((n_features as f64) * feature_fraction) as usize;
385
386 let mut selected_features = Vec::new();
388 for _ in 0..n_selected {
389 let feature_idx = fastrand::usize(0..n_features);
390 if !selected_features.contains(&feature_idx) {
391 selected_features.push(feature_idx);
392 }
393 }
394
395 if selected_features.is_empty() {
397 selected_features.push(0);
398 }
399
400 let mut subset_data = Array2::zeros((data.nrows(), selected_features.len()));
402 for (new_idx, &old_idx) in selected_features.iter().enumerate() {
403 subset_data
404 .column_mut(new_idx)
405 .assign(&data.column(old_idx));
406 }
407
408 Ok((subset_data, targets.clone()))
409 }
410
411 fn quantum_diversity_transform(
413 &self,
414 data: &Array2<f64>,
415 targets: &Array2<f64>,
416 model_idx: usize,
417 ) -> Result<(Array2<f64>, Array2<f64>)> {
418 Self::quantum_diversity_transform_static(data, targets, model_idx, &self.voting_circuits)
419 }
420
421 fn quantum_diversity_transform_static(
423 data: &Array2<f64>,
424 targets: &Array2<f64>,
425 model_idx: usize,
426 voting_circuits: &[Array1<f64>],
427 ) -> Result<(Array2<f64>, Array2<f64>)> {
428 let mut transformed = data.clone();
429
430 if model_idx < voting_circuits.len() {
431 let circuit_params = &voting_circuits[model_idx];
432
433 for mut row in transformed.rows_mut() {
435 for (i, val) in row.iter_mut().enumerate() {
436 let param_idx = i % circuit_params.len();
437 let phase = circuit_params[param_idx];
438 *val = *val * phase.cos() + 0.1 * (phase * *val).sin();
439 }
440 }
441 }
442
443 Ok((transformed, targets.clone()))
444 }
445
446 fn calculate_model_performance(
448 &self,
449 predictions: &Array2<f64>,
450 targets: &Array2<f64>,
451 ) -> Result<f64> {
452 Self::calculate_model_performance_static(predictions, targets)
453 }
454
455 fn calculate_model_performance_static(
457 predictions: &Array2<f64>,
458 targets: &Array2<f64>,
459 ) -> Result<f64> {
460 if predictions.shape() != targets.shape() {
461 return Err(MLError::DimensionMismatch(
462 "Predictions and targets must have same shape".to_string(),
463 ));
464 }
465
466 let mae: f64 = predictions
468 .iter()
469 .zip(targets.iter())
470 .map(|(p, t)| (p - t).abs())
471 .sum::<f64>()
472 / predictions.len() as f64;
473
474 Ok(1.0 / (1.0 + mae))
476 }
477
478 fn update_diversity_metrics(
480 &mut self,
481 data: &Array2<f64>,
482 targets: &Array2<f64>,
483 ) -> Result<()> {
484 let num_models = self.models.len();
485
486 let mut predictions = Vec::new();
488 for model in &self.models {
489 let pred = model.predict(data, targets.ncols())?;
490 predictions.push(pred);
491 }
492
493 for i in 0..num_models {
495 for j in 0..num_models {
496 let correlation =
497 self.calculate_prediction_correlation(&predictions[i], &predictions[j])?;
498 self.diversity_metrics.prediction_correlations[[i, j]] = correlation;
499 }
500 }
501
502 for i in 0..num_models {
504 let mut disagreement = 0.0;
505 for j in 0..num_models {
506 if i != j {
507 disagreement +=
508 1.0 - self.diversity_metrics.prediction_correlations[[i, j]].abs();
509 }
510 }
511 self.diversity_metrics.disagreement_scores[i] = disagreement / (num_models - 1) as f64;
512 }
513
514 let avg_correlation = self
516 .diversity_metrics
517 .prediction_correlations
518 .mean()
519 .unwrap_or(0.0);
520 self.diversity_metrics.overall_diversity = 1.0 - avg_correlation.abs();
521
522 Ok(())
523 }
524
525 fn calculate_prediction_correlation(
527 &self,
528 pred1: &Array2<f64>,
529 pred2: &Array2<f64>,
530 ) -> Result<f64> {
531 if pred1.shape() != pred2.shape() {
532 return Err(MLError::DimensionMismatch(
533 "Prediction arrays must have same shape".to_string(),
534 ));
535 }
536
537 let flat1: Vec<f64> = pred1.iter().cloned().collect();
538 let flat2: Vec<f64> = pred2.iter().cloned().collect();
539
540 let mean1 = flat1.iter().sum::<f64>() / flat1.len() as f64;
541 let mean2 = flat2.iter().sum::<f64>() / flat2.len() as f64;
542
543 let mut numerator = 0.0;
544 let mut sum_sq1 = 0.0;
545 let mut sum_sq2 = 0.0;
546
547 for (v1, v2) in flat1.iter().zip(flat2.iter()) {
548 let dev1 = v1 - mean1;
549 let dev2 = v2 - mean2;
550
551 numerator += dev1 * dev2;
552 sum_sq1 += dev1 * dev1;
553 sum_sq2 += dev2 * dev2;
554 }
555
556 let denominator = (sum_sq1 * sum_sq2).sqrt();
557 if denominator < 1e-10 {
558 Ok(0.0)
559 } else {
560 Ok(numerator / denominator)
561 }
562 }
563
564 fn optimize_ensemble_weights(
566 &mut self,
567 data: &Array2<f64>,
568 targets: &Array2<f64>,
569 ) -> Result<()> {
570 let num_models = self.models.len();
571
572 let mut model_performances = Vec::new();
574 for model in &self.models {
575 let predictions = model.predict(data, targets.ncols())?;
576 let performance = self.calculate_model_performance(&predictions, targets)?;
577 model_performances.push(performance);
578 }
579
580 let total_performance: f64 = model_performances.iter().sum();
582 if total_performance > 1e-10 {
583 for (i, &performance) in model_performances.iter().enumerate() {
584 self.model_weights[i] = performance / total_performance;
585 }
586 } else {
587 self.model_weights.fill(1.0 / num_models as f64);
589 }
590
591 Ok(())
592 }
593
594 fn average_predictions(&self, predictions: &[Array2<f64>]) -> Result<Array2<f64>> {
596 if predictions.is_empty() {
597 return Err(MLError::DataError("No predictions to average".to_string()));
598 }
599
600 let mut avg_pred = Array2::zeros(predictions[0].dim());
601 for pred in predictions {
602 avg_pred = avg_pred + pred;
603 }
604
605 Ok(avg_pred / predictions.len() as f64)
606 }
607
608 fn weighted_average_predictions(
610 &self,
611 predictions: &[Array2<f64>],
612 weights: &[f64],
613 ) -> Result<Array2<f64>> {
614 if predictions.is_empty() {
615 return Err(MLError::DataError("No predictions to average".to_string()));
616 }
617
618 if predictions.len() != weights.len() {
619 return Err(MLError::DimensionMismatch(
620 "Number of predictions must match number of weights".to_string(),
621 ));
622 }
623
624 let mut weighted_pred = Array2::zeros(predictions[0].dim());
625 for (pred, &weight) in predictions.iter().zip(weights.iter()) {
626 weighted_pred = weighted_pred + pred * weight;
627 }
628
629 Ok(weighted_pred)
630 }
631
632 fn quantum_superposition_predictions(
634 &self,
635 predictions: &[Array2<f64>],
636 ) -> Result<Array2<f64>> {
637 if predictions.is_empty() {
638 return Err(MLError::DataError(
639 "No predictions for quantum superposition".to_string(),
640 ));
641 }
642
643 let (n_samples, n_features) = predictions[0].dim();
644 let mut ensemble_pred = Array2::zeros((n_samples, n_features));
645
646 for i in 0..n_samples {
648 for j in 0..n_features {
649 let mut superposition = 0.0;
650 let mut normalization = 0.0;
651
652 for (k, pred) in predictions.iter().enumerate() {
653 let amplitude = ((k as f64 + 1.0) * PI / predictions.len() as f64).cos();
655 superposition += pred[[i, j]] * amplitude;
656 normalization += amplitude * amplitude;
657 }
658
659 if normalization > 1e-10 {
660 ensemble_pred[[i, j]] = superposition / normalization.sqrt();
661 } else {
662 ensemble_pred[[i, j]] = superposition;
663 }
664 }
665 }
666
667 Ok(ensemble_pred)
668 }
669
670 fn stacking_predictions(
672 &self,
673 predictions: &[Array2<f64>],
674 data: &Array2<f64>,
675 ) -> Result<Array2<f64>> {
676 self.weighted_average_predictions(predictions, &self.model_weights.to_vec())
678 }
679
680 fn bayesian_average_predictions(&self, predictions: &[Array2<f64>]) -> Result<Array2<f64>> {
682 let weights: Vec<f64> = self
684 .performance_history
685 .iter()
686 .map(|h| h.get_average_accuracy())
687 .collect();
688
689 self.weighted_average_predictions(predictions, &weights)
690 }
691
692 pub fn get_diversity_metrics(&self) -> &DiversityMetrics {
694 &self.diversity_metrics
695 }
696
697 pub fn get_model_weights(&self) -> &Array1<f64> {
699 &self.model_weights
700 }
701
702 pub fn get_performance_history(&self) -> &[ModelPerformanceHistory] {
704 &self.performance_history
705 }
706}
707
708impl ModelPerformanceHistory {
709 pub fn new(model_id: usize) -> Self {
711 Self {
712 model_id,
713 accuracies: Vec::new(),
714 losses: Vec::new(),
715 confidence_scores: Vec::new(),
716 quantum_fidelities: Vec::new(),
717 }
718 }
719
720 pub fn update_performance(&mut self, accuracy: f64) {
722 self.accuracies.push(accuracy);
723 self.losses.push(1.0 - accuracy); self.confidence_scores.push(accuracy);
725 self.quantum_fidelities.push(accuracy * 0.9); }
727
728 pub fn get_average_accuracy(&self) -> f64 {
730 if self.accuracies.is_empty() {
731 0.5 } else {
733 self.accuracies.iter().sum::<f64>() / self.accuracies.len() as f64
734 }
735 }
736
737 pub fn get_latest_accuracy(&self) -> f64 {
739 self.accuracies.last().copied().unwrap_or(0.5)
740 }
741}
742
743impl DiversityMetrics {
744 pub fn new(num_models: usize) -> Self {
746 Self {
747 prediction_correlations: Array2::zeros((num_models, num_models)),
748 disagreement_scores: Array1::zeros(num_models),
749 quantum_entanglement: Array2::zeros((num_models, num_models)),
750 overall_diversity: 0.0,
751 }
752 }
753}
754
755impl QuantumVotingMechanism {
756 pub fn new(strategy: VotingStrategy, num_qubits: usize) -> Self {
758 let voting_circuit = VotingCircuit::new(num_qubits);
759
760 Self {
761 strategy,
762 voting_circuit,
763 confidence_aggregation: ConfidenceAggregation::QuantumCoherence,
764 }
765 }
766
767 pub fn quantum_vote(
769 &self,
770 predictions: &[Array2<f64>],
771 confidences: &[f64],
772 ) -> Result<Array2<f64>> {
773 match &self.strategy {
774 VotingStrategy::QuantumSuperposition => {
775 self.quantum_superposition_vote(predictions, confidences)
776 }
777 VotingStrategy::Adaptive => self.adaptive_quantum_vote(predictions, confidences),
778 _ => {
779 self.weighted_vote(predictions, confidences)
781 }
782 }
783 }
784
785 fn quantum_superposition_vote(
787 &self,
788 predictions: &[Array2<f64>],
789 confidences: &[f64],
790 ) -> Result<Array2<f64>> {
791 if predictions.is_empty() {
792 return Err(MLError::DataError("No predictions for voting".to_string()));
793 }
794
795 let (n_samples, n_features) = predictions[0].dim();
796 let mut voted_pred = Array2::zeros((n_samples, n_features));
797
798 for i in 0..n_samples {
800 for j in 0..n_features {
801 let mut superposition = 0.0;
802 let mut normalization = 0.0;
803
804 for (k, pred) in predictions.iter().enumerate() {
805 let confidence = confidences.get(k).copied().unwrap_or(1.0);
806 let quantum_amplitude = confidence.sqrt()
807 * ((k as f64 + 1.0) * PI / predictions.len() as f64).cos();
808
809 superposition += pred[[i, j]] * quantum_amplitude;
810 normalization += quantum_amplitude * quantum_amplitude;
811 }
812
813 if normalization > 1e-10 {
814 voted_pred[[i, j]] = superposition / normalization.sqrt();
815 } else {
816 voted_pred[[i, j]] = superposition;
817 }
818 }
819 }
820
821 Ok(voted_pred)
822 }
823
824 fn adaptive_quantum_vote(
826 &self,
827 predictions: &[Array2<f64>],
828 confidences: &[f64],
829 ) -> Result<Array2<f64>> {
830 self.quantum_superposition_vote(predictions, confidences)
832 }
833
834 fn weighted_vote(
836 &self,
837 predictions: &[Array2<f64>],
838 confidences: &[f64],
839 ) -> Result<Array2<f64>> {
840 if predictions.is_empty() {
841 return Err(MLError::DataError("No predictions for voting".to_string()));
842 }
843
844 let mut weighted_pred = Array2::zeros(predictions[0].dim());
845 let total_confidence: f64 = confidences.iter().sum();
846
847 if total_confidence > 1e-10 {
848 for (pred, &confidence) in predictions.iter().zip(confidences.iter()) {
849 weighted_pred = weighted_pred + pred * (confidence / total_confidence);
850 }
851 } else {
852 for pred in predictions {
854 weighted_pred = weighted_pred + pred;
855 }
856 weighted_pred = weighted_pred / predictions.len() as f64;
857 }
858
859 Ok(weighted_pred)
860 }
861}
862
863impl VotingCircuit {
864 pub fn new(num_qubits: usize) -> Self {
866 let parameters =
867 Array1::from_shape_fn(num_qubits * 2, |i| PI * i as f64 / (num_qubits * 2) as f64);
868
869 let entanglement_patterns = vec![EntanglementPattern {
870 qubits: (0..num_qubits).collect(),
871 strength: 1.0,
872 pattern_type: EntanglementType::GHZ,
873 }];
874
875 Self {
876 num_qubits,
877 parameters,
878 entanglement_patterns,
879 measurement_strategy: MeasurementStrategy::Computational,
880 }
881 }
882
883 pub fn execute_voting(&self, inputs: &[f64]) -> Result<Array1<f64>> {
885 let mut outputs = Array1::zeros(inputs.len());
887
888 for (i, &input) in inputs.iter().enumerate() {
889 let param_idx = i % self.parameters.len();
890 let phase = self.parameters[param_idx] * input;
891 outputs[i] = phase.cos(); }
893
894 Ok(outputs)
895 }
896}
897
898pub struct EnsemblePerformanceAnalyzer {
900 metrics: Vec<String>,
901}
902
903impl EnsemblePerformanceAnalyzer {
904 pub fn new() -> Self {
906 Self {
907 metrics: vec![
908 "ensemble_accuracy".to_string(),
909 "diversity_score".to_string(),
910 "individual_contributions".to_string(),
911 "quantum_coherence".to_string(),
912 ],
913 }
914 }
915
916 pub fn analyze_performance(
918 &self,
919 ensemble: &QuantumEnsembleManager,
920 test_data: &Array2<f64>,
921 test_targets: &Array2<f64>,
922 ) -> Result<HashMap<String, f64>> {
923 let mut results = HashMap::new();
924
925 let ensemble_pred = ensemble.predict_ensemble(test_data, test_targets.ncols())?;
927
928 let ensemble_accuracy = self.calculate_accuracy(&ensemble_pred, test_targets)?;
930 results.insert("ensemble_accuracy".to_string(), ensemble_accuracy);
931
932 let diversity_score = ensemble.get_diversity_metrics().overall_diversity;
934 results.insert("diversity_score".to_string(), diversity_score);
935
936 let avg_individual_contrib = ensemble.get_model_weights().mean().unwrap_or(0.0);
938 results.insert(
939 "individual_contributions".to_string(),
940 avg_individual_contrib,
941 );
942
943 let quantum_coherence = diversity_score * ensemble_accuracy;
945 results.insert("quantum_coherence".to_string(), quantum_coherence);
946
947 Ok(results)
948 }
949
950 fn calculate_accuracy(&self, predictions: &Array2<f64>, targets: &Array2<f64>) -> Result<f64> {
952 if predictions.shape() != targets.shape() {
953 return Err(MLError::DimensionMismatch(
954 "Predictions and targets must have same shape".to_string(),
955 ));
956 }
957
958 let mae: f64 = predictions
959 .iter()
960 .zip(targets.iter())
961 .map(|(p, t)| (p - t).abs())
962 .sum::<f64>()
963 / predictions.len() as f64;
964
965 Ok(1.0 / (1.0 + mae))
966 }
967}