Skip to main content

quantrs2_anneal/rl_embedding_optimizer/
utils.rs

1//! Utility functions and helper methods for RL embedding optimization
2
3use super::error::{RLEmbeddingError, RLEmbeddingResult};
4use super::types::{
5    ExperienceContext, ExplorationConfig, ObjectiveWeights, RLEmbeddingConfig,
6    TransferLearningConfig,
7};
8
9/// Create default RL embedding optimizer configuration
10#[must_use]
11pub fn create_default_config() -> RLEmbeddingConfig {
12    RLEmbeddingConfig::default()
13}
14
15/// Create custom RL embedding optimizer configuration
16#[must_use]
17pub fn create_custom_config(
18    dqn_layers: Vec<usize>,
19    policy_layers: Vec<usize>,
20    learning_rate: f64,
21) -> RLEmbeddingConfig {
22    RLEmbeddingConfig {
23        dqn_layers,
24        policy_layers,
25        learning_rate,
26        ..Default::default()
27    }
28}
29
30/// Create exploration configuration for different strategies
31#[must_use]
32pub fn create_exploration_config(strategy: ExplorationStrategy) -> ExplorationConfig {
33    match strategy {
34        ExplorationStrategy::Conservative => ExplorationConfig {
35            initial_epsilon: 0.5,
36            final_epsilon: 0.05,
37            epsilon_decay_steps: 5000,
38            policy_noise: 0.05,
39            curiosity_weight: 0.05,
40        },
41        ExplorationStrategy::Moderate => ExplorationConfig::default(),
42        ExplorationStrategy::Aggressive => ExplorationConfig {
43            initial_epsilon: 1.0,
44            final_epsilon: 0.01,
45            epsilon_decay_steps: 20_000,
46            policy_noise: 0.2,
47            curiosity_weight: 0.2,
48        },
49    }
50}
51
52/// Exploration strategies
53pub enum ExplorationStrategy {
54    Conservative,
55    Moderate,
56    Aggressive,
57}
58
59/// Create objective weights for different optimization goals
60#[must_use]
61pub fn create_objective_weights(goal: OptimizationGoal) -> ObjectiveWeights {
62    match goal {
63        OptimizationGoal::MinimizeChainLength => ObjectiveWeights {
64            chain_length_weight: 0.6,
65            efficiency_weight: 0.2,
66            utilization_weight: 0.1,
67            connectivity_weight: 0.05,
68            performance_weight: 0.05,
69        },
70        OptimizationGoal::MaximizeEfficiency => ObjectiveWeights {
71            chain_length_weight: 0.1,
72            efficiency_weight: 0.5,
73            utilization_weight: 0.2,
74            connectivity_weight: 0.1,
75            performance_weight: 0.1,
76        },
77        OptimizationGoal::MaximizeUtilization => ObjectiveWeights {
78            chain_length_weight: 0.1,
79            efficiency_weight: 0.2,
80            utilization_weight: 0.5,
81            connectivity_weight: 0.1,
82            performance_weight: 0.1,
83        },
84        OptimizationGoal::Balanced => ObjectiveWeights::default(),
85    }
86}
87
88/// Optimization goals
89pub enum OptimizationGoal {
90    MinimizeChainLength,
91    MaximizeEfficiency,
92    MaximizeUtilization,
93    Balanced,
94}
95
96/// Create transfer learning configuration for different scenarios
97#[must_use]
98pub const fn create_transfer_learning_config(scenario: TransferScenario) -> TransferLearningConfig {
99    match scenario {
100        TransferScenario::SimilarProblems => TransferLearningConfig {
101            enabled: true,
102            source_weight_decay: 0.95,
103            adaptation_lr: 0.00_005,
104            fine_tuning_epochs: 50,
105            similarity_threshold: 0.8,
106        },
107        TransferScenario::DifferentProblems => TransferLearningConfig {
108            enabled: true,
109            source_weight_decay: 0.8,
110            adaptation_lr: 0.0001,
111            fine_tuning_epochs: 200,
112            similarity_threshold: 0.5,
113        },
114        TransferScenario::NoTransfer => TransferLearningConfig {
115            enabled: false,
116            source_weight_decay: 0.0,
117            adaptation_lr: 0.0,
118            fine_tuning_epochs: 0,
119            similarity_threshold: 0.0,
120        },
121    }
122}
123
124/// Transfer learning scenarios
125pub enum TransferScenario {
126    SimilarProblems,
127    DifferentProblems,
128    NoTransfer,
129}
130
131/// Validate configuration parameters
132pub fn validate_config(config: &RLEmbeddingConfig) -> RLEmbeddingResult<()> {
133    // Validate DQN layers
134    if config.dqn_layers.len() < 2 {
135        return Err(RLEmbeddingError::ConfigurationError(
136            "DQN must have at least input and output layers".to_string(),
137        ));
138    }
139
140    // Validate policy layers
141    if config.policy_layers.len() < 2 {
142        return Err(RLEmbeddingError::ConfigurationError(
143            "Policy network must have at least input and output layers".to_string(),
144        ));
145    }
146
147    // Validate learning rate
148    if config.learning_rate <= 0.0 || config.learning_rate > 1.0 {
149        return Err(RLEmbeddingError::ConfigurationError(
150            "Learning rate must be between 0 and 1".to_string(),
151        ));
152    }
153
154    // Validate buffer size
155    if config.buffer_size == 0 {
156        return Err(RLEmbeddingError::ConfigurationError(
157            "Buffer size must be greater than 0".to_string(),
158        ));
159    }
160
161    // Validate batch size
162    if config.batch_size == 0 || config.batch_size > config.buffer_size {
163        return Err(RLEmbeddingError::ConfigurationError(
164            "Batch size must be between 1 and buffer size".to_string(),
165        ));
166    }
167
168    // Validate discount factor
169    if config.discount_factor < 0.0 || config.discount_factor > 1.0 {
170        return Err(RLEmbeddingError::ConfigurationError(
171            "Discount factor must be between 0 and 1".to_string(),
172        ));
173    }
174
175    // Validate exploration config
176    if config.exploration_config.initial_epsilon < 0.0
177        || config.exploration_config.initial_epsilon > 1.0
178    {
179        return Err(RLEmbeddingError::ConfigurationError(
180            "Initial epsilon must be between 0 and 1".to_string(),
181        ));
182    }
183
184    if config.exploration_config.final_epsilon < 0.0
185        || config.exploration_config.final_epsilon > 1.0
186    {
187        return Err(RLEmbeddingError::ConfigurationError(
188            "Final epsilon must be between 0 and 1".to_string(),
189        ));
190    }
191
192    if config.exploration_config.epsilon_decay_steps == 0 {
193        return Err(RLEmbeddingError::ConfigurationError(
194            "Epsilon decay steps must be greater than 0".to_string(),
195        ));
196    }
197
198    // Validate objective weights (should sum to approximately 1.0)
199    let weight_sum = config.objective_weights.chain_length_weight
200        + config.objective_weights.efficiency_weight
201        + config.objective_weights.utilization_weight
202        + config.objective_weights.connectivity_weight
203        + config.objective_weights.performance_weight;
204
205    if (weight_sum - 1.0).abs() > 0.1 {
206        return Err(RLEmbeddingError::ConfigurationError(
207            "Objective weights should sum to approximately 1.0".to_string(),
208        ));
209    }
210
211    Ok(())
212}
213
214/// Calculate memory usage estimate for configuration
215#[must_use]
216pub fn estimate_memory_usage(config: &RLEmbeddingConfig) -> usize {
217    let mut memory = 0;
218
219    // DQN memory
220    for i in 0..config.dqn_layers.len() - 1 {
221        let weights = config.dqn_layers[i] * config.dqn_layers[i + 1];
222        let biases = config.dqn_layers[i + 1];
223        memory += (weights + biases) * std::mem::size_of::<f64>();
224    }
225
226    // Policy network memory (actor + critic)
227    for i in 0..config.policy_layers.len() - 1 {
228        let weights = config.policy_layers[i] * config.policy_layers[i + 1];
229        let biases = config.policy_layers[i + 1];
230        memory += 2 * (weights + biases) * std::mem::size_of::<f64>(); // actor + critic
231    }
232
233    // Experience buffer memory (estimate)
234    let experience_size = 1000; // Rough estimate per experience
235    memory += config.buffer_size * experience_size;
236
237    memory
238}
239
240/// Create configuration summary string
241#[must_use]
242pub fn config_summary(config: &RLEmbeddingConfig) -> String {
243    format!(
244        "RL Embedding Optimizer Configuration:\n\
245         DQN Architecture: {:?}\n\
246         Policy Architecture: {:?}\n\
247         Learning Rate: {:.6}\n\
248         Buffer Size: {}\n\
249         Batch Size: {}\n\
250         Discount Factor: {:.3}\n\
251         Exploration: ε={:.3} → {:.3} over {} steps\n\
252         Transfer Learning: {}\n\
253         Estimated Memory: {:.1} MB",
254        config.dqn_layers,
255        config.policy_layers,
256        config.learning_rate,
257        config.buffer_size,
258        config.batch_size,
259        config.discount_factor,
260        config.exploration_config.initial_epsilon,
261        config.exploration_config.final_epsilon,
262        config.exploration_config.epsilon_decay_steps,
263        if config.transfer_learning.enabled {
264            "Enabled"
265        } else {
266            "Disabled"
267        },
268        estimate_memory_usage(config) as f64 / (1024.0 * 1024.0)
269    )
270}
271
272/// Helper function to create experience context
273#[must_use]
274pub fn create_experience_context(
275    problem_type: String,
276    hardware_id: String,
277    episode_id: usize,
278    step: usize,
279) -> ExperienceContext {
280    ExperienceContext {
281        problem_type,
282        hardware_id,
283        timestamp: std::time::Instant::now(),
284        episode_id,
285        step,
286        metadata: std::collections::HashMap::new(),
287    }
288}
289
290/// Helper function to format hardware topology as string
291#[must_use]
292pub fn hardware_topology_to_string(hardware: &crate::embedding::HardwareTopology) -> String {
293    match hardware {
294        crate::embedding::HardwareTopology::Chimera(m, n, t) => {
295            format!("Chimera_{m}x{n}x{t}")
296        }
297        crate::embedding::HardwareTopology::Pegasus(n) => {
298            format!("Pegasus_{n}")
299        }
300        crate::embedding::HardwareTopology::Zephyr(n) => {
301            format!("Zephyr_{n}")
302        }
303        crate::embedding::HardwareTopology::Custom => "Custom".to_string(),
304    }
305}