1use crate::error::{QuantRS2Error, QuantRS2Result};
9use crate::gate::GateOp;
10use crate::qubit::QubitId;
11use scirs2_core::ndarray::{Array1, Array2};
12use scirs2_core::random::{thread_rng, Rng};
13use std::collections::HashMap;
14use std::sync::{Arc, RwLock};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
18pub enum OptimizationAction {
19 MergeSingleQubitGates { gate_index: usize },
21 CancelInversePairs { gate_index: usize },
23 CommuteGates {
25 gate1_index: usize,
26 gate2_index: usize,
27 },
28 ReplaceSequence {
30 start_index: usize,
31 end_index: usize,
32 },
33 OptimizeTwoQubitGate { gate_index: usize },
35 NoOp,
37}
38
39#[derive(Debug, Clone)]
41pub struct CircuitState {
42 pub depth: usize,
44 pub gate_count: usize,
46 pub two_qubit_count: usize,
48 pub fidelity: f64,
50 pub qubit_count: usize,
52 pub connectivity_density: f64,
54 pub entanglement_measure: f64,
56}
57
58impl CircuitState {
59 pub fn to_features(&self) -> Vec<f64> {
61 vec![
62 self.depth as f64 / 100.0, self.gate_count as f64 / 1000.0,
64 self.two_qubit_count as f64 / 500.0,
65 self.fidelity,
66 self.qubit_count as f64 / 50.0,
67 self.connectivity_density,
68 self.entanglement_measure,
69 ]
70 }
71
72 pub fn from_circuit(gates: &[Box<dyn GateOp>], num_qubits: usize) -> Self {
74 let mut depth_map: HashMap<QubitId, usize> = HashMap::new();
75 let mut two_qubit_count = 0;
76 let mut connectivity_edges = 0;
77
78 for gate in gates {
79 let qubits = gate.qubits();
80
81 if qubits.len() == 2 {
82 two_qubit_count += 1;
83 connectivity_edges += 1;
84 }
85
86 let max_depth = qubits
88 .iter()
89 .map(|q| *depth_map.get(q).unwrap_or(&0))
90 .max()
91 .unwrap_or(0);
92
93 for qubit in qubits {
94 depth_map.insert(qubit, max_depth + 1);
95 }
96 }
97
98 let depth = depth_map.values().max().copied().unwrap_or(0);
99 let gate_count = gates.len();
100
101 let fidelity = 0.9999_f64.powi(gate_count as i32 - two_qubit_count as i32)
103 * 0.99_f64.powi(two_qubit_count as i32);
104
105 let max_edges = num_qubits * (num_qubits - 1) / 2;
107 let connectivity_density = if max_edges > 0 {
108 connectivity_edges as f64 / max_edges as f64
109 } else {
110 0.0
111 };
112
113 let entanglement_measure = (two_qubit_count as f64 / num_qubits as f64).min(1.0);
115
116 Self {
117 depth,
118 gate_count,
119 two_qubit_count,
120 fidelity,
121 qubit_count: num_qubits,
122 connectivity_density,
123 entanglement_measure,
124 }
125 }
126}
127
128pub struct QLearningOptimizer {
130 q_table: Arc<RwLock<HashMap<(Vec<u8>, OptimizationAction), f64>>>,
132 learning_rate: f64,
134 discount_factor: f64,
136 epsilon: f64,
138 epsilon_decay: f64,
140 min_epsilon: f64,
142 episodes: Arc<RwLock<usize>>,
144 performance_history: Arc<RwLock<Vec<OptimizationEpisode>>>,
146}
147
148#[derive(Debug, Clone)]
150pub struct OptimizationEpisode {
151 pub initial_depth: usize,
152 pub final_depth: usize,
153 pub initial_gate_count: usize,
154 pub final_gate_count: usize,
155 pub reward: f64,
156 pub steps_taken: usize,
157}
158
159impl QLearningOptimizer {
160 pub fn new(learning_rate: f64, discount_factor: f64, initial_epsilon: f64) -> Self {
167 Self {
168 q_table: Arc::new(RwLock::new(HashMap::new())),
169 learning_rate,
170 discount_factor,
171 epsilon: initial_epsilon,
172 epsilon_decay: 0.995,
173 min_epsilon: 0.01,
174 episodes: Arc::new(RwLock::new(0)),
175 performance_history: Arc::new(RwLock::new(Vec::new())),
176 }
177 }
178
179 pub fn choose_action(
185 &self,
186 state: &CircuitState,
187 available_actions: &[OptimizationAction],
188 ) -> OptimizationAction {
189 if available_actions.is_empty() {
190 return OptimizationAction::NoOp;
191 }
192
193 let mut rng = thread_rng();
194
195 if rng.random::<f64>() < self.epsilon {
197 available_actions[rng.random_range(0..available_actions.len())]
199 } else {
200 self.get_best_action(state, available_actions)
202 }
203 }
204
205 fn get_best_action(
207 &self,
208 state: &CircuitState,
209 available_actions: &[OptimizationAction],
210 ) -> OptimizationAction {
211 let state_key = self.state_to_key(state);
212 let q_table = self.q_table.read().unwrap_or_else(|e| e.into_inner());
213
214 let mut best_action = available_actions[0];
215 let mut best_q_value = f64::NEG_INFINITY;
216
217 for &action in available_actions {
218 let q_value = *q_table.get(&(state_key.clone(), action)).unwrap_or(&0.0);
219 if q_value > best_q_value {
220 best_q_value = q_value;
221 best_action = action;
222 }
223 }
224
225 best_action
226 }
227
228 pub fn update_q_value(
237 &mut self,
238 state: &CircuitState,
239 action: OptimizationAction,
240 reward: f64,
241 next_state: &CircuitState,
242 next_actions: &[OptimizationAction],
243 ) {
244 let state_key = self.state_to_key(state);
245 let next_state_key = self.state_to_key(next_state);
246
247 let q_table = self.q_table.read().unwrap_or_else(|e| e.into_inner());
249 let max_next_q = if next_actions.is_empty() {
250 0.0
251 } else {
252 next_actions
253 .iter()
254 .map(|&a| *q_table.get(&(next_state_key.clone(), a)).unwrap_or(&0.0))
255 .fold(f64::NEG_INFINITY, f64::max)
256 };
257 drop(q_table);
258
259 let mut q_table = self.q_table.write().unwrap_or_else(|e| e.into_inner());
262 let current_q = *q_table.get(&(state_key.clone(), action)).unwrap_or(&0.0);
263 let new_q = self.learning_rate.mul_add(
264 self.discount_factor.mul_add(max_next_q, reward) - current_q,
265 current_q,
266 );
267 q_table.insert((state_key, action), new_q);
268 }
269
270 pub fn calculate_reward(&self, old_state: &CircuitState, new_state: &CircuitState) -> f64 {
277 let mut reward = 0.0;
278
279 let depth_improvement = old_state.depth as f64 - new_state.depth as f64;
281 reward += depth_improvement * 2.0;
282
283 let gate_improvement = old_state.gate_count as f64 - new_state.gate_count as f64;
285 reward += gate_improvement * 1.0;
286
287 let two_qubit_improvement =
289 old_state.two_qubit_count as f64 - new_state.two_qubit_count as f64;
290 reward += two_qubit_improvement * 3.0;
291
292 let fidelity_change = new_state.fidelity - old_state.fidelity;
294 reward += fidelity_change * 100.0; if reward == 0.0 {
298 reward = -0.1;
299 }
300
301 reward
302 }
303
304 pub fn finish_episode(&mut self, episode: OptimizationEpisode) {
306 self.epsilon = (self.epsilon * self.epsilon_decay).max(self.min_epsilon);
308
309 {
311 let mut episodes = self.episodes.write().unwrap_or_else(|e| e.into_inner());
312 *episodes += 1;
313
314 let mut history = self
315 .performance_history
316 .write()
317 .unwrap_or_else(|e| e.into_inner());
318 history.push(episode);
319
320 if history.len() > 1000 {
322 let len = history.len();
323 history.drain(0..len - 1000);
324 }
325 }
326 }
327
328 pub fn get_statistics(&self) -> OptimizationStatistics {
330 let history = self
331 .performance_history
332 .read()
333 .unwrap_or_else(|e| e.into_inner());
334
335 if history.is_empty() {
336 return OptimizationStatistics {
337 total_episodes: 0,
338 average_depth_improvement: 0.0,
339 average_gate_reduction: 0.0,
340 average_reward: 0.0,
341 current_epsilon: self.epsilon,
342 q_table_size: self.q_table.read().unwrap_or_else(|e| e.into_inner()).len(),
343 };
344 }
345
346 let total_episodes = history.len();
347 let avg_depth_improvement: f64 = history
348 .iter()
349 .map(|e| (e.initial_depth - e.final_depth) as f64)
350 .sum::<f64>()
351 / total_episodes as f64;
352
353 let avg_gate_reduction: f64 = history
354 .iter()
355 .map(|e| (e.initial_gate_count - e.final_gate_count) as f64)
356 .sum::<f64>()
357 / total_episodes as f64;
358
359 let avg_reward: f64 = history.iter().map(|e| e.reward).sum::<f64>() / total_episodes as f64;
360
361 OptimizationStatistics {
362 total_episodes,
363 average_depth_improvement: avg_depth_improvement,
364 average_gate_reduction: avg_gate_reduction,
365 average_reward: avg_reward,
366 current_epsilon: self.epsilon,
367 q_table_size: self.q_table.read().unwrap_or_else(|e| e.into_inner()).len(),
368 }
369 }
370
371 fn state_to_key(&self, state: &CircuitState) -> Vec<u8> {
373 let features = state.to_features();
375 features
376 .iter()
377 .map(|&f| ((f * 10.0).round() as i32).clamp(0, 255) as u8)
378 .collect()
379 }
380
381 pub fn save_q_table(&self, path: &str) -> QuantRS2Result<()> {
388 let q_table = self.q_table.read().unwrap_or_else(|e| e.into_inner());
389 let entries: Vec<((Vec<u8>, OptimizationAction), f64)> = q_table
390 .iter()
391 .map(|((state, action), value)| ((state.clone(), *action), *value))
392 .collect();
393 drop(q_table);
394
395 let json = serde_json::to_string(&entries).map_err(|e| {
396 QuantRS2Error::RuntimeError(format!("failed to serialize Q-table: {e}"))
397 })?;
398 std::fs::write(path, json).map_err(|e| {
399 QuantRS2Error::RuntimeError(format!("failed to write Q-table to '{path}': {e}"))
400 })?;
401 Ok(())
402 }
403
404 pub fn load_q_table(&mut self, path: &str) -> QuantRS2Result<()> {
409 let json = std::fs::read_to_string(path).map_err(|e| {
410 QuantRS2Error::RuntimeError(format!("failed to read Q-table from '{path}': {e}"))
411 })?;
412 let entries: Vec<((Vec<u8>, OptimizationAction), f64)> = serde_json::from_str(&json)
413 .map_err(|e| {
414 QuantRS2Error::RuntimeError(format!("failed to deserialize Q-table: {e}"))
415 })?;
416
417 let mut q_table = self.q_table.write().unwrap_or_else(|e| e.into_inner());
418 q_table.clear();
419 for ((state, action), value) in entries {
420 q_table.insert((state, action), value);
421 }
422 Ok(())
423 }
424}
425
426#[derive(Debug, Clone)]
428pub struct OptimizationStatistics {
429 pub total_episodes: usize,
430 pub average_depth_improvement: f64,
431 pub average_gate_reduction: f64,
432 pub average_reward: f64,
433 pub current_epsilon: f64,
434 pub q_table_size: usize,
435}
436
437impl Default for QLearningOptimizer {
438 fn default() -> Self {
439 Self::new(0.1, 0.95, 0.3)
440 }
441}
442
443#[cfg(test)]
444mod tests {
445 use super::*;
446
447 #[test]
448 fn test_circuit_state_creation() {
449 let state = CircuitState {
450 depth: 10,
451 gate_count: 50,
452 two_qubit_count: 15,
453 fidelity: 0.95,
454 qubit_count: 5,
455 connectivity_density: 0.6,
456 entanglement_measure: 0.8,
457 };
458
459 let features = state.to_features();
460 assert_eq!(features.len(), 7);
461 assert!(features.iter().all(|&f| f >= 0.0 && f <= 1.1)); }
463
464 #[test]
465 fn test_q_learning_optimizer_creation() {
466 let optimizer = QLearningOptimizer::new(0.1, 0.95, 0.3);
467 assert_eq!(optimizer.learning_rate, 0.1);
468 assert_eq!(optimizer.discount_factor, 0.95);
469 assert_eq!(optimizer.epsilon, 0.3);
470 }
471
472 #[test]
473 fn test_action_selection() {
474 let optimizer = QLearningOptimizer::new(0.1, 0.95, 0.0); let state = CircuitState {
477 depth: 10,
478 gate_count: 50,
479 two_qubit_count: 15,
480 fidelity: 0.95,
481 qubit_count: 5,
482 connectivity_density: 0.6,
483 entanglement_measure: 0.8,
484 };
485
486 let actions = vec![
487 OptimizationAction::MergeSingleQubitGates { gate_index: 0 },
488 OptimizationAction::CancelInversePairs { gate_index: 1 },
489 ];
490
491 let action = optimizer.choose_action(&state, &actions);
492 assert!(actions.contains(&action));
493 }
494
495 #[test]
496 fn test_reward_calculation() {
497 let optimizer = QLearningOptimizer::new(0.1, 0.95, 0.3);
498
499 let old_state = CircuitState {
500 depth: 10,
501 gate_count: 50,
502 two_qubit_count: 15,
503 fidelity: 0.95,
504 qubit_count: 5,
505 connectivity_density: 0.6,
506 entanglement_measure: 0.8,
507 };
508
509 let new_state = CircuitState {
510 depth: 8,
511 gate_count: 45,
512 two_qubit_count: 12,
513 fidelity: 0.96,
514 qubit_count: 5,
515 connectivity_density: 0.6,
516 entanglement_measure: 0.8,
517 };
518
519 let reward = optimizer.calculate_reward(&old_state, &new_state);
520 assert!(reward > 0.0); }
522
523 #[test]
524 fn test_q_value_update() {
525 let mut optimizer = QLearningOptimizer::new(0.1, 0.95, 0.3);
526
527 let state = CircuitState {
528 depth: 10,
529 gate_count: 50,
530 two_qubit_count: 15,
531 fidelity: 0.95,
532 qubit_count: 5,
533 connectivity_density: 0.6,
534 entanglement_measure: 0.8,
535 };
536
537 let action = OptimizationAction::MergeSingleQubitGates { gate_index: 0 };
538
539 let next_state = CircuitState {
540 depth: 9,
541 gate_count: 48,
542 two_qubit_count: 15,
543 fidelity: 0.95,
544 qubit_count: 5,
545 connectivity_density: 0.6,
546 entanglement_measure: 0.8,
547 };
548
549 optimizer.update_q_value(&state, action, 5.0, &next_state, &[]);
550
551 let q_table = optimizer
553 .q_table
554 .read()
555 .expect("Failed to acquire Q-table read lock");
556 assert!(!q_table.is_empty());
557 }
558
559 #[test]
560 fn test_epsilon_decay() {
561 let mut optimizer = QLearningOptimizer::new(0.1, 0.95, 0.5);
562 let initial_epsilon = optimizer.epsilon;
563
564 let episode = OptimizationEpisode {
565 initial_depth: 10,
566 final_depth: 8,
567 initial_gate_count: 50,
568 final_gate_count: 45,
569 reward: 10.0,
570 steps_taken: 5,
571 };
572
573 optimizer.finish_episode(episode);
574
575 assert!(optimizer.epsilon < initial_epsilon);
576 assert!(optimizer.epsilon >= optimizer.min_epsilon);
577 }
578
579 #[test]
580 fn test_statistics() {
581 let mut optimizer = QLearningOptimizer::new(0.1, 0.95, 0.3);
582
583 let episode1 = OptimizationEpisode {
584 initial_depth: 10,
585 final_depth: 8,
586 initial_gate_count: 50,
587 final_gate_count: 45,
588 reward: 10.0,
589 steps_taken: 5,
590 };
591
592 let episode2 = OptimizationEpisode {
593 initial_depth: 12,
594 final_depth: 9,
595 initial_gate_count: 60,
596 final_gate_count: 52,
597 reward: 15.0,
598 steps_taken: 7,
599 };
600
601 optimizer.finish_episode(episode1);
602 optimizer.finish_episode(episode2);
603
604 let stats = optimizer.get_statistics();
605 assert_eq!(stats.total_episodes, 2);
606 assert!(stats.average_depth_improvement > 0.0);
607 assert!(stats.average_gate_reduction > 0.0);
608 }
609
610 #[test]
611 fn test_q_table_save_load_roundtrip() {
612 let mut optimizer = QLearningOptimizer::new(0.1, 0.95, 0.3);
613
614 let state = CircuitState {
616 depth: 10,
617 gate_count: 50,
618 two_qubit_count: 15,
619 fidelity: 0.95,
620 qubit_count: 5,
621 connectivity_density: 0.6,
622 entanglement_measure: 0.8,
623 };
624 let next_state = CircuitState {
625 depth: 9,
626 gate_count: 48,
627 two_qubit_count: 15,
628 fidelity: 0.95,
629 qubit_count: 5,
630 connectivity_density: 0.6,
631 entanglement_measure: 0.8,
632 };
633 optimizer.update_q_value(
634 &state,
635 OptimizationAction::MergeSingleQubitGates { gate_index: 0 },
636 5.0,
637 &next_state,
638 &[],
639 );
640 optimizer.update_q_value(
641 &state,
642 OptimizationAction::CancelInversePairs { gate_index: 2 },
643 -1.5,
644 &next_state,
645 &[],
646 );
647
648 let original: HashMap<(Vec<u8>, OptimizationAction), f64> = optimizer
649 .q_table
650 .read()
651 .unwrap_or_else(|e| e.into_inner())
652 .clone();
653 assert!(!original.is_empty());
654
655 let mut path = std::env::temp_dir();
657 path.push(format!("quantrs_qtable_test_{}.json", std::process::id()));
658 let path_str = path.to_string_lossy().to_string();
659 optimizer
660 .save_q_table(&path_str)
661 .expect("saving Q-table should succeed");
662
663 let mut loaded_optimizer = QLearningOptimizer::new(0.1, 0.95, 0.3);
665 loaded_optimizer
666 .load_q_table(&path_str)
667 .expect("loading Q-table should succeed");
668
669 let loaded: HashMap<(Vec<u8>, OptimizationAction), f64> = loaded_optimizer
670 .q_table
671 .read()
672 .unwrap_or_else(|e| e.into_inner())
673 .clone();
674
675 assert_eq!(loaded.len(), original.len());
676 for (key, value) in &original {
677 let loaded_value = loaded
678 .get(key)
679 .expect("loaded Q-table must contain every original key");
680 assert!((loaded_value - value).abs() < 1e-12);
681 }
682
683 let _ = std::fs::remove_file(&path);
685 }
686
687 #[test]
688 fn test_load_q_table_missing_file_errors() {
689 let mut optimizer = QLearningOptimizer::new(0.1, 0.95, 0.3);
690 let mut path = std::env::temp_dir();
691 path.push("quantrs_qtable_definitely_missing_xyz.json");
692 let _ = std::fs::remove_file(&path);
693 let result = optimizer.load_q_table(&path.to_string_lossy());
694 assert!(result.is_err(), "loading a missing file must be an error");
695 }
696}