Skip to main content

quantrs2_anneal/
adaptive_schedules.rs

1//! Neural Network Guided Annealing Schedules
2//!
3//! This module implements intelligent annealing schedule optimization using neural networks
4//! and reinforcement learning. It adaptively learns optimal temperature schedules, cooling
5//! rates, and other annealing parameters based on problem characteristics and real-time
6//! performance feedback.
7//!
8//! Key features:
9//! - Neural network based schedule prediction
10//! - Reinforcement learning for parameter optimization
11//! - Real-time performance monitoring and adaptation
12//! - Problem-aware schedule customization
13//! - Multi-objective schedule optimization
14//! - Transfer learning across problem types
15
16use scirs2_core::random::ChaCha8Rng;
17use scirs2_core::random::{thread_rng, Rng, SeedableRng};
18use scirs2_core::RngExt;
19use std::collections::HashMap;
20use std::time::{Duration, Instant};
21use thiserror::Error;
22
23use crate::ising::{IsingError, IsingModel};
24use crate::simulator::{AnnealingParams, AnnealingSolution, TemperatureSchedule};
25
26/// Errors that can occur in adaptive scheduling
27#[derive(Error, Debug)]
28pub enum AdaptiveScheduleError {
29    /// Ising model error
30    #[error("Ising error: {0}")]
31    IsingError(#[from] IsingError),
32
33    /// Neural network error
34    #[error("Neural network error: {0}")]
35    NeuralNetworkError(String),
36
37    /// Training error
38    #[error("Training error: {0}")]
39    TrainingError(String),
40
41    /// Configuration error
42    #[error("Configuration error: {0}")]
43    ConfigurationError(String),
44
45    /// Data processing error
46    #[error("Data processing error: {0}")]
47    DataError(String),
48
49    /// Optimization error
50    #[error("Optimization error: {0}")]
51    OptimizationError(String),
52}
53
54/// Result type for adaptive scheduling operations
55pub type AdaptiveScheduleResult<T> = Result<T, AdaptiveScheduleError>;
56
57/// Neural network guided annealing scheduler
58#[derive(Debug, Clone)]
59pub struct NeuralAnnealingScheduler {
60    /// Neural network for schedule prediction
61    pub network: SchedulePredictionNetwork,
62    /// Reinforcement learning agent
63    pub rl_agent: ScheduleRLAgent,
64    /// Configuration
65    pub config: SchedulerConfig,
66    /// Training history
67    pub training_history: TrainingHistory,
68    /// Problem feature cache
69    pub feature_cache: HashMap<String, ProblemFeatures>,
70    /// Performance statistics
71    pub performance_stats: PerformanceStatistics,
72}
73
74/// Configuration for the neural annealing scheduler
75#[derive(Debug, Clone)]
76pub struct SchedulerConfig {
77    /// Network architecture
78    pub network_layers: Vec<usize>,
79    /// Learning rate
80    pub learning_rate: f64,
81    /// Training epochs
82    pub training_epochs: usize,
83    /// Experience buffer size
84    pub buffer_size: usize,
85    /// Exploration rate for RL
86    pub exploration_rate: f64,
87    /// Discount factor
88    pub discount_factor: f64,
89    /// Update frequency
90    pub update_frequency: usize,
91    /// Enable transfer learning
92    pub use_transfer_learning: bool,
93    /// Random seed
94    pub seed: Option<u64>,
95}
96
97impl Default for SchedulerConfig {
98    fn default() -> Self {
99        Self {
100            network_layers: vec![32, 64, 32, 16],
101            learning_rate: 0.001,
102            training_epochs: 100,
103            buffer_size: 1000,
104            exploration_rate: 0.1,
105            discount_factor: 0.95,
106            update_frequency: 10,
107            use_transfer_learning: true,
108            seed: None,
109        }
110    }
111}
112
113/// Neural network for schedule prediction
114#[derive(Debug, Clone)]
115pub struct SchedulePredictionNetwork {
116    /// Network layers
117    pub layers: Vec<NetworkLayer>,
118    /// Input normalization parameters
119    pub input_normalization: NormalizationParams,
120    /// Output scaling parameters
121    pub output_scaling: NormalizationParams,
122    /// Network training state
123    pub training_state: NetworkTrainingState,
124}
125
126/// Neural network layer
127#[derive(Debug, Clone)]
128pub struct NetworkLayer {
129    /// Layer weights
130    pub weights: Vec<Vec<f64>>,
131    /// Layer biases
132    pub biases: Vec<f64>,
133    /// Activation function
134    pub activation: ActivationFunction,
135}
136
137/// Activation functions for neural network
138#[derive(Debug, Clone, PartialEq)]
139pub enum ActivationFunction {
140    /// `ReLU` activation
141    ReLU,
142    /// Sigmoid activation
143    Sigmoid,
144    /// Tanh activation
145    Tanh,
146    /// Linear activation
147    Linear,
148    /// Leaky `ReLU`
149    LeakyReLU(f64),
150}
151
152/// Normalization parameters
153#[derive(Debug, Clone)]
154pub struct NormalizationParams {
155    /// Mean values
156    pub means: Vec<f64>,
157    /// Standard deviations
158    pub stds: Vec<f64>,
159    /// Min values
160    pub mins: Vec<f64>,
161    /// Max values
162    pub maxs: Vec<f64>,
163}
164
165/// Network training state
166#[derive(Debug, Clone)]
167pub struct NetworkTrainingState {
168    /// Current epoch
169    pub epoch: usize,
170    /// Training loss
171    pub training_loss: f64,
172    /// Validation loss
173    pub validation_loss: f64,
174    /// Learning rate schedule
175    pub learning_rate: f64,
176    /// Training metrics
177    pub metrics: HashMap<String, f64>,
178}
179
180/// Reinforcement learning agent for schedule optimization
181#[derive(Debug, Clone)]
182pub struct ScheduleRLAgent {
183    /// Q-network for action-value estimation
184    pub q_network: SchedulePredictionNetwork,
185    /// Target network for stable training
186    pub target_network: SchedulePredictionNetwork,
187    /// Experience replay buffer
188    pub experience_buffer: Vec<ScheduleExperience>,
189    /// Agent configuration
190    pub config: RLAgentConfig,
191    /// Training statistics
192    pub stats: RLStats,
193}
194
195/// Reinforcement learning agent configuration
196#[derive(Debug, Clone)]
197pub struct RLAgentConfig {
198    /// Action space size
199    pub action_space_size: usize,
200    /// State space size
201    pub state_space_size: usize,
202    /// Batch size for training
203    pub batch_size: usize,
204    /// Target network update frequency
205    pub target_update_frequency: usize,
206    /// Epsilon decay rate
207    pub epsilon_decay: f64,
208    /// Minimum epsilon
209    pub min_epsilon: f64,
210}
211
212/// Experience tuple for reinforcement learning
213#[derive(Debug, Clone)]
214pub struct ScheduleExperience {
215    /// Current state (problem features + current schedule)
216    pub state: Vec<f64>,
217    /// Action taken (schedule modification)
218    pub action: usize,
219    /// Reward received (performance improvement)
220    pub reward: f64,
221    /// Next state
222    pub next_state: Vec<f64>,
223    /// Episode done flag
224    pub done: bool,
225    /// Additional metadata
226    pub metadata: ExperienceMetadata,
227}
228
229/// Metadata for RL experience
230#[derive(Debug, Clone)]
231pub struct ExperienceMetadata {
232    /// Problem type
233    pub problem_type: String,
234    /// Problem size
235    pub problem_size: usize,
236    /// Execution time
237    pub execution_time: Duration,
238    /// Final energy achieved
239    pub final_energy: f64,
240}
241
242/// RL training statistics
243#[derive(Debug, Clone)]
244pub struct RLStats {
245    /// Episode rewards
246    pub episode_rewards: Vec<f64>,
247    /// Average reward over episodes
248    pub average_reward: f64,
249    /// Exploration rate over time
250    pub exploration_history: Vec<f64>,
251    /// Q-network loss over time
252    pub loss_history: Vec<f64>,
253    /// Action selection frequency
254    pub action_frequency: HashMap<usize, usize>,
255}
256
257/// Problem features for neural network input
258#[derive(Debug, Clone)]
259pub struct ProblemFeatures {
260    /// Problem size (number of variables)
261    pub size: usize,
262    /// Connectivity density
263    pub connectivity_density: f64,
264    /// Coupling strength statistics
265    pub coupling_stats: CouplingStatistics,
266    /// Problem type classification
267    pub problem_type: ProblemType,
268    /// Energy landscape characteristics
269    pub landscape_features: LandscapeFeatures,
270    /// Historical performance data
271    pub historical_performance: Vec<PerformancePoint>,
272}
273
274/// Statistics about coupling strengths in the problem
275#[derive(Debug, Clone)]
276pub struct CouplingStatistics {
277    /// Mean coupling strength
278    pub mean: f64,
279    /// Standard deviation
280    pub std: f64,
281    /// Maximum absolute coupling
282    pub max_abs: f64,
283    /// Range of couplings
284    pub range: f64,
285    /// Coupling distribution skewness
286    pub skewness: f64,
287}
288
289/// Problem type classification
290#[derive(Debug, Clone, PartialEq, Eq)]
291pub enum ProblemType {
292    /// Random Ising model
293    Random,
294    /// Structured problem (e.g., grid, tree)
295    Structured,
296    /// Optimization problem (e.g., Max-Cut, TSP)
297    Optimization,
298    /// Machine learning problem
299    MachineLearning,
300    /// Industry-specific problem
301    IndustrySpecific(String),
302    /// Unknown type
303    Unknown,
304}
305
306/// Energy landscape characteristics
307#[derive(Debug, Clone)]
308pub struct LandscapeFeatures {
309    /// Estimated number of local minima
310    pub num_local_minima: usize,
311    /// Ruggedness measure
312    pub ruggedness: f64,
313    /// Connectivity of energy levels
314    pub energy_connectivity: f64,
315    /// Barrier heights between minima
316    pub barrier_heights: Vec<f64>,
317    /// Funnel structure measure
318    pub funnel_structure: f64,
319}
320
321/// Performance point for historical data
322#[derive(Debug, Clone)]
323pub struct PerformancePoint {
324    /// Schedule parameters used
325    pub schedule_params: ScheduleParameters,
326    /// Performance achieved
327    pub performance: PerformanceMetrics,
328    /// Problem context
329    pub context: ProblemContext,
330}
331
332/// Schedule parameters
333#[derive(Debug, Clone)]
334pub struct ScheduleParameters {
335    /// Initial temperature
336    pub initial_temp: f64,
337    /// Final temperature
338    pub final_temp: f64,
339    /// Number of sweeps
340    pub num_sweeps: usize,
341    /// Cooling rate
342    pub cooling_rate: f64,
343    /// Schedule type
344    pub schedule_type: ScheduleType,
345    /// Additional parameters
346    pub additional_params: HashMap<String, f64>,
347}
348
349/// Types of annealing schedules
350#[derive(Debug, Clone, PartialEq)]
351pub enum ScheduleType {
352    /// Linear cooling
353    Linear,
354    /// Exponential cooling
355    Exponential,
356    /// Logarithmic cooling
357    Logarithmic,
358    /// Custom schedule
359    Custom(Vec<f64>),
360    /// Adaptive schedule
361    Adaptive,
362}
363
364/// Performance metrics for a run
365#[derive(Debug, Clone)]
366pub struct PerformanceMetrics {
367    /// Final energy achieved
368    pub final_energy: f64,
369    /// Number of evaluations
370    pub num_evaluations: usize,
371    /// Execution time
372    pub execution_time: Duration,
373    /// Success rate (for multiple runs)
374    pub success_rate: f64,
375    /// Convergence speed
376    pub convergence_speed: f64,
377    /// Quality relative to known optimum
378    pub solution_quality: f64,
379}
380
381/// Problem context information
382#[derive(Debug, Clone)]
383pub struct ProblemContext {
384    /// Problem identifier
385    pub problem_id: String,
386    /// Timestamp
387    pub timestamp: Instant,
388    /// Hardware used
389    pub hardware_type: String,
390    /// Environment conditions
391    pub environment: HashMap<String, f64>,
392}
393
394/// Training history for the scheduler
395#[derive(Debug, Clone)]
396pub struct TrainingHistory {
397    /// Network training losses over time
398    pub network_losses: Vec<f64>,
399    /// RL rewards over time
400    pub rl_rewards: Vec<f64>,
401    /// Validation scores
402    pub validation_scores: Vec<f64>,
403    /// Feature importance evolution
404    pub feature_importance: Vec<HashMap<String, f64>>,
405    /// Training times per epoch
406    pub training_times: Vec<Duration>,
407}
408
409/// Performance statistics for the scheduler
410#[derive(Debug, Clone)]
411pub struct PerformanceStatistics {
412    /// Number of problems solved
413    pub problems_solved: usize,
414    /// Average improvement over baseline
415    pub avg_improvement: f64,
416    /// Best improvement achieved
417    pub best_improvement: f64,
418    /// Adaptation time
419    pub adaptation_time: Duration,
420    /// Success rate
421    pub success_rate: f64,
422    /// Transfer learning effectiveness
423    pub transfer_effectiveness: f64,
424}
425
426impl NeuralAnnealingScheduler {
427    /// Create a new neural annealing scheduler
428    pub fn new(config: SchedulerConfig) -> AdaptiveScheduleResult<Self> {
429        let network = SchedulePredictionNetwork::new(&config.network_layers, config.seed)?;
430        let rl_agent = ScheduleRLAgent::new(RLAgentConfig {
431            action_space_size: 10,                          // Number of discrete actions
432            state_space_size: config.network_layers[0] + 4, // Features + 4 schedule parameters
433            batch_size: 32,
434            target_update_frequency: 100,
435            epsilon_decay: 0.995,
436            min_epsilon: 0.01,
437        })?;
438
439        Ok(Self {
440            network,
441            rl_agent,
442            config,
443            training_history: TrainingHistory {
444                network_losses: Vec::new(),
445                rl_rewards: Vec::new(),
446                validation_scores: Vec::new(),
447                feature_importance: Vec::new(),
448                training_times: Vec::new(),
449            },
450            feature_cache: HashMap::new(),
451            performance_stats: PerformanceStatistics {
452                problems_solved: 0,
453                avg_improvement: 0.0,
454                best_improvement: 0.0,
455                adaptation_time: Duration::from_secs(0),
456                success_rate: 0.0,
457                transfer_effectiveness: 0.0,
458            },
459        })
460    }
461
462    /// Generate optimal annealing schedule for a given problem
463    pub fn generate_schedule(
464        &mut self,
465        problem: &IsingModel,
466    ) -> AdaptiveScheduleResult<AnnealingParams> {
467        let start_time = Instant::now();
468
469        // Extract problem features
470        let features = self.extract_problem_features(problem)?;
471
472        // Check cache for similar problems
473        if let Some(cached_schedule) = self.check_feature_cache(&features) {
474            return Ok(cached_schedule);
475        }
476
477        // Use neural network to predict optimal schedule
478        let predicted_params = self.predict_schedule_parameters(&features)?;
479
480        // Use RL agent to refine parameters
481        let refined_params = self.refine_with_rl(&features, predicted_params)?;
482
483        // Convert to AnnealingParams
484        let schedule = self.convert_to_annealing_params(refined_params)?;
485
486        // Cache the result
487        self.cache_schedule(&features, schedule.clone());
488
489        // Update adaptation time
490        self.performance_stats.adaptation_time = start_time.elapsed();
491
492        Ok(schedule)
493    }
494
495    /// Extract features from an Ising model
496    fn extract_problem_features(
497        &self,
498        problem: &IsingModel,
499    ) -> AdaptiveScheduleResult<ProblemFeatures> {
500        let size = problem.num_qubits;
501
502        // Calculate connectivity density
503        let mut num_couplings = 0;
504        let mut coupling_values = Vec::new();
505
506        for i in 0..size {
507            for j in (i + 1)..size {
508                if let Ok(coupling) = problem.get_coupling(i, j) {
509                    if coupling.abs() > 1e-10 {
510                        num_couplings += 1;
511                        coupling_values.push(coupling.abs());
512                    }
513                }
514            }
515        }
516
517        let max_possible_couplings = size * (size - 1) / 2;
518        let connectivity_density = f64::from(num_couplings) / max_possible_couplings as f64;
519
520        // Calculate coupling statistics
521        let coupling_stats = if coupling_values.is_empty() {
522            CouplingStatistics {
523                mean: 0.0,
524                std: 0.0,
525                max_abs: 0.0,
526                range: 0.0,
527                skewness: 0.0,
528            }
529        } else {
530            let mean = coupling_values.iter().sum::<f64>() / coupling_values.len() as f64;
531            let variance = coupling_values
532                .iter()
533                .map(|x| (x - mean).powi(2))
534                .sum::<f64>()
535                / coupling_values.len() as f64;
536            let std = variance.sqrt();
537            let max_abs = coupling_values.iter().fold(0.0f64, |a, &b| a.max(b));
538            let min_val = coupling_values.iter().fold(f64::INFINITY, |a, &b| a.min(b));
539            let range = max_abs - min_val;
540
541            // Simple skewness calculation
542            let skewness = if std > 1e-10 {
543                coupling_values
544                    .iter()
545                    .map(|x| ((x - mean) / std).powi(3))
546                    .sum::<f64>()
547                    / coupling_values.len() as f64
548            } else {
549                0.0
550            };
551
552            CouplingStatistics {
553                mean,
554                std,
555                max_abs,
556                range,
557                skewness,
558            }
559        };
560
561        // Classify problem type (simplified)
562        let problem_type = if connectivity_density > 0.8 {
563            ProblemType::Random
564        } else if connectivity_density < 0.2 {
565            ProblemType::Structured
566        } else {
567            ProblemType::Optimization
568        };
569
570        // Simple landscape features estimation
571        let landscape_features = LandscapeFeatures {
572            num_local_minima: (size as f64 * connectivity_density * 10.0) as usize,
573            ruggedness: coupling_stats.std / coupling_stats.mean.max(1e-10),
574            energy_connectivity: connectivity_density,
575            barrier_heights: vec![coupling_stats.mean; 5], // Simplified
576            funnel_structure: 1.0 - connectivity_density,  // Simplified metric
577        };
578
579        Ok(ProblemFeatures {
580            size,
581            connectivity_density,
582            coupling_stats,
583            problem_type,
584            landscape_features,
585            historical_performance: Vec::new(),
586        })
587    }
588
589    /// Check feature cache for similar problems
590    fn check_feature_cache(&self, features: &ProblemFeatures) -> Option<AnnealingParams> {
591        // Simple similarity matching (in practice, would use more sophisticated matching)
592        for (_, cached_features) in &self.feature_cache {
593            if (cached_features.size as f64 - features.size as f64).abs() / (features.size as f64)
594                < 0.1
595                && (cached_features.connectivity_density - features.connectivity_density).abs()
596                    < 0.1
597                && cached_features.problem_type == features.problem_type
598            {
599                // Return a default schedule for now (would return cached optimized schedule)
600                return Some(AnnealingParams {
601                    num_sweeps: 1000 + features.size * 10,
602                    initial_temperature: features.coupling_stats.max_abs * 10.0,
603                    final_temperature: features.coupling_stats.max_abs * 0.001,
604                    ..Default::default()
605                });
606            }
607        }
608        None
609    }
610
611    /// Use neural network to predict schedule parameters
612    fn predict_schedule_parameters(
613        &self,
614        features: &ProblemFeatures,
615    ) -> AdaptiveScheduleResult<ScheduleParameters> {
616        // Convert features to input vector
617        let input = self.features_to_input_vector(features);
618
619        // Forward pass through network
620        let output = self.network.forward(&input)?;
621
622        // Convert output to schedule parameters
623        let initial_temp = output[0].max(1.0).min(100.0);
624        let mut final_temp = output[1].max(0.001).min(1.0);
625
626        // Ensure initial_temp > final_temp
627        if initial_temp <= final_temp {
628            final_temp = initial_temp * 0.01; // Make final_temp 1% of initial
629        }
630
631        let schedule_params = ScheduleParameters {
632            initial_temp,
633            final_temp,
634            num_sweeps: (output[2].max(100.0).min(100_000.0)) as usize,
635            cooling_rate: output[3].max(0.01).min(0.99),
636            schedule_type: ScheduleType::Exponential, // Default
637            additional_params: HashMap::new(),
638        };
639
640        Ok(schedule_params)
641    }
642
643    /// Convert features to input vector for neural network
644    fn features_to_input_vector(&self, features: &ProblemFeatures) -> Vec<f64> {
645        vec![
646            features.size as f64 / 1000.0, // Normalized size
647            features.connectivity_density,
648            features.coupling_stats.mean,
649            features.coupling_stats.std,
650            features.coupling_stats.max_abs,
651            features.coupling_stats.skewness,
652            features.landscape_features.ruggedness,
653            features.landscape_features.energy_connectivity,
654            match features.problem_type {
655                ProblemType::Random => 1.0,
656                ProblemType::Structured => 2.0,
657                ProblemType::Optimization => 3.0,
658                ProblemType::MachineLearning => 4.0,
659                _ => 0.0,
660            },
661            features.landscape_features.funnel_structure,
662        ]
663    }
664
665    /// Use RL agent to refine schedule parameters
666    fn refine_with_rl(
667        &self,
668        features: &ProblemFeatures,
669        initial_params: ScheduleParameters,
670    ) -> AdaptiveScheduleResult<ScheduleParameters> {
671        // Convert to state representation
672        let state = self.create_rl_state(features, &initial_params);
673
674        // Get action from RL agent
675        let action = self.rl_agent.select_action(&state)?;
676
677        // Apply action to modify parameters
678        let refined_params = self.apply_rl_action(initial_params, action)?;
679
680        Ok(refined_params)
681    }
682
683    /// Create RL state representation
684    fn create_rl_state(&self, features: &ProblemFeatures, params: &ScheduleParameters) -> Vec<f64> {
685        let mut state = self.features_to_input_vector(features);
686
687        // Add current parameters to state
688        state.extend(vec![
689            params.initial_temp / 100.0, // Normalized
690            params.final_temp / 1.0,
691            params.num_sweeps as f64 / 10_000.0,
692            params.cooling_rate,
693        ]);
694
695        state
696    }
697
698    /// Apply RL action to modify parameters
699    fn apply_rl_action(
700        &self,
701        mut params: ScheduleParameters,
702        action: usize,
703    ) -> AdaptiveScheduleResult<ScheduleParameters> {
704        match action {
705            0 => params.initial_temp *= 1.2, // Increase initial temp
706            1 => params.initial_temp *= 0.8, // Decrease initial temp
707            2 => params.final_temp *= 1.5,   // Increase final temp
708            3 => params.final_temp *= 0.7,   // Decrease final temp
709            4 => params.num_sweeps = (params.num_sweeps as f64 * 1.3) as usize, // Increase sweeps
710            5 => params.num_sweeps = (params.num_sweeps as f64 * 0.8) as usize, // Decrease sweeps
711            6 => params.cooling_rate = (params.cooling_rate * 1.1).min(0.99), // Slower cooling
712            7 => params.cooling_rate = (params.cooling_rate * 0.9).max(0.01), // Faster cooling
713            8 => {
714                // Change schedule type
715                params.schedule_type = ScheduleType::Linear;
716            }
717            9 => {
718                // Change schedule type
719                params.schedule_type = ScheduleType::Logarithmic;
720            }
721            _ => {} // No action
722        }
723
724        Ok(params)
725    }
726
727    /// Convert schedule parameters to `AnnealingParams`
728    fn convert_to_annealing_params(
729        &self,
730        params: ScheduleParameters,
731    ) -> AdaptiveScheduleResult<AnnealingParams> {
732        Ok(AnnealingParams {
733            num_sweeps: params.num_sweeps,
734            initial_temperature: params.initial_temp,
735            final_temperature: params.final_temp,
736            temperature_schedule: match params.schedule_type {
737                ScheduleType::Linear => TemperatureSchedule::Linear,
738                ScheduleType::Exponential => TemperatureSchedule::Exponential(1.0),
739                ScheduleType::Logarithmic => TemperatureSchedule::Exponential(0.5), // Fallback to Exponential with slower cooling
740                _ => TemperatureSchedule::Exponential(1.0),
741            },
742            ..Default::default()
743        })
744    }
745
746    /// Cache schedule for similar problems
747    fn cache_schedule(&mut self, features: &ProblemFeatures, schedule: AnnealingParams) {
748        let cache_key = format!(
749            "{}_{:.2}_{:?}",
750            features.size, features.connectivity_density, features.problem_type
751        );
752        self.feature_cache.insert(cache_key, features.clone());
753    }
754
755    /// Train the scheduler on historical data
756    pub fn train(&mut self, training_data: &[TrainingExample]) -> AdaptiveScheduleResult<()> {
757        println!(
758            "Training neural annealing scheduler with {} examples",
759            training_data.len()
760        );
761
762        for epoch in 0..self.config.training_epochs {
763            let start_time = Instant::now();
764
765            // Train neural network
766            let network_loss = self.train_network_epoch(training_data)?;
767
768            // Train RL agent
769            let rl_reward = self.train_rl_epoch(training_data)?;
770
771            // Update training history
772            self.training_history.network_losses.push(network_loss);
773            self.training_history.rl_rewards.push(rl_reward);
774            self.training_history
775                .training_times
776                .push(start_time.elapsed());
777
778            if epoch % 10 == 0 {
779                println!(
780                    "Epoch {epoch}: Network Loss = {network_loss:.6}, RL Reward = {rl_reward:.6}"
781                );
782            }
783        }
784
785        Ok(())
786    }
787
788    /// Train network for one epoch
789    fn train_network_epoch(
790        &mut self,
791        training_data: &[TrainingExample],
792    ) -> AdaptiveScheduleResult<f64> {
793        let mut total_loss = 0.0;
794
795        for example in training_data {
796            let input = self.features_to_input_vector(&example.features);
797            let target = self.params_to_output_vector(&example.optimal_params);
798
799            // Forward pass
800            let prediction = self.network.forward(&input)?;
801
802            // Calculate loss (simplified MSE)
803            let loss: f64 = prediction
804                .iter()
805                .zip(target.iter())
806                .map(|(pred, targ)| (pred - targ).powi(2))
807                .sum::<f64>()
808                / prediction.len() as f64;
809
810            total_loss += loss;
811
812            // Backward pass (simplified - in practice would implement proper backpropagation)
813            self.network.backward(&input, &target, &prediction)?;
814        }
815
816        Ok(total_loss / training_data.len() as f64)
817    }
818
819    /// Train RL agent for one epoch
820    fn train_rl_epoch(&mut self, training_data: &[TrainingExample]) -> AdaptiveScheduleResult<f64> {
821        let mut total_reward = 0.0;
822
823        for example in training_data {
824            // Create experience from training example
825            let state = self.create_rl_state(&example.features, &example.baseline_params);
826            let optimal_state = self.create_rl_state(&example.features, &example.optimal_params);
827
828            // Calculate reward based on performance improvement
829            let reward = example.performance_improvement;
830
831            // Store experience (simplified)
832            let experience = ScheduleExperience {
833                state: state.clone(),
834                action: 0, // Would need to infer action from parameter differences
835                reward,
836                next_state: optimal_state,
837                done: true,
838                metadata: ExperienceMetadata {
839                    problem_type: format!("{:?}", example.features.problem_type),
840                    problem_size: example.features.size,
841                    execution_time: Duration::from_secs(1),
842                    final_energy: 0.0, // Would be filled from example
843                },
844            };
845
846            self.rl_agent.store_experience(experience);
847            total_reward += reward;
848        }
849
850        // Train RL agent
851        self.rl_agent.train()?;
852
853        Ok(total_reward / training_data.len() as f64)
854    }
855
856    /// Convert parameters to output vector
857    fn params_to_output_vector(&self, params: &ScheduleParameters) -> Vec<f64> {
858        vec![
859            params.initial_temp / 100.0,
860            params.final_temp,
861            params.num_sweeps as f64 / 10_000.0,
862            params.cooling_rate,
863        ]
864    }
865}
866
867/// Training example for the scheduler
868#[derive(Debug, Clone)]
869pub struct TrainingExample {
870    /// Problem features
871    pub features: ProblemFeatures,
872    /// Baseline parameters
873    pub baseline_params: ScheduleParameters,
874    /// Optimal parameters found
875    pub optimal_params: ScheduleParameters,
876    /// Performance improvement achieved
877    pub performance_improvement: f64,
878    /// Additional metadata
879    pub metadata: HashMap<String, f64>,
880}
881
882impl SchedulePredictionNetwork {
883    /// Create a new schedule prediction network
884    pub fn new(layer_sizes: &[usize], seed: Option<u64>) -> AdaptiveScheduleResult<Self> {
885        if layer_sizes.len() < 2 {
886            return Err(AdaptiveScheduleError::ConfigurationError(
887                "Network must have at least input and output layers".to_string(),
888            ));
889        }
890
891        let mut rng = match seed {
892            Some(s) => ChaCha8Rng::seed_from_u64(s),
893            None => ChaCha8Rng::seed_from_u64(thread_rng().random()),
894        };
895
896        let mut layers = Vec::new();
897
898        for i in 0..layer_sizes.len() - 1 {
899            let input_size = layer_sizes[i];
900            let output_size = layer_sizes[i + 1];
901
902            // Initialize weights with Xavier initialization
903            let mut weights = vec![vec![0.0; input_size]; output_size];
904            let scale = (2.0 / input_size as f64).sqrt();
905
906            for row in &mut weights {
907                for weight in row {
908                    *weight = rng.random_range(-scale..scale);
909                }
910            }
911
912            let biases = vec![0.0; output_size];
913
914            let activation = if i == layer_sizes.len() - 2 {
915                ActivationFunction::Linear // Output layer
916            } else {
917                ActivationFunction::ReLU // Hidden layers
918            };
919
920            layers.push(NetworkLayer {
921                weights,
922                biases,
923                activation,
924            });
925        }
926
927        let input_size = layer_sizes[0];
928        let output_size = layer_sizes[layer_sizes.len() - 1];
929
930        Ok(Self {
931            layers,
932            input_normalization: NormalizationParams {
933                means: vec![0.0; input_size],
934                stds: vec![1.0; input_size],
935                mins: vec![0.0; input_size],
936                maxs: vec![1.0; input_size],
937            },
938            output_scaling: NormalizationParams {
939                means: vec![0.0; output_size],
940                stds: vec![1.0; output_size],
941                mins: vec![0.0; output_size],
942                maxs: vec![1.0; output_size],
943            },
944            training_state: NetworkTrainingState {
945                epoch: 0,
946                training_loss: 0.0,
947                validation_loss: 0.0,
948                learning_rate: 0.001,
949                metrics: HashMap::new(),
950            },
951        })
952    }
953
954    /// Forward pass through the network
955    pub fn forward(&self, input: &[f64]) -> AdaptiveScheduleResult<Vec<f64>> {
956        let mut activations = input.to_vec();
957
958        for layer in &self.layers {
959            activations = self.layer_forward(&activations, layer)?;
960        }
961
962        Ok(activations)
963    }
964
965    /// Forward pass through a single layer
966    fn layer_forward(
967        &self,
968        input: &[f64],
969        layer: &NetworkLayer,
970    ) -> AdaptiveScheduleResult<Vec<f64>> {
971        if input.len() != layer.weights[0].len() {
972            return Err(AdaptiveScheduleError::NeuralNetworkError(format!(
973                "Input size {} doesn't match layer input size {}",
974                input.len(),
975                layer.weights[0].len()
976            )));
977        }
978
979        let mut output = Vec::new();
980
981        for (neuron_weights, &bias) in layer.weights.iter().zip(&layer.biases) {
982            let mut activation = bias;
983
984            for (&inp, &weight) in input.iter().zip(neuron_weights) {
985                activation += inp * weight;
986            }
987
988            // Apply activation function
989            activation = match layer.activation {
990                ActivationFunction::ReLU => activation.max(0.0),
991                ActivationFunction::Sigmoid => 1.0 / (1.0 + (-activation).exp()),
992                ActivationFunction::Tanh => activation.tanh(),
993                ActivationFunction::Linear => activation,
994                ActivationFunction::LeakyReLU(alpha) => {
995                    if activation > 0.0 {
996                        activation
997                    } else {
998                        alpha * activation
999                    }
1000                }
1001            };
1002
1003            output.push(activation);
1004        }
1005
1006        Ok(output)
1007    }
1008
1009    /// Backward pass (simplified implementation)
1010    pub const fn backward(
1011        &mut self,
1012        _input: &[f64],
1013        _target: &[f64],
1014        _prediction: &[f64],
1015    ) -> AdaptiveScheduleResult<()> {
1016        // Simplified backward pass - in practice would implement full backpropagation
1017        // For now, just update training state
1018        self.training_state.epoch += 1;
1019        Ok(())
1020    }
1021}
1022
1023impl ScheduleRLAgent {
1024    /// Create a new RL agent
1025    pub fn new(config: RLAgentConfig) -> AdaptiveScheduleResult<Self> {
1026        let q_network = SchedulePredictionNetwork::new(
1027            &[config.state_space_size, 64, 32, config.action_space_size],
1028            None,
1029        )?;
1030        let target_network = q_network.clone();
1031
1032        Ok(Self {
1033            q_network,
1034            target_network,
1035            experience_buffer: Vec::new(),
1036            config,
1037            stats: RLStats {
1038                episode_rewards: Vec::new(),
1039                average_reward: 0.0,
1040                exploration_history: Vec::new(),
1041                loss_history: Vec::new(),
1042                action_frequency: HashMap::new(),
1043            },
1044        })
1045    }
1046
1047    /// Select action using epsilon-greedy policy
1048    pub fn select_action(&self, state: &[f64]) -> AdaptiveScheduleResult<usize> {
1049        let mut rng = ChaCha8Rng::seed_from_u64(thread_rng().random());
1050
1051        if rng.random::<f64>() < self.config.min_epsilon {
1052            // Random exploration
1053            Ok(rng.random_range(0..self.config.action_space_size))
1054        } else {
1055            // Greedy action selection
1056            let q_values = self.q_network.forward(state)?;
1057            let best_action = q_values
1058                .iter()
1059                .enumerate()
1060                .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
1061                .map_or(0, |(idx, _)| idx);
1062            Ok(best_action)
1063        }
1064    }
1065
1066    /// Store experience in replay buffer
1067    pub fn store_experience(&mut self, experience: ScheduleExperience) {
1068        self.experience_buffer.push(experience);
1069
1070        // Maintain buffer size
1071        if self.experience_buffer.len() > 1000 {
1072            self.experience_buffer.remove(0);
1073        }
1074    }
1075
1076    /// Train the RL agent
1077    pub fn train(&mut self) -> AdaptiveScheduleResult<()> {
1078        if self.experience_buffer.len() < self.config.batch_size {
1079            return Ok(());
1080        }
1081
1082        // Sample batch from experience buffer
1083        // In practice, would implement proper experience replay
1084
1085        Ok(())
1086    }
1087}
1088
1089/// Create a default neural annealing scheduler
1090pub fn create_neural_scheduler() -> AdaptiveScheduleResult<NeuralAnnealingScheduler> {
1091    NeuralAnnealingScheduler::new(SchedulerConfig::default())
1092}
1093
1094/// Create a neural scheduler with custom configuration
1095pub fn create_custom_neural_scheduler(
1096    network_layers: Vec<usize>,
1097    learning_rate: f64,
1098    exploration_rate: f64,
1099) -> AdaptiveScheduleResult<NeuralAnnealingScheduler> {
1100    let config = SchedulerConfig {
1101        network_layers,
1102        learning_rate,
1103        exploration_rate,
1104        ..Default::default()
1105    };
1106
1107    NeuralAnnealingScheduler::new(config)
1108}
1109
1110#[cfg(test)]
1111mod tests {
1112    use super::*;
1113
1114    #[test]
1115    fn test_neural_scheduler_creation() {
1116        let scheduler = create_neural_scheduler().expect("Failed to create scheduler");
1117        assert_eq!(scheduler.config.network_layers, vec![32, 64, 32, 16]);
1118    }
1119
1120    #[test]
1121    fn test_network_creation() {
1122        let network = SchedulePredictionNetwork::new(&[10, 20, 5], Some(42))
1123            .expect("Failed to create network");
1124        assert_eq!(network.layers.len(), 2);
1125        assert_eq!(network.layers[0].weights.len(), 20);
1126        assert_eq!(network.layers[0].weights[0].len(), 10);
1127    }
1128
1129    #[test]
1130    fn test_network_forward_pass() {
1131        let network =
1132            SchedulePredictionNetwork::new(&[3, 5, 2], Some(42)).expect("Failed to create network");
1133        let input = vec![1.0, 0.5, -0.5];
1134        let output = network.forward(&input).expect("Failed forward pass");
1135        assert_eq!(output.len(), 2);
1136    }
1137
1138    #[test]
1139    fn test_feature_extraction() {
1140        let mut ising = IsingModel::new(4);
1141        ising.set_bias(0, 1.0).expect("Failed to set bias");
1142        ising
1143            .set_coupling(0, 1, -0.5)
1144            .expect("Failed to set coupling");
1145        ising
1146            .set_coupling(1, 2, 0.3)
1147            .expect("Failed to set coupling");
1148
1149        let scheduler = create_neural_scheduler().expect("Failed to create scheduler");
1150        let features = scheduler
1151            .extract_problem_features(&ising)
1152            .expect("Failed to extract features");
1153
1154        assert_eq!(features.size, 4);
1155        assert!(features.connectivity_density > 0.0);
1156        assert!(features.coupling_stats.mean > 0.0);
1157    }
1158
1159    #[test]
1160    fn test_rl_agent_creation() {
1161        let config = RLAgentConfig {
1162            action_space_size: 10,
1163            state_space_size: 15,
1164            batch_size: 32,
1165            target_update_frequency: 100,
1166            epsilon_decay: 0.995,
1167            min_epsilon: 0.01,
1168        };
1169
1170        let agent = ScheduleRLAgent::new(config).expect("Failed to create RL agent");
1171        assert_eq!(agent.config.action_space_size, 10);
1172        assert_eq!(agent.config.state_space_size, 15);
1173    }
1174
1175    #[test]
1176    fn test_schedule_generation() {
1177        // Create scheduler with correct input size (10 features)
1178        let mut scheduler = create_custom_neural_scheduler(
1179            vec![10, 16, 8, 4], // Match the 10 features from features_to_input_vector
1180            0.001,
1181            0.1,
1182        )
1183        .expect("Failed to create custom scheduler");
1184        let mut ising = IsingModel::new(5);
1185        ising.set_bias(0, 1.0).expect("Failed to set bias");
1186        ising
1187            .set_coupling(0, 1, -0.5)
1188            .expect("Failed to set coupling");
1189
1190        let schedule = scheduler
1191            .generate_schedule(&ising)
1192            .expect("Failed to generate schedule");
1193        assert!(schedule.num_sweeps > 0);
1194        assert!(schedule.initial_temperature > 0.0);
1195        assert!(schedule.final_temperature > 0.0);
1196        assert!(schedule.initial_temperature > schedule.final_temperature);
1197    }
1198}