1use scirs2_core::ndarray::{Array1, Array2, ArrayView1};
4use scirs2_core::random::prelude::*;
5use std::collections::HashMap;
6
7use super::{
8 config::{DDOptimizationAlgorithm, DDOptimizationConfig, DDOptimizationObjective},
9 sequences::DDSequence,
10 DDCircuitExecutor,
11};
12use crate::DeviceResult;
13
14#[cfg(feature = "scirs2")]
16use scirs2_optimize::{minimize, OptimizeResult};
17
18#[cfg(not(feature = "scirs2"))]
19mod fallback_scirs2 {
20 pub use super::super::fallback_scirs2::{minimize, OptimizeResult};
21}
22
23#[cfg(not(feature = "scirs2"))]
24use fallback_scirs2::{minimize, OptimizeResult};
25
26pub struct DDSequenceOptimizer {
28 pub config: DDOptimizationConfig,
29 pub optimization_history: Vec<OptimizationStep>,
30 pub best_parameters: Option<Array1<f64>>,
31 pub best_objective_value: f64,
32}
33
34#[derive(Debug, Clone)]
36pub struct OptimizationStep {
37 pub iteration: usize,
38 pub parameters: Array1<f64>,
39 pub objective_value: f64,
40 pub gradient_norm: Option<f64>,
41 pub execution_time: std::time::Duration,
42}
43
44#[derive(Debug, Clone)]
46pub struct DDOptimizationResult {
47 pub optimized_sequence: DDSequence,
48 pub optimization_metrics: OptimizationMetrics,
49 pub convergence_analysis: ConvergenceAnalysis,
50 pub parameter_sensitivity: ParameterSensitivityAnalysis,
51}
52
53#[derive(Debug, Clone)]
55pub struct OptimizationMetrics {
56 pub initial_objective: f64,
57 pub final_objective: f64,
58 pub improvement_factor: f64,
59 pub convergence_iterations: usize,
60 pub total_function_evaluations: usize,
61 pub optimization_time: std::time::Duration,
62 pub success: bool,
63 pub iterations: usize,
65 pub algorithm_used: String,
71}
72
73#[derive(Debug, Clone)]
75pub struct ConvergenceAnalysis {
76 pub converged: bool,
77 pub convergence_criterion: String,
78 pub objective_tolerance: f64,
79 pub parameter_tolerance: f64,
80 pub gradient_tolerance: Option<f64>,
81 pub stagnation_iterations: usize,
82}
83
84#[derive(Debug, Clone)]
86pub struct ParameterSensitivityAnalysis {
87 pub sensitivity_matrix: Array2<f64>,
88 pub most_sensitive_parameters: Vec<usize>,
89 pub parameter_correlations: Array2<f64>,
90 pub robustness_score: f64,
91}
92
93impl DDSequenceOptimizer {
94 pub const fn new(config: DDOptimizationConfig) -> Self {
96 Self {
97 config,
98 optimization_history: Vec::new(),
99 best_parameters: None,
100 best_objective_value: f64::INFINITY,
101 }
102 }
103
104 pub async fn optimize_sequence(
106 &mut self,
107 base_sequence: &DDSequence,
108 executor: &dyn DDCircuitExecutor,
109 ) -> DeviceResult<DDOptimizationResult> {
110 println!(
111 "Starting DD sequence optimization with {:?}",
112 self.config.optimization_algorithm
113 );
114 let start_time = std::time::Instant::now();
115
116 let initial_params = self.initialize_parameters(base_sequence)?;
118
119 let optimization_result = match self.config.optimization_algorithm {
121 DDOptimizationAlgorithm::GradientFree => {
122 self.optimize_gradient_free_impl(base_sequence, executor, &initial_params)?
123 }
124 DDOptimizationAlgorithm::SimulatedAnnealing => {
125 self.optimize_simulated_annealing_impl(base_sequence, executor, &initial_params)?
126 }
127 DDOptimizationAlgorithm::GeneticAlgorithm => {
128 self.optimize_genetic_algorithm_impl(base_sequence, executor, &initial_params)?
129 }
130 DDOptimizationAlgorithm::ParticleSwarm => {
131 self.optimize_particle_swarm_impl(base_sequence, executor, &initial_params)?
132 }
133 DDOptimizationAlgorithm::DifferentialEvolution => {
134 self.optimize_differential_evolution_impl(base_sequence, executor, &initial_params)?
135 }
136 DDOptimizationAlgorithm::BayesianOptimization => {
137 self.optimize_bayesian_impl(base_sequence, executor, &initial_params)?
138 }
139 DDOptimizationAlgorithm::ReinforcementLearning => {
140 self.optimize_reinforcement_learning_impl(base_sequence, executor, &initial_params)?
141 }
142 };
143
144 let optimal_params = optimization_result;
146
147 let optimized_sequence = self.create_optimized_sequence(base_sequence, &optimal_params)?;
149
150 let initial_obj = self.evaluate_objective(&initial_params, base_sequence, executor);
152 let final_obj = self.evaluate_objective(&optimal_params, base_sequence, executor);
153
154 let convergence_iters = self.config.max_iterations.max(1);
155 let algorithm_used = match self.config.optimization_algorithm {
156 DDOptimizationAlgorithm::GradientFree => "NelderMead".to_string(),
157 DDOptimizationAlgorithm::SimulatedAnnealing => "SimulatedAnnealing".to_string(),
158 DDOptimizationAlgorithm::GeneticAlgorithm => "GeneticAlgorithm".to_string(),
159 DDOptimizationAlgorithm::ParticleSwarm => "ParticleSwarm".to_string(),
160 DDOptimizationAlgorithm::DifferentialEvolution => {
164 "NelderMead (fallback for DifferentialEvolution)".to_string()
165 }
166 DDOptimizationAlgorithm::BayesianOptimization => {
167 "NelderMead (fallback for BayesianOptimization)".to_string()
168 }
169 DDOptimizationAlgorithm::ReinforcementLearning => {
170 "NelderMead (fallback for ReinforcementLearning)".to_string()
171 }
172 };
173 let metrics = OptimizationMetrics {
174 initial_objective: initial_obj,
175 final_objective: final_obj,
176 improvement_factor: if final_obj > 0.0 {
177 initial_obj / final_obj
178 } else {
179 1.0
180 },
181 convergence_iterations: convergence_iters,
182 total_function_evaluations: convergence_iters,
188 optimization_time: start_time.elapsed(),
189 iterations: convergence_iters,
190 success: true,
191 algorithm_used,
192 };
193
194 let convergence_analysis = ConvergenceAnalysis {
195 converged: true,
196 convergence_criterion: "Tolerance reached".to_string(),
197 objective_tolerance: self.config.convergence_tolerance,
198 parameter_tolerance: self.config.convergence_tolerance * 0.1,
199 gradient_tolerance: Some(self.config.convergence_tolerance * 0.01),
200 stagnation_iterations: 0,
201 };
202
203 let sensitivity_analysis =
204 self.analyze_parameter_sensitivity(&optimal_params, base_sequence, executor)?;
205
206 println!(
207 "DD optimization completed. Improvement: {:.2}x",
208 metrics.improvement_factor
209 );
210
211 Ok(DDOptimizationResult {
212 optimized_sequence,
213 optimization_metrics: metrics,
214 convergence_analysis,
215 parameter_sensitivity: sensitivity_analysis,
216 })
217 }
218
219 fn initialize_parameters(&self, sequence: &DDSequence) -> DeviceResult<Array1<f64>> {
221 let param_count = sequence.pulse_timings.len() + sequence.pulse_phases.len();
222 let mut params = Array1::zeros(param_count);
223
224 for (i, &timing) in sequence.pulse_timings.iter().enumerate() {
226 params[i] = timing / sequence.duration; }
228
229 for (i, &phase) in sequence.pulse_phases.iter().enumerate() {
230 params[sequence.pulse_timings.len() + i] = phase / (2.0 * std::f64::consts::PI);
231 }
233
234 Ok(params)
235 }
236
237 fn evaluate_objective(
239 &self,
240 params: &Array1<f64>,
241 base_sequence: &DDSequence,
242 executor: &dyn DDCircuitExecutor,
243 ) -> f64 {
244 if let Ok(temp_sequence) = self.create_optimized_sequence(base_sequence, params) {
246 match self.config.optimization_objective {
247 DDOptimizationObjective::MaximizeCoherenceTime => {
248 self.evaluate_coherence_time(&temp_sequence, executor)
249 }
250 DDOptimizationObjective::MinimizeDecoherenceRate => {
251 1.0 / self.evaluate_coherence_time(&temp_sequence, executor)
252 }
253 DDOptimizationObjective::MaximizeProcessFidelity => {
254 self.evaluate_process_fidelity(&temp_sequence, executor)
255 }
256 DDOptimizationObjective::MinimizeGateOverhead => {
257 -(temp_sequence.properties.pulse_count as f64)
258 }
259 DDOptimizationObjective::MaximizeRobustness => {
260 self.evaluate_robustness(&temp_sequence, executor)
261 }
262 DDOptimizationObjective::MultiObjective => {
263 self.evaluate_multi_objective(&temp_sequence, executor)
264 }
265 DDOptimizationObjective::Custom(_) => {
266 self.evaluate_custom_objective(&temp_sequence, executor)
267 }
268 }
269 } else {
270 f64::NEG_INFINITY }
272 }
273
274 fn evaluate_coherence_time(
276 &self,
277 sequence: &DDSequence,
278 _executor: &dyn DDCircuitExecutor,
279 ) -> f64 {
280 let base_t2 = 50e-6; let noise_suppression: f64 = sequence.properties.noise_suppression.values().sum();
283 let suppression_factor =
284 1.0 + noise_suppression / sequence.properties.noise_suppression.len() as f64;
285
286 base_t2 * suppression_factor * 1e6 }
288
289 fn evaluate_process_fidelity(
291 &self,
292 sequence: &DDSequence,
293 _executor: &dyn DDCircuitExecutor,
294 ) -> f64 {
295 let base_fidelity = 0.99;
297 let order_bonus = 0.01 * (sequence.properties.sequence_order as f64).log2();
298 let overhead_penalty = -0.001 * (sequence.properties.pulse_count as f64).sqrt();
299
300 base_fidelity + order_bonus + overhead_penalty
301 }
302
303 fn evaluate_robustness(&self, sequence: &DDSequence, _executor: &dyn DDCircuitExecutor) -> f64 {
305 let mut robustness = 0.0;
307
308 if sequence.properties.symmetry.time_reversal {
309 robustness += 0.25;
310 }
311 if sequence.properties.symmetry.phase_symmetry {
312 robustness += 0.25;
313 }
314 if sequence.properties.symmetry.rotational_symmetry {
315 robustness += 0.25;
316 }
317 if sequence.properties.symmetry.inversion_symmetry {
318 robustness += 0.25;
319 }
320
321 let noise_types = sequence.properties.noise_suppression.len() as f64;
323 robustness += 0.1 * noise_types;
324
325 robustness
326 }
327
328 fn evaluate_multi_objective(
330 &self,
331 sequence: &DDSequence,
332 executor: &dyn DDCircuitExecutor,
333 ) -> f64 {
334 let mut total_objective = 0.0;
335
336 for (objective_name, weight) in &self.config.multi_objective_weights {
338 let objective_value = match objective_name.as_str() {
339 "coherence_time" => self.evaluate_coherence_time(sequence, executor),
340 "process_fidelity" => self.evaluate_process_fidelity(sequence, executor),
341 "robustness" => self.evaluate_robustness(sequence, executor),
342 "gate_overhead" => -(sequence.properties.pulse_count as f64),
343 _ => 0.0,
344 };
345
346 total_objective += weight * objective_value;
347 }
348
349 total_objective
350 }
351
352 fn evaluate_custom_objective(
354 &self,
355 _sequence: &DDSequence,
356 _executor: &dyn DDCircuitExecutor,
357 ) -> f64 {
358 1.0
360 }
361
362 fn optimize_gradient_free_impl(
364 &mut self,
365 base_sequence: &DDSequence,
366 executor: &dyn DDCircuitExecutor,
367 initial_params: &Array1<f64>,
368 ) -> DeviceResult<Array1<f64>> {
369 #[cfg(feature = "scirs2")]
370 {
371 let params_slice = initial_params.as_slice().ok_or_else(|| {
372 crate::DeviceError::ExecutionFailed(
373 "Failed to get contiguous slice from parameters".into(),
374 )
375 })?;
376 let result = minimize(
377 |params: &ArrayView1<f64>| {
378 let params_array = params.to_owned();
379 -self.evaluate_objective(¶ms_array, base_sequence, executor)
380 },
382 params_slice,
383 scirs2_optimize::unconstrained::Method::NelderMead,
384 None,
385 )
386 .map_err(|e| crate::DeviceError::OptimizationError(format!("{e:?}")))?;
387
388 Ok(Array1::from_vec(result.x.to_vec()))
389 }
390
391 #[cfg(not(feature = "scirs2"))]
392 {
393 let params_slice = initial_params.as_slice().ok_or_else(|| {
394 crate::DeviceError::ExecutionFailed(
395 "Failed to get contiguous slice from parameters".into(),
396 )
397 })?;
398 let result = minimize(
399 |params: &Array1<f64>| {
400 -self.evaluate_objective(params, base_sequence, executor)
401 },
403 params_slice,
404 "nelder-mead",
405 )
406 .map_err(|e| crate::DeviceError::OptimizationError(format!("{:?}", e)))?;
407
408 Ok(result.x)
409 }
410 }
411
412 fn optimize_simulated_annealing_impl(
419 &mut self,
420 base_sequence: &DDSequence,
421 executor: &dyn DDCircuitExecutor,
422 initial_params: &Array1<f64>,
423 ) -> DeviceResult<Array1<f64>> {
424 let mut rng = thread_rng();
425 let n = initial_params.len();
426 let max_iterations = self.config.max_iterations.max(1);
427
428 let mut current = initial_params.clone();
429 let mut current_obj = self.evaluate_objective(¤t, base_sequence, executor);
430 let mut best = current.clone();
431 let mut best_obj = current_obj;
432
433 const INITIAL_TEMP: f64 = 1.0;
434 const FINAL_TEMP: f64 = 1e-3;
435
436 for iter in 0..max_iterations {
437 let progress = iter as f64 / max_iterations as f64;
438 let temperature = INITIAL_TEMP * (FINAL_TEMP / INITIAL_TEMP).powf(progress);
439
440 let mut candidate = current.clone();
441 for i in 0..n {
442 candidate[i] += (rng.random::<f64>() - 0.5) * 2.0 * temperature;
443 }
444 let candidate_obj = self.evaluate_objective(&candidate, base_sequence, executor);
445 let delta = candidate_obj - current_obj;
446
447 let accept = delta > 0.0 || {
448 let acceptance_probability = (delta / temperature.max(1e-12)).exp();
449 rng.random::<f64>() < acceptance_probability
450 };
451
452 if accept {
453 current = candidate;
454 current_obj = candidate_obj;
455 if current_obj > best_obj {
456 best = current.clone();
457 best_obj = current_obj;
458 }
459 }
460 }
461
462 Ok(best)
463 }
464
465 fn optimize_genetic_algorithm_impl(
470 &mut self,
471 base_sequence: &DDSequence,
472 executor: &dyn DDCircuitExecutor,
473 initial_params: &Array1<f64>,
474 ) -> DeviceResult<Array1<f64>> {
475 let mut rng = thread_rng();
476 let n = initial_params.len();
477 const POPULATION_SIZE: usize = 20;
478 const MUTATION_RATE: f64 = 0.1;
479 const MUTATION_SCALE: f64 = 0.1;
480 let generations = self.config.max_iterations.max(1).min(200);
481
482 let mut population: Vec<Array1<f64>> = (0..POPULATION_SIZE)
483 .map(|i| {
484 if i == 0 {
485 initial_params.clone()
486 } else {
487 Array1::from_shape_fn(n, |j| {
488 initial_params[j] + (rng.random::<f64>() - 0.5) * 0.5
489 })
490 }
491 })
492 .collect();
493
494 let mut best = initial_params.clone();
495 let mut best_obj = self.evaluate_objective(&best, base_sequence, executor);
496
497 for _ in 0..generations {
498 let fitness: Vec<f64> = population
499 .iter()
500 .map(|individual| self.evaluate_objective(individual, base_sequence, executor))
501 .collect();
502
503 for (individual, &score) in population.iter().zip(fitness.iter()) {
504 if score > best_obj {
505 best_obj = score;
506 best = individual.clone();
507 }
508 }
509
510 let mut next_generation = Vec::with_capacity(POPULATION_SIZE);
511 while next_generation.len() < POPULATION_SIZE {
512 let parent1 = Self::tournament_select(&population, &fitness, &mut rng);
513 let parent2 = Self::tournament_select(&population, &fitness, &mut rng);
514 let mut child = Array1::zeros(n);
515 for i in 0..n {
516 child[i] = if rng.random::<f64>() < 0.5 {
517 parent1[i]
518 } else {
519 parent2[i]
520 };
521 if rng.random::<f64>() < MUTATION_RATE {
522 child[i] += (rng.random::<f64>() - 0.5) * MUTATION_SCALE;
523 }
524 }
525 next_generation.push(child);
526 }
527 population = next_generation;
528 }
529
530 Ok(best)
531 }
532
533 fn tournament_select<'a>(
536 population: &'a [Array1<f64>],
537 fitness: &[f64],
538 rng: &mut impl Rng,
539 ) -> &'a Array1<f64> {
540 let mut best_idx = rng.random_range(0..population.len());
541 for _ in 0..2 {
542 let candidate_idx = rng.random_range(0..population.len());
543 if fitness[candidate_idx] > fitness[best_idx] {
544 best_idx = candidate_idx;
545 }
546 }
547 &population[best_idx]
548 }
549
550 fn optimize_particle_swarm_impl(
555 &mut self,
556 base_sequence: &DDSequence,
557 executor: &dyn DDCircuitExecutor,
558 initial_params: &Array1<f64>,
559 ) -> DeviceResult<Array1<f64>> {
560 let mut rng = thread_rng();
561 let n = initial_params.len();
562 const SWARM_SIZE: usize = 15;
563 const INERTIA: f64 = 0.7;
564 const COGNITIVE: f64 = 1.4;
565 const SOCIAL: f64 = 1.4;
566 let iterations = self.config.max_iterations.max(1).min(200);
567
568 let mut positions: Vec<Array1<f64>> = (0..SWARM_SIZE)
569 .map(|i| {
570 if i == 0 {
571 initial_params.clone()
572 } else {
573 Array1::from_shape_fn(n, |j| {
574 initial_params[j] + (rng.random::<f64>() - 0.5) * 0.5
575 })
576 }
577 })
578 .collect();
579 let mut velocities: Vec<Array1<f64>> = (0..SWARM_SIZE).map(|_| Array1::zeros(n)).collect();
580 let mut personal_best = positions.clone();
581 let mut personal_best_obj: Vec<f64> = positions
582 .iter()
583 .map(|p| self.evaluate_objective(p, base_sequence, executor))
584 .collect();
585
586 let global_best_idx = personal_best_obj
587 .iter()
588 .enumerate()
589 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
590 .map(|(i, _)| i)
591 .unwrap_or(0);
592 let mut global_best = personal_best[global_best_idx].clone();
593 let mut global_best_obj = personal_best_obj[global_best_idx];
594
595 for _ in 0..iterations {
596 for i in 0..SWARM_SIZE {
597 for d in 0..n {
598 let r1 = rng.random::<f64>();
599 let r2 = rng.random::<f64>();
600 velocities[i][d] = INERTIA.mul_add(
601 velocities[i][d],
602 COGNITIVE.mul_add(
603 r1 * (personal_best[i][d] - positions[i][d]),
604 SOCIAL * r2 * (global_best[d] - positions[i][d]),
605 ),
606 );
607 positions[i][d] += velocities[i][d];
608 }
609 let obj = self.evaluate_objective(&positions[i], base_sequence, executor);
610 if obj > personal_best_obj[i] {
611 personal_best_obj[i] = obj;
612 personal_best[i] = positions[i].clone();
613 if obj > global_best_obj {
614 global_best_obj = obj;
615 global_best = positions[i].clone();
616 }
617 }
618 }
619 }
620
621 Ok(global_best)
622 }
623
624 fn optimize_differential_evolution_impl(
630 &mut self,
631 base_sequence: &DDSequence,
632 executor: &dyn DDCircuitExecutor,
633 initial_params: &Array1<f64>,
634 ) -> DeviceResult<Array1<f64>> {
635 self.optimize_gradient_free_impl(base_sequence, executor, initial_params)
636 }
637
638 fn optimize_bayesian_impl(
643 &mut self,
644 base_sequence: &DDSequence,
645 executor: &dyn DDCircuitExecutor,
646 initial_params: &Array1<f64>,
647 ) -> DeviceResult<Array1<f64>> {
648 self.optimize_gradient_free_impl(base_sequence, executor, initial_params)
649 }
650
651 fn optimize_reinforcement_learning_impl(
656 &mut self,
657 base_sequence: &DDSequence,
658 executor: &dyn DDCircuitExecutor,
659 initial_params: &Array1<f64>,
660 ) -> DeviceResult<Array1<f64>> {
661 self.optimize_gradient_free_impl(base_sequence, executor, initial_params)
662 }
663
664 fn create_optimized_sequence(
666 &self,
667 base_sequence: &DDSequence,
668 params: &Array1<f64>,
669 ) -> DeviceResult<DDSequence> {
670 let mut optimized = base_sequence.clone();
671
672 let timing_count = base_sequence.pulse_timings.len();
674 for i in 0..timing_count {
675 if i < params.len() {
676 optimized.pulse_timings[i] = params[i] * base_sequence.duration;
677 }
679 }
680
681 for i in 0..base_sequence.pulse_phases.len() {
683 let param_idx = timing_count + i;
684 if param_idx < params.len() {
685 optimized.pulse_phases[i] = params[param_idx] * 2.0 * std::f64::consts::PI;
686 }
688 }
689
690 Ok(optimized)
691 }
692
693 fn analyze_parameter_sensitivity(
695 &self,
696 optimal_params: &Array1<f64>,
697 base_sequence: &DDSequence,
698 executor: &dyn DDCircuitExecutor,
699 ) -> DeviceResult<ParameterSensitivityAnalysis> {
700 let param_count = optimal_params.len();
701 let mut sensitivity_matrix = Array2::zeros((param_count, param_count));
702 let perturbation = 0.01; for i in 0..param_count {
706 let mut perturbed_params = optimal_params.clone();
707 perturbed_params[i] *= 1.0 + perturbation;
708
709 let base_objective = self.evaluate_objective(optimal_params, base_sequence, executor);
710 let perturbed_objective =
711 self.evaluate_objective(&perturbed_params, base_sequence, executor);
712
713 let sensitivity =
714 (perturbed_objective - base_objective) / (perturbation * optimal_params[i]);
715 sensitivity_matrix[[i, i]] = sensitivity;
716 }
717
718 let mut sensitivities: Vec<(usize, f64)> = (0..param_count)
720 .map(|i| (i, sensitivity_matrix[[i, i]].abs()))
721 .collect();
722 sensitivities.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
723
724 let most_sensitive_parameters: Vec<usize> =
725 sensitivities.iter().take(5).map(|(idx, _)| *idx).collect();
726
727 let parameter_correlations = Array2::eye(param_count);
729
730 let avg_sensitivity =
732 sensitivities.iter().map(|(_, s)| s).sum::<f64>() / param_count as f64;
733 let robustness_score = 1.0 / (1.0 + avg_sensitivity);
734
735 Ok(ParameterSensitivityAnalysis {
736 sensitivity_matrix,
737 most_sensitive_parameters,
738 parameter_correlations,
739 robustness_score,
740 })
741 }
742}