quantrs2_device/dynamical_decoupling/
optimization.rs

1//! DD sequence optimization using SciRS2
2
3use scirs2_core::ndarray::{Array1, Array2, ArrayView1};
4use std::collections::HashMap;
5
6use super::{
7    config::{DDOptimizationAlgorithm, DDOptimizationConfig, DDOptimizationObjective},
8    sequences::DDSequence,
9    DDCircuitExecutor,
10};
11use crate::DeviceResult;
12
13// SciRS2 dependencies with fallbacks
14#[cfg(feature = "scirs2")]
15use scirs2_optimize::{minimize, OptimizeResult};
16
17#[cfg(not(feature = "scirs2"))]
18mod fallback_scirs2 {
19    pub use super::super::fallback_scirs2::{minimize, OptimizeResult};
20}
21
22#[cfg(not(feature = "scirs2"))]
23use fallback_scirs2::{minimize, OptimizeResult};
24
25/// DD sequence optimizer using SciRS2
26pub struct DDSequenceOptimizer {
27    pub config: DDOptimizationConfig,
28    pub optimization_history: Vec<OptimizationStep>,
29    pub best_parameters: Option<Array1<f64>>,
30    pub best_objective_value: f64,
31}
32
33/// Single optimization step record
34#[derive(Debug, Clone)]
35pub struct OptimizationStep {
36    pub iteration: usize,
37    pub parameters: Array1<f64>,
38    pub objective_value: f64,
39    pub gradient_norm: Option<f64>,
40    pub execution_time: std::time::Duration,
41}
42
43/// Optimization result for DD sequences
44#[derive(Debug, Clone)]
45pub struct DDOptimizationResult {
46    pub optimized_sequence: DDSequence,
47    pub optimization_metrics: OptimizationMetrics,
48    pub convergence_analysis: ConvergenceAnalysis,
49    pub parameter_sensitivity: ParameterSensitivityAnalysis,
50}
51
52/// Optimization performance metrics
53#[derive(Debug, Clone)]
54pub struct OptimizationMetrics {
55    pub initial_objective: f64,
56    pub final_objective: f64,
57    pub improvement_factor: f64,
58    pub convergence_iterations: usize,
59    pub total_function_evaluations: usize,
60    pub optimization_time: std::time::Duration,
61    pub success: bool,
62    /// Alias for convergence_iterations for compatibility
63    pub iterations: usize,
64}
65
66/// Convergence analysis results
67#[derive(Debug, Clone)]
68pub struct ConvergenceAnalysis {
69    pub converged: bool,
70    pub convergence_criterion: String,
71    pub objective_tolerance: f64,
72    pub parameter_tolerance: f64,
73    pub gradient_tolerance: Option<f64>,
74    pub stagnation_iterations: usize,
75}
76
77/// Parameter sensitivity analysis
78#[derive(Debug, Clone)]
79pub struct ParameterSensitivityAnalysis {
80    pub sensitivity_matrix: Array2<f64>,
81    pub most_sensitive_parameters: Vec<usize>,
82    pub parameter_correlations: Array2<f64>,
83    pub robustness_score: f64,
84}
85
86impl DDSequenceOptimizer {
87    /// Create new DD sequence optimizer
88    pub const fn new(config: DDOptimizationConfig) -> Self {
89        Self {
90            config,
91            optimization_history: Vec::new(),
92            best_parameters: None,
93            best_objective_value: f64::INFINITY,
94        }
95    }
96
97    /// Optimize DD sequence
98    pub async fn optimize_sequence(
99        &mut self,
100        base_sequence: &DDSequence,
101        executor: &dyn DDCircuitExecutor,
102    ) -> DeviceResult<DDOptimizationResult> {
103        println!(
104            "Starting DD sequence optimization with {:?}",
105            self.config.optimization_algorithm
106        );
107        let start_time = std::time::Instant::now();
108
109        // Initialize optimization parameters
110        let initial_params = self.initialize_parameters(base_sequence)?;
111
112        // Perform optimization based on algorithm (pass base_sequence and executor directly)
113        let optimization_result = match self.config.optimization_algorithm {
114            DDOptimizationAlgorithm::GradientFree => {
115                self.optimize_gradient_free_impl(base_sequence, executor, &initial_params)?
116            }
117            DDOptimizationAlgorithm::SimulatedAnnealing => {
118                self.optimize_simulated_annealing_impl(base_sequence, executor, &initial_params)?
119            }
120            DDOptimizationAlgorithm::GeneticAlgorithm => {
121                self.optimize_genetic_algorithm_impl(base_sequence, executor, &initial_params)?
122            }
123            DDOptimizationAlgorithm::ParticleSwarm => {
124                self.optimize_particle_swarm_impl(base_sequence, executor, &initial_params)?
125            }
126            DDOptimizationAlgorithm::DifferentialEvolution => {
127                self.optimize_differential_evolution_impl(base_sequence, executor, &initial_params)?
128            }
129            DDOptimizationAlgorithm::BayesianOptimization => {
130                self.optimize_bayesian_impl(base_sequence, executor, &initial_params)?
131            }
132            DDOptimizationAlgorithm::ReinforcementLearning => {
133                self.optimize_reinforcement_learning_impl(base_sequence, executor, &initial_params)?
134            }
135        };
136
137        // Handle optimization result (extract the optimized parameters)
138        let optimal_params = optimization_result;
139
140        // Create optimized sequence
141        let optimized_sequence = self.create_optimized_sequence(base_sequence, &optimal_params)?;
142
143        // Analyze optimization results
144        let initial_obj = self.evaluate_objective(&initial_params, base_sequence, executor);
145        let final_obj = self.evaluate_objective(&optimal_params, base_sequence, executor);
146
147        let convergence_iters = 100; // Placeholder
148        let metrics = OptimizationMetrics {
149            initial_objective: initial_obj,
150            final_objective: final_obj,
151            improvement_factor: if final_obj > 0.0 {
152                initial_obj / final_obj
153            } else {
154                1.0
155            },
156            convergence_iterations: convergence_iters,
157            total_function_evaluations: 1000, // Placeholder
158            optimization_time: start_time.elapsed(),
159            iterations: convergence_iters,
160            success: true,
161        };
162
163        let convergence_analysis = ConvergenceAnalysis {
164            converged: true,
165            convergence_criterion: "Tolerance reached".to_string(),
166            objective_tolerance: self.config.convergence_tolerance,
167            parameter_tolerance: self.config.convergence_tolerance * 0.1,
168            gradient_tolerance: Some(self.config.convergence_tolerance * 0.01),
169            stagnation_iterations: 0,
170        };
171
172        let sensitivity_analysis =
173            self.analyze_parameter_sensitivity(&optimal_params, base_sequence, executor)?;
174
175        println!(
176            "DD optimization completed. Improvement: {:.2}x",
177            metrics.improvement_factor
178        );
179
180        Ok(DDOptimizationResult {
181            optimized_sequence,
182            optimization_metrics: metrics,
183            convergence_analysis,
184            parameter_sensitivity: sensitivity_analysis,
185        })
186    }
187
188    /// Initialize optimization parameters
189    fn initialize_parameters(&self, sequence: &DDSequence) -> DeviceResult<Array1<f64>> {
190        let param_count = sequence.pulse_timings.len() + sequence.pulse_phases.len();
191        let mut params = Array1::zeros(param_count);
192
193        // Initialize with current sequence parameters
194        for (i, &timing) in sequence.pulse_timings.iter().enumerate() {
195            params[i] = timing / sequence.duration; // Normalize
196        }
197
198        for (i, &phase) in sequence.pulse_phases.iter().enumerate() {
199            params[sequence.pulse_timings.len() + i] = phase / (2.0 * std::f64::consts::PI);
200            // Normalize
201        }
202
203        Ok(params)
204    }
205
206    /// Evaluate optimization objective
207    fn evaluate_objective(
208        &self,
209        params: &Array1<f64>,
210        base_sequence: &DDSequence,
211        executor: &dyn DDCircuitExecutor,
212    ) -> f64 {
213        // Create temporary sequence with new parameters
214        if let Ok(temp_sequence) = self.create_optimized_sequence(base_sequence, params) {
215            match self.config.optimization_objective {
216                DDOptimizationObjective::MaximizeCoherenceTime => {
217                    self.evaluate_coherence_time(&temp_sequence, executor)
218                }
219                DDOptimizationObjective::MinimizeDecoherenceRate => {
220                    1.0 / self.evaluate_coherence_time(&temp_sequence, executor)
221                }
222                DDOptimizationObjective::MaximizeProcessFidelity => {
223                    self.evaluate_process_fidelity(&temp_sequence, executor)
224                }
225                DDOptimizationObjective::MinimizeGateOverhead => {
226                    -(temp_sequence.properties.pulse_count as f64)
227                }
228                DDOptimizationObjective::MaximizeRobustness => {
229                    self.evaluate_robustness(&temp_sequence, executor)
230                }
231                DDOptimizationObjective::MultiObjective => {
232                    self.evaluate_multi_objective(&temp_sequence, executor)
233                }
234                DDOptimizationObjective::Custom(_) => {
235                    self.evaluate_custom_objective(&temp_sequence, executor)
236                }
237            }
238        } else {
239            f64::NEG_INFINITY // Invalid parameters
240        }
241    }
242
243    /// Evaluate coherence time
244    fn evaluate_coherence_time(
245        &self,
246        sequence: &DDSequence,
247        _executor: &dyn DDCircuitExecutor,
248    ) -> f64 {
249        // Simplified coherence time estimation
250        let base_t2 = 50e-6; // 50 μs base T2
251        let noise_suppression: f64 = sequence.properties.noise_suppression.values().sum();
252        let suppression_factor =
253            1.0 + noise_suppression / sequence.properties.noise_suppression.len() as f64;
254
255        base_t2 * suppression_factor * 1e6 // Convert to microseconds for optimization
256    }
257
258    /// Evaluate process fidelity
259    fn evaluate_process_fidelity(
260        &self,
261        sequence: &DDSequence,
262        _executor: &dyn DDCircuitExecutor,
263    ) -> f64 {
264        // Simplified fidelity estimation based on sequence properties
265        let base_fidelity = 0.99;
266        let order_bonus = 0.01 * (sequence.properties.sequence_order as f64).log2();
267        let overhead_penalty = -0.001 * (sequence.properties.pulse_count as f64).sqrt();
268
269        base_fidelity + order_bonus + overhead_penalty
270    }
271
272    /// Evaluate robustness
273    fn evaluate_robustness(&self, sequence: &DDSequence, _executor: &dyn DDCircuitExecutor) -> f64 {
274        // Robustness based on symmetry properties and noise suppression diversity
275        let mut robustness = 0.0;
276
277        if sequence.properties.symmetry.time_reversal {
278            robustness += 0.25;
279        }
280        if sequence.properties.symmetry.phase_symmetry {
281            robustness += 0.25;
282        }
283        if sequence.properties.symmetry.rotational_symmetry {
284            robustness += 0.25;
285        }
286        if sequence.properties.symmetry.inversion_symmetry {
287            robustness += 0.25;
288        }
289
290        // Add noise suppression diversity bonus
291        let noise_types = sequence.properties.noise_suppression.len() as f64;
292        robustness += 0.1 * noise_types;
293
294        robustness
295    }
296
297    /// Evaluate multi-objective function
298    fn evaluate_multi_objective(
299        &self,
300        sequence: &DDSequence,
301        executor: &dyn DDCircuitExecutor,
302    ) -> f64 {
303        let mut total_objective = 0.0;
304
305        // Weight objectives based on configuration
306        for (objective_name, weight) in &self.config.multi_objective_weights {
307            let objective_value = match objective_name.as_str() {
308                "coherence_time" => self.evaluate_coherence_time(sequence, executor),
309                "process_fidelity" => self.evaluate_process_fidelity(sequence, executor),
310                "robustness" => self.evaluate_robustness(sequence, executor),
311                "gate_overhead" => -(sequence.properties.pulse_count as f64),
312                _ => 0.0,
313            };
314
315            total_objective += weight * objective_value;
316        }
317
318        total_objective
319    }
320
321    /// Evaluate custom objective
322    fn evaluate_custom_objective(
323        &self,
324        _sequence: &DDSequence,
325        _executor: &dyn DDCircuitExecutor,
326    ) -> f64 {
327        // Placeholder for custom objective functions
328        1.0
329    }
330
331    /// Optimize using gradient-free methods
332    fn optimize_gradient_free_impl(
333        &mut self,
334        base_sequence: &DDSequence,
335        executor: &dyn DDCircuitExecutor,
336        initial_params: &Array1<f64>,
337    ) -> DeviceResult<Array1<f64>> {
338        #[cfg(feature = "scirs2")]
339        {
340            let params_slice = initial_params.as_slice().ok_or_else(|| {
341                crate::DeviceError::ExecutionFailed(
342                    "Failed to get contiguous slice from parameters".into(),
343                )
344            })?;
345            let result = minimize(
346                |params: &ArrayView1<f64>| {
347                    let params_array = params.to_owned();
348                    -self.evaluate_objective(&params_array, base_sequence, executor)
349                    // Minimize negative for maximization
350                },
351                params_slice,
352                scirs2_optimize::unconstrained::Method::NelderMead,
353                None,
354            )
355            .map_err(|e| crate::DeviceError::OptimizationError(format!("{e:?}")))?;
356
357            Ok(Array1::from_vec(result.x.to_vec()))
358        }
359
360        #[cfg(not(feature = "scirs2"))]
361        {
362            let params_slice = initial_params.as_slice().ok_or_else(|| {
363                crate::DeviceError::ExecutionFailed(
364                    "Failed to get contiguous slice from parameters".into(),
365                )
366            })?;
367            let result = minimize(
368                |params: &Array1<f64>| {
369                    -self.evaluate_objective(params, base_sequence, executor)
370                    // Minimize negative for maximization
371                },
372                params_slice,
373                "nelder-mead",
374            )
375            .map_err(|e| crate::DeviceError::OptimizationError(format!("{:?}", e)))?;
376
377            Ok(result.x)
378        }
379    }
380
381    /// Optimize using simulated annealing (placeholder)
382    fn optimize_simulated_annealing_impl(
383        &mut self,
384        base_sequence: &DDSequence,
385        executor: &dyn DDCircuitExecutor,
386        initial_params: &Array1<f64>,
387    ) -> DeviceResult<Array1<f64>> {
388        // Fallback to gradient-free for now
389        self.optimize_gradient_free_impl(base_sequence, executor, initial_params)
390    }
391
392    /// Other optimization algorithms (placeholders)
393    fn optimize_genetic_algorithm_impl(
394        &mut self,
395        base_sequence: &DDSequence,
396        executor: &dyn DDCircuitExecutor,
397        initial_params: &Array1<f64>,
398    ) -> DeviceResult<Array1<f64>> {
399        self.optimize_gradient_free_impl(base_sequence, executor, initial_params)
400    }
401
402    fn optimize_particle_swarm_impl(
403        &mut self,
404        base_sequence: &DDSequence,
405        executor: &dyn DDCircuitExecutor,
406        initial_params: &Array1<f64>,
407    ) -> DeviceResult<Array1<f64>> {
408        self.optimize_gradient_free_impl(base_sequence, executor, initial_params)
409    }
410
411    fn optimize_differential_evolution_impl(
412        &mut self,
413        base_sequence: &DDSequence,
414        executor: &dyn DDCircuitExecutor,
415        initial_params: &Array1<f64>,
416    ) -> DeviceResult<Array1<f64>> {
417        self.optimize_gradient_free_impl(base_sequence, executor, initial_params)
418    }
419
420    fn optimize_bayesian_impl(
421        &mut self,
422        base_sequence: &DDSequence,
423        executor: &dyn DDCircuitExecutor,
424        initial_params: &Array1<f64>,
425    ) -> DeviceResult<Array1<f64>> {
426        self.optimize_gradient_free_impl(base_sequence, executor, initial_params)
427    }
428
429    fn optimize_reinforcement_learning_impl(
430        &mut self,
431        base_sequence: &DDSequence,
432        executor: &dyn DDCircuitExecutor,
433        initial_params: &Array1<f64>,
434    ) -> DeviceResult<Array1<f64>> {
435        self.optimize_gradient_free_impl(base_sequence, executor, initial_params)
436    }
437
438    /// Create optimized sequence from parameters
439    fn create_optimized_sequence(
440        &self,
441        base_sequence: &DDSequence,
442        params: &Array1<f64>,
443    ) -> DeviceResult<DDSequence> {
444        let mut optimized = base_sequence.clone();
445
446        // Update timing parameters
447        let timing_count = base_sequence.pulse_timings.len();
448        for i in 0..timing_count {
449            if i < params.len() {
450                optimized.pulse_timings[i] = params[i] * base_sequence.duration;
451                // Denormalize
452            }
453        }
454
455        // Update phase parameters
456        for i in 0..base_sequence.pulse_phases.len() {
457            let param_idx = timing_count + i;
458            if param_idx < params.len() {
459                optimized.pulse_phases[i] = params[param_idx] * 2.0 * std::f64::consts::PI;
460                // Denormalize
461            }
462        }
463
464        Ok(optimized)
465    }
466
467    /// Analyze parameter sensitivity
468    fn analyze_parameter_sensitivity(
469        &self,
470        optimal_params: &Array1<f64>,
471        base_sequence: &DDSequence,
472        executor: &dyn DDCircuitExecutor,
473    ) -> DeviceResult<ParameterSensitivityAnalysis> {
474        let param_count = optimal_params.len();
475        let mut sensitivity_matrix = Array2::zeros((param_count, param_count));
476        let perturbation = 0.01; // 1% perturbation
477
478        // Calculate sensitivity for each parameter
479        for i in 0..param_count {
480            let mut perturbed_params = optimal_params.clone();
481            perturbed_params[i] *= 1.0 + perturbation;
482
483            let base_objective = self.evaluate_objective(optimal_params, base_sequence, executor);
484            let perturbed_objective =
485                self.evaluate_objective(&perturbed_params, base_sequence, executor);
486
487            let sensitivity =
488                (perturbed_objective - base_objective) / (perturbation * optimal_params[i]);
489            sensitivity_matrix[[i, i]] = sensitivity;
490        }
491
492        // Find most sensitive parameters
493        let mut sensitivities: Vec<(usize, f64)> = (0..param_count)
494            .map(|i| (i, sensitivity_matrix[[i, i]].abs()))
495            .collect();
496        sensitivities.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
497
498        let most_sensitive_parameters: Vec<usize> =
499            sensitivities.iter().take(5).map(|(idx, _)| *idx).collect();
500
501        // Simple correlation matrix (identity for now)
502        let parameter_correlations = Array2::eye(param_count);
503
504        // Robustness score based on sensitivity distribution
505        let avg_sensitivity =
506            sensitivities.iter().map(|(_, s)| s).sum::<f64>() / param_count as f64;
507        let robustness_score = 1.0 / (1.0 + avg_sensitivity);
508
509        Ok(ParameterSensitivityAnalysis {
510            sensitivity_matrix,
511            most_sensitive_parameters,
512            parameter_correlations,
513            robustness_score,
514        })
515    }
516}