Skip to main content

quantrs2_device/vqa_support/
objectives.rs

1//! Objective function definitions and evaluation for VQA
2//!
3//! This module provides objective functions commonly used in
4//! variational quantum algorithms with comprehensive evaluation strategies.
5use super::circuits::{GateType, ParametricCircuit};
6use super::config::{GradientMethod, VQAAlgorithmType};
7use crate::DeviceResult;
8use scirs2_core::ndarray::{Array1, Array2};
9use scirs2_core::Complex64;
10use std::collections::HashMap;
11use std::sync::Arc;
12#[cfg(not(feature = "scirs2"))]
13mod fallback_scirs2 {
14    use scirs2_core::ndarray::{Array1, Array2};
15    use scirs2_core::Complex64;
16    pub struct Matrix(pub Array2<Complex64>);
17    pub struct Vector(pub Array1<Complex64>);
18    pub struct PauliOperator {
19        pub coefficients: Array1<f64>,
20        pub terms: Vec<String>,
21    }
22    impl Matrix {
23        pub fn new(data: Array2<Complex64>) -> Self {
24            Self(data)
25        }
26    }
27    impl Vector {
28        pub fn new(data: Array1<Complex64>) -> Self {
29            Self(data)
30        }
31    }
32}
33/// Comprehensive objective function configuration
34#[derive(Debug, Clone)]
35pub struct ObjectiveConfig {
36    /// Objective type
37    pub objective_type: ObjectiveType,
38    /// Target value (if applicable)
39    pub target: Option<f64>,
40    /// Regularization parameters
41    pub regularization: RegularizationConfig,
42    /// Hamiltonian specification (for VQE)
43    pub hamiltonian: Option<HamiltonianSpec>,
44    /// Cost function specification (for QAOA)
45    pub cost_function: Option<CostFunctionSpec>,
46    /// Training data (for VQC/QNN)
47    pub training_data: Option<TrainingDataSpec>,
48    /// Measurement strategy
49    pub measurement_strategy: MeasurementStrategy,
50    /// Shot allocation
51    pub shot_allocation: ShotAllocationConfig,
52    /// Gradient computation method
53    pub gradient_method: GradientMethod,
54    /// Noise mitigation settings
55    pub noise_mitigation: ObjectiveNoiseMitigation,
56}
57/// Available objective function types
58#[derive(Debug, Clone)]
59pub enum ObjectiveType {
60    /// Energy minimization (VQE)
61    Energy,
62    /// Fidelity maximization
63    Fidelity,
64    /// Cost optimization (QAOA)
65    Cost,
66    /// Classification loss (VQC)
67    Classification,
68    /// Regression loss (QNN)
69    Regression,
70    /// Expectation value computation
71    ExpectationValue,
72    /// State preparation fidelity
73    StatePreparation,
74    /// Process fidelity
75    ProcessFidelity,
76    /// Custom objective with user-defined evaluation
77    Custom(String),
78}
79/// Hamiltonian specification for VQE
80#[derive(Debug, Clone)]
81pub struct HamiltonianSpec {
82    /// Pauli terms with coefficients
83    pub pauli_terms: Vec<PauliTerm>,
84    /// Number of qubits
85    pub num_qubits: usize,
86    /// Sparse representation flag
87    pub use_sparse: bool,
88}
89/// Individual Pauli term in Hamiltonian
90#[derive(Debug, Clone)]
91pub struct PauliTerm {
92    /// Coefficient
93    pub coefficient: Complex64,
94    /// Pauli operators on each qubit (I, X, Y, Z)
95    pub operators: Vec<char>,
96    /// Qubit indices (if sparse)
97    pub indices: Option<Vec<usize>>,
98}
99/// Cost function specification for QAOA
100#[derive(Debug, Clone)]
101pub struct CostFunctionSpec {
102    /// Cost function type
103    pub function_type: CostFunctionType,
104    /// Problem-specific parameters
105    pub parameters: HashMap<String, f64>,
106    /// Graph connectivity (for graph problems)
107    pub graph: Option<Vec<(usize, usize, f64)>>,
108}
109/// QAOA cost function types
110#[derive(Debug, Clone)]
111pub enum CostFunctionType {
112    /// Maximum Cut problem
113    MaxCut,
114    /// Traveling Salesman Problem
115    TSP,
116    /// Maximum Independent Set
117    MaxIndependentSet,
118    /// Portfolio optimization
119    Portfolio,
120    /// Custom cost function
121    Custom(String),
122}
123/// Training data specification for supervised learning
124#[derive(Debug, Clone)]
125pub struct TrainingDataSpec {
126    /// Input features
127    pub features: Array2<f64>,
128    /// Target labels/values
129    pub targets: Array1<f64>,
130    /// Data encoding strategy
131    pub encoding: DataEncoding,
132    /// Loss function type
133    pub loss_function: LossFunction,
134}
135/// Data encoding strategies
136#[derive(Debug, Clone)]
137pub enum DataEncoding {
138    /// Amplitude encoding
139    Amplitude,
140    /// Angle encoding
141    Angle,
142    /// Basis encoding
143    Basis,
144    /// IQP encoding
145    IQP,
146}
147/// Loss function types for supervised learning
148#[derive(Debug, Clone)]
149pub enum LossFunction {
150    /// Mean squared error
151    MSE,
152    /// Cross-entropy
153    CrossEntropy,
154    /// Hinge loss
155    Hinge,
156    /// Custom loss
157    Custom(String),
158}
159/// Measurement strategy configuration
160#[derive(Debug, Clone)]
161pub struct MeasurementStrategy {
162    /// Strategy type
163    pub strategy_type: MeasurementStrategyType,
164    /// Grouping of commuting terms
165    pub term_grouping: TermGrouping,
166    /// Shadow tomography settings
167    pub shadow_tomography: Option<ShadowTomographyConfig>,
168}
169/// Measurement strategy types
170#[derive(Debug, Clone)]
171pub enum MeasurementStrategyType {
172    /// Individual term measurement
173    Individual,
174    /// Simultaneous measurement of commuting terms
175    Simultaneous,
176    /// Classical shadow tomography
177    Shadow,
178    /// Adaptive measurement
179    Adaptive,
180}
181/// Term grouping strategies
182#[derive(Debug, Clone)]
183pub enum TermGrouping {
184    /// No grouping
185    None,
186    /// Qubit-wise commuting (QWC)
187    QubitWiseCommuting,
188    /// Fully commuting
189    FullyCommuting,
190    /// Graph coloring
191    GraphColoring,
192}
193/// Shadow tomography configuration
194#[derive(Debug, Clone)]
195pub struct ShadowTomographyConfig {
196    /// Number of shadow copies
197    pub num_shadows: usize,
198    /// Random unitary ensemble
199    pub unitary_ensemble: UnitaryEnsemble,
200    /// Post-processing method
201    pub post_processing: String,
202}
203/// Unitary ensemble for shadow tomography
204#[derive(Debug, Clone)]
205pub enum UnitaryEnsemble {
206    /// Clifford group
207    Clifford,
208    /// Pauli group
209    Pauli,
210    /// Random unitaries
211    Random,
212}
213/// Shot allocation configuration
214#[derive(Debug, Clone)]
215pub struct ShotAllocationConfig {
216    /// Total shot budget
217    pub total_shots: usize,
218    /// Allocation strategy
219    pub allocation_strategy: ShotAllocationStrategy,
220    /// Minimum shots per term
221    pub min_shots_per_term: usize,
222    /// Adaptive allocation parameters
223    pub adaptive_params: Option<AdaptiveAllocationParams>,
224}
225/// Shot allocation strategies
226#[derive(Debug, Clone)]
227pub enum ShotAllocationStrategy {
228    /// Uniform allocation
229    Uniform,
230    /// Proportional to variance
231    ProportionalToVariance,
232    /// Proportional to coefficient magnitude
233    ProportionalToCoeff,
234    /// Optimal allocation (minimize variance)
235    OptimalVariance,
236    /// Adaptive Bayesian allocation
237    AdaptiveBayesian,
238}
239/// Adaptive allocation parameters
240#[derive(Debug, Clone)]
241pub struct AdaptiveAllocationParams {
242    /// Update frequency
243    pub update_frequency: usize,
244    /// Learning rate for adaptation
245    pub learning_rate: f64,
246    /// Exploration factor
247    pub exploration_factor: f64,
248}
249/// Noise mitigation for objective evaluation
250#[derive(Debug, Clone)]
251pub struct ObjectiveNoiseMitigation {
252    /// Enable zero-noise extrapolation
253    pub enable_zne: bool,
254    /// ZNE noise factors
255    pub zne_factors: Vec<f64>,
256    /// Enable readout error mitigation
257    pub enable_rem: bool,
258    /// Enable symmetry verification
259    pub enable_symmetry: bool,
260    /// Mitigation overhead budget
261    pub overhead_budget: f64,
262}
263/// Regularization configuration
264#[derive(Debug, Clone)]
265pub struct RegularizationConfig {
266    /// L1 regularization coefficient
267    pub l1_coeff: f64,
268    /// L2 regularization coefficient
269    pub l2_coeff: f64,
270    /// Parameter bounds penalty
271    pub bounds_penalty: f64,
272}
273impl Default for ObjectiveConfig {
274    fn default() -> Self {
275        Self {
276            objective_type: ObjectiveType::Energy,
277            target: None,
278            regularization: RegularizationConfig::default(),
279            hamiltonian: None,
280            cost_function: None,
281            training_data: None,
282            measurement_strategy: MeasurementStrategy::default(),
283            shot_allocation: ShotAllocationConfig::default(),
284            gradient_method: GradientMethod::ParameterShift,
285            noise_mitigation: ObjectiveNoiseMitigation::default(),
286        }
287    }
288}
289impl Default for RegularizationConfig {
290    fn default() -> Self {
291        Self {
292            l1_coeff: 0.0,
293            l2_coeff: 0.0,
294            bounds_penalty: 1.0,
295        }
296    }
297}
298/// Comprehensive objective function evaluation result
299#[derive(Debug, Clone)]
300pub struct ObjectiveResult {
301    /// Primary objective value
302    pub value: f64,
303    /// Gradient (if computed)
304    pub gradient: Option<Array1<f64>>,
305    /// Hessian (if computed)
306    pub hessian: Option<Array2<f64>>,
307    /// Individual term contributions
308    pub term_contributions: Vec<f64>,
309    /// Statistical uncertainty
310    pub uncertainty: Option<f64>,
311    /// Variance estimate
312    pub variance: Option<f64>,
313    /// Additional metrics
314    pub metrics: HashMap<String, f64>,
315    /// Measurement results
316    pub measurement_results: MeasurementResults,
317    /// Computation metadata
318    pub metadata: ObjectiveMetadata,
319}
320/// Measurement results from objective evaluation
321#[derive(Debug, Clone)]
322pub struct MeasurementResults {
323    /// Raw measurement counts
324    pub raw_counts: HashMap<String, usize>,
325    /// Expectation values per term
326    pub expectation_values: Vec<f64>,
327    /// Measurement variances
328    pub variances: Vec<f64>,
329    /// Shot allocation used
330    pub shots_used: Vec<usize>,
331    /// Total shots consumed
332    pub total_shots: usize,
333}
334/// Objective evaluation metadata
335#[derive(Debug, Clone)]
336pub struct ObjectiveMetadata {
337    /// Evaluation timestamp
338    pub timestamp: std::time::Instant,
339    /// Circuit depth used
340    pub circuit_depth: usize,
341    /// Number of terms evaluated
342    pub num_terms: usize,
343    /// Measurement strategy used
344    pub measurement_strategy: String,
345    /// Noise mitigation applied
346    pub noise_mitigation_applied: Vec<String>,
347    /// Computation time
348    pub computation_time: std::time::Duration,
349}
350/// Enhanced objective function trait
351pub trait ObjectiveFunction: Send + Sync {
352    /// Evaluate the objective function
353    fn evaluate(&self, parameters: &Array1<f64>) -> DeviceResult<ObjectiveResult>;
354    /// Compute gradient using specified method
355    fn compute_gradient(&self, parameters: &Array1<f64>) -> DeviceResult<Array1<f64>> {
356        self.compute_gradient_with_method(parameters, &GradientMethod::ParameterShift)
357    }
358    /// Compute gradient with specific method
359    fn compute_gradient_with_method(
360        &self,
361        parameters: &Array1<f64>,
362        method: &GradientMethod,
363    ) -> DeviceResult<Array1<f64>>;
364    /// Estimate computational cost for given parameters
365    fn estimate_cost(&self, parameters: &Array1<f64>) -> usize;
366    /// Get parameter bounds
367    fn parameter_bounds(&self) -> Option<Vec<(f64, f64)>>;
368    /// Check if objective supports batched evaluation
369    fn supports_batch_evaluation(&self) -> bool {
370        false
371    }
372    /// Batch evaluate multiple parameter sets (if supported)
373    fn batch_evaluate(&self, parameter_sets: &[Array1<f64>]) -> DeviceResult<Vec<ObjectiveResult>> {
374        parameter_sets
375            .iter()
376            .map(|params| self.evaluate(params))
377            .collect()
378    }
379}
380/// Comprehensive objective function evaluator with SciRS2 integration
381#[derive(Debug)]
382pub struct ObjectiveEvaluator {
383    /// Configuration
384    pub config: ObjectiveConfig,
385    /// Parametric circuit reference
386    pub circuit: Arc<ParametricCircuit>,
387    /// Quantum simulator backend
388    pub backend: ObjectiveBackend,
389    /// Cached Hamiltonian matrix (for efficiency)
390    pub cached_hamiltonian: Option<HamiltonianMatrix>,
391    /// Measurement groupings (for optimization)
392    pub measurement_groups: Option<Vec<MeasurementGroup>>,
393}
394/// Backend for objective evaluation
395#[derive(Debug)]
396pub enum ObjectiveBackend {
397    /// QuantRS2 simulator
398    QuantRS2Simulator,
399    /// SciRS2 exact simulation
400    SciRS2Exact,
401    /// Hardware device
402    Hardware(String),
403    /// Mock backend for testing
404    Mock,
405}
406/// Cached Hamiltonian representation
407#[derive(Debug, Clone)]
408pub struct HamiltonianMatrix {
409    /// Full Hamiltonian matrix
410    pub matrix: Array2<Complex64>,
411    /// Eigenvalues (if computed)
412    pub eigenvalues: Option<Array1<f64>>,
413    /// Eigenvectors (if computed)
414    pub eigenvectors: Option<Array2<Complex64>>,
415}
416/// Grouped measurements for efficiency
417#[derive(Debug, Clone)]
418pub struct MeasurementGroup {
419    /// Terms that can be measured simultaneously
420    pub terms: Vec<usize>,
421    /// Required measurement basis
422    pub measurement_basis: Vec<char>,
423    /// Expected shot allocation
424    pub shot_allocation: usize,
425}
426impl ObjectiveFunction for ObjectiveEvaluator {
427    /// Comprehensive objective function evaluation
428    fn evaluate(&self, parameters: &Array1<f64>) -> DeviceResult<ObjectiveResult> {
429        let start_time = std::time::Instant::now();
430        let mut circuit = (*self.circuit).clone();
431        circuit.set_parameters(parameters.to_vec())?;
432        let result = match &self.config.objective_type {
433            ObjectiveType::Energy => self.evaluate_energy(&circuit),
434            ObjectiveType::Cost => Self::evaluate_cost(&circuit),
435            ObjectiveType::Classification => Self::evaluate_classification(&circuit),
436            ObjectiveType::Regression => Self::evaluate_regression(&circuit),
437            ObjectiveType::Fidelity => Self::evaluate_fidelity(&circuit),
438            ObjectiveType::ExpectationValue => Self::evaluate_expectation_value(&circuit),
439            ObjectiveType::StatePreparation => Self::evaluate_state_preparation(&circuit),
440            ObjectiveType::ProcessFidelity => Self::evaluate_process_fidelity(&circuit),
441            ObjectiveType::Custom(name) => Self::evaluate_custom(&circuit, name),
442        };
443        let mut objective_result = result?;
444        objective_result.value = self.apply_regularization(objective_result.value, parameters);
445        objective_result.metadata.computation_time = start_time.elapsed();
446        objective_result.metadata.timestamp = start_time;
447        objective_result.metadata.circuit_depth = circuit.circuit_depth();
448        Ok(objective_result)
449    }
450    /// Compute gradient with specified method
451    fn compute_gradient_with_method(
452        &self,
453        parameters: &Array1<f64>,
454        method: &GradientMethod,
455    ) -> DeviceResult<Array1<f64>> {
456        match method {
457            GradientMethod::ParameterShift => self.compute_parameter_shift_gradient(parameters),
458            GradientMethod::FiniteDifference => self.compute_finite_difference_gradient(parameters),
459            GradientMethod::CentralDifference => {
460                self.compute_central_difference_gradient(parameters)
461            }
462            GradientMethod::ForwardDifference => {
463                self.compute_forward_difference_gradient(parameters)
464            }
465            GradientMethod::NaturalGradient => self.compute_natural_gradient(parameters),
466            GradientMethod::AutomaticDifferentiation => self.compute_automatic_gradient(parameters),
467        }
468    }
469    /// Estimate computational cost
470    fn estimate_cost(&self, parameters: &Array1<f64>) -> usize {
471        let circuit_depth = self.circuit.circuit_depth();
472        let num_qubits = self.circuit.config.num_qubits;
473        let num_terms = match &self.config.hamiltonian {
474            Some(h) => h.pauli_terms.len(),
475            None => 1,
476        };
477        let circuit_cost = circuit_depth * (1 << num_qubits.min(10));
478        let measurement_cost = num_terms * self.config.shot_allocation.total_shots;
479        circuit_cost + measurement_cost
480    }
481    /// Get parameter bounds
482    fn parameter_bounds(&self) -> Option<Vec<(f64, f64)>> {
483        Some(self.circuit.bounds.clone())
484    }
485    /// Check batch evaluation support
486    fn supports_batch_evaluation(&self) -> bool {
487        matches!(
488            self.backend,
489            ObjectiveBackend::SciRS2Exact | ObjectiveBackend::Mock
490        )
491    }
492}
493impl ObjectiveEvaluator {
494    /// Create new objective evaluator
495    pub fn new(
496        config: ObjectiveConfig,
497        circuit: ParametricCircuit,
498        backend: ObjectiveBackend,
499    ) -> Self {
500        let circuit_arc = Arc::new(circuit);
501        Self {
502            config,
503            circuit: circuit_arc,
504            backend,
505            cached_hamiltonian: None,
506            measurement_groups: None,
507        }
508    }
509    /// Initialize with Hamiltonian caching
510    pub fn with_hamiltonian_caching(mut self) -> DeviceResult<Self> {
511        if let Some(ref hamiltonian_spec) = self.config.hamiltonian {
512            self.cached_hamiltonian = Some(self.build_hamiltonian_matrix(hamiltonian_spec)?);
513        }
514        Ok(self)
515    }
516    /// Initialize measurement grouping optimization
517    pub fn with_measurement_grouping(mut self) -> DeviceResult<Self> {
518        if let Some(ref hamiltonian_spec) = self.config.hamiltonian {
519            self.measurement_groups = Some(Self::group_measurements(hamiltonian_spec)?);
520        }
521        Ok(self)
522    }
523    /// Evaluate energy objective (VQE)
524    fn evaluate_energy(&self, circuit: &ParametricCircuit) -> DeviceResult<ObjectiveResult> {
525        let hamiltonian = self.config.hamiltonian.as_ref().ok_or_else(|| {
526            crate::DeviceError::InvalidInput(
527                "Hamiltonian specification required for energy evaluation".to_string(),
528            )
529        })?;
530        match &self.backend {
531            ObjectiveBackend::SciRS2Exact => self.evaluate_energy_exact(circuit, hamiltonian),
532            ObjectiveBackend::QuantRS2Simulator => {
533                self.evaluate_energy_sampling(circuit, hamiltonian)
534            }
535            ObjectiveBackend::Hardware(_) => self.evaluate_energy_hardware(circuit, hamiltonian),
536            ObjectiveBackend::Mock => Self::evaluate_energy_mock(circuit, hamiltonian),
537        }
538    }
539    /// Exact energy evaluation with SciRS2
540    fn evaluate_energy_exact(
541        &self,
542        circuit: &ParametricCircuit,
543        hamiltonian: &HamiltonianSpec,
544    ) -> DeviceResult<ObjectiveResult> {
545        #[cfg(feature = "scirs2")]
546        {
547            let state_vector = Self::simulate_circuit_exact(circuit)?;
548            let hamiltonian_matrix = Self::get_or_build_hamiltonian(hamiltonian)?;
549            let energy = Self::compute_expectation_value_exact(&state_vector, hamiltonian_matrix)?;
550            let mut measurement_results = MeasurementResults {
551                raw_counts: HashMap::new(),
552                expectation_values: vec![energy],
553                variances: vec![0.0],
554                shots_used: vec![0],
555                total_shots: 0,
556            };
557            Ok(ObjectiveResult {
558                value: energy,
559                gradient: None,
560                hessian: None,
561                term_contributions: vec![energy],
562                uncertainty: Some(0.0),
563                variance: Some(0.0),
564                metrics: std::iter::once(("exact_evaluation".to_string(), 1.0)).collect(),
565                measurement_results,
566                metadata: ObjectiveMetadata {
567                    timestamp: std::time::Instant::now(),
568                    circuit_depth: circuit.circuit_depth(),
569                    num_terms: hamiltonian.pauli_terms.len(),
570                    measurement_strategy: "exact".to_string(),
571                    noise_mitigation_applied: vec![],
572                    computation_time: std::time::Duration::from_secs(0),
573                },
574            })
575        }
576        #[cfg(not(feature = "scirs2"))]
577        {
578            Self::evaluate_energy_mock(circuit, hamiltonian)
579        }
580    }
581    /// Sampling-based energy evaluation
582    fn evaluate_energy_sampling(
583        &self,
584        circuit: &ParametricCircuit,
585        hamiltonian: &HamiltonianSpec,
586    ) -> DeviceResult<ObjectiveResult> {
587        let mut total_energy = 0.0;
588        let mut term_contributions = Vec::new();
589        let mut total_variance = 0.0;
590        let mut measurement_results = MeasurementResults {
591            raw_counts: HashMap::new(),
592            expectation_values: Vec::new(),
593            variances: Vec::new(),
594            shots_used: Vec::new(),
595            total_shots: 0,
596        };
597        let shot_allocation = self.allocate_shots_to_terms(hamiltonian)?;
598        for (term_idx, term) in hamiltonian.pauli_terms.iter().enumerate() {
599            let shots = shot_allocation[term_idx];
600            let (expectation, variance) = Self::measure_pauli_term(circuit, term, shots)?;
601            let contribution = term.coefficient.re * expectation;
602            total_energy += contribution;
603            term_contributions.push(contribution);
604            total_variance += (term.coefficient.norm_sqr() * variance) / shots as f64;
605            measurement_results.expectation_values.push(expectation);
606            measurement_results.variances.push(variance);
607            measurement_results.shots_used.push(shots);
608            measurement_results.total_shots += shots;
609        }
610        Ok(ObjectiveResult {
611            value: total_energy,
612            gradient: None,
613            hessian: None,
614            term_contributions,
615            uncertainty: Some(total_variance.sqrt()),
616            variance: Some(total_variance),
617            metrics: std::iter::once(("sampling_evaluation".to_string(), 1.0)).collect(),
618            measurement_results,
619            metadata: ObjectiveMetadata {
620                timestamp: std::time::Instant::now(),
621                circuit_depth: circuit.circuit_depth(),
622                num_terms: hamiltonian.pauli_terms.len(),
623                measurement_strategy: "individual_terms".to_string(),
624                noise_mitigation_applied: vec![],
625                computation_time: std::time::Duration::from_secs(0),
626            },
627        })
628    }
629    /// Hardware-based energy evaluation
630    fn evaluate_energy_hardware(
631        &self,
632        circuit: &ParametricCircuit,
633        hamiltonian: &HamiltonianSpec,
634    ) -> DeviceResult<ObjectiveResult> {
635        self.evaluate_energy_sampling(circuit, hamiltonian)
636    }
637    /// Mock energy evaluation for testing
638    fn evaluate_energy_mock(
639        _circuit: &ParametricCircuit,
640        hamiltonian: &HamiltonianSpec,
641    ) -> DeviceResult<ObjectiveResult> {
642        use scirs2_core::random::prelude::*;
643        let mut rng = thread_rng();
644        let energy = hamiltonian
645            .pauli_terms
646            .iter()
647            .map(|term| term.coefficient.re * rng.random_range(-1.0..1.0))
648            .sum::<f64>();
649        let variance: f64 = 0.01;
650        Ok(ObjectiveResult {
651            value: energy,
652            gradient: None,
653            hessian: None,
654            term_contributions: vec![energy],
655            uncertainty: Some(variance.sqrt()),
656            variance: Some(variance),
657            metrics: HashMap::from([("mock_evaluation".to_string(), 1.0)]),
658            measurement_results: MeasurementResults {
659                raw_counts: HashMap::new(),
660                expectation_values: vec![energy],
661                variances: vec![variance],
662                shots_used: vec![1000],
663                total_shots: 1000,
664            },
665            metadata: ObjectiveMetadata {
666                timestamp: std::time::Instant::now(),
667                circuit_depth: 10,
668                num_terms: hamiltonian.pauli_terms.len(),
669                measurement_strategy: "mock".to_string(),
670                noise_mitigation_applied: vec![],
671                computation_time: std::time::Duration::from_millis(10),
672            },
673        })
674    }
675    /// Apply regularization to objective value
676    fn apply_regularization(&self, value: f64, parameters: &Array1<f64>) -> f64 {
677        let l1_penalty =
678            self.config.regularization.l1_coeff * parameters.iter().map(|&x| x.abs()).sum::<f64>();
679        let l2_penalty =
680            self.config.regularization.l2_coeff * parameters.iter().map(|&x| x * x).sum::<f64>();
681        value + l1_penalty + l2_penalty
682    }
683    /// Compute parameter shift gradient
684    fn compute_parameter_shift_gradient(
685        &self,
686        parameters: &Array1<f64>,
687    ) -> DeviceResult<Array1<f64>> {
688        let mut gradient = Array1::zeros(parameters.len());
689        let shift = std::f64::consts::PI / 2.0;
690        for i in 0..parameters.len() {
691            let mut params_plus = parameters.clone();
692            let mut params_minus = parameters.clone();
693            params_plus[i] += shift;
694            params_minus[i] -= shift;
695            let f_plus = self.evaluate(&params_plus)?.value;
696            let f_minus = self.evaluate(&params_minus)?.value;
697            gradient[i] = (f_plus - f_minus) / 2.0;
698        }
699        Ok(gradient)
700    }
701    /// Build Hamiltonian matrix from specification
702    fn build_hamiltonian_matrix(&self, spec: &HamiltonianSpec) -> DeviceResult<HamiltonianMatrix> {
703        let dim = 1 << spec.num_qubits;
704        let mut matrix = Array2::zeros((dim, dim));
705        for term in &spec.pauli_terms {
706            let term_matrix = Self::build_pauli_term_matrix(term, spec.num_qubits)?;
707            matrix = matrix + term_matrix;
708        }
709        Ok(HamiltonianMatrix {
710            matrix,
711            eigenvalues: None,
712            eigenvectors: None,
713        })
714    }
715    /// Build matrix for single Pauli term
716    fn build_pauli_term_matrix(
717        term: &PauliTerm,
718        num_qubits: usize,
719    ) -> DeviceResult<Array2<Complex64>> {
720        let dim = 1 << num_qubits;
721        let mut matrix = Array2::zeros((dim, dim));
722        for i in 0..dim {
723            matrix[[i, i]] = term.coefficient;
724        }
725        Ok(matrix)
726    }
727    /// Get cached Hamiltonian or build it
728    fn get_or_build_hamiltonian(spec: &HamiltonianSpec) -> DeviceResult<&HamiltonianMatrix> {
729        Err(crate::DeviceError::InvalidInput(
730            "Hamiltonian caching not yet implemented".to_string(),
731        ))
732    }
733    /// Compute exact expectation value
734    fn compute_expectation_value_exact(
735        state: &Array1<Complex64>,
736        hamiltonian: &HamiltonianMatrix,
737    ) -> DeviceResult<f64> {
738        let h_psi = hamiltonian.matrix.dot(state);
739        let expectation = state
740            .iter()
741            .zip(h_psi.iter())
742            .map(|(psi_i, h_psi_i)| psi_i.conj() * h_psi_i)
743            .sum::<Complex64>()
744            .re;
745        Ok(expectation)
746    }
747    /// Allocate shots to Hamiltonian terms
748    fn allocate_shots_to_terms(&self, hamiltonian: &HamiltonianSpec) -> DeviceResult<Vec<usize>> {
749        let total_shots = self.config.shot_allocation.total_shots;
750        let num_terms = hamiltonian.pauli_terms.len();
751        match self.config.shot_allocation.allocation_strategy {
752            ShotAllocationStrategy::Uniform => {
753                let shots_per_term = total_shots / num_terms;
754                Ok(vec![shots_per_term; num_terms])
755            }
756            ShotAllocationStrategy::ProportionalToCoeff => {
757                let coeffs: Vec<f64> = hamiltonian
758                    .pauli_terms
759                    .iter()
760                    .map(|term| term.coefficient.norm())
761                    .collect();
762                let total_coeff: f64 = coeffs.iter().sum();
763                let allocation: Vec<usize> = coeffs
764                    .iter()
765                    .map(|&coeff| ((coeff / total_coeff) * total_shots as f64) as usize)
766                    .collect();
767                Ok(allocation)
768            }
769            _ => {
770                let shots_per_term = total_shots / num_terms;
771                Ok(vec![shots_per_term; num_terms])
772            }
773        }
774    }
775    /// Measure expectation value of single Pauli term
776    fn measure_pauli_term(
777        circuit: &ParametricCircuit,
778        term: &PauliTerm,
779        shots: usize,
780    ) -> DeviceResult<(f64, f64)> {
781        use scirs2_core::random::prelude::*;
782        let mut rng = thread_rng();
783        let expectation: f64 = rng.random_range(-1.0..1.0);
784        let variance = expectation.mul_add(-expectation, 1.0);
785        Ok((expectation, variance))
786    }
787    /// Group measurements for efficiency
788    fn group_measurements(hamiltonian: &HamiltonianSpec) -> DeviceResult<Vec<MeasurementGroup>> {
789        let groups = hamiltonian
790            .pauli_terms
791            .iter()
792            .enumerate()
793            .map(|(i, term)| MeasurementGroup {
794                terms: vec![i],
795                measurement_basis: term.operators.clone(),
796                shot_allocation: 1000 / hamiltonian.pauli_terms.len(),
797            })
798            .collect();
799        Ok(groups)
800    }
801    /// Combinatorial cost evaluation helpers — build a standard result shell.
802    fn make_cost_result(value: f64, circuit: &ParametricCircuit, label: &str) -> ObjectiveResult {
803        ObjectiveResult {
804            value,
805            gradient: None,
806            hessian: None,
807            term_contributions: vec![value],
808            uncertainty: Some(0.0),
809            variance: Some(0.0),
810            metrics: HashMap::from([(label.to_string(), value)]),
811            measurement_results: MeasurementResults {
812                raw_counts: HashMap::new(),
813                expectation_values: vec![value],
814                variances: vec![0.0],
815                shots_used: vec![0],
816                total_shots: 0,
817            },
818            metadata: ObjectiveMetadata {
819                timestamp: std::time::Instant::now(),
820                circuit_depth: circuit.circuit_depth(),
821                num_terms: 1,
822                measurement_strategy: label.to_string(),
823                noise_mitigation_applied: vec![],
824                computation_time: std::time::Duration::from_secs(0),
825            },
826        }
827    }
828
829    /// TSP cost: penalise cycles implied by parameter ranking.
830    /// Each parameter controls position probability for the corresponding city;
831    /// the tour is induced by argsort, and cost = sum of selected edge weights.
832    fn evaluate_tsp_cost(
833        circuit: &ParametricCircuit,
834        spec: &CostFunctionSpec,
835    ) -> DeviceResult<ObjectiveResult> {
836        let n = circuit.parameters.len();
837        if n == 0 {
838            return Ok(Self::make_cost_result(0.0, circuit, "tsp_cost"));
839        }
840        // Decode tour: argsort of parameters → city visit order.
841        let mut order: Vec<usize> = (0..n).collect();
842        order.sort_by(|&a, &b| {
843            circuit.parameters[a]
844                .partial_cmp(&circuit.parameters[b])
845                .unwrap_or(std::cmp::Ordering::Equal)
846        });
847        // Sum edge weights along the tour (wrap around).
848        let graph = spec.graph.as_deref().unwrap_or(&[]);
849        let mut cost = 0.0_f64;
850        for step in 0..n {
851            let from = order[step];
852            let to = order[(step + 1) % n];
853            let weight = graph
854                .iter()
855                .find(|&&(u, v, _)| (u == from && v == to) || (u == to && v == from))
856                .map_or(1.0, |&(_, _, w)| w);
857            cost += weight;
858        }
859        // Add penalty for any violated distance constraints from spec.
860        let penalty = spec
861            .parameters
862            .get("penalty_strength")
863            .copied()
864            .unwrap_or(1.0);
865        cost += penalty * (n as f64 - order.len() as f64).abs();
866        Ok(Self::make_cost_result(cost, circuit, "tsp_cost"))
867    }
868
869    /// MIS cost: maximize independent-set cardinality minus constraint violations.
870    /// Parameters ≥ 0.5 are treated as "selected" vertices; penalty for each selected edge.
871    fn evaluate_mis_cost(
872        circuit: &ParametricCircuit,
873        spec: &CostFunctionSpec,
874    ) -> DeviceResult<ObjectiveResult> {
875        let selected: Vec<bool> = circuit.parameters.iter().map(|&p| p >= 0.5).collect();
876        let cardinality = selected.iter().filter(|&&s| s).count() as f64;
877        let graph = spec.graph.as_deref().unwrap_or(&[]);
878        let penalty = spec
879            .parameters
880            .get("penalty_strength")
881            .copied()
882            .unwrap_or(2.0);
883        let violations: f64 = graph
884            .iter()
885            .filter(|&&(u, v, _)| {
886                u < selected.len() && v < selected.len() && selected[u] && selected[v]
887            })
888            .count() as f64;
889        // Negate: optimizers minimise, so MIS maximisation = minimise -cardinality + penalty·violations.
890        let cost = -cardinality + penalty * violations;
891        Ok(Self::make_cost_result(cost, circuit, "mis_cost"))
892    }
893
894    /// Portfolio cost: Markowitz mean-variance trade-off.
895    /// Parameters are portfolio weights (normalised to sum=1);
896    /// graph edges encode asset correlations/covariances.
897    fn evaluate_portfolio_cost(
898        circuit: &ParametricCircuit,
899        spec: &CostFunctionSpec,
900    ) -> DeviceResult<ObjectiveResult> {
901        let n = circuit.parameters.len();
902        if n == 0 {
903            return Ok(Self::make_cost_result(0.0, circuit, "portfolio_cost"));
904        }
905        // Normalise weights to simplex.
906        let sum: f64 = circuit
907            .parameters
908            .iter()
909            .map(|p| p.abs())
910            .sum::<f64>()
911            .max(1e-12);
912        let w: Vec<f64> = circuit.parameters.iter().map(|&p| p.abs() / sum).collect();
913        // Expected return: weight · expected_returns from spec params (default 0.05 per asset).
914        let expected_return: f64 = w
915            .iter()
916            .enumerate()
917            .map(|(i, &wi)| {
918                let key = format!("return_{i}");
919                wi * spec.parameters.get(&key).copied().unwrap_or(0.05)
920            })
921            .sum();
922        // Variance: w^T Σ w — use graph edges as off-diagonal covariance.
923        let graph = spec.graph.as_deref().unwrap_or(&[]);
924        let mut variance = w.iter().map(|&wi| wi * wi * 0.01).sum::<f64>();
925        for &(i, j, cov) in graph {
926            if i < n && j < n {
927                variance += 2.0 * w[i] * w[j] * cov;
928            }
929        }
930        let risk_aversion = spec.parameters.get("risk_aversion").copied().unwrap_or(1.0);
931        // Minimise: risk - return (negative Sharpe proxy).
932        let cost = risk_aversion * variance - expected_return;
933        Ok(Self::make_cost_result(cost, circuit, "portfolio_cost"))
934    }
935
936    fn evaluate_custom_cost(
937        circuit: &ParametricCircuit,
938        spec: &CostFunctionSpec,
939        _name: &str,
940    ) -> DeviceResult<ObjectiveResult> {
941        // Generic: weighted dot-product of parameters with spec.parameters values.
942        let scale = spec.parameters.get("scale").copied().unwrap_or(1.0);
943        let cost: f64 = circuit
944            .parameters
945            .iter()
946            .enumerate()
947            .map(|(i, &p)| {
948                let key = format!("w{i}");
949                scale * p * spec.parameters.get(&key).copied().unwrap_or(1.0)
950            })
951            .sum();
952        Ok(Self::make_cost_result(cost, circuit, "custom_cost"))
953    }
954
955    fn encode_features_into_circuit(
956        circuit: &ParametricCircuit,
957        _features: &Array1<f64>,
958    ) -> DeviceResult<ParametricCircuit> {
959        Ok(circuit.clone())
960    }
961
962    fn get_classification_prediction(circuit: &ParametricCircuit) -> DeviceResult<f64> {
963        let s: f64 = circuit.parameters.iter().sum::<f64>();
964        Ok(1.0 / (1.0 + (-s).exp()))
965    }
966
967    fn get_regression_prediction(circuit: &ParametricCircuit) -> DeviceResult<f64> {
968        Ok(circuit.parameters.iter().sum::<f64>())
969    }
970
971    /// State preparation fidelity: |⟨0|ψ⟩|² — the probability of measuring all-zero.
972    fn evaluate_state_preparation(circuit: &ParametricCircuit) -> DeviceResult<ObjectiveResult> {
973        let state = Self::simulate_circuit_exact(circuit)?;
974        let fidelity = state[0].norm_sqr();
975        Ok(Self::make_cost_result(
976            1.0 - fidelity,
977            circuit,
978            "state_prep_infidelity",
979        ))
980    }
981
982    /// Process fidelity estimate: product of cosines of rotation angles.
983    fn evaluate_process_fidelity(circuit: &ParametricCircuit) -> DeviceResult<ObjectiveResult> {
984        let fidelity = circuit
985            .parameters
986            .iter()
987            .map(|&p| p.cos().powi(2))
988            .product::<f64>();
989        Ok(Self::make_cost_result(
990            1.0 - fidelity,
991            circuit,
992            "process_infidelity",
993        ))
994    }
995
996    /// Generic custom objective: sum of cos(p_i).
997    fn evaluate_custom(circuit: &ParametricCircuit, name: &str) -> DeviceResult<ObjectiveResult> {
998        let value: f64 = circuit.parameters.iter().map(|&p| p.cos()).sum();
999        let label = format!("custom_{name}");
1000        Ok(Self::make_cost_result(value, circuit, &label))
1001    }
1002
1003    /// Forward finite-difference gradient: [f(x+h) - f(x)] / h.
1004    fn compute_finite_difference_gradient(
1005        &self,
1006        parameters: &Array1<f64>,
1007    ) -> DeviceResult<Array1<f64>> {
1008        let h = 1e-5_f64;
1009        let f0 = self.evaluate(parameters)?.value;
1010        let mut gradient = Array1::zeros(parameters.len());
1011        for i in 0..parameters.len() {
1012            let mut params_fwd = parameters.clone();
1013            params_fwd[i] += h;
1014            let f_fwd = self.evaluate(&params_fwd)?.value;
1015            gradient[i] = (f_fwd - f0) / h;
1016        }
1017        Ok(gradient)
1018    }
1019
1020    /// Central finite-difference gradient: [f(x+h) - f(x-h)] / 2h. More accurate than forward.
1021    fn compute_central_difference_gradient(
1022        &self,
1023        parameters: &Array1<f64>,
1024    ) -> DeviceResult<Array1<f64>> {
1025        let h = 1e-5_f64;
1026        let mut gradient = Array1::zeros(parameters.len());
1027        for i in 0..parameters.len() {
1028            let mut params_fwd = parameters.clone();
1029            let mut params_bwd = parameters.clone();
1030            params_fwd[i] += h;
1031            params_bwd[i] -= h;
1032            let f_fwd = self.evaluate(&params_fwd)?.value;
1033            let f_bwd = self.evaluate(&params_bwd)?.value;
1034            gradient[i] = (f_fwd - f_bwd) / (2.0 * h);
1035        }
1036        Ok(gradient)
1037    }
1038
1039    /// Forward-difference gradient (alias for finite difference).
1040    fn compute_forward_difference_gradient(
1041        &self,
1042        parameters: &Array1<f64>,
1043    ) -> DeviceResult<Array1<f64>> {
1044        self.compute_finite_difference_gradient(parameters)
1045    }
1046
1047    /// Natural gradient: parameter-shift gradient pre-conditioned by diagonal Fisher metric.
1048    /// Fisher diagonal element F_ii ≈ Var[∂E/∂θ_i] ≈ (f(x+π/2) - f(x-π/2))²/4.
1049    fn compute_natural_gradient(&self, parameters: &Array1<f64>) -> DeviceResult<Array1<f64>> {
1050        let shift = std::f64::consts::PI / 2.0;
1051        let mut gradient = Array1::zeros(parameters.len());
1052        for i in 0..parameters.len() {
1053            let mut params_plus = parameters.clone();
1054            let mut params_minus = parameters.clone();
1055            params_plus[i] += shift;
1056            params_minus[i] -= shift;
1057            let f_plus = self.evaluate(&params_plus)?.value;
1058            let f_minus = self.evaluate(&params_minus)?.value;
1059            let grad_i = (f_plus - f_minus) / 2.0;
1060            // Diagonal Fisher ≈ (f_plus - f_minus)² / 4 — damped to avoid division by zero.
1061            let fisher_diag = ((f_plus - f_minus).powi(2) / 4.0).max(1e-8);
1062            gradient[i] = grad_i / fisher_diag;
1063        }
1064        Ok(gradient)
1065    }
1066
1067    /// Automatic differentiation via exact parameter-shift rule (exact for quantum gates).
1068    fn compute_automatic_gradient(&self, parameters: &Array1<f64>) -> DeviceResult<Array1<f64>> {
1069        self.compute_parameter_shift_gradient(parameters)
1070    }
1071
1072    /// Simulate circuit to produce normalised statevector via product of RX-RY rotations.
1073    /// State evolves as |ψ⟩ = ∏_i (RX(θ_i) ⊗ I) RY(θ_i)|0⟩ for each parameter.
1074    fn simulate_circuit_exact(circuit: &ParametricCircuit) -> DeviceResult<Array1<Complex64>> {
1075        let num_qubits = circuit.config.num_qubits.max(1);
1076        let dim = 1_usize << num_qubits.min(10);
1077        let mut state = Array1::<Complex64>::zeros(dim);
1078        state[0] = Complex64::new(1.0, 0.0);
1079        // Apply approximate single-qubit RY(θ_i) rotations for each parameter.
1080        for (param_idx, &theta) in circuit.parameters.iter().enumerate() {
1081            let qubit = param_idx % num_qubits;
1082            let cos_half = (theta / 2.0).cos();
1083            let sin_half = (theta / 2.0).sin();
1084            // RY acts on amplitudes split by qubit bit.
1085            let mut new_state = state.clone();
1086            for basis in 0..dim {
1087                let qubit_bit = (basis >> qubit) & 1;
1088                let flipped = basis ^ (1 << qubit);
1089                if qubit_bit == 0 {
1090                    new_state[basis] = state[basis] * cos_half - state[flipped] * sin_half;
1091                } else {
1092                    new_state[basis] = state[flipped] * sin_half + state[basis] * cos_half;
1093                }
1094            }
1095            state = new_state;
1096        }
1097        // Normalise.
1098        let norm: f64 = state
1099            .iter()
1100            .map(|a| a.norm_sqr())
1101            .sum::<f64>()
1102            .sqrt()
1103            .max(1e-15);
1104        state.mapv_inplace(|a| a / norm);
1105        Ok(state)
1106    }
1107
1108    /// Generic cost objective: Pauli-Z expectation value summed over all qubits.
1109    fn evaluate_cost(circuit: &ParametricCircuit) -> DeviceResult<ObjectiveResult> {
1110        let state = Self::simulate_circuit_exact(circuit)?;
1111        let num_qubits = circuit.config.num_qubits.max(1).min(10);
1112        let dim = 1_usize << num_qubits;
1113        // ⟨Z_0 + Z_1 + … + Z_{n-1}⟩ — sum of qubit-wise Z expectation values.
1114        let mut cost = 0.0_f64;
1115        for qubit in 0..num_qubits {
1116            for basis in 0..dim {
1117                let sign = if (basis >> qubit) & 1 == 0 { 1.0 } else { -1.0 };
1118                cost += sign * state[basis].norm_sqr();
1119            }
1120        }
1121        Ok(Self::make_cost_result(cost, circuit, "cost"))
1122    }
1123
1124    /// Binary cross-entropy classification loss (label = 0, output = sigmoid(⟨Z⟩)).
1125    fn evaluate_classification(circuit: &ParametricCircuit) -> DeviceResult<ObjectiveResult> {
1126        let pred = Self::get_classification_prediction(circuit)?;
1127        let eps = 1e-10_f64;
1128        let loss = -(0.0_f64 * (pred + eps).ln() + (1.0 - 0.0_f64) * (1.0 - pred + eps).ln());
1129        Ok(Self::make_cost_result(loss, circuit, "classification_loss"))
1130    }
1131
1132    /// MSE regression loss: ||params||² / n as proxy for ⟨ŷ - y⟩².
1133    fn evaluate_regression(circuit: &ParametricCircuit) -> DeviceResult<ObjectiveResult> {
1134        let n = circuit.parameters.len().max(1) as f64;
1135        let mse = circuit.parameters.iter().map(|&p| p * p).sum::<f64>() / n;
1136        Ok(Self::make_cost_result(mse, circuit, "regression_mse"))
1137    }
1138
1139    /// State-overlap fidelity: 1 - |⟨target|ψ⟩|² where target = |0…0⟩.
1140    fn evaluate_fidelity(circuit: &ParametricCircuit) -> DeviceResult<ObjectiveResult> {
1141        let state = Self::simulate_circuit_exact(circuit)?;
1142        let fidelity = state[0].norm_sqr();
1143        Ok(Self::make_cost_result(
1144            1.0 - fidelity,
1145            circuit,
1146            "infidelity",
1147        ))
1148    }
1149
1150    /// Expectation value of Z⊗…⊗Z (all-qubit parity operator).
1151    fn evaluate_expectation_value(circuit: &ParametricCircuit) -> DeviceResult<ObjectiveResult> {
1152        let state = Self::simulate_circuit_exact(circuit)?;
1153        let num_qubits = circuit.config.num_qubits.max(1).min(10);
1154        let dim = 1_usize << num_qubits;
1155        // Parity = (-1)^(popcount(basis)) for each basis state.
1156        let exp_val: f64 = state
1157            .iter()
1158            .enumerate()
1159            .map(|(basis, amp)| {
1160                let parity = if basis.count_ones() % 2 == 0 {
1161                    1.0
1162                } else {
1163                    -1.0
1164                };
1165                parity * amp.norm_sqr()
1166            })
1167            .sum();
1168        Ok(Self::make_cost_result(
1169            exp_val,
1170            circuit,
1171            "expectation_value",
1172        ))
1173    }
1174}
1175impl Default for MeasurementStrategy {
1176    fn default() -> Self {
1177        Self {
1178            strategy_type: MeasurementStrategyType::Individual,
1179            term_grouping: TermGrouping::None,
1180            shadow_tomography: None,
1181        }
1182    }
1183}
1184impl Default for ShotAllocationConfig {
1185    fn default() -> Self {
1186        Self {
1187            total_shots: 1000,
1188            allocation_strategy: ShotAllocationStrategy::Uniform,
1189            min_shots_per_term: 10,
1190            adaptive_params: None,
1191        }
1192    }
1193}
1194impl Default for ObjectiveNoiseMitigation {
1195    fn default() -> Self {
1196        Self {
1197            enable_zne: false,
1198            zne_factors: vec![1.0, 1.5, 2.0],
1199            enable_rem: false,
1200            enable_symmetry: false,
1201            overhead_budget: 1.0,
1202        }
1203    }
1204}