1use 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#[derive(Error, Debug)]
28pub enum AdaptiveScheduleError {
29 #[error("Ising error: {0}")]
31 IsingError(#[from] IsingError),
32
33 #[error("Neural network error: {0}")]
35 NeuralNetworkError(String),
36
37 #[error("Training error: {0}")]
39 TrainingError(String),
40
41 #[error("Configuration error: {0}")]
43 ConfigurationError(String),
44
45 #[error("Data processing error: {0}")]
47 DataError(String),
48
49 #[error("Optimization error: {0}")]
51 OptimizationError(String),
52}
53
54pub type AdaptiveScheduleResult<T> = Result<T, AdaptiveScheduleError>;
56
57#[derive(Debug, Clone)]
59pub struct NeuralAnnealingScheduler {
60 pub network: SchedulePredictionNetwork,
62 pub rl_agent: ScheduleRLAgent,
64 pub config: SchedulerConfig,
66 pub training_history: TrainingHistory,
68 pub feature_cache: HashMap<String, ProblemFeatures>,
70 pub performance_stats: PerformanceStatistics,
72}
73
74#[derive(Debug, Clone)]
76pub struct SchedulerConfig {
77 pub network_layers: Vec<usize>,
79 pub learning_rate: f64,
81 pub training_epochs: usize,
83 pub buffer_size: usize,
85 pub exploration_rate: f64,
87 pub discount_factor: f64,
89 pub update_frequency: usize,
91 pub use_transfer_learning: bool,
93 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#[derive(Debug, Clone)]
115pub struct SchedulePredictionNetwork {
116 pub layers: Vec<NetworkLayer>,
118 pub input_normalization: NormalizationParams,
120 pub output_scaling: NormalizationParams,
122 pub training_state: NetworkTrainingState,
124}
125
126#[derive(Debug, Clone)]
128pub struct NetworkLayer {
129 pub weights: Vec<Vec<f64>>,
131 pub biases: Vec<f64>,
133 pub activation: ActivationFunction,
135}
136
137#[derive(Debug, Clone, PartialEq)]
139pub enum ActivationFunction {
140 ReLU,
142 Sigmoid,
144 Tanh,
146 Linear,
148 LeakyReLU(f64),
150}
151
152#[derive(Debug, Clone)]
154pub struct NormalizationParams {
155 pub means: Vec<f64>,
157 pub stds: Vec<f64>,
159 pub mins: Vec<f64>,
161 pub maxs: Vec<f64>,
163}
164
165#[derive(Debug, Clone)]
167pub struct NetworkTrainingState {
168 pub epoch: usize,
170 pub training_loss: f64,
172 pub validation_loss: f64,
174 pub learning_rate: f64,
176 pub metrics: HashMap<String, f64>,
178}
179
180#[derive(Debug, Clone)]
182pub struct ScheduleRLAgent {
183 pub q_network: SchedulePredictionNetwork,
185 pub target_network: SchedulePredictionNetwork,
187 pub experience_buffer: Vec<ScheduleExperience>,
189 pub config: RLAgentConfig,
191 pub stats: RLStats,
193}
194
195#[derive(Debug, Clone)]
197pub struct RLAgentConfig {
198 pub action_space_size: usize,
200 pub state_space_size: usize,
202 pub batch_size: usize,
204 pub target_update_frequency: usize,
206 pub epsilon_decay: f64,
208 pub min_epsilon: f64,
210}
211
212#[derive(Debug, Clone)]
214pub struct ScheduleExperience {
215 pub state: Vec<f64>,
217 pub action: usize,
219 pub reward: f64,
221 pub next_state: Vec<f64>,
223 pub done: bool,
225 pub metadata: ExperienceMetadata,
227}
228
229#[derive(Debug, Clone)]
231pub struct ExperienceMetadata {
232 pub problem_type: String,
234 pub problem_size: usize,
236 pub execution_time: Duration,
238 pub final_energy: f64,
240}
241
242#[derive(Debug, Clone)]
244pub struct RLStats {
245 pub episode_rewards: Vec<f64>,
247 pub average_reward: f64,
249 pub exploration_history: Vec<f64>,
251 pub loss_history: Vec<f64>,
253 pub action_frequency: HashMap<usize, usize>,
255}
256
257#[derive(Debug, Clone)]
259pub struct ProblemFeatures {
260 pub size: usize,
262 pub connectivity_density: f64,
264 pub coupling_stats: CouplingStatistics,
266 pub problem_type: ProblemType,
268 pub landscape_features: LandscapeFeatures,
270 pub historical_performance: Vec<PerformancePoint>,
272}
273
274#[derive(Debug, Clone)]
276pub struct CouplingStatistics {
277 pub mean: f64,
279 pub std: f64,
281 pub max_abs: f64,
283 pub range: f64,
285 pub skewness: f64,
287}
288
289#[derive(Debug, Clone, PartialEq, Eq)]
291pub enum ProblemType {
292 Random,
294 Structured,
296 Optimization,
298 MachineLearning,
300 IndustrySpecific(String),
302 Unknown,
304}
305
306#[derive(Debug, Clone)]
308pub struct LandscapeFeatures {
309 pub num_local_minima: usize,
311 pub ruggedness: f64,
313 pub energy_connectivity: f64,
315 pub barrier_heights: Vec<f64>,
317 pub funnel_structure: f64,
319}
320
321#[derive(Debug, Clone)]
323pub struct PerformancePoint {
324 pub schedule_params: ScheduleParameters,
326 pub performance: PerformanceMetrics,
328 pub context: ProblemContext,
330}
331
332#[derive(Debug, Clone)]
334pub struct ScheduleParameters {
335 pub initial_temp: f64,
337 pub final_temp: f64,
339 pub num_sweeps: usize,
341 pub cooling_rate: f64,
343 pub schedule_type: ScheduleType,
345 pub additional_params: HashMap<String, f64>,
347}
348
349#[derive(Debug, Clone, PartialEq)]
351pub enum ScheduleType {
352 Linear,
354 Exponential,
356 Logarithmic,
358 Custom(Vec<f64>),
360 Adaptive,
362}
363
364#[derive(Debug, Clone)]
366pub struct PerformanceMetrics {
367 pub final_energy: f64,
369 pub num_evaluations: usize,
371 pub execution_time: Duration,
373 pub success_rate: f64,
375 pub convergence_speed: f64,
377 pub solution_quality: f64,
379}
380
381#[derive(Debug, Clone)]
383pub struct ProblemContext {
384 pub problem_id: String,
386 pub timestamp: Instant,
388 pub hardware_type: String,
390 pub environment: HashMap<String, f64>,
392}
393
394#[derive(Debug, Clone)]
396pub struct TrainingHistory {
397 pub network_losses: Vec<f64>,
399 pub rl_rewards: Vec<f64>,
401 pub validation_scores: Vec<f64>,
403 pub feature_importance: Vec<HashMap<String, f64>>,
405 pub training_times: Vec<Duration>,
407}
408
409#[derive(Debug, Clone)]
411pub struct PerformanceStatistics {
412 pub problems_solved: usize,
414 pub avg_improvement: f64,
416 pub best_improvement: f64,
418 pub adaptation_time: Duration,
420 pub success_rate: f64,
422 pub transfer_effectiveness: f64,
424}
425
426impl NeuralAnnealingScheduler {
427 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, state_space_size: config.network_layers[0] + 4, 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 pub fn generate_schedule(
464 &mut self,
465 problem: &IsingModel,
466 ) -> AdaptiveScheduleResult<AnnealingParams> {
467 let start_time = Instant::now();
468
469 let features = self.extract_problem_features(problem)?;
471
472 if let Some(cached_schedule) = self.check_feature_cache(&features) {
474 return Ok(cached_schedule);
475 }
476
477 let predicted_params = self.predict_schedule_parameters(&features)?;
479
480 let refined_params = self.refine_with_rl(&features, predicted_params)?;
482
483 let schedule = self.convert_to_annealing_params(refined_params)?;
485
486 self.cache_schedule(&features, schedule.clone());
488
489 self.performance_stats.adaptation_time = start_time.elapsed();
491
492 Ok(schedule)
493 }
494
495 fn extract_problem_features(
497 &self,
498 problem: &IsingModel,
499 ) -> AdaptiveScheduleResult<ProblemFeatures> {
500 let size = problem.num_qubits;
501
502 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 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 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 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 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], funnel_structure: 1.0 - connectivity_density, };
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 fn check_feature_cache(&self, features: &ProblemFeatures) -> Option<AnnealingParams> {
591 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 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 fn predict_schedule_parameters(
613 &self,
614 features: &ProblemFeatures,
615 ) -> AdaptiveScheduleResult<ScheduleParameters> {
616 let input = self.features_to_input_vector(features);
618
619 let output = self.network.forward(&input)?;
621
622 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 if initial_temp <= final_temp {
628 final_temp = initial_temp * 0.01; }
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, additional_params: HashMap::new(),
638 };
639
640 Ok(schedule_params)
641 }
642
643 fn features_to_input_vector(&self, features: &ProblemFeatures) -> Vec<f64> {
645 vec![
646 features.size as f64 / 1000.0, 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 fn refine_with_rl(
667 &self,
668 features: &ProblemFeatures,
669 initial_params: ScheduleParameters,
670 ) -> AdaptiveScheduleResult<ScheduleParameters> {
671 let state = self.create_rl_state(features, &initial_params);
673
674 let action = self.rl_agent.select_action(&state)?;
676
677 let refined_params = self.apply_rl_action(initial_params, action)?;
679
680 Ok(refined_params)
681 }
682
683 fn create_rl_state(&self, features: &ProblemFeatures, params: &ScheduleParameters) -> Vec<f64> {
685 let mut state = self.features_to_input_vector(features);
686
687 state.extend(vec![
689 params.initial_temp / 100.0, 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 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, 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 => {
714 params.schedule_type = ScheduleType::Linear;
716 }
717 9 => {
718 params.schedule_type = ScheduleType::Logarithmic;
720 }
721 _ => {} }
723
724 Ok(params)
725 }
726
727 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), _ => TemperatureSchedule::Exponential(1.0),
741 },
742 ..Default::default()
743 })
744 }
745
746 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 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 let network_loss = self.train_network_epoch(training_data)?;
767
768 let rl_reward = self.train_rl_epoch(training_data)?;
770
771 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 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 let prediction = self.network.forward(&input)?;
801
802 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 self.network.backward(&input, &target, &prediction)?;
814 }
815
816 Ok(total_loss / training_data.len() as f64)
817 }
818
819 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 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 let reward = example.performance_improvement;
830
831 let experience = ScheduleExperience {
833 state: state.clone(),
834 action: 0, 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, },
844 };
845
846 self.rl_agent.store_experience(experience);
847 total_reward += reward;
848 }
849
850 self.rl_agent.train()?;
852
853 Ok(total_reward / training_data.len() as f64)
854 }
855
856 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#[derive(Debug, Clone)]
869pub struct TrainingExample {
870 pub features: ProblemFeatures,
872 pub baseline_params: ScheduleParameters,
874 pub optimal_params: ScheduleParameters,
876 pub performance_improvement: f64,
878 pub metadata: HashMap<String, f64>,
880}
881
882impl SchedulePredictionNetwork {
883 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 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 } else {
917 ActivationFunction::ReLU };
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 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 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 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 pub const fn backward(
1011 &mut self,
1012 _input: &[f64],
1013 _target: &[f64],
1014 _prediction: &[f64],
1015 ) -> AdaptiveScheduleResult<()> {
1016 self.training_state.epoch += 1;
1019 Ok(())
1020 }
1021}
1022
1023impl ScheduleRLAgent {
1024 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 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 Ok(rng.random_range(0..self.config.action_space_size))
1054 } else {
1055 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 pub fn store_experience(&mut self, experience: ScheduleExperience) {
1068 self.experience_buffer.push(experience);
1069
1070 if self.experience_buffer.len() > 1000 {
1072 self.experience_buffer.remove(0);
1073 }
1074 }
1075
1076 pub fn train(&mut self) -> AdaptiveScheduleResult<()> {
1078 if self.experience_buffer.len() < self.config.batch_size {
1079 return Ok(());
1080 }
1081
1082 Ok(())
1086 }
1087}
1088
1089pub fn create_neural_scheduler() -> AdaptiveScheduleResult<NeuralAnnealingScheduler> {
1091 NeuralAnnealingScheduler::new(SchedulerConfig::default())
1092}
1093
1094pub 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 let mut scheduler = create_custom_neural_scheduler(
1179 vec![10, 16, 8, 4], 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}