1use 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#[derive(Error, Debug)]
27pub enum AdaptiveScheduleError {
28 #[error("Ising error: {0}")]
30 IsingError(#[from] IsingError),
31
32 #[error("Neural network error: {0}")]
34 NeuralNetworkError(String),
35
36 #[error("Training error: {0}")]
38 TrainingError(String),
39
40 #[error("Configuration error: {0}")]
42 ConfigurationError(String),
43
44 #[error("Data processing error: {0}")]
46 DataError(String),
47
48 #[error("Optimization error: {0}")]
50 OptimizationError(String),
51}
52
53pub type AdaptiveScheduleResult<T> = Result<T, AdaptiveScheduleError>;
55
56#[derive(Debug, Clone)]
58pub struct NeuralAnnealingScheduler {
59 pub network: SchedulePredictionNetwork,
61 pub rl_agent: ScheduleRLAgent,
63 pub config: SchedulerConfig,
65 pub training_history: TrainingHistory,
67 pub feature_cache: HashMap<String, ProblemFeatures>,
69 pub performance_stats: PerformanceStatistics,
71}
72
73#[derive(Debug, Clone)]
75pub struct SchedulerConfig {
76 pub network_layers: Vec<usize>,
78 pub learning_rate: f64,
80 pub training_epochs: usize,
82 pub buffer_size: usize,
84 pub exploration_rate: f64,
86 pub discount_factor: f64,
88 pub update_frequency: usize,
90 pub use_transfer_learning: bool,
92 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#[derive(Debug, Clone)]
114pub struct SchedulePredictionNetwork {
115 pub layers: Vec<NetworkLayer>,
117 pub input_normalization: NormalizationParams,
119 pub output_scaling: NormalizationParams,
121 pub training_state: NetworkTrainingState,
123}
124
125#[derive(Debug, Clone)]
127pub struct NetworkLayer {
128 pub weights: Vec<Vec<f64>>,
130 pub biases: Vec<f64>,
132 pub activation: ActivationFunction,
134}
135
136#[derive(Debug, Clone, PartialEq)]
138pub enum ActivationFunction {
139 ReLU,
141 Sigmoid,
143 Tanh,
145 Linear,
147 LeakyReLU(f64),
149}
150
151#[derive(Debug, Clone)]
153pub struct NormalizationParams {
154 pub means: Vec<f64>,
156 pub stds: Vec<f64>,
158 pub mins: Vec<f64>,
160 pub maxs: Vec<f64>,
162}
163
164#[derive(Debug, Clone)]
166pub struct NetworkTrainingState {
167 pub epoch: usize,
169 pub training_loss: f64,
171 pub validation_loss: f64,
173 pub learning_rate: f64,
175 pub metrics: HashMap<String, f64>,
177}
178
179#[derive(Debug, Clone)]
181pub struct ScheduleRLAgent {
182 pub q_network: SchedulePredictionNetwork,
184 pub target_network: SchedulePredictionNetwork,
186 pub experience_buffer: Vec<ScheduleExperience>,
188 pub config: RLAgentConfig,
190 pub stats: RLStats,
192}
193
194#[derive(Debug, Clone)]
196pub struct RLAgentConfig {
197 pub action_space_size: usize,
199 pub state_space_size: usize,
201 pub batch_size: usize,
203 pub target_update_frequency: usize,
205 pub epsilon_decay: f64,
207 pub min_epsilon: f64,
209}
210
211#[derive(Debug, Clone)]
213pub struct ScheduleExperience {
214 pub state: Vec<f64>,
216 pub action: usize,
218 pub reward: f64,
220 pub next_state: Vec<f64>,
222 pub done: bool,
224 pub metadata: ExperienceMetadata,
226}
227
228#[derive(Debug, Clone)]
230pub struct ExperienceMetadata {
231 pub problem_type: String,
233 pub problem_size: usize,
235 pub execution_time: Duration,
237 pub final_energy: f64,
239}
240
241#[derive(Debug, Clone)]
243pub struct RLStats {
244 pub episode_rewards: Vec<f64>,
246 pub average_reward: f64,
248 pub exploration_history: Vec<f64>,
250 pub loss_history: Vec<f64>,
252 pub action_frequency: HashMap<usize, usize>,
254}
255
256#[derive(Debug, Clone)]
258pub struct ProblemFeatures {
259 pub size: usize,
261 pub connectivity_density: f64,
263 pub coupling_stats: CouplingStatistics,
265 pub problem_type: ProblemType,
267 pub landscape_features: LandscapeFeatures,
269 pub historical_performance: Vec<PerformancePoint>,
271}
272
273#[derive(Debug, Clone)]
275pub struct CouplingStatistics {
276 pub mean: f64,
278 pub std: f64,
280 pub max_abs: f64,
282 pub range: f64,
284 pub skewness: f64,
286}
287
288#[derive(Debug, Clone, PartialEq, Eq)]
290pub enum ProblemType {
291 Random,
293 Structured,
295 Optimization,
297 MachineLearning,
299 IndustrySpecific(String),
301 Unknown,
303}
304
305#[derive(Debug, Clone)]
307pub struct LandscapeFeatures {
308 pub num_local_minima: usize,
310 pub ruggedness: f64,
312 pub energy_connectivity: f64,
314 pub barrier_heights: Vec<f64>,
316 pub funnel_structure: f64,
318}
319
320#[derive(Debug, Clone)]
322pub struct PerformancePoint {
323 pub schedule_params: ScheduleParameters,
325 pub performance: PerformanceMetrics,
327 pub context: ProblemContext,
329}
330
331#[derive(Debug, Clone)]
333pub struct ScheduleParameters {
334 pub initial_temp: f64,
336 pub final_temp: f64,
338 pub num_sweeps: usize,
340 pub cooling_rate: f64,
342 pub schedule_type: ScheduleType,
344 pub additional_params: HashMap<String, f64>,
346}
347
348#[derive(Debug, Clone, PartialEq)]
350pub enum ScheduleType {
351 Linear,
353 Exponential,
355 Logarithmic,
357 Custom(Vec<f64>),
359 Adaptive,
361}
362
363#[derive(Debug, Clone)]
365pub struct PerformanceMetrics {
366 pub final_energy: f64,
368 pub num_evaluations: usize,
370 pub execution_time: Duration,
372 pub success_rate: f64,
374 pub convergence_speed: f64,
376 pub solution_quality: f64,
378}
379
380#[derive(Debug, Clone)]
382pub struct ProblemContext {
383 pub problem_id: String,
385 pub timestamp: Instant,
387 pub hardware_type: String,
389 pub environment: HashMap<String, f64>,
391}
392
393#[derive(Debug, Clone)]
395pub struct TrainingHistory {
396 pub network_losses: Vec<f64>,
398 pub rl_rewards: Vec<f64>,
400 pub validation_scores: Vec<f64>,
402 pub feature_importance: Vec<HashMap<String, f64>>,
404 pub training_times: Vec<Duration>,
406}
407
408#[derive(Debug, Clone)]
410pub struct PerformanceStatistics {
411 pub problems_solved: usize,
413 pub avg_improvement: f64,
415 pub best_improvement: f64,
417 pub adaptation_time: Duration,
419 pub success_rate: f64,
421 pub transfer_effectiveness: f64,
423}
424
425impl NeuralAnnealingScheduler {
426 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, state_space_size: config.network_layers[0] + 4, 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 pub fn generate_schedule(
463 &mut self,
464 problem: &IsingModel,
465 ) -> AdaptiveScheduleResult<AnnealingParams> {
466 let start_time = Instant::now();
467
468 let features = self.extract_problem_features(problem)?;
470
471 if let Some(cached_schedule) = self.check_feature_cache(&features) {
473 return Ok(cached_schedule);
474 }
475
476 let predicted_params = self.predict_schedule_parameters(&features)?;
478
479 let refined_params = self.refine_with_rl(&features, predicted_params)?;
481
482 let schedule = self.convert_to_annealing_params(refined_params)?;
484
485 self.cache_schedule(&features, schedule.clone());
487
488 self.performance_stats.adaptation_time = start_time.elapsed();
490
491 Ok(schedule)
492 }
493
494 fn extract_problem_features(
496 &self,
497 problem: &IsingModel,
498 ) -> AdaptiveScheduleResult<ProblemFeatures> {
499 let size = problem.num_qubits;
500
501 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 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 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 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 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], funnel_structure: 1.0 - connectivity_density, };
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 fn check_feature_cache(&self, features: &ProblemFeatures) -> Option<AnnealingParams> {
590 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 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 fn predict_schedule_parameters(
612 &self,
613 features: &ProblemFeatures,
614 ) -> AdaptiveScheduleResult<ScheduleParameters> {
615 let input = self.features_to_input_vector(features);
617
618 let output = self.network.forward(&input)?;
620
621 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 if initial_temp <= final_temp {
627 final_temp = initial_temp * 0.01; }
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, additional_params: HashMap::new(),
637 };
638
639 Ok(schedule_params)
640 }
641
642 fn features_to_input_vector(&self, features: &ProblemFeatures) -> Vec<f64> {
644 vec![
645 features.size as f64 / 1000.0, 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 fn refine_with_rl(
666 &self,
667 features: &ProblemFeatures,
668 initial_params: ScheduleParameters,
669 ) -> AdaptiveScheduleResult<ScheduleParameters> {
670 let state = self.create_rl_state(features, &initial_params);
672
673 let action = self.rl_agent.select_action(&state)?;
675
676 let refined_params = self.apply_rl_action(initial_params, action)?;
678
679 Ok(refined_params)
680 }
681
682 fn create_rl_state(&self, features: &ProblemFeatures, params: &ScheduleParameters) -> Vec<f64> {
684 let mut state = self.features_to_input_vector(features);
685
686 state.extend(vec![
688 params.initial_temp / 100.0, 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 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, 1 => params.initial_temp *= 0.8, 2 => params.final_temp *= 1.5, 3 => params.final_temp *= 0.7, 4 => params.num_sweeps = (params.num_sweeps as f64 * 1.3) as usize, 5 => params.num_sweeps = (params.num_sweeps as f64 * 0.8) as usize, 6 => params.cooling_rate = (params.cooling_rate * 1.1).min(0.99), 7 => params.cooling_rate = (params.cooling_rate * 0.9).max(0.01), 8 => {
713 params.schedule_type = ScheduleType::Linear;
715 }
716 9 => {
717 params.schedule_type = ScheduleType::Logarithmic;
719 }
720 _ => {} }
722
723 Ok(params)
724 }
725
726 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), _ => TemperatureSchedule::Exponential(1.0),
740 },
741 ..Default::default()
742 })
743 }
744
745 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 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 let network_loss = self.train_network_epoch(training_data)?;
766
767 let rl_reward = self.train_rl_epoch(training_data)?;
769
770 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 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 let prediction = self.network.forward(&input)?;
800
801 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 self.network.backward(&input, &target, &prediction)?;
813 }
814
815 Ok(total_loss / training_data.len() as f64)
816 }
817
818 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 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 let reward = example.performance_improvement;
829
830 let experience = ScheduleExperience {
832 state: state.clone(),
833 action: 0, 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, },
843 };
844
845 self.rl_agent.store_experience(experience);
846 total_reward += reward;
847 }
848
849 self.rl_agent.train()?;
851
852 Ok(total_reward / training_data.len() as f64)
853 }
854
855 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#[derive(Debug, Clone)]
868pub struct TrainingExample {
869 pub features: ProblemFeatures,
871 pub baseline_params: ScheduleParameters,
873 pub optimal_params: ScheduleParameters,
875 pub performance_improvement: f64,
877 pub metadata: HashMap<String, f64>,
879}
880
881impl SchedulePredictionNetwork {
882 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 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 } else {
916 ActivationFunction::ReLU };
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 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 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 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 pub const fn backward(
1010 &mut self,
1011 _input: &[f64],
1012 _target: &[f64],
1013 _prediction: &[f64],
1014 ) -> AdaptiveScheduleResult<()> {
1015 self.training_state.epoch += 1;
1018 Ok(())
1019 }
1020}
1021
1022impl ScheduleRLAgent {
1023 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 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 Ok(rng.gen_range(0..self.config.action_space_size))
1053 } else {
1054 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 pub fn store_experience(&mut self, experience: ScheduleExperience) {
1067 self.experience_buffer.push(experience);
1068
1069 if self.experience_buffer.len() > 1000 {
1071 self.experience_buffer.remove(0);
1072 }
1073 }
1074
1075 pub fn train(&mut self) -> AdaptiveScheduleResult<()> {
1077 if self.experience_buffer.len() < self.config.batch_size {
1078 return Ok(());
1079 }
1080
1081 Ok(())
1085 }
1086}
1087
1088pub fn create_neural_scheduler() -> AdaptiveScheduleResult<NeuralAnnealingScheduler> {
1090 NeuralAnnealingScheduler::new(SchedulerConfig::default())
1091}
1092
1093pub 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 let mut scheduler = create_custom_neural_scheduler(
1178 vec![10, 16, 8, 4], 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}