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 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 result = minimize(
341                |params: &ArrayView1<f64>| {
342                    let params_array = params.to_owned();
343                    -self.evaluate_objective(&params_array, base_sequence, executor)
344                    // Minimize negative for maximization
345                },
346                initial_params.as_slice().unwrap(),
347                scirs2_optimize::unconstrained::Method::NelderMead,
348                None,
349            )
350            .map_err(|e| crate::DeviceError::OptimizationError(format!("{:?}", e)))?;
351
352            Ok(Array1::from_vec(result.x.to_vec()))
353        }
354
355        #[cfg(not(feature = "scirs2"))]
356        {
357            let result = minimize(
358                |params: &Array1<f64>| {
359                    -self.evaluate_objective(params, base_sequence, executor)
360                    // Minimize negative for maximization
361                },
362                initial_params.as_slice().unwrap(),
363                "nelder-mead",
364            )
365            .map_err(|e| crate::DeviceError::OptimizationError(format!("{:?}", e)))?;
366
367            Ok(result.x)
368        }
369    }
370
371    /// Optimize using simulated annealing (placeholder)
372    fn optimize_simulated_annealing_impl(
373        &mut self,
374        base_sequence: &DDSequence,
375        executor: &dyn DDCircuitExecutor,
376        initial_params: &Array1<f64>,
377    ) -> DeviceResult<Array1<f64>> {
378        // Fallback to gradient-free for now
379        self.optimize_gradient_free_impl(base_sequence, executor, initial_params)
380    }
381
382    /// Other optimization algorithms (placeholders)
383    fn optimize_genetic_algorithm_impl(
384        &mut self,
385        base_sequence: &DDSequence,
386        executor: &dyn DDCircuitExecutor,
387        initial_params: &Array1<f64>,
388    ) -> DeviceResult<Array1<f64>> {
389        self.optimize_gradient_free_impl(base_sequence, executor, initial_params)
390    }
391
392    fn optimize_particle_swarm_impl(
393        &mut self,
394        base_sequence: &DDSequence,
395        executor: &dyn DDCircuitExecutor,
396        initial_params: &Array1<f64>,
397    ) -> DeviceResult<Array1<f64>> {
398        self.optimize_gradient_free_impl(base_sequence, executor, initial_params)
399    }
400
401    fn optimize_differential_evolution_impl(
402        &mut self,
403        base_sequence: &DDSequence,
404        executor: &dyn DDCircuitExecutor,
405        initial_params: &Array1<f64>,
406    ) -> DeviceResult<Array1<f64>> {
407        self.optimize_gradient_free_impl(base_sequence, executor, initial_params)
408    }
409
410    fn optimize_bayesian_impl(
411        &mut self,
412        base_sequence: &DDSequence,
413        executor: &dyn DDCircuitExecutor,
414        initial_params: &Array1<f64>,
415    ) -> DeviceResult<Array1<f64>> {
416        self.optimize_gradient_free_impl(base_sequence, executor, initial_params)
417    }
418
419    fn optimize_reinforcement_learning_impl(
420        &mut self,
421        base_sequence: &DDSequence,
422        executor: &dyn DDCircuitExecutor,
423        initial_params: &Array1<f64>,
424    ) -> DeviceResult<Array1<f64>> {
425        self.optimize_gradient_free_impl(base_sequence, executor, initial_params)
426    }
427
428    /// Create optimized sequence from parameters
429    fn create_optimized_sequence(
430        &self,
431        base_sequence: &DDSequence,
432        params: &Array1<f64>,
433    ) -> DeviceResult<DDSequence> {
434        let mut optimized = base_sequence.clone();
435
436        // Update timing parameters
437        let timing_count = base_sequence.pulse_timings.len();
438        for i in 0..timing_count {
439            if i < params.len() {
440                optimized.pulse_timings[i] = params[i] * base_sequence.duration;
441                // Denormalize
442            }
443        }
444
445        // Update phase parameters
446        for i in 0..base_sequence.pulse_phases.len() {
447            let param_idx = timing_count + i;
448            if param_idx < params.len() {
449                optimized.pulse_phases[i] = params[param_idx] * 2.0 * std::f64::consts::PI;
450                // Denormalize
451            }
452        }
453
454        Ok(optimized)
455    }
456
457    /// Analyze parameter sensitivity
458    fn analyze_parameter_sensitivity(
459        &self,
460        optimal_params: &Array1<f64>,
461        base_sequence: &DDSequence,
462        executor: &dyn DDCircuitExecutor,
463    ) -> DeviceResult<ParameterSensitivityAnalysis> {
464        let param_count = optimal_params.len();
465        let mut sensitivity_matrix = Array2::zeros((param_count, param_count));
466        let perturbation = 0.01; // 1% perturbation
467
468        // Calculate sensitivity for each parameter
469        for i in 0..param_count {
470            let mut perturbed_params = optimal_params.clone();
471            perturbed_params[i] *= 1.0 + perturbation;
472
473            let base_objective = self.evaluate_objective(optimal_params, base_sequence, executor);
474            let perturbed_objective =
475                self.evaluate_objective(&perturbed_params, base_sequence, executor);
476
477            let sensitivity =
478                (perturbed_objective - base_objective) / (perturbation * optimal_params[i]);
479            sensitivity_matrix[[i, i]] = sensitivity;
480        }
481
482        // Find most sensitive parameters
483        let mut sensitivities: Vec<(usize, f64)> = (0..param_count)
484            .map(|i| (i, sensitivity_matrix[[i, i]].abs()))
485            .collect();
486        sensitivities.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
487
488        let most_sensitive_parameters: Vec<usize> =
489            sensitivities.iter().take(5).map(|(idx, _)| *idx).collect();
490
491        // Simple correlation matrix (identity for now)
492        let parameter_correlations = Array2::eye(param_count);
493
494        // Robustness score based on sensitivity distribution
495        let avg_sensitivity =
496            sensitivities.iter().map(|(_, s)| s).sum::<f64>() / param_count as f64;
497        let robustness_score = 1.0 / (1.0 + avg_sensitivity);
498
499        Ok(ParameterSensitivityAnalysis {
500            sensitivity_matrix,
501            most_sensitive_parameters,
502            parameter_correlations,
503            robustness_score,
504        })
505    }
506}