quantrs2_anneal/rl_embedding_optimizer/
utils.rs1use super::error::{RLEmbeddingError, RLEmbeddingResult};
4use super::types::{
5 ExperienceContext, ExplorationConfig, ObjectiveWeights, RLEmbeddingConfig,
6 TransferLearningConfig,
7};
8
9#[must_use]
11pub fn create_default_config() -> RLEmbeddingConfig {
12 RLEmbeddingConfig::default()
13}
14
15#[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#[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
52pub enum ExplorationStrategy {
54 Conservative,
55 Moderate,
56 Aggressive,
57}
58
59#[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
88pub enum OptimizationGoal {
90 MinimizeChainLength,
91 MaximizeEfficiency,
92 MaximizeUtilization,
93 Balanced,
94}
95
96#[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
124pub enum TransferScenario {
126 SimilarProblems,
127 DifferentProblems,
128 NoTransfer,
129}
130
131pub fn validate_config(config: &RLEmbeddingConfig) -> RLEmbeddingResult<()> {
133 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 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 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 if config.buffer_size == 0 {
156 return Err(RLEmbeddingError::ConfigurationError(
157 "Buffer size must be greater than 0".to_string(),
158 ));
159 }
160
161 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 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 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 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#[must_use]
216pub fn estimate_memory_usage(config: &RLEmbeddingConfig) -> usize {
217 let mut memory = 0;
218
219 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 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>(); }
232
233 let experience_size = 1000; memory += config.buffer_size * experience_size;
236
237 memory
238}
239
240#[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#[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#[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}