1use 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#[derive(Debug, Clone)]
35pub struct ObjectiveConfig {
36 pub objective_type: ObjectiveType,
38 pub target: Option<f64>,
40 pub regularization: RegularizationConfig,
42 pub hamiltonian: Option<HamiltonianSpec>,
44 pub cost_function: Option<CostFunctionSpec>,
46 pub training_data: Option<TrainingDataSpec>,
48 pub measurement_strategy: MeasurementStrategy,
50 pub shot_allocation: ShotAllocationConfig,
52 pub gradient_method: GradientMethod,
54 pub noise_mitigation: ObjectiveNoiseMitigation,
56}
57#[derive(Debug, Clone)]
59pub enum ObjectiveType {
60 Energy,
62 Fidelity,
64 Cost,
66 Classification,
68 Regression,
70 ExpectationValue,
72 StatePreparation,
74 ProcessFidelity,
76 Custom(String),
78}
79#[derive(Debug, Clone)]
81pub struct HamiltonianSpec {
82 pub pauli_terms: Vec<PauliTerm>,
84 pub num_qubits: usize,
86 pub use_sparse: bool,
88}
89#[derive(Debug, Clone)]
91pub struct PauliTerm {
92 pub coefficient: Complex64,
94 pub operators: Vec<char>,
96 pub indices: Option<Vec<usize>>,
98}
99#[derive(Debug, Clone)]
101pub struct CostFunctionSpec {
102 pub function_type: CostFunctionType,
104 pub parameters: HashMap<String, f64>,
106 pub graph: Option<Vec<(usize, usize, f64)>>,
108}
109#[derive(Debug, Clone)]
111pub enum CostFunctionType {
112 MaxCut,
114 TSP,
116 MaxIndependentSet,
118 Portfolio,
120 Custom(String),
122}
123#[derive(Debug, Clone)]
125pub struct TrainingDataSpec {
126 pub features: Array2<f64>,
128 pub targets: Array1<f64>,
130 pub encoding: DataEncoding,
132 pub loss_function: LossFunction,
134}
135#[derive(Debug, Clone)]
137pub enum DataEncoding {
138 Amplitude,
140 Angle,
142 Basis,
144 IQP,
146}
147#[derive(Debug, Clone)]
149pub enum LossFunction {
150 MSE,
152 CrossEntropy,
154 Hinge,
156 Custom(String),
158}
159#[derive(Debug, Clone)]
161pub struct MeasurementStrategy {
162 pub strategy_type: MeasurementStrategyType,
164 pub term_grouping: TermGrouping,
166 pub shadow_tomography: Option<ShadowTomographyConfig>,
168}
169#[derive(Debug, Clone)]
171pub enum MeasurementStrategyType {
172 Individual,
174 Simultaneous,
176 Shadow,
178 Adaptive,
180}
181#[derive(Debug, Clone)]
183pub enum TermGrouping {
184 None,
186 QubitWiseCommuting,
188 FullyCommuting,
190 GraphColoring,
192}
193#[derive(Debug, Clone)]
195pub struct ShadowTomographyConfig {
196 pub num_shadows: usize,
198 pub unitary_ensemble: UnitaryEnsemble,
200 pub post_processing: String,
202}
203#[derive(Debug, Clone)]
205pub enum UnitaryEnsemble {
206 Clifford,
208 Pauli,
210 Random,
212}
213#[derive(Debug, Clone)]
215pub struct ShotAllocationConfig {
216 pub total_shots: usize,
218 pub allocation_strategy: ShotAllocationStrategy,
220 pub min_shots_per_term: usize,
222 pub adaptive_params: Option<AdaptiveAllocationParams>,
224}
225#[derive(Debug, Clone)]
227pub enum ShotAllocationStrategy {
228 Uniform,
230 ProportionalToVariance,
232 ProportionalToCoeff,
234 OptimalVariance,
236 AdaptiveBayesian,
238}
239#[derive(Debug, Clone)]
241pub struct AdaptiveAllocationParams {
242 pub update_frequency: usize,
244 pub learning_rate: f64,
246 pub exploration_factor: f64,
248}
249#[derive(Debug, Clone)]
251pub struct ObjectiveNoiseMitigation {
252 pub enable_zne: bool,
254 pub zne_factors: Vec<f64>,
256 pub enable_rem: bool,
258 pub enable_symmetry: bool,
260 pub overhead_budget: f64,
262}
263#[derive(Debug, Clone)]
265pub struct RegularizationConfig {
266 pub l1_coeff: f64,
268 pub l2_coeff: f64,
270 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#[derive(Debug, Clone)]
300pub struct ObjectiveResult {
301 pub value: f64,
303 pub gradient: Option<Array1<f64>>,
305 pub hessian: Option<Array2<f64>>,
307 pub term_contributions: Vec<f64>,
309 pub uncertainty: Option<f64>,
311 pub variance: Option<f64>,
313 pub metrics: HashMap<String, f64>,
315 pub measurement_results: MeasurementResults,
317 pub metadata: ObjectiveMetadata,
319}
320#[derive(Debug, Clone)]
322pub struct MeasurementResults {
323 pub raw_counts: HashMap<String, usize>,
325 pub expectation_values: Vec<f64>,
327 pub variances: Vec<f64>,
329 pub shots_used: Vec<usize>,
331 pub total_shots: usize,
333}
334#[derive(Debug, Clone)]
336pub struct ObjectiveMetadata {
337 pub timestamp: std::time::Instant,
339 pub circuit_depth: usize,
341 pub num_terms: usize,
343 pub measurement_strategy: String,
345 pub noise_mitigation_applied: Vec<String>,
347 pub computation_time: std::time::Duration,
349}
350pub trait ObjectiveFunction: Send + Sync {
352 fn evaluate(&self, parameters: &Array1<f64>) -> DeviceResult<ObjectiveResult>;
354 fn compute_gradient(&self, parameters: &Array1<f64>) -> DeviceResult<Array1<f64>> {
356 self.compute_gradient_with_method(parameters, &GradientMethod::ParameterShift)
357 }
358 fn compute_gradient_with_method(
360 &self,
361 parameters: &Array1<f64>,
362 method: &GradientMethod,
363 ) -> DeviceResult<Array1<f64>>;
364 fn estimate_cost(&self, parameters: &Array1<f64>) -> usize;
366 fn parameter_bounds(&self) -> Option<Vec<(f64, f64)>>;
368 fn supports_batch_evaluation(&self) -> bool {
370 false
371 }
372 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#[derive(Debug)]
382pub struct ObjectiveEvaluator {
383 pub config: ObjectiveConfig,
385 pub circuit: Arc<ParametricCircuit>,
387 pub backend: ObjectiveBackend,
389 pub cached_hamiltonian: Option<HamiltonianMatrix>,
391 pub measurement_groups: Option<Vec<MeasurementGroup>>,
393}
394#[derive(Debug)]
396pub enum ObjectiveBackend {
397 QuantRS2Simulator,
399 SciRS2Exact,
401 Hardware(String),
403 Mock,
405}
406#[derive(Debug, Clone)]
408pub struct HamiltonianMatrix {
409 pub matrix: Array2<Complex64>,
411 pub eigenvalues: Option<Array1<f64>>,
413 pub eigenvectors: Option<Array2<Complex64>>,
415}
416#[derive(Debug, Clone)]
418pub struct MeasurementGroup {
419 pub terms: Vec<usize>,
421 pub measurement_basis: Vec<char>,
423 pub shot_allocation: usize,
425}
426impl ObjectiveFunction for ObjectiveEvaluator {
427 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 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 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 fn parameter_bounds(&self) -> Option<Vec<(f64, f64)>> {
483 Some(self.circuit.bounds.clone())
484 }
485 fn supports_batch_evaluation(&self) -> bool {
487 matches!(
488 self.backend,
489 ObjectiveBackend::SciRS2Exact | ObjectiveBackend::Mock
490 )
491 }
492}
493impl ObjectiveEvaluator {
494 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 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 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 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 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 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 fn evaluate_energy_hardware(
631 &self,
632 circuit: &ParametricCircuit,
633 hamiltonian: &HamiltonianSpec,
634 ) -> DeviceResult<ObjectiveResult> {
635 self.evaluate_energy_sampling(circuit, hamiltonian)
636 }
637 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 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 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(¶ms_plus)?.value;
696 let f_minus = self.evaluate(¶ms_minus)?.value;
697 gradient[i] = (f_plus - f_minus) / 2.0;
698 }
699 Ok(gradient)
700 }
701 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 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 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 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 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 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 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 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 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 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 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 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 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 let cost = -cardinality + penalty * violations;
891 Ok(Self::make_cost_result(cost, circuit, "mis_cost"))
892 }
893
894 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 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 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 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 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 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 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 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 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 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(¶ms_fwd)?.value;
1015 gradient[i] = (f_fwd - f0) / h;
1016 }
1017 Ok(gradient)
1018 }
1019
1020 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(¶ms_fwd)?.value;
1033 let f_bwd = self.evaluate(¶ms_bwd)?.value;
1034 gradient[i] = (f_fwd - f_bwd) / (2.0 * h);
1035 }
1036 Ok(gradient)
1037 }
1038
1039 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 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(¶ms_plus)?.value;
1058 let f_minus = self.evaluate(¶ms_minus)?.value;
1059 let grad_i = (f_plus - f_minus) / 2.0;
1060 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 fn compute_automatic_gradient(&self, parameters: &Array1<f64>) -> DeviceResult<Array1<f64>> {
1069 self.compute_parameter_shift_gradient(parameters)
1070 }
1071
1072 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 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 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 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 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 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 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 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 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 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 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}