Skip to main content

quantrs2_device/quantum_network/quantum_aware_load_balancing/
implementations.rs

1//! Implementations and tests
2
3use super::types_and_defaults::*;
4use crate::quantum_network::distributed_protocols::{
5    self, CircuitPartition, DistributedComputationError, ExecutionRequirements, LoadBalancer,
6    LoadBalancerMetrics, NodeId, NodeInfo, PerformanceHistory, PerformanceMetrics,
7    ResourceRequirements, Result as DistributedResult, TrainingDataPoint,
8};
9use crate::quantum_network::network_optimization::{
10    self as netopt, FeatureVector, FeedbackData, MLModel, ModelMetrics,
11    NetworkOptimizationError as OptimizationError, PredictionResult, Priority, TrainingResult,
12};
13use async_trait::async_trait;
14use chrono::{DateTime, Datelike, Duration as ChronoDuration, Timelike, Utc};
15use serde::{Deserialize, Serialize};
16use std::collections::{BTreeMap, HashMap, VecDeque};
17use std::sync::{Arc, Mutex, RwLock};
18use std::time::Duration;
19use tokio::sync::{mpsc, Semaphore};
20use uuid::Uuid;
21
22impl QuantumLoadBalancingMetricsCollector {
23    pub fn new() -> Self {
24        Self::default()
25    }
26}
27
28/// Implementation of the quantum-aware load balancer
29impl MLOptimizedQuantumLoadBalancer {
30    /// Create a new quantum-aware ML load balancer
31    pub fn new() -> Self {
32        Self {
33            base_strategy: Arc::new(CapabilityBasedQuantumBalancer::new()),
34            ml_predictor: Arc::new(QuantumLoadPredictionModel::new()),
35            quantum_scheduler: Arc::new(QuantumAwareScheduler::new()),
36            performance_learner: Arc::new(QuantumPerformanceLearner::new()),
37            adaptive_weights: Arc::new(Mutex::new(QuantumLoadBalancingWeights::default())),
38            entanglement_tracker: Arc::new(EntanglementQualityTracker::new()),
39            coherence_monitor: Arc::new(CoherenceTimeMonitor::new()),
40            fidelity_preserver: Arc::new(FidelityPreservationSystem::new()),
41            metrics_collector: Arc::new(QuantumLoadBalancingMetricsCollector::new()),
42        }
43    }
44
45    /// Select optimal node for quantum circuit partition
46    pub async fn select_optimal_node(
47        &self,
48        available_nodes: &[NodeInfo],
49        circuit_partition: &CircuitPartition,
50        quantum_requirements: &QuantumResourceRequirements,
51    ) -> Result<NodeId> {
52        // Extract quantum features for ML prediction
53        let features = self
54            .extract_quantum_features(available_nodes, circuit_partition, quantum_requirements)
55            .await?;
56
57        // Get ML prediction for optimal node
58        let ml_prediction = self
59            .ml_predictor
60            .predict_optimal_node(available_nodes, circuit_partition, &features)
61            .await?;
62
63        // Apply quantum-aware scheduling constraints
64        let quantum_constraints = self
65            .evaluate_quantum_constraints(available_nodes, circuit_partition, quantum_requirements)
66            .await?;
67
68        // Combine ML prediction with quantum constraints
69        let optimal_node = self
70            .combine_ml_and_quantum_decisions(
71                &ml_prediction,
72                &quantum_constraints,
73                available_nodes,
74                circuit_partition,
75            )
76            .await?;
77
78        // Update performance learning system
79        self.update_performance_learning(&optimal_node, circuit_partition, quantum_requirements)
80            .await?;
81
82        // Update metrics
83        self.update_quantum_metrics(&optimal_node, &features, &ml_prediction)
84            .await?;
85
86        Ok(optimal_node)
87    }
88
89    /// Extract quantum-specific features for ML prediction
90    async fn extract_quantum_features(
91        &self,
92        available_nodes: &[NodeInfo],
93        circuit_partition: &CircuitPartition,
94        quantum_requirements: &QuantumResourceRequirements,
95    ) -> Result<FeatureVector> {
96        let mut features = HashMap::new();
97
98        // Circuit complexity features
99        features.insert(
100            "circuit_depth".to_string(),
101            circuit_partition.gates.len() as f64,
102        );
103        features.insert(
104            "entanglement_pairs_needed".to_string(),
105            quantum_requirements.entanglement_pairs as f64,
106        );
107        features.insert(
108            "fidelity_requirement".to_string(),
109            quantum_requirements.fidelity_requirement,
110        );
111
112        // Node capability features
113        for (i, node) in available_nodes.iter().enumerate() {
114            let node_prefix = format!("node_{i}");
115
116            // Quantum hardware features
117            features.insert(
118                format!("{node_prefix}_max_qubits"),
119                node.capabilities.max_qubits as f64,
120            );
121            features.insert(
122                format!("{node_prefix}_readout_fidelity"),
123                node.capabilities.readout_fidelity,
124            );
125
126            // Current load features
127            features.insert(
128                format!("{node_prefix}_qubits_in_use"),
129                node.current_load.qubits_in_use as f64,
130            );
131            features.insert(
132                format!("{node_prefix}_queue_length"),
133                node.current_load.queue_length as f64,
134            );
135
136            // Quantum-specific features
137            if let Some(entanglement_quality) =
138                self.get_node_entanglement_quality(&node.node_id).await?
139            {
140                features.insert(
141                    format!("{node_prefix}_entanglement_quality"),
142                    entanglement_quality,
143                );
144            }
145
146            if let Some(coherence_metrics) = self.get_node_coherence_metrics(&node.node_id).await? {
147                features.insert(
148                    format!("{node_prefix}_avg_coherence_time"),
149                    coherence_metrics.average_coherence_time.as_secs_f64(),
150                );
151            }
152        }
153
154        // Temporal features
155        let now = Utc::now();
156        features.insert("hour_of_day".to_string(), now.hour() as f64);
157        features.insert(
158            "day_of_week".to_string(),
159            now.weekday().number_from_monday() as f64,
160        );
161
162        Ok(FeatureVector {
163            features,
164            timestamp: now,
165            context: self
166                .extract_quantum_context(circuit_partition, quantum_requirements)
167                .await?,
168        })
169    }
170
171    /// Evaluate quantum constraints for scheduling
172    async fn evaluate_quantum_constraints(
173        &self,
174        available_nodes: &[NodeInfo],
175        circuit_partition: &CircuitPartition,
176        quantum_requirements: &QuantumResourceRequirements,
177    ) -> Result<QuantumSchedulingConstraints> {
178        let mut constraints = QuantumSchedulingConstraints {
179            entanglement_constraints: HashMap::new(),
180            coherence_constraints: HashMap::new(),
181            fidelity_constraints: HashMap::new(),
182            error_correction_constraints: HashMap::new(),
183            deadline_constraints: HashMap::new(),
184        };
185
186        for node in available_nodes {
187            // Evaluate entanglement constraints
188            let entanglement_constraint = self
189                .evaluate_entanglement_constraint(
190                    &node.node_id,
191                    circuit_partition,
192                    quantum_requirements,
193                )
194                .await?;
195
196            constraints
197                .entanglement_constraints
198                .insert(node.node_id.clone(), entanglement_constraint);
199
200            // Evaluate coherence constraints
201            let coherence_constraint = self
202                .evaluate_coherence_constraint(
203                    &node.node_id,
204                    circuit_partition,
205                    quantum_requirements,
206                )
207                .await?;
208
209            constraints
210                .coherence_constraints
211                .insert(node.node_id.clone(), coherence_constraint);
212
213            // Evaluate fidelity constraints
214            let fidelity_constraint = self
215                .evaluate_fidelity_constraint(
216                    &node.node_id,
217                    circuit_partition,
218                    quantum_requirements,
219                )
220                .await?;
221
222            constraints
223                .fidelity_constraints
224                .insert(node.node_id.clone(), fidelity_constraint);
225        }
226
227        Ok(constraints)
228    }
229
230    /// Combine ML prediction with quantum constraints to make final decision
231    async fn combine_ml_and_quantum_decisions(
232        &self,
233        ml_prediction: &QuantumPredictionResult,
234        quantum_constraints: &QuantumSchedulingConstraints,
235        available_nodes: &[NodeInfo],
236        circuit_partition: &CircuitPartition,
237    ) -> Result<NodeId> {
238        let weights = self
239            .adaptive_weights
240            .lock()
241            .unwrap_or_else(|e| e.into_inner())
242            .clone();
243
244        let mut node_scores: HashMap<NodeId, f64> = HashMap::new();
245
246        for node in available_nodes {
247            let mut score = 0.0;
248
249            // ML prediction score
250            let ml_score = if node.node_id == ml_prediction.predicted_node {
251                ml_prediction.confidence
252            } else {
253                0.0
254            };
255
256            // Entanglement quality score
257            let entanglement_score = quantum_constraints
258                .entanglement_constraints
259                .get(&node.node_id)
260                .map_or(0.0, |c| c.quality_score);
261
262            // Coherence time score
263            let coherence_score = quantum_constraints
264                .coherence_constraints
265                .get(&node.node_id)
266                .map_or(0.0, |c| c.adequacy_score);
267
268            // Fidelity preservation score
269            let fidelity_score = quantum_constraints
270                .fidelity_constraints
271                .get(&node.node_id)
272                .map_or(0.0, |c| c.preservation_score);
273
274            // Classical resource score
275            let classical_score = self
276                .calculate_classical_resource_score(node, circuit_partition)
277                .await?;
278
279            // Combine scores with adaptive weights
280            score += ml_score * 0.3; // Base ML weight
281            score += entanglement_score * weights.entanglement_quality_weight;
282            score += coherence_score * weights.coherence_time_weight;
283            score += fidelity_score * weights.fidelity_preservation_weight;
284            score += classical_score * weights.classical_resources_weight;
285
286            node_scores.insert(node.node_id.clone(), score);
287        }
288
289        // Select node with highest combined score
290        let optimal_node = node_scores
291            .into_iter()
292            .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
293            .map(|(node_id, _)| node_id)
294            .ok_or_else(|| {
295                QuantumLoadBalancingError::QuantumSchedulingConflict(
296                    "No suitable node found ".to_string(),
297                )
298            })?;
299
300        Ok(optimal_node)
301    }
302
303    /// Get entanglement quality for a node
304    async fn get_node_entanglement_quality(&self, node_id: &NodeId) -> Result<Option<f64>> {
305        let entanglement_states = self
306            .entanglement_tracker
307            .entanglement_states
308            .read()
309            .unwrap_or_else(|e| e.into_inner());
310
311        let quality: f64 = entanglement_states
312            .iter()
313            .filter(|((n1, n2), _)| n1 == node_id || n2 == node_id)
314            .map(|(_, state)| state.current_fidelity)
315            .sum::<f64>()
316            / entanglement_states.len() as f64;
317
318        Ok(if quality > 0.0 { Some(quality) } else { None })
319    }
320
321    /// Get coherence metrics for a node
322    async fn get_node_coherence_metrics(
323        &self,
324        node_id: &NodeId,
325    ) -> Result<Option<NodeCoherenceMetrics>> {
326        let coherence_states = self
327            .coherence_monitor
328            .coherence_times
329            .read()
330            .unwrap_or_else(|e| e.into_inner());
331
332        let node_coherence_data: Vec<_> = coherence_states
333            .iter()
334            .filter(|((n, _), _)| n == node_id)
335            .collect();
336
337        if node_coherence_data.is_empty() {
338            return Ok(None);
339        }
340
341        let total_t1: Duration = node_coherence_data
342            .iter()
343            .map(|(_, state)| state.t1_time)
344            .sum();
345
346        let total_t2: Duration = node_coherence_data
347            .iter()
348            .map(|(_, state)| state.t2_time)
349            .sum();
350
351        let count = node_coherence_data.len();
352
353        Ok(Some(NodeCoherenceMetrics {
354            average_coherence_time: total_t1 / count as u32,
355            average_dephasing_time: total_t2 / count as u32,
356            coherence_stability: 0.95, // Placeholder calculation
357        }))
358    }
359
360    /// Extract quantum context information
361    async fn extract_quantum_context(
362        &self,
363        circuit_partition: &CircuitPartition,
364        quantum_requirements: &QuantumResourceRequirements,
365    ) -> Result<crate::quantum_network::network_optimization::ContextInfo> {
366        Ok(crate::quantum_network::network_optimization::ContextInfo {
367            network_state: "quantum_active".to_string(),
368            time_of_day: Utc::now().hour() as u8,
369            day_of_week: Utc::now().weekday().number_from_monday() as u8,
370            quantum_experiment_type: Some(
371                self.classify_quantum_experiment(circuit_partition).await?,
372            ),
373            user_priority: Some("high".to_string()), // Placeholder
374        })
375    }
376
377    /// Classify type of quantum experiment
378    async fn classify_quantum_experiment(
379        &self,
380        circuit_partition: &CircuitPartition,
381    ) -> Result<String> {
382        // Simple classification based on gate types and circuit structure
383        let gate_types: Vec<&str> = circuit_partition
384            .gates
385            .iter()
386            .map(|g| g.gate_type.as_str())
387            .collect();
388
389        let experiment_type = if gate_types.contains(&"H") && gate_types.contains(&"CNOT") {
390            "entanglement_experiment"
391        } else if gate_types.iter().any(|&g| g.starts_with('R')) {
392            "variational_algorithm"
393        } else if gate_types.contains(&"QFT") {
394            "quantum_fourier_transform"
395        } else {
396            "general_quantum_computation"
397        };
398
399        Ok(experiment_type.to_string())
400    }
401
402    /// Evaluate entanglement constraint for a node
403    async fn evaluate_entanglement_constraint(
404        &self,
405        node_id: &NodeId,
406        _circuit_partition: &CircuitPartition,
407        quantum_requirements: &QuantumResourceRequirements,
408    ) -> Result<EntanglementConstraint> {
409        let entanglement_states = self
410            .entanglement_tracker
411            .entanglement_states
412            .read()
413            .unwrap_or_else(|e| e.into_inner());
414
415        // Calculate available entanglement quality
416        let available_quality: f64 = entanglement_states
417            .iter()
418            .filter(|((n1, n2), _)| n1 == node_id || n2 == node_id)
419            .map(|(_, state)| state.current_fidelity)
420            .sum::<f64>()
421            / (entanglement_states.len().max(1) as f64);
422
423        // Calculate quality score based on requirements
424        let quality_score = if available_quality >= quantum_requirements.fidelity_requirement {
425            1.0
426        } else {
427            available_quality / quantum_requirements.fidelity_requirement
428        };
429
430        Ok(EntanglementConstraint {
431            available_pairs: entanglement_states.len() as u32,
432            required_pairs: quantum_requirements.entanglement_pairs,
433            quality_score,
434            is_feasible: quality_score >= 0.8, // Threshold for feasibility
435        })
436    }
437
438    /// Evaluate coherence constraint for a node
439    async fn evaluate_coherence_constraint(
440        &self,
441        node_id: &NodeId,
442        circuit_partition: &CircuitPartition,
443        quantum_requirements: &QuantumResourceRequirements,
444    ) -> Result<CoherenceConstraint> {
445        let coherence_states = self
446            .coherence_monitor
447            .coherence_times
448            .read()
449            .unwrap_or_else(|e| e.into_inner());
450
451        // Get minimum coherence time available on this node
452        let min_coherence_time = coherence_states
453            .iter()
454            .filter(|((n, _), _)| n == node_id)
455            .map(|(_, state)| state.t2_time.min(state.t1_time))
456            .min()
457            .unwrap_or(Duration::from_secs(0));
458
459        // Estimate required coherence time based on circuit
460        let estimated_execution_time = circuit_partition.estimated_execution_time;
461        let required_coherence_time = quantum_requirements
462            .coherence_time_needed
463            .max(estimated_execution_time);
464
465        // Calculate adequacy score
466        let adequacy_score = if min_coherence_time >= required_coherence_time {
467            1.0
468        } else {
469            min_coherence_time.as_secs_f64() / required_coherence_time.as_secs_f64()
470        };
471
472        Ok(CoherenceConstraint {
473            available_coherence_time: min_coherence_time,
474            required_coherence_time,
475            adequacy_score,
476            is_adequate: adequacy_score >= 0.9, // High threshold for coherence adequacy
477        })
478    }
479
480    /// Evaluate fidelity constraint for a node
481    async fn evaluate_fidelity_constraint(
482        &self,
483        node_id: &NodeId,
484        circuit_partition: &CircuitPartition,
485        quantum_requirements: &QuantumResourceRequirements,
486    ) -> Result<FidelityConstraint> {
487        // Get historical fidelity data for this node
488        let performance_history = self
489            .performance_learner
490            .performance_history
491            .read()
492            .unwrap_or_else(|e| e.into_inner());
493
494        let fidelity_history = performance_history
495            .get(node_id)
496            .map(|h| &h.fidelity_history)
497            .cloned()
498            .unwrap_or_default();
499
500        // Calculate expected fidelity based on circuit complexity
501        let circuit_complexity = circuit_partition.gates.len() as f64;
502        let base_fidelity = if fidelity_history.is_empty() {
503            0.95 // Default assumption
504        } else {
505            fidelity_history
506                .iter()
507                .map(|m| m.process_fidelity)
508                .sum::<f64>()
509                / fidelity_history.len() as f64
510        };
511
512        // Apply fidelity degradation based on circuit complexity
513        let expected_fidelity = base_fidelity * (0.99_f64).powf(circuit_complexity / 10.0);
514
515        // Calculate preservation score
516        let preservation_score = if expected_fidelity >= quantum_requirements.fidelity_requirement {
517            1.0
518        } else {
519            expected_fidelity / quantum_requirements.fidelity_requirement
520        };
521
522        Ok(FidelityConstraint {
523            expected_fidelity,
524            required_fidelity: quantum_requirements.fidelity_requirement,
525            preservation_score,
526            can_preserve: preservation_score >= 0.95, // High threshold for fidelity preservation
527        })
528    }
529
530    /// Calculate classical resource score for a node
531    async fn calculate_classical_resource_score(
532        &self,
533        node: &NodeInfo,
534        circuit_partition: &CircuitPartition,
535    ) -> Result<f64> {
536        let cpu_score = 1.0 - node.current_load.cpu_utilization;
537        let memory_score = 1.0 - node.current_load.memory_utilization;
538        let network_score = 1.0 - node.current_load.network_utilization;
539
540        // Consider queue length
541        let queue_score = if node.current_load.queue_length == 0 {
542            1.0
543        } else {
544            1.0 / (1.0 + node.current_load.queue_length as f64 / 10.0)
545        };
546
547        // Consider resource requirements
548        let resource_adequacy = if node.capabilities.max_qubits
549            >= circuit_partition.resource_requirements.qubits_needed
550        {
551            1.0
552        } else {
553            node.capabilities.max_qubits as f64
554                / circuit_partition.resource_requirements.qubits_needed as f64
555        };
556
557        // Combine all classical scores
558        let combined_score =
559            (cpu_score + memory_score + network_score + queue_score + resource_adequacy) / 5.0;
560
561        Ok(combined_score)
562    }
563
564    /// Update performance learning system with feedback
565    async fn update_performance_learning(
566        &self,
567        selected_node: &NodeId,
568        circuit_partition: &CircuitPartition,
569        quantum_requirements: &QuantumResourceRequirements,
570    ) -> Result<()> {
571        // Record the decision for future learning
572        let learning_data = QuantumLearningDataPoint {
573            timestamp: Utc::now(),
574            selected_node: selected_node.clone(),
575            circuit_partition: circuit_partition.clone(),
576            quantum_requirements: quantum_requirements.clone(),
577            context_features: HashMap::new(), // To be filled with context
578        };
579
580        // Convert to TrainingDataPoint for compatibility
581        let training_data = TrainingDataPoint {
582            features: learning_data.context_features.clone(),
583            target_node: learning_data.selected_node.clone(),
584            actual_performance: PerformanceMetrics {
585                execution_time: Duration::from_millis(100), // Placeholder
586                fidelity: 0.95,                             // Placeholder
587                success: true,                              // Placeholder
588                resource_utilization: 0.75,                 // Placeholder
589            },
590            timestamp: learning_data.timestamp,
591        };
592
593        // Add to learning system (placeholder implementation)
594        self.performance_learner
595            .learning_algorithm
596            .add_training_data(training_data)
597            .await?;
598
599        Ok(())
600    }
601
602    /// Update quantum-specific metrics
603    async fn update_quantum_metrics(
604        &self,
605        selected_node: &NodeId,
606        features: &FeatureVector,
607        prediction: &QuantumPredictionResult,
608    ) -> Result<()> {
609        let mut quantum_metrics = self
610            .metrics_collector
611            .quantum_metrics
612            .lock()
613            .unwrap_or_else(|e| e.into_inner());
614
615        quantum_metrics.total_quantum_decisions += 1;
616
617        // Update prediction accuracy if we have feedback
618        if selected_node == &prediction.predicted_node {
619            // Prediction was followed - potentially good decision
620            quantum_metrics.quantum_advantage_achieved += 0.01; // Incremental improvement
621        }
622
623        // Update other metrics based on features
624        if let Some(fidelity) = features.features.get("fidelity_requirement") {
625            quantum_metrics.fidelity_improvement_factor =
626                (quantum_metrics.fidelity_improvement_factor + fidelity) / 2.0;
627        }
628
629        Ok(())
630    }
631}
632
633/// Quantum scheduling constraints
634#[derive(Debug, Clone)]
635pub struct QuantumSchedulingConstraints {
636    pub entanglement_constraints: HashMap<NodeId, EntanglementConstraint>,
637    pub coherence_constraints: HashMap<NodeId, CoherenceConstraint>,
638    pub fidelity_constraints: HashMap<NodeId, FidelityConstraint>,
639    pub error_correction_constraints: HashMap<NodeId, ErrorCorrectionConstraint>,
640    pub deadline_constraints: HashMap<NodeId, DeadlineConstraint>,
641}
642
643/// Entanglement constraint for scheduling
644#[derive(Debug, Clone, Serialize, Deserialize)]
645pub struct EntanglementConstraint {
646    pub available_pairs: u32,
647    pub required_pairs: u32,
648    pub quality_score: f64,
649    pub is_feasible: bool,
650}
651
652/// Coherence constraint for scheduling
653#[derive(Debug, Clone, Serialize, Deserialize)]
654pub struct CoherenceConstraint {
655    pub available_coherence_time: Duration,
656    pub required_coherence_time: Duration,
657    pub adequacy_score: f64,
658    pub is_adequate: bool,
659}
660
661/// Fidelity constraint for scheduling
662#[derive(Debug, Clone, Serialize, Deserialize)]
663pub struct FidelityConstraint {
664    pub expected_fidelity: f64,
665    pub required_fidelity: f64,
666    pub preservation_score: f64,
667    pub can_preserve: bool,
668}
669
670/// Error correction constraint for scheduling
671#[derive(Debug, Clone, Serialize, Deserialize)]
672pub struct ErrorCorrectionConstraint {
673    pub available_schemes: Vec<String>,
674    pub required_schemes: Vec<String>,
675    pub overhead_factor: f64,
676    pub is_compatible: bool,
677}
678
679/// Deadline constraint for scheduling
680#[derive(Debug, Clone, Serialize, Deserialize)]
681pub struct DeadlineConstraint {
682    pub hard_deadline: Option<DateTime<Utc>>,
683    pub soft_deadline: Option<DateTime<Utc>>,
684    pub estimated_completion: DateTime<Utc>,
685    pub can_meet_deadline: bool,
686}
687
688/// Node coherence metrics
689#[derive(Debug, Clone, Serialize, Deserialize)]
690pub struct NodeCoherenceMetrics {
691    pub average_coherence_time: Duration,
692    pub average_dephasing_time: Duration,
693    pub coherence_stability: f64,
694}
695
696/// Quantum resource requirements
697#[derive(Debug, Clone, Serialize, Deserialize)]
698pub struct QuantumResourceRequirements {
699    pub qubits_needed: u32,
700    pub gate_count_estimate: u32,
701    pub circuit_depth: u32,
702    pub fidelity_requirement: f64,
703    pub coherence_time_needed: Duration,
704    pub entanglement_pairs: u32,
705}
706
707/// Quantum learning data point
708#[derive(Debug, Clone, Serialize, Deserialize)]
709pub struct QuantumLearningDataPoint {
710    pub timestamp: DateTime<Utc>,
711    pub selected_node: NodeId,
712    pub circuit_partition: CircuitPartition,
713    pub quantum_requirements: QuantumResourceRequirements,
714    pub context_features: HashMap<String, f64>,
715}
716
717/// Capability-based quantum load balancer (base implementation)
718#[derive(Debug)]
719pub struct CapabilityBasedQuantumBalancer {
720    pub quantum_capability_weights: HashMap<String, f64>,
721    pub quantum_performance_history: Arc<RwLock<HashMap<NodeId, QuantumPerformanceHistory>>>,
722}
723
724impl Default for CapabilityBasedQuantumBalancer {
725    fn default() -> Self {
726        Self::new()
727    }
728}
729
730impl CapabilityBasedQuantumBalancer {
731    pub fn new() -> Self {
732        let mut weights = HashMap::new();
733        weights.insert("qubit_count".to_string(), 0.3);
734        weights.insert("gate_fidelity".to_string(), 0.4);
735        weights.insert("coherence_time".to_string(), 0.3);
736
737        Self {
738            quantum_capability_weights: weights,
739            quantum_performance_history: Arc::new(RwLock::new(HashMap::new())),
740        }
741    }
742}
743
744#[async_trait]
745impl LoadBalancer for CapabilityBasedQuantumBalancer {
746    async fn select_node(
747        &self,
748        available_nodes: &[NodeInfo],
749        requirements: &ResourceRequirements,
750    ) -> std::result::Result<
751        NodeId,
752        crate::quantum_network::distributed_protocols::DistributedComputationError,
753    > {
754        // Simple capability-based selection with quantum awareness
755        let mut best_node = None;
756        let mut best_score = 0.0;
757
758        for node in available_nodes {
759            let mut score = 0.0;
760
761            // Quantum capability score
762            let qubit_score = if node.capabilities.max_qubits >= requirements.qubits_needed {
763                1.0
764            } else {
765                node.capabilities.max_qubits as f64 / requirements.qubits_needed as f64
766            };
767
768            let fidelity_score = node.capabilities.readout_fidelity;
769
770            // Load-based score
771            let load_score = 1.0
772                - (node.current_load.qubits_in_use as f64 / node.capabilities.max_qubits as f64);
773
774            score = qubit_score * self.quantum_capability_weights["qubit_count"]
775                + fidelity_score * self.quantum_capability_weights["gate_fidelity"]
776                + load_score * 0.3; // Load balancing component
777
778            if score > best_score {
779                best_score = score;
780                best_node = Some(node.node_id.clone());
781            }
782        }
783
784        best_node.ok_or_else(||
785            crate::quantum_network::distributed_protocols::DistributedComputationError::ResourceAllocation(
786                "No suitable node found ".to_string()
787            )
788        )
789    }
790
791    async fn update_node_metrics(
792        &self,
793        node_id: &NodeId,
794        metrics: &PerformanceMetrics,
795    ) -> std::result::Result<
796        (),
797        crate::quantum_network::distributed_protocols::DistributedComputationError,
798    > {
799        // Update performance history
800        let mut history = self
801            .quantum_performance_history
802            .write()
803            .unwrap_or_else(|e| e.into_inner());
804        if !history.contains_key(node_id) {
805            history.insert(node_id.clone(), QuantumPerformanceHistory::default());
806        }
807
808        // Add performance data point
809        let Some(node_history) = history.get_mut(node_id) else {
810            return Ok(());
811        };
812
813        // Update classical metrics
814        node_history
815            .classical_metrics
816            .execution_times
817            .push_back(metrics.execution_time);
818        if node_history.classical_metrics.execution_times.len() > 100 {
819            node_history.classical_metrics.execution_times.pop_front();
820        }
821
822        node_history.classical_metrics.success_rate = node_history
823            .classical_metrics
824            .success_rate
825            .mul_add(0.9, if metrics.success { 1.0 } else { 0.0 } * 0.1);
826
827        Ok(())
828    }
829
830    fn get_balancer_metrics(&self) -> LoadBalancerMetrics {
831        LoadBalancerMetrics {
832            total_decisions: 0, // Placeholder
833            average_decision_time: Duration::from_millis(10),
834            prediction_accuracy: 0.85,
835            load_distribution_variance: 0.15,
836            total_requests: 0,
837            successful_allocations: 0,
838            failed_allocations: 0,
839            average_response_time: Duration::from_millis(5),
840            node_utilization: HashMap::new(),
841        }
842    }
843
844    /// Real per-partition, capability- and load-aware node assignment:
845    /// each partition is scored against every node using its actual
846    /// `resource_requirements` (qubit count) and the node's real
847    /// capabilities/fidelity, and a running per-node assigned-qubit tally
848    /// is tracked so later partitions are genuinely load-balanced across
849    /// capable nodes instead of every partition landing on whichever node
850    /// `HashMap::iter().next()` happened to return.
851    fn select_nodes(
852        &self,
853        partitions: &[CircuitPartition],
854        available_nodes: &HashMap<NodeId, NodeInfo>,
855        _requirements: &ExecutionRequirements,
856    ) -> std::result::Result<HashMap<Uuid, NodeId>, DistributedComputationError> {
857        if available_nodes.is_empty() {
858            return Err(DistributedComputationError::ResourceAllocation(
859                "no available nodes for partition assignment".to_string(),
860            ));
861        }
862
863        let qubit_weight = self
864            .quantum_capability_weights
865            .get("qubit_count")
866            .copied()
867            .unwrap_or(0.3);
868        let fidelity_weight = self
869            .quantum_capability_weights
870            .get("gate_fidelity")
871            .copied()
872            .unwrap_or(0.4);
873
874        let mut allocation = HashMap::new();
875        let mut assigned_qubits: HashMap<NodeId, u32> = HashMap::new();
876
877        for partition in partitions {
878            let required_qubits = partition.resource_requirements.qubits_needed.max(1);
879            let mut best_node: Option<NodeId> = None;
880            let mut best_score = f64::NEG_INFINITY;
881
882            for (node_id, node) in available_nodes {
883                if node.capabilities.max_qubits < required_qubits {
884                    continue;
885                }
886                let already_assigned = *assigned_qubits.get(node_id).unwrap_or(&0);
887                let projected_load = already_assigned + required_qubits;
888                let max_qubits = node.capabilities.max_qubits.max(1);
889                let utilization = projected_load as f64 / max_qubits as f64;
890                if utilization > 1.0 {
891                    // This node cannot take on this partition on top of
892                    // what has already been assigned to it in this batch.
893                    continue;
894                }
895
896                let qubit_score =
897                    (node.capabilities.max_qubits as f64 / required_qubits as f64).min(1.0);
898                let fidelity_score = node.capabilities.readout_fidelity;
899                let load_score = 1.0 - utilization;
900
901                let score = qubit_score * qubit_weight
902                    + fidelity_score * fidelity_weight
903                    + load_score * 0.3;
904
905                if score > best_score {
906                    best_score = score;
907                    best_node = Some(node_id.clone());
908                }
909            }
910
911            let Some(node_id) = best_node else {
912                return Err(DistributedComputationError::ResourceAllocation(format!(
913                    "no node has sufficient remaining capacity ({required_qubits} qubits) for partition {}",
914                    partition.partition_id
915                )));
916            };
917
918            *assigned_qubits.entry(node_id.clone()).or_insert(0) += required_qubits;
919            allocation.insert(partition.partition_id, node_id);
920        }
921
922        Ok(allocation)
923    }
924
925    fn rebalance_load(
926        &self,
927        current_allocation: &HashMap<Uuid, NodeId>,
928        nodes: &HashMap<NodeId, NodeInfo>,
929    ) -> Option<HashMap<Uuid, NodeId>> {
930        None // No rebalancing needed in simplified implementation
931    }
932
933    fn predict_execution_time(&self, partition: &CircuitPartition, node: &NodeInfo) -> Duration {
934        Duration::from_millis(partition.gates.len() as u64 * 15) // Slightly higher than basic implementation
935    }
936}
937
938impl Default for QuantumLoadBalancingWeights {
939    fn default() -> Self {
940        Self {
941            entanglement_quality_weight: 0.25,
942            coherence_time_weight: 0.25,
943            fidelity_preservation_weight: 0.20,
944            classical_resources_weight: 0.15,
945            network_latency_weight: 0.10,
946            error_correction_weight: 0.03,
947            fairness_weight: 0.02,
948            dynamic_adjustment_enabled: true,
949        }
950    }
951}
952
953impl Default for QuantumPerformanceHistory {
954    fn default() -> Self {
955        Self {
956            classical_metrics: PerformanceHistory {
957                execution_times: VecDeque::new(),
958                success_rate: 0.95,
959                average_fidelity: 0.90,
960                last_updated: Utc::now(),
961            },
962            fidelity_history: VecDeque::new(),
963            coherence_measurements: VecDeque::new(),
964            entanglement_measurements: VecDeque::new(),
965            error_rate_history: VecDeque::new(),
966            gate_statistics: HashMap::new(),
967        }
968    }
969}
970
971// Individual default implementations are provided below
972
973// Individual implementations for each type to avoid unsafe operations
974impl Default for QuantumLoadPredictionModel {
975    fn default() -> Self {
976        Self {
977            model: Arc::new(Mutex::new(Box::new(SimpleMLModel::new()))),
978            feature_extractor: Arc::new(QuantumFeatureExtractor::default()),
979            prediction_cache: Arc::new(RwLock::new(HashMap::new())),
980            training_collector: Arc::new(QuantumTrainingDataCollector::default()),
981            performance_tracker: Arc::new(ModelPerformanceTracker::default()),
982        }
983    }
984}
985
986impl Default for QuantumAwareScheduler {
987    fn default() -> Self {
988        Self {
989            entanglement_aware_scheduling: true,
990            coherence_time_optimization: true,
991            fidelity_preservation_priority: true,
992            error_correction_scheduler: Arc::new(ErrorCorrectionScheduler::default()),
993            deadline_scheduler: Arc::new(QuantumDeadlineScheduler::default()),
994            urgency_evaluator: Arc::new(QuantumUrgencyEvaluator::default()),
995            entanglement_resolver: Arc::new(EntanglementDependencyResolver::default()),
996            gate_conflict_resolver: Arc::new(QuantumGateConflictResolver::default()),
997        }
998    }
999}
1000
1001impl QuantumAwareScheduler {
1002    pub fn new() -> Self {
1003        Self::default()
1004    }
1005}
1006
1007impl Default for QuantumPerformanceLearner {
1008    fn default() -> Self {
1009        Self {
1010            performance_history: Arc::new(RwLock::new(HashMap::new())),
1011            learning_algorithm: Arc::new(QuantumReinforcementLearning::default()),
1012            adaptation_strategy: Arc::new(QuantumAdaptationStrategy::default()),
1013            feedback_processor: Arc::new(QuantumFeedbackProcessor::default()),
1014        }
1015    }
1016}
1017
1018impl Default for EntanglementQualityTracker {
1019    fn default() -> Self {
1020        Self {
1021            entanglement_states: Arc::new(RwLock::new(HashMap::new())),
1022            quality_thresholds: Arc::new(EntanglementQualityThresholds::default()),
1023            quality_predictor: Arc::new(EntanglementQualityPredictor::default()),
1024            quality_optimizer: Arc::new(EntanglementQualityOptimizer::default()),
1025        }
1026    }
1027}
1028
1029impl EntanglementQualityTracker {
1030    pub fn new() -> Self {
1031        Self::default()
1032    }
1033}
1034
1035impl Default for CoherenceTimeMonitor {
1036    fn default() -> Self {
1037        Self {
1038            coherence_times: Arc::new(RwLock::new(HashMap::new())),
1039            coherence_predictor: Arc::new(CoherenceTimePredictor::default()),
1040            coherence_optimizer: Arc::new(CoherenceTimeOptimizer::default()),
1041            real_time_monitor: Arc::new(RealTimeCoherenceMonitor::default()),
1042        }
1043    }
1044}
1045
1046impl CoherenceTimeMonitor {
1047    pub fn new() -> Self {
1048        Self::default()
1049    }
1050}
1051
1052impl Default for FidelityPreservationSystem {
1053    fn default() -> Self {
1054        Self {
1055            fidelity_tracker: Arc::new(FidelityTracker::default()),
1056            preservation_strategies: Arc::new(FidelityPreservationStrategies::default()),
1057            error_mitigation: Arc::new(ErrorMitigationCoordinator::default()),
1058            optimization_scheduler: Arc::new(FidelityOptimizationScheduler::default()),
1059        }
1060    }
1061}
1062
1063impl FidelityPreservationSystem {
1064    pub fn new() -> Self {
1065        Self::default()
1066    }
1067}
1068
1069impl Default for QuantumLoadBalancingMetricsCollector {
1070    fn default() -> Self {
1071        Self {
1072            classical_metrics: Arc::new(Mutex::new(LoadBalancerMetrics {
1073                total_decisions: 0,
1074                average_decision_time: Duration::from_millis(10),
1075                prediction_accuracy: 0.95,
1076                load_distribution_variance: 0.1,
1077                total_requests: 0,
1078                successful_allocations: 0,
1079                failed_allocations: 0,
1080                average_response_time: Duration::from_millis(5),
1081                node_utilization: HashMap::new(),
1082            })),
1083            quantum_metrics: Arc::new(Mutex::new(QuantumLoadBalancingMetrics::default())),
1084            performance_tracker: Arc::new(RealTimeQuantumPerformanceTracker::default()),
1085            metrics_aggregator: Arc::new(QuantumMetricsAggregator::default()),
1086        }
1087    }
1088}
1089
1090impl Default for QuantumLoadBalancingMetrics {
1091    fn default() -> Self {
1092        Self {
1093            total_quantum_decisions: 0,
1094            average_quantum_decision_time: Duration::from_millis(15),
1095            entanglement_preservation_rate: 0.9,
1096            coherence_utilization_efficiency: 0.85,
1097            fidelity_improvement_factor: 1.1,
1098            quantum_advantage_achieved: 0.2,
1099            error_correction_overhead_ratio: 0.15,
1100            quantum_fairness_index: 0.95,
1101        }
1102    }
1103}
1104
1105impl Default for EntanglementQualityThresholds {
1106    fn default() -> Self {
1107        Self {
1108            min_fidelity: 0.8,
1109            warning_fidelity: 0.85,
1110            optimal_fidelity: 0.95,
1111            max_decay_rate: 0.05,
1112            min_lifetime: Duration::from_millis(100),
1113        }
1114    }
1115}
1116
1117// Simple ML model implementation for stubs
1118#[derive(Debug)]
1119pub struct SimpleMLModel {
1120    pub model_type: String,
1121}
1122
1123impl Default for SimpleMLModel {
1124    fn default() -> Self {
1125        Self::new()
1126    }
1127}
1128
1129impl SimpleMLModel {
1130    pub fn new() -> Self {
1131        Self {
1132            model_type: "simple_stub".to_string(),
1133        }
1134    }
1135}
1136
1137#[async_trait]
1138impl crate::quantum_network::network_optimization::MLModel for SimpleMLModel {
1139    async fn predict(
1140        &self,
1141        _features: &FeatureVector,
1142    ) -> std::result::Result<PredictionResult, OptimizationError> {
1143        Ok(PredictionResult {
1144            predicted_values: HashMap::new(),
1145            confidence_intervals: HashMap::new(),
1146            uncertainty_estimate: 0.1,
1147            prediction_timestamp: Utc::now(),
1148        })
1149    }
1150
1151    async fn train(
1152        &mut self,
1153        _training_data: &[TrainingDataPoint],
1154    ) -> std::result::Result<TrainingResult, OptimizationError> {
1155        Ok(TrainingResult {
1156            training_accuracy: 0.85,
1157            validation_accuracy: 0.8,
1158            loss_value: 0.2,
1159            training_duration: Duration::from_secs(10),
1160            model_size_bytes: 1024,
1161        })
1162    }
1163
1164    async fn update_weights(
1165        &mut self,
1166        _feedback: &FeedbackData,
1167    ) -> std::result::Result<(), OptimizationError> {
1168        Ok(())
1169    }
1170
1171    fn get_model_metrics(&self) -> ModelMetrics {
1172        ModelMetrics {
1173            accuracy: 0.85,
1174            precision: 0.8,
1175            recall: 0.9,
1176            f1_score: 0.84,
1177            mae: 0.15,
1178            rmse: 0.2,
1179        }
1180    }
1181}
1182
1183// Stub implementations with placeholder fields are provided individually
1184
1185// Additional implementations for key functionality
1186impl QuantumLoadPredictionModel {
1187    pub fn new() -> Self {
1188        Self::default()
1189    }
1190
1191    /// Predict the optimal node from the real per-node features extracted
1192    /// by `extract_quantum_features` (keyed `node_{i}_...` in
1193    /// `features.features`, matching `available_nodes`'s order) -- rather
1194    /// than always returning a fixed `NodeId("node_1")` regardless of
1195    /// which nodes actually exist. Nodes without enough qubit capacity for
1196    /// `circuit_partition` are excluded; among the remaining nodes the
1197    /// candidate with the best weighted score (readout fidelity,
1198    /// entanglement quality, coherence time, queue pressure, and
1199    /// utilization) is selected.
1200    pub async fn predict_optimal_node(
1201        &self,
1202        available_nodes: &[NodeInfo],
1203        circuit_partition: &CircuitPartition,
1204        features: &FeatureVector,
1205    ) -> Result<QuantumPredictionResult> {
1206        if available_nodes.is_empty() {
1207            return Err(QuantumLoadBalancingError::QuantumSchedulingConflict(
1208                "no available nodes to predict from".to_string(),
1209            ));
1210        }
1211        let required_qubits = circuit_partition.input_qubits.len().max(1) as f64;
1212
1213        let mut best_index: Option<usize> = None;
1214        let mut best_score = f64::NEG_INFINITY;
1215        let mut best_fidelity = 0.95_f64;
1216
1217        for (i, node) in available_nodes.iter().enumerate() {
1218            let prefix = format!("node_{i}");
1219            let get = |suffix: &str, default: f64| -> f64 {
1220                features
1221                    .features
1222                    .get(&format!("{prefix}_{suffix}"))
1223                    .copied()
1224                    .unwrap_or(default)
1225            };
1226
1227            let max_qubits = get("max_qubits", node.capabilities.max_qubits as f64);
1228            if max_qubits < required_qubits {
1229                continue;
1230            }
1231            let readout_fidelity = get("readout_fidelity", node.capabilities.readout_fidelity);
1232            let qubits_in_use = get("qubits_in_use", node.current_load.qubits_in_use as f64);
1233            let queue_length = get("queue_length", node.current_load.queue_length as f64);
1234            // Neutral defaults (0.5 quality, 50us coherence) when this
1235            // node has no recorded entanglement/coherence measurements
1236            // yet, rather than silently scoring it as perfect or zero.
1237            let entanglement_quality = get("entanglement_quality", 0.5);
1238            let coherence_time = get("avg_coherence_time", 50e-6);
1239
1240            let utilization = if max_qubits > 0.0 {
1241                (qubits_in_use / max_qubits).clamp(0.0, 1.0)
1242            } else {
1243                1.0
1244            };
1245            let queue_penalty = 1.0 / (1.0 + queue_length.max(0.0));
1246            let coherence_score = (coherence_time / 100e-6).clamp(0.0, 1.0);
1247
1248            let score = readout_fidelity * 0.35
1249                + entanglement_quality * 0.25
1250                + coherence_score * 0.15
1251                + queue_penalty * 0.15
1252                + (1.0 - utilization) * 0.10;
1253
1254            if score > best_score {
1255                best_score = score;
1256                best_index = Some(i);
1257                best_fidelity = readout_fidelity;
1258            }
1259        }
1260
1261        let Some(best_index) = best_index else {
1262            return Err(QuantumLoadBalancingError::QuantumSchedulingConflict(
1263                format!(
1264                    "no node has sufficient qubit capacity ({required_qubits}) for this partition"
1265                ),
1266            ));
1267        };
1268
1269        let predicted_node = available_nodes[best_index].node_id.clone();
1270        let confidence = best_score.clamp(0.0, 1.0);
1271        let predicted_entanglement_overhead = (required_qubits / 2.0).ceil() as u32;
1272
1273        Ok(QuantumPredictionResult {
1274            predicted_node,
1275            predicted_execution_time: Duration::from_millis((100.0 / confidence.max(0.1)) as u64),
1276            predicted_fidelity: best_fidelity,
1277            predicted_entanglement_overhead,
1278            confidence,
1279            quantum_uncertainty: QuantumUncertaintyFactors {
1280                decoherence_uncertainty: (1.0 - confidence) * 0.1,
1281                entanglement_uncertainty: (1.0 - confidence) * 0.06,
1282                measurement_uncertainty: (1.0 - confidence) * 0.04,
1283                calibration_uncertainty: (1.0 - confidence) * 0.02,
1284            },
1285            prediction_timestamp: Utc::now(),
1286        })
1287    }
1288}
1289
1290impl QuantumPerformanceLearner {
1291    pub fn new() -> Self {
1292        Self::default()
1293    }
1294
1295    /// Actually record training feedback in `performance_history` (a real
1296    /// `Arc<RwLock<HashMap<NodeId, QuantumPerformanceHistory>>>` field that
1297    /// previously sat unused) rather than silently discarding every
1298    /// training data point.
1299    pub async fn add_training_data(&self, data: QuantumLearningDataPoint) -> Result<()> {
1300        let mut history = self
1301            .performance_history
1302            .write()
1303            .unwrap_or_else(std::sync::PoisonError::into_inner);
1304        let node_history = history
1305            .entry(data.selected_node.clone())
1306            .or_insert_with(QuantumPerformanceHistory::default);
1307
1308        node_history
1309            .classical_metrics
1310            .execution_times
1311            .push_back(data.circuit_partition.estimated_execution_time);
1312        if node_history.classical_metrics.execution_times.len() > 100 {
1313            node_history.classical_metrics.execution_times.pop_front();
1314        }
1315
1316        node_history
1317            .fidelity_history
1318            .push_back(FidelityMeasurement {
1319                timestamp: data.timestamp,
1320                process_fidelity: data.quantum_requirements.fidelity_requirement,
1321                state_fidelity: data.quantum_requirements.fidelity_requirement,
1322                gate_fidelities: HashMap::new(),
1323                measurement_context: FidelityMeasurementContext {
1324                    temperature: data
1325                        .context_features
1326                        .get("temperature")
1327                        .copied()
1328                        .unwrap_or(0.0),
1329                    time_since_calibration: Duration::from_secs(0),
1330                    circuit_depth: data.circuit_partition.gates.len() as u32,
1331                    concurrent_operations: 0,
1332                },
1333            });
1334        if node_history.fidelity_history.len() > 100 {
1335            node_history.fidelity_history.pop_front();
1336        }
1337
1338        Ok(())
1339    }
1340}
1341
1342/// Test module for quantum-aware load balancing
1343#[cfg(test)]
1344mod tests {
1345    use super::*;
1346
1347    #[tokio::test]
1348    async fn test_quantum_load_balancer_creation() {
1349        let balancer = MLOptimizedQuantumLoadBalancer::new();
1350        assert!(
1351            !balancer
1352                .adaptive_weights
1353                .lock()
1354                .expect("Mutex should not be poisoned")
1355                .dynamic_adjustment_enabled
1356                || balancer
1357                    .adaptive_weights
1358                    .lock()
1359                    .expect("Mutex should not be poisoned")
1360                    .dynamic_adjustment_enabled
1361        );
1362    }
1363
1364    #[tokio::test]
1365    async fn test_quantum_feature_extraction() {
1366        let balancer = MLOptimizedQuantumLoadBalancer::new();
1367
1368        let nodes = vec![NodeInfo {
1369            node_id: NodeId("test_node".to_string()),
1370            capabilities: crate::quantum_network::distributed_protocols::NodeCapabilities {
1371                max_qubits: 10,
1372                supported_gates: vec!["H".to_string(), "CNOT".to_string()],
1373                connectivity_graph: vec![(0, 1), (1, 2)],
1374                gate_fidelities: HashMap::new(),
1375                readout_fidelity: 0.95,
1376                coherence_times: HashMap::new(),
1377                classical_compute_power: 1000.0,
1378                memory_capacity_gb: 8,
1379                network_bandwidth_mbps: 1000.0,
1380            },
1381            current_load: crate::quantum_network::distributed_protocols::NodeLoad {
1382                qubits_in_use: 3,
1383                active_circuits: 2,
1384                cpu_utilization: 0.4,
1385                memory_utilization: 0.3,
1386                network_utilization: 0.2,
1387                queue_length: 1,
1388                estimated_completion_time: Duration::from_secs(30),
1389            },
1390            network_info: crate::quantum_network::distributed_protocols::NetworkInfo {
1391                ip_address: "192.168.1.100".to_string(),
1392                port: 8080,
1393                latency_to_nodes: HashMap::new(),
1394                bandwidth_to_nodes: HashMap::new(),
1395                connection_quality: HashMap::new(),
1396            },
1397            status: crate::quantum_network::distributed_protocols::NodeStatus::Active,
1398            last_heartbeat: Utc::now(),
1399        }];
1400
1401        let circuit_partition = CircuitPartition {
1402            partition_id: Uuid::new_v4(),
1403            node_id: NodeId("test".to_string()),
1404            gates: vec![],
1405            dependencies: vec![],
1406            input_qubits: vec![],
1407            output_qubits: vec![],
1408            classical_inputs: vec![],
1409            estimated_execution_time: Duration::from_millis(100),
1410            resource_requirements: ResourceRequirements {
1411                qubits_needed: 5,
1412                gates_count: 10,
1413                memory_mb: 50,
1414                execution_time_estimate: Duration::from_millis(100),
1415                entanglement_pairs_needed: 2,
1416                classical_communication_bits: 100,
1417            },
1418        };
1419
1420        let quantum_requirements = QuantumResourceRequirements {
1421            qubits_needed: 5,
1422            gate_count_estimate: 10,
1423            circuit_depth: 5,
1424            fidelity_requirement: 0.9,
1425            coherence_time_needed: Duration::from_micros(100),
1426            entanglement_pairs: 2,
1427        };
1428
1429        let features = balancer
1430            .extract_quantum_features(&nodes, &circuit_partition, &quantum_requirements)
1431            .await;
1432        assert!(features.is_ok());
1433
1434        let feature_vector = features.expect("Feature extraction should succeed");
1435        assert!(!feature_vector.features.is_empty());
1436        assert!(feature_vector.features.contains_key("circuit_depth"));
1437        assert!(feature_vector
1438            .features
1439            .contains_key("entanglement_pairs_needed"));
1440    }
1441
1442    #[tokio::test]
1443    async fn test_capability_based_quantum_balancer() {
1444        let balancer = CapabilityBasedQuantumBalancer::new();
1445
1446        let nodes = vec![
1447            NodeInfo {
1448                node_id: NodeId("high_capability_node".to_string()),
1449                capabilities: crate::quantum_network::distributed_protocols::NodeCapabilities {
1450                    max_qubits: 20,
1451                    supported_gates: vec!["H".to_string(), "CNOT".to_string(), "T".to_string()],
1452                    connectivity_graph: vec![],
1453                    gate_fidelities: HashMap::new(),
1454                    readout_fidelity: 0.98,
1455                    coherence_times: HashMap::new(),
1456                    classical_compute_power: 2000.0,
1457                    memory_capacity_gb: 16,
1458                    network_bandwidth_mbps: 2000.0,
1459                },
1460                current_load: crate::quantum_network::distributed_protocols::NodeLoad {
1461                    qubits_in_use: 5,
1462                    active_circuits: 1,
1463                    cpu_utilization: 0.2,
1464                    memory_utilization: 0.1,
1465                    network_utilization: 0.1,
1466                    queue_length: 0,
1467                    estimated_completion_time: Duration::from_secs(10),
1468                },
1469                network_info: crate::quantum_network::distributed_protocols::NetworkInfo {
1470                    ip_address: "192.168.1.101".to_string(),
1471                    port: 8080,
1472                    latency_to_nodes: HashMap::new(),
1473                    bandwidth_to_nodes: HashMap::new(),
1474                    connection_quality: HashMap::new(),
1475                },
1476                status: crate::quantum_network::distributed_protocols::NodeStatus::Active,
1477                last_heartbeat: Utc::now(),
1478            },
1479            NodeInfo {
1480                node_id: NodeId("low_capability_node".to_string()),
1481                capabilities: crate::quantum_network::distributed_protocols::NodeCapabilities {
1482                    max_qubits: 5,
1483                    supported_gates: vec!["H".to_string(), "CNOT".to_string()],
1484                    connectivity_graph: vec![],
1485                    gate_fidelities: HashMap::new(),
1486                    readout_fidelity: 0.90,
1487                    coherence_times: HashMap::new(),
1488                    classical_compute_power: 500.0,
1489                    memory_capacity_gb: 4,
1490                    network_bandwidth_mbps: 500.0,
1491                },
1492                current_load: crate::quantum_network::distributed_protocols::NodeLoad {
1493                    qubits_in_use: 4,
1494                    active_circuits: 2,
1495                    cpu_utilization: 0.8,
1496                    memory_utilization: 0.7,
1497                    network_utilization: 0.6,
1498                    queue_length: 3,
1499                    estimated_completion_time: Duration::from_secs(60),
1500                },
1501                network_info: crate::quantum_network::distributed_protocols::NetworkInfo {
1502                    ip_address: "192.168.1.102".to_string(),
1503                    port: 8080,
1504                    latency_to_nodes: HashMap::new(),
1505                    bandwidth_to_nodes: HashMap::new(),
1506                    connection_quality: HashMap::new(),
1507                },
1508                status: crate::quantum_network::distributed_protocols::NodeStatus::Active,
1509                last_heartbeat: Utc::now(),
1510            },
1511        ];
1512
1513        let requirements = ResourceRequirements {
1514            qubits_needed: 10,
1515            gates_count: 20,
1516            memory_mb: 100,
1517            execution_time_estimate: Duration::from_millis(200),
1518            entanglement_pairs_needed: 3,
1519            classical_communication_bits: 500,
1520        };
1521
1522        let selected_node = balancer.select_node(&nodes, &requirements).await;
1523        assert!(selected_node.is_ok());
1524
1525        // Should select the high capability node
1526        let node_id = selected_node.expect("Node selection should succeed");
1527        assert_eq!(node_id.0, "high_capability_node");
1528    }
1529
1530    fn make_test_node(
1531        id: &str,
1532        max_qubits: u32,
1533        readout_fidelity: f64,
1534        qubits_in_use: u32,
1535        queue_length: u32,
1536    ) -> NodeInfo {
1537        NodeInfo {
1538            node_id: NodeId(id.to_string()),
1539            capabilities: crate::quantum_network::distributed_protocols::NodeCapabilities {
1540                max_qubits,
1541                supported_gates: vec!["H".to_string(), "CNOT".to_string()],
1542                connectivity_graph: vec![],
1543                gate_fidelities: HashMap::new(),
1544                readout_fidelity,
1545                coherence_times: HashMap::new(),
1546                classical_compute_power: 1000.0,
1547                memory_capacity_gb: 8,
1548                network_bandwidth_mbps: 1000.0,
1549            },
1550            current_load: crate::quantum_network::distributed_protocols::NodeLoad {
1551                qubits_in_use,
1552                active_circuits: 1,
1553                cpu_utilization: 0.2,
1554                memory_utilization: 0.2,
1555                network_utilization: 0.2,
1556                queue_length,
1557                estimated_completion_time: Duration::from_secs(10),
1558            },
1559            network_info: crate::quantum_network::distributed_protocols::NetworkInfo {
1560                ip_address: "127.0.0.1".to_string(),
1561                port: 8080,
1562                latency_to_nodes: HashMap::new(),
1563                bandwidth_to_nodes: HashMap::new(),
1564                connection_quality: HashMap::new(),
1565            },
1566            status: crate::quantum_network::distributed_protocols::NodeStatus::Active,
1567            last_heartbeat: Utc::now(),
1568        }
1569    }
1570
1571    fn make_test_partition(qubits_needed: u32) -> CircuitPartition {
1572        let node_id = NodeId("unassigned".to_string());
1573        CircuitPartition {
1574            partition_id: Uuid::new_v4(),
1575            node_id: node_id.clone(),
1576            gates: vec![],
1577            dependencies: vec![],
1578            input_qubits: (0..qubits_needed)
1579                .map(|i| crate::quantum_network::distributed_protocols::QubitId {
1580                    node_id: node_id.clone(),
1581                    local_id: i,
1582                    global_id: Uuid::new_v4(),
1583                })
1584                .collect(),
1585            output_qubits: vec![],
1586            classical_inputs: vec![],
1587            estimated_execution_time: Duration::from_millis(100),
1588            resource_requirements: ResourceRequirements {
1589                qubits_needed,
1590                gates_count: 10,
1591                memory_mb: 50,
1592                execution_time_estimate: Duration::from_millis(100),
1593                entanglement_pairs_needed: 1,
1594                classical_communication_bits: 100,
1595            },
1596        }
1597    }
1598
1599    #[tokio::test]
1600    async fn test_predict_optimal_node_not_fixed_to_node1() {
1601        let predictor = QuantumLoadPredictionModel::new();
1602        let balancer = MLOptimizedQuantumLoadBalancer::new();
1603
1604        // node index 0 ("best_node") clearly dominates node index 1
1605        // ("weak_node") on every feature: higher fidelity, less load, no
1606        // queue. The old placeholder always returned NodeId("node_1")
1607        // regardless of quality -- a real predictor must pick the actually
1608        // better node "best_node" here.
1609        let nodes = vec![
1610            make_test_node("best_node", 20, 0.99, 0, 0),
1611            make_test_node("weak_node", 20, 0.5, 18, 20),
1612        ];
1613        let partition = make_test_partition(2);
1614        let quantum_requirements = QuantumResourceRequirements {
1615            qubits_needed: 2,
1616            gate_count_estimate: 5,
1617            circuit_depth: 5,
1618            fidelity_requirement: 0.9,
1619            coherence_time_needed: Duration::from_micros(50),
1620            entanglement_pairs: 1,
1621        };
1622        let features = balancer
1623            .extract_quantum_features(&nodes, &partition, &quantum_requirements)
1624            .await
1625            .expect("feature extraction should succeed");
1626
1627        let prediction = predictor
1628            .predict_optimal_node(&nodes, &partition, &features)
1629            .await
1630            .expect("prediction should succeed");
1631        assert_eq!(prediction.predicted_node.0, "best_node");
1632
1633        // Swap node order: the winner must still be "best_node" by
1634        // identity/quality, not by fixed position "node_1".
1635        let nodes_swapped = vec![
1636            make_test_node("weak_node", 20, 0.5, 18, 20),
1637            make_test_node("best_node", 20, 0.99, 0, 0),
1638        ];
1639        let features_swapped = balancer
1640            .extract_quantum_features(&nodes_swapped, &partition, &quantum_requirements)
1641            .await
1642            .expect("feature extraction should succeed");
1643        let prediction_swapped = predictor
1644            .predict_optimal_node(&nodes_swapped, &partition, &features_swapped)
1645            .await
1646            .expect("prediction should succeed");
1647        assert_eq!(prediction_swapped.predicted_node.0, "best_node");
1648    }
1649
1650    #[tokio::test]
1651    async fn test_predict_optimal_node_respects_qubit_capacity() {
1652        let predictor = QuantumLoadPredictionModel::new();
1653        let balancer = MLOptimizedQuantumLoadBalancer::new();
1654
1655        // "tiny_node" has excellent fidelity but cannot fit the partition;
1656        // a real capacity-aware predictor must never select it.
1657        let nodes = vec![
1658            make_test_node("tiny_node", 2, 0.999, 0, 0),
1659            make_test_node("adequate_node", 10, 0.8, 2, 1),
1660        ];
1661        let partition = make_test_partition(8);
1662        let quantum_requirements = QuantumResourceRequirements {
1663            qubits_needed: 8,
1664            gate_count_estimate: 20,
1665            circuit_depth: 10,
1666            fidelity_requirement: 0.7,
1667            coherence_time_needed: Duration::from_micros(50),
1668            entanglement_pairs: 2,
1669        };
1670        let features = balancer
1671            .extract_quantum_features(&nodes, &partition, &quantum_requirements)
1672            .await
1673            .expect("feature extraction should succeed");
1674
1675        let prediction = predictor
1676            .predict_optimal_node(&nodes, &partition, &features)
1677            .await
1678            .expect("prediction should succeed");
1679        assert_eq!(prediction.predicted_node.0, "adequate_node");
1680    }
1681
1682    #[tokio::test]
1683    async fn test_add_training_data_is_actually_recorded() {
1684        let learner = QuantumPerformanceLearner::new();
1685        let node_id = NodeId("trained_node".to_string());
1686        let data_point = QuantumLearningDataPoint {
1687            timestamp: Utc::now(),
1688            selected_node: node_id.clone(),
1689            circuit_partition: make_test_partition(4),
1690            quantum_requirements: QuantumResourceRequirements {
1691                qubits_needed: 4,
1692                gate_count_estimate: 8,
1693                circuit_depth: 4,
1694                fidelity_requirement: 0.92,
1695                coherence_time_needed: Duration::from_micros(40),
1696                entanglement_pairs: 1,
1697            },
1698            context_features: HashMap::new(),
1699        };
1700
1701        learner
1702            .add_training_data(data_point)
1703            .await
1704            .expect("add_training_data should succeed");
1705
1706        // The training data point must actually be reflected in the
1707        // learner's real performance-history state, not silently discarded.
1708        let history = learner
1709            .performance_history
1710            .read()
1711            .expect("history lock should not be poisoned");
1712        let node_history = history
1713            .get(&node_id)
1714            .expect("training data should have created a history entry for the node");
1715        assert_eq!(node_history.fidelity_history.len(), 1);
1716        assert_eq!(node_history.classical_metrics.execution_times.len(), 1);
1717    }
1718
1719    #[tokio::test]
1720    async fn test_select_nodes_load_balances_across_partitions() {
1721        let balancer = CapabilityBasedQuantumBalancer::new();
1722        let mut nodes = HashMap::new();
1723        nodes.insert(
1724            NodeId("node_a".to_string()),
1725            make_test_node("node_a", 8, 0.9, 0, 0),
1726        );
1727        nodes.insert(
1728            NodeId("node_b".to_string()),
1729            make_test_node("node_b", 8, 0.9, 0, 0),
1730        );
1731
1732        // Two partitions each needing 6 qubits: no single 8-qubit node can
1733        // hold more than one (6 + 6 > 8), so a real load-balanced
1734        // assignment must spread them across both nodes rather than
1735        // routing every partition to whichever node
1736        // `HashMap::iter().next()` returns.
1737        let partitions = vec![make_test_partition(6), make_test_partition(6)];
1738        let requirements = ExecutionRequirements {
1739            min_fidelity: 0.5,
1740            max_latency: Duration::from_secs(10),
1741            fault_tolerance: false,
1742            preferred_nodes: vec![],
1743            excluded_nodes: vec![],
1744            resource_constraints:
1745                crate::quantum_network::distributed_protocols::ResourceConstraints {
1746                    max_cost: None,
1747                    max_execution_time: Duration::from_secs(60),
1748                    max_memory_usage: 1024,
1749                    preferred_providers: vec![],
1750                },
1751        };
1752
1753        let allocation = balancer
1754            .select_nodes(&partitions, &nodes, &requirements)
1755            .expect("at least some partitions should be assignable");
1756
1757        let assigned_node_ids: std::collections::HashSet<_> = allocation.values().collect();
1758        assert!(
1759            assigned_node_ids.len() > 1,
1760            "expected partitions to be spread across more than one node, got {assigned_node_ids:?}"
1761        );
1762    }
1763}