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