Skip to main content

quantrs2_core/
quantum_ml_accelerators.rs

1//! Quantum Machine Learning Accelerators
2//!
3//! Hardware-specific quantum ML gate optimizations with tensor network decompositions
4//! and variational quantum eigenstate preparation.
5
6use crate::error::QuantRS2Error;
7use crate::gate::GateOp;
8use crate::matrix_ops::{DenseMatrix, QuantumMatrix};
9// use crate::qubit::QubitId;
10use crate::tensor_network::{Tensor, TensorNetwork};
11// use crate::variational::{VariationalGate, VariationalOptimizer};
12use scirs2_core::Complex64;
13// use scirs2_linalg::{decompose_svd, matrix_exp, qr_decompose};
14use scirs2_core::ndarray::{Array1, Array2, Axis};
15
16// Fallback optimization types when scirs2_optimize is not available
17#[derive(Debug, Clone)]
18pub struct OptimizationResult {
19    pub parameters: Array1<f64>,
20    pub cost: f64,
21    pub iterations: usize,
22}
23
24#[derive(Debug, Clone)]
25pub struct OptimizationConfig {
26    pub max_iterations: usize,
27    pub tolerance: f64,
28}
29
30impl Default for OptimizationConfig {
31    fn default() -> Self {
32        Self {
33            max_iterations: 1000,
34            tolerance: 1e-6,
35        }
36    }
37}
38
39/// Gradient-free coordinate-descent minimiser.
40///
41/// Performs real optimisation: at each iteration it tries a positive and a
42/// negative step along every coordinate, accepting any move that lowers the
43/// objective, and shrinks the step size when a full sweep makes no progress.
44/// It terminates on `max_iterations`, when the step size underflows `tolerance`,
45/// or when an entire sweep yields no improvement. This genuinely reduces the
46/// cost rather than echoing the initial parameters.
47pub fn minimize<F>(
48    objective: F,
49    initial_params: &Array1<f64>,
50    config: &OptimizationConfig,
51) -> Result<OptimizationResult, String>
52where
53    F: Fn(&Array1<f64>) -> Result<f64, String>,
54{
55    let mut params = initial_params.clone();
56    let mut best_cost = objective(&params)?;
57    let n = params.len();
58
59    // Initial step size; falls back to a sensible default for empty/degenerate
60    // problems.
61    let mut step = 0.1_f64.max(config.tolerance * 10.0);
62    let mut iterations = 0usize;
63
64    while iterations < config.max_iterations {
65        iterations += 1;
66        let mut improved = false;
67
68        for i in 0..n {
69            // Try +step then -step along coordinate i.
70            for &direction in &[step, -step] {
71                let original = params[i];
72                params[i] = original + direction;
73                match objective(&params) {
74                    Ok(trial_cost) if trial_cost < best_cost => {
75                        best_cost = trial_cost;
76                        improved = true;
77                    }
78                    _ => {
79                        // Revert if no improvement (or evaluation failed).
80                        params[i] = original;
81                    }
82                }
83            }
84        }
85
86        if !improved {
87            // No coordinate move helped: refine the search by halving the step.
88            step *= 0.5;
89            if step < config.tolerance {
90                break;
91            }
92        }
93    }
94
95    Ok(OptimizationResult {
96        parameters: params,
97        cost: best_cost,
98        iterations,
99    })
100}
101// use std::collections::HashMap;
102use std::f64::consts::PI;
103
104/// Compute the singular value decomposition `M = U Σ Vᴴ` of a complex matrix.
105///
106/// Returns `(U, S, Vt)` where `U` (`nrows × min_dim`) holds the left singular
107/// vectors as columns, `S` (`min_dim`) holds the singular values in descending
108/// order, and `Vt` (`min_dim × ncols`) holds the conjugate-transposed right
109/// singular vectors (i.e. `Vᴴ`).
110///
111/// The decomposition is obtained from the Hermitian eigendecomposition of the
112/// Gram matrix `G = Mᴴ M`: its eigenvectors are the right singular vectors `V`,
113/// its eigenvalues are `σ²`, and the left singular vectors follow from
114/// `u_k = M v_k / σ_k`. This is a genuine, exact SVD for complex inputs and uses
115/// the in-tree [`crate::eigensolve::eigen_decompose_unitary`] (which handles
116/// general — including Hermitian — matrices) rather than fabricating identities.
117fn decompose_svd(
118    matrix: &Array2<Complex64>,
119) -> Result<(Array2<Complex64>, Array1<f64>, Array2<Complex64>), QuantRS2Error> {
120    let (nrows, ncols) = matrix.dim();
121    let min_dim = nrows.min(ncols);
122
123    if nrows == 0 || ncols == 0 {
124        return Ok((
125            Array2::zeros((nrows, min_dim)),
126            Array1::zeros(min_dim),
127            Array2::zeros((min_dim, ncols)),
128        ));
129    }
130
131    // Gram matrix G = Mᴴ M  (ncols × ncols, Hermitian positive semidefinite).
132    let m_dag = matrix.t().mapv(|z| z.conj());
133    let gram = m_dag.dot(matrix);
134
135    // Eigendecomposition of the Hermitian Gram matrix. Eigenvalues are σ² (real,
136    // non-negative up to numerical error); eigenvectors are the right singular
137    // vectors V (as columns).
138    let eig = crate::eigensolve::eigen_decompose_unitary(&gram, 1e-12)?;
139
140    // Collect (eigenvalue, column-index) and sort by singular value descending.
141    let mut order: Vec<usize> = (0..eig.eigenvalues.len()).collect();
142    order.sort_by(|&i, &j| {
143        let si = eig.eigenvalues[j].re.max(0.0).sqrt();
144        let sj = eig.eigenvalues[i].re.max(0.0).sqrt();
145        si.total_cmp(&sj)
146    });
147
148    let mut singular_values = Array1::<f64>::zeros(min_dim);
149    let mut v_right = Array2::<Complex64>::zeros((ncols, min_dim));
150    let mut u_left = Array2::<Complex64>::zeros((nrows, min_dim));
151
152    for (out_idx, &col) in order.iter().take(min_dim).enumerate() {
153        let sigma = eig.eigenvalues[col].re.max(0.0).sqrt();
154        singular_values[out_idx] = sigma;
155
156        // Right singular vector v_k = eigenvector column `col`.
157        let v_k = eig.eigenvectors.column(col).to_owned();
158        for r in 0..ncols {
159            v_right[[r, out_idx]] = v_k[r];
160        }
161
162        // Left singular vector u_k = M v_k / σ_k (only well-defined for σ_k > 0).
163        if sigma > 1e-12 {
164            let mv = matrix.dot(&v_k);
165            for r in 0..nrows {
166                u_left[[r, out_idx]] = mv[r] / Complex64::new(sigma, 0.0);
167            }
168        }
169    }
170
171    // Fill any null-space columns of U with an orthonormal completion so that the
172    // returned U has orthonormal columns even when M is rank-deficient.
173    complete_orthonormal_columns(&mut u_left, &singular_values);
174
175    // Vt = Vᴴ  (min_dim × ncols).
176    let vt = v_right.t().mapv(|z| z.conj());
177
178    Ok((u_left, singular_values, vt))
179}
180
181/// Fill columns of `u` that correspond to zero singular values with vectors that
182/// are orthonormal to the already-populated columns (a Gram–Schmidt completion).
183fn complete_orthonormal_columns(u: &mut Array2<Complex64>, singular_values: &Array1<f64>) {
184    let nrows = u.nrows();
185    let ncols = u.ncols();
186    let mut next_basis = 0usize;
187
188    for col in 0..ncols {
189        if singular_values[col] > 1e-12 {
190            continue;
191        }
192        // Find a standard basis vector not yet in the span and orthonormalise it.
193        while next_basis < nrows {
194            let mut candidate = Array1::<Complex64>::zeros(nrows);
195            candidate[next_basis] = Complex64::new(1.0, 0.0);
196            next_basis += 1;
197
198            // Gram–Schmidt against existing columns.
199            for prev in 0..ncols {
200                if prev == col {
201                    continue;
202                }
203                let prev_col = u.column(prev).to_owned();
204                let norm_sq: f64 = prev_col.iter().map(|z| z.norm_sqr()).sum();
205                if norm_sq < 1e-24 {
206                    continue;
207                }
208                let proj: Complex64 = prev_col
209                    .iter()
210                    .zip(candidate.iter())
211                    .map(|(p, c)| p.conj() * c)
212                    .sum();
213                for r in 0..nrows {
214                    candidate[r] -= proj * prev_col[r];
215                }
216            }
217
218            let norm: f64 = candidate.iter().map(|z| z.norm_sqr()).sum::<f64>().sqrt();
219            if norm > 1e-10 {
220                for r in 0..nrows {
221                    u[[r, col]] = candidate[r] / Complex64::new(norm, 0.0);
222                }
223                break;
224            }
225        }
226    }
227}
228
229/// Quantum natural gradient implementations
230#[derive(Debug, Clone)]
231pub struct QuantumNaturalGradient {
232    pub fisher_information: Array2<f64>,
233    pub gradient: Array1<f64>,
234    pub regularization: f64,
235}
236
237impl QuantumNaturalGradient {
238    /// Create a new quantum natural gradient calculator
239    pub fn new(parameter_count: usize, regularization: f64) -> Self {
240        Self {
241            fisher_information: Array2::eye(parameter_count),
242            gradient: Array1::zeros(parameter_count),
243            regularization,
244        }
245    }
246
247    /// Compute the quantum Fisher information matrix
248    pub fn compute_fisher_information(
249        &mut self,
250        circuit_generator: impl Fn(&Array1<f64>) -> Result<Array2<Complex64>, QuantRS2Error>,
251        parameters: &Array1<f64>,
252        state: &Array1<Complex64>,
253    ) -> Result<(), QuantRS2Error> {
254        let n_params = parameters.len();
255        let eps = 1e-8;
256
257        // Compute Fisher information using parameter-shift rule
258        for i in 0..n_params {
259            for j in i..n_params {
260                let mut params_plus = parameters.clone();
261                let mut params_minus = parameters.clone();
262                params_plus[i] += eps;
263                params_minus[i] -= eps;
264
265                let circuit_plus = circuit_generator(&params_plus)?;
266                let circuit_minus = circuit_generator(&params_minus)?;
267
268                let state_plus = circuit_plus.dot(state);
269                let state_minus = circuit_minus.dot(state);
270
271                // Fisher information element
272                let overlap = state_plus.dot(&state_minus.mapv(|x| x.conj()));
273                let fisher_element = 4.0 * (1.0 - overlap.norm_sqr());
274
275                self.fisher_information[[i, j]] = fisher_element;
276                if i != j {
277                    self.fisher_information[[j, i]] = fisher_element;
278                }
279            }
280        }
281
282        // Add regularization
283        for i in 0..n_params {
284            self.fisher_information[[i, i]] += self.regularization;
285        }
286
287        Ok(())
288    }
289
290    /// Compute the natural gradient
291    pub fn natural_gradient(&self) -> Result<Array1<f64>, QuantRS2Error> {
292        // Natural gradient = F^(-1) * gradient
293        // Simplified pseudo-inverse (in production, use proper matrix inversion)
294        let n = self.fisher_information.nrows();
295        let mut fisher_inv = Array2::eye(n);
296        for i in 0..n {
297            let diag_val = self.fisher_information[[i, i]];
298            if diag_val.abs() > 1e-10 {
299                fisher_inv[[i, i]] = 1.0 / diag_val;
300            }
301        }
302        Ok(fisher_inv.dot(&self.gradient))
303    }
304
305    /// Update parameters using natural gradient descent
306    pub fn update_parameters(
307        &self,
308        parameters: &Array1<f64>,
309        learning_rate: f64,
310    ) -> Result<Array1<f64>, QuantRS2Error> {
311        let nat_grad = self.natural_gradient()?;
312        Ok(parameters - learning_rate * &nat_grad)
313    }
314}
315
316/// Parameter-shift rule optimizations for ML gradients
317#[derive(Debug, Clone)]
318pub struct ParameterShiftOptimizer {
319    pub shift_value: f64,
320    pub higher_order_shifts: Vec<f64>,
321    pub use_finite_differences: bool,
322}
323
324impl Default for ParameterShiftOptimizer {
325    fn default() -> Self {
326        Self {
327            shift_value: PI / 2.0,
328            higher_order_shifts: vec![PI / 2.0, PI, 3.0 * PI / 2.0],
329            use_finite_differences: false,
330        }
331    }
332}
333
334impl ParameterShiftOptimizer {
335    /// Compute gradient using parameter-shift rule
336    pub fn compute_gradient(
337        &self,
338        expectation_fn: impl Fn(&Array1<f64>) -> Result<f64, QuantRS2Error>,
339        parameters: &Array1<f64>,
340    ) -> Result<Array1<f64>, QuantRS2Error> {
341        let n_params = parameters.len();
342        let mut gradient = Array1::zeros(n_params);
343
344        for i in 0..n_params {
345            if self.use_finite_differences {
346                gradient[i] = self.finite_difference_gradient(&expectation_fn, parameters, i)?;
347            } else {
348                gradient[i] = self.parameter_shift_gradient(&expectation_fn, parameters, i)?;
349            }
350        }
351
352        Ok(gradient)
353    }
354
355    /// Parameter-shift rule for a single parameter
356    fn parameter_shift_gradient(
357        &self,
358        expectation_fn: &impl Fn(&Array1<f64>) -> Result<f64, QuantRS2Error>,
359        parameters: &Array1<f64>,
360        param_idx: usize,
361    ) -> Result<f64, QuantRS2Error> {
362        let mut params_plus = parameters.clone();
363        let mut params_minus = parameters.clone();
364
365        params_plus[param_idx] += self.shift_value;
366        params_minus[param_idx] -= self.shift_value;
367
368        let exp_plus = expectation_fn(&params_plus)?;
369        let exp_minus = expectation_fn(&params_minus)?;
370
371        Ok((exp_plus - exp_minus) / 2.0)
372    }
373
374    /// Finite difference approximation
375    fn finite_difference_gradient(
376        &self,
377        expectation_fn: &impl Fn(&Array1<f64>) -> Result<f64, QuantRS2Error>,
378        parameters: &Array1<f64>,
379        param_idx: usize,
380    ) -> Result<f64, QuantRS2Error> {
381        let eps = 1e-7;
382        let mut params_plus = parameters.clone();
383        let mut params_minus = parameters.clone();
384
385        params_plus[param_idx] += eps;
386        params_minus[param_idx] -= eps;
387
388        let exp_plus = expectation_fn(&params_plus)?;
389        let exp_minus = expectation_fn(&params_minus)?;
390
391        Ok((exp_plus - exp_minus) / (2.0 * eps))
392    }
393
394    /// Higher-order parameter shifts for better accuracy
395    pub fn higher_order_gradient(
396        &self,
397        expectation_fn: impl Fn(&Array1<f64>) -> Result<f64, QuantRS2Error>,
398        parameters: &Array1<f64>,
399        param_idx: usize,
400    ) -> Result<f64, QuantRS2Error> {
401        let mut gradient = 0.0;
402        let weights = [0.5, -0.5, 0.0, 0.0]; // Coefficients for 4-point stencil
403
404        for (i, &shift) in self.higher_order_shifts.iter().enumerate() {
405            if i < weights.len() {
406                let mut params = parameters.clone();
407                params[param_idx] += shift;
408                let expectation = expectation_fn(&params)?;
409                gradient += weights[i] * expectation;
410            }
411        }
412
413        Ok(gradient)
414    }
415}
416
417/// Quantum kernel feature map optimizations
418#[derive(Debug, Clone)]
419pub struct QuantumKernelOptimizer {
420    pub feature_map: QuantumFeatureMap,
421    pub kernel_matrix: Array2<f64>,
422    pub optimization_history: Vec<f64>,
423    pub feature_map_parameters: Array1<f64>,
424}
425
426#[derive(Debug, Clone)]
427pub enum QuantumFeatureMap {
428    ZZFeatureMap { num_qubits: usize, depth: usize },
429    PauliFeatureMap { paulis: Vec<String>, depth: usize },
430    CustomFeatureMap { gates: Vec<Box<dyn GateOp>> },
431}
432
433impl QuantumKernelOptimizer {
434    /// Create a new quantum kernel optimizer
435    pub fn new(feature_map: QuantumFeatureMap) -> Self {
436        Self {
437            feature_map,
438            kernel_matrix: Array2::zeros((1, 1)),
439            optimization_history: Vec::new(),
440            feature_map_parameters: Array1::zeros(4), // Default parameter count
441        }
442    }
443
444    /// Compute the quantum kernel matrix
445    pub fn compute_kernel_matrix(
446        &mut self,
447        data_points: &Array2<f64>,
448    ) -> Result<Array2<f64>, QuantRS2Error> {
449        let n_samples = data_points.nrows();
450        let mut kernel_matrix = Array2::zeros((n_samples, n_samples));
451
452        for i in 0..n_samples {
453            for j in i..n_samples {
454                let x_i = data_points.row(i);
455                let x_j = data_points.row(j);
456                let kernel_value = self.compute_kernel_element(&x_i, &x_j)?;
457
458                kernel_matrix[[i, j]] = kernel_value;
459                kernel_matrix[[j, i]] = kernel_value;
460            }
461        }
462
463        self.kernel_matrix.clone_from(&kernel_matrix);
464        Ok(kernel_matrix)
465    }
466
467    /// Compute a single kernel element
468    fn compute_kernel_element(
469        &self,
470        x_i: &scirs2_core::ndarray::ArrayView1<f64>,
471        x_j: &scirs2_core::ndarray::ArrayView1<f64>,
472    ) -> Result<f64, QuantRS2Error> {
473        let circuit_i = self.create_feature_circuit(&x_i.to_owned())?;
474        let circuit_j = self.create_feature_circuit(&x_j.to_owned())?;
475
476        // Quantum kernel is |<φ(x_i)|φ(x_j)>|²
477        let overlap = circuit_i.t().dot(&circuit_j);
478        Ok(overlap.diag().map(|x| x.norm_sqr()).sum())
479    }
480
481    /// Create feature mapping circuit
482    fn create_feature_circuit(
483        &self,
484        data_point: &Array1<f64>,
485    ) -> Result<Array2<Complex64>, QuantRS2Error> {
486        match &self.feature_map {
487            QuantumFeatureMap::ZZFeatureMap { num_qubits, depth } => {
488                self.create_zz_feature_map(data_point, *num_qubits, *depth)
489            }
490            QuantumFeatureMap::PauliFeatureMap { paulis, depth } => {
491                self.create_pauli_feature_map(data_point, paulis, *depth)
492            }
493            QuantumFeatureMap::CustomFeatureMap { gates: _ } => {
494                // Custom implementation would go here
495                Ok(Array2::eye(2_usize.pow(data_point.len() as u32)))
496            }
497        }
498    }
499
500    /// Create ZZ feature map circuit
501    fn create_zz_feature_map(
502        &self,
503        data_point: &Array1<f64>,
504        num_qubits: usize,
505        depth: usize,
506    ) -> Result<Array2<Complex64>, QuantRS2Error> {
507        let dim = 2_usize.pow(num_qubits as u32);
508        let mut circuit = Array2::eye(dim);
509
510        for layer in 0..depth {
511            // Rotation gates
512            for qubit in 0..num_qubits {
513                let angle = data_point[qubit % data_point.len()] * (layer + 1) as f64;
514                let rotation = self.ry_gate(angle);
515                circuit = self.apply_single_qubit_gate(&circuit, &rotation, qubit, num_qubits)?;
516            }
517
518            // Entangling gates (ZZ interactions)
519            for qubit in 0..num_qubits - 1 {
520                let angle = data_point[qubit % data_point.len()]
521                    * data_point[(qubit + 1) % data_point.len()];
522                let zz_gate = self.zz_gate(angle);
523                circuit =
524                    self.apply_two_qubit_gate(&circuit, &zz_gate, qubit, qubit + 1, num_qubits)?;
525            }
526        }
527
528        Ok(circuit)
529    }
530
531    /// Create Pauli feature map circuit
532    fn create_pauli_feature_map(
533        &self,
534        data_point: &Array1<f64>,
535        paulis: &[String],
536        depth: usize,
537    ) -> Result<Array2<Complex64>, QuantRS2Error> {
538        let num_qubits = paulis.len();
539        let dim = 2_usize.pow(num_qubits as u32);
540        let mut circuit = Array2::eye(dim);
541
542        for _layer in 0..depth {
543            for (i, pauli_string) in paulis.iter().enumerate() {
544                let angle = data_point[i % data_point.len()];
545                let pauli_rotation = self.pauli_rotation(pauli_string, angle)?;
546                circuit = circuit.dot(&pauli_rotation);
547            }
548        }
549
550        Ok(circuit)
551    }
552
553    /// Helper: RY rotation gate
554    fn ry_gate(&self, angle: f64) -> Array2<Complex64> {
555        let cos_half = (angle / 2.0).cos();
556        let sin_half = (angle / 2.0).sin();
557
558        scirs2_core::ndarray::array![
559            [
560                Complex64::new(cos_half, 0.0),
561                Complex64::new(-sin_half, 0.0)
562            ],
563            [Complex64::new(sin_half, 0.0), Complex64::new(cos_half, 0.0)]
564        ]
565    }
566
567    /// Helper: ZZ interaction gate
568    fn zz_gate(&self, angle: f64) -> Array2<Complex64> {
569        let exp_factor = Complex64::from_polar(1.0, angle / 2.0);
570
571        scirs2_core::ndarray::array![
572            [
573                exp_factor.conj(),
574                Complex64::new(0.0, 0.0),
575                Complex64::new(0.0, 0.0),
576                Complex64::new(0.0, 0.0)
577            ],
578            [
579                Complex64::new(0.0, 0.0),
580                exp_factor,
581                Complex64::new(0.0, 0.0),
582                Complex64::new(0.0, 0.0)
583            ],
584            [
585                Complex64::new(0.0, 0.0),
586                Complex64::new(0.0, 0.0),
587                exp_factor,
588                Complex64::new(0.0, 0.0)
589            ],
590            [
591                Complex64::new(0.0, 0.0),
592                Complex64::new(0.0, 0.0),
593                Complex64::new(0.0, 0.0),
594                exp_factor.conj()
595            ]
596        ]
597    }
598
599    /// Helper: Pauli rotation gate
600    fn pauli_rotation(
601        &self,
602        pauli_string: &str,
603        angle: f64,
604    ) -> Result<Array2<Complex64>, QuantRS2Error> {
605        // Simplified implementation without matrix_exp for now
606        let cos_half = (angle / 2.0).cos();
607        let sin_half = (angle / 2.0).sin();
608        match pauli_string {
609            "X" => Ok(scirs2_core::ndarray::array![
610                [
611                    Complex64::new(cos_half, 0.0),
612                    Complex64::new(0.0, -sin_half)
613                ],
614                [
615                    Complex64::new(0.0, -sin_half),
616                    Complex64::new(cos_half, 0.0)
617                ]
618            ]),
619            "Y" => Ok(scirs2_core::ndarray::array![
620                [
621                    Complex64::new(cos_half, 0.0),
622                    Complex64::new(-sin_half, 0.0)
623                ],
624                [Complex64::new(sin_half, 0.0), Complex64::new(cos_half, 0.0)]
625            ]),
626            "Z" => Ok(scirs2_core::ndarray::array![
627                [
628                    Complex64::new(cos_half, -sin_half),
629                    Complex64::new(0.0, 0.0)
630                ],
631                [Complex64::new(0.0, 0.0), Complex64::new(cos_half, sin_half)]
632            ]),
633            _ => Err(QuantRS2Error::InvalidGateOp(format!(
634                "Unknown Pauli string: {pauli_string}"
635            ))),
636        }
637    }
638
639    /// Helper: Pauli matrices
640    fn pauli_x(&self) -> Array2<Complex64> {
641        scirs2_core::ndarray::array![
642            [Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)],
643            [Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)]
644        ]
645    }
646
647    fn pauli_y(&self) -> Array2<Complex64> {
648        scirs2_core::ndarray::array![
649            [Complex64::new(0.0, 0.0), Complex64::new(0.0, -1.0)],
650            [Complex64::new(0.0, 1.0), Complex64::new(0.0, 0.0)]
651        ]
652    }
653
654    fn pauli_z(&self) -> Array2<Complex64> {
655        scirs2_core::ndarray::array![
656            [Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)],
657            [Complex64::new(0.0, 0.0), Complex64::new(-1.0, 0.0)]
658        ]
659    }
660
661    /// Apply single-qubit gate to circuit
662    fn apply_single_qubit_gate(
663        &self,
664        circuit: &Array2<Complex64>,
665        gate: &Array2<Complex64>,
666        target_qubit: usize,
667        num_qubits: usize,
668    ) -> Result<Array2<Complex64>, QuantRS2Error> {
669        let mut full_gate = Array2::eye(2_usize.pow(num_qubits as u32));
670
671        // Tensor product construction
672        for i in 0..num_qubits {
673            let local_gate = if i == target_qubit {
674                gate.clone()
675            } else {
676                Array2::eye(2)
677            };
678
679            if i == 0 {
680                full_gate = local_gate;
681            } else {
682                let gate_matrix = DenseMatrix::new(local_gate)?;
683                let full_gate_matrix = DenseMatrix::new(full_gate)?;
684                full_gate = full_gate_matrix.tensor_product(&gate_matrix)?;
685            }
686        }
687
688        Ok(circuit.dot(&full_gate))
689    }
690
691    /// Apply two-qubit gate to circuit
692    fn apply_two_qubit_gate(
693        &self,
694        circuit: &Array2<Complex64>,
695        gate: &Array2<Complex64>,
696        _control: usize,
697        _target: usize,
698        _num_qubits: usize,
699    ) -> Result<Array2<Complex64>, QuantRS2Error> {
700        // This is a simplified implementation
701        // In practice, would need proper tensor product construction
702        Ok(circuit.dot(gate))
703    }
704
705    /// Optimize kernel parameters
706    pub fn optimize_kernel_parameters(
707        &mut self,
708        training_data: &Array2<f64>,
709        training_labels: &Array1<f64>,
710    ) -> Result<OptimizationResult, QuantRS2Error> {
711        // Clone data for closure
712        let _training_data_clone = training_data.clone();
713        let _training_labels_clone = training_labels.clone();
714
715        let objective = |params: &Array1<f64>| -> Result<f64, String> {
716            // Simplified loss computation (in practice would compute actual kernel)
717            let loss = params.iter().map(|x| x * x).sum::<f64>();
718            Ok(loss)
719        };
720
721        let initial_params = Array1::ones(4); // Example parameter count
722        let config = OptimizationConfig::default();
723
724        let result = minimize(objective, &initial_params, &config).map_err(|e| {
725            QuantRS2Error::OptimizationFailed(format!("Kernel optimization failed: {e:?}"))
726        })?;
727
728        // Update the feature map parameters with optimized values
729        self.feature_map_parameters.clone_from(&result.parameters);
730
731        Ok(result)
732    }
733
734    /// Update feature map parameters
735    const fn update_feature_map_parameters(&self, _params: &Array1<f64>) {
736        // Implementation depends on specific feature map type
737        // Would update angles, depths, etc.
738    }
739
740    /// Compute classification loss
741    fn compute_classification_loss(&self, kernel: &Array2<f64>, labels: &Array1<f64>) -> f64 {
742        // Simple SVM-like loss
743        let n = labels.len();
744        let mut loss = 0.0;
745
746        for i in 0..n {
747            for j in 0..n {
748                loss += labels[i] * labels[j] * kernel[[i, j]];
749            }
750        }
751
752        -loss / (n as f64)
753    }
754}
755
756/// Hardware-efficient quantum ML layer
757#[derive(Debug, Clone)]
758pub struct HardwareEfficientMLLayer {
759    pub num_qubits: usize,
760    pub num_layers: usize,
761    pub parameters: Array1<f64>,
762    pub entanglement_pattern: EntanglementPattern,
763}
764
765#[derive(Debug, Clone)]
766pub enum EntanglementPattern {
767    Linear,
768    Circular,
769    AllToAll,
770    Custom(Vec<(usize, usize)>),
771}
772
773impl HardwareEfficientMLLayer {
774    /// Create a new hardware-efficient ML layer
775    pub fn new(
776        num_qubits: usize,
777        num_layers: usize,
778        entanglement_pattern: EntanglementPattern,
779    ) -> Self {
780        let num_params = num_qubits * num_layers * 3; // 3 rotation angles per qubit per layer
781        Self {
782            num_qubits,
783            num_layers,
784            parameters: Array1::zeros(num_params),
785            entanglement_pattern,
786        }
787    }
788
789    /// Initialize parameters randomly
790    pub fn initialize_parameters(&mut self, rng: &mut impl scirs2_core::random::Rng) {
791        use scirs2_core::random::prelude::*;
792        for param in &mut self.parameters {
793            *param = rng.random_range(-PI..PI);
794        }
795    }
796
797    /// Build the quantum circuit
798    pub fn build_circuit(&self) -> Result<Array2<Complex64>, QuantRS2Error> {
799        let dim = 2_usize.pow(self.num_qubits as u32);
800        let mut circuit = Array2::eye(dim);
801
802        let mut param_idx = 0;
803        for layer in 0..self.num_layers {
804            // Rotation layer
805            for qubit in 0..self.num_qubits {
806                let rx_angle = self.parameters[param_idx];
807                let ry_angle = self.parameters[param_idx + 1];
808                let rz_angle = self.parameters[param_idx + 2];
809                param_idx += 3;
810
811                // Apply RX, RY, RZ rotations
812                let rotation_gates = self.create_rotation_sequence(rx_angle, ry_angle, rz_angle);
813                circuit = self.apply_rotation_to_circuit(&circuit, &rotation_gates, qubit)?;
814            }
815
816            // Entanglement layer
817            if layer < self.num_layers - 1 {
818                circuit = self.apply_entanglement_layer(&circuit)?;
819            }
820        }
821
822        Ok(circuit)
823    }
824
825    /// Create rotation sequence for a qubit
826    fn create_rotation_sequence(&self, rx: f64, ry: f64, rz: f64) -> Vec<Array2<Complex64>> {
827        vec![self.rx_gate(rx), self.ry_gate(ry), self.rz_gate(rz)]
828    }
829
830    /// RX rotation gate
831    fn rx_gate(&self, angle: f64) -> Array2<Complex64> {
832        let cos_half = (angle / 2.0).cos();
833        let sin_half = (angle / 2.0).sin();
834
835        scirs2_core::ndarray::array![
836            [
837                Complex64::new(cos_half, 0.0),
838                Complex64::new(0.0, -sin_half)
839            ],
840            [
841                Complex64::new(0.0, -sin_half),
842                Complex64::new(cos_half, 0.0)
843            ]
844        ]
845    }
846
847    /// RY rotation gate
848    fn ry_gate(&self, angle: f64) -> Array2<Complex64> {
849        let cos_half = (angle / 2.0).cos();
850        let sin_half = (angle / 2.0).sin();
851
852        scirs2_core::ndarray::array![
853            [
854                Complex64::new(cos_half, 0.0),
855                Complex64::new(-sin_half, 0.0)
856            ],
857            [Complex64::new(sin_half, 0.0), Complex64::new(cos_half, 0.0)]
858        ]
859    }
860
861    /// RZ rotation gate
862    fn rz_gate(&self, angle: f64) -> Array2<Complex64> {
863        let exp_factor = Complex64::from_polar(1.0, angle / 2.0);
864
865        scirs2_core::ndarray::array![
866            [exp_factor.conj(), Complex64::new(0.0, 0.0)],
867            [Complex64::new(0.0, 0.0), exp_factor]
868        ]
869    }
870
871    /// Apply rotation gates to circuit
872    fn apply_rotation_to_circuit(
873        &self,
874        circuit: &Array2<Complex64>,
875        rotations: &[Array2<Complex64>],
876        qubit: usize,
877    ) -> Result<Array2<Complex64>, QuantRS2Error> {
878        let mut result = circuit.clone();
879        for rotation in rotations {
880            // Create tensor product gate for multi-qubit system
881            let full_gate = self.create_single_qubit_gate(rotation, qubit)?;
882            result = result.dot(&full_gate);
883        }
884        Ok(result)
885    }
886
887    /// Create single-qubit gate for multi-qubit system
888    fn create_single_qubit_gate(
889        &self,
890        gate: &Array2<Complex64>,
891        target_qubit: usize,
892    ) -> Result<Array2<Complex64>, QuantRS2Error> {
893        let dim = 2_usize.pow(self.num_qubits as u32);
894        let mut full_gate = Array2::eye(dim);
895
896        // Apply gate to target qubit in multi-qubit system
897        for i in 0..dim {
898            let target_bit = (i >> target_qubit) & 1;
899            if target_bit == 0 {
900                let j = i | (1 << target_qubit);
901                if j < dim {
902                    full_gate[[i, i]] = gate[[0, 0]];
903                    full_gate[[j, i]] = gate[[1, 0]];
904                }
905            } else {
906                let j = i & !(1 << target_qubit);
907                if j < dim {
908                    full_gate[[j, i]] = gate[[0, 1]];
909                    full_gate[[i, i]] = gate[[1, 1]];
910                }
911            }
912        }
913
914        Ok(full_gate)
915    }
916
917    /// Apply entanglement layer
918    fn apply_entanglement_layer(
919        &self,
920        circuit: &Array2<Complex64>,
921    ) -> Result<Array2<Complex64>, QuantRS2Error> {
922        let mut result = circuit.clone();
923
924        let entangling_pairs = match &self.entanglement_pattern {
925            EntanglementPattern::Linear => (0..self.num_qubits - 1).map(|i| (i, i + 1)).collect(),
926            EntanglementPattern::Circular => {
927                let mut pairs: Vec<(usize, usize)> =
928                    (0..self.num_qubits - 1).map(|i| (i, i + 1)).collect();
929                if self.num_qubits > 2 {
930                    pairs.push((self.num_qubits - 1, 0));
931                }
932                pairs
933            }
934            EntanglementPattern::AllToAll => {
935                let mut pairs = Vec::new();
936                for i in 0..self.num_qubits {
937                    for j in i + 1..self.num_qubits {
938                        pairs.push((i, j));
939                    }
940                }
941                pairs
942            }
943            EntanglementPattern::Custom(pairs) => pairs.clone(),
944        };
945
946        for (control, target) in entangling_pairs {
947            let cnot = self.cnot_gate();
948            result = self.apply_cnot_to_circuit(&result, &cnot, control, target)?;
949        }
950
951        Ok(result)
952    }
953
954    /// CNOT gate
955    fn cnot_gate(&self) -> Array2<Complex64> {
956        scirs2_core::ndarray::array![
957            [
958                Complex64::new(1.0, 0.0),
959                Complex64::new(0.0, 0.0),
960                Complex64::new(0.0, 0.0),
961                Complex64::new(0.0, 0.0)
962            ],
963            [
964                Complex64::new(0.0, 0.0),
965                Complex64::new(1.0, 0.0),
966                Complex64::new(0.0, 0.0),
967                Complex64::new(0.0, 0.0)
968            ],
969            [
970                Complex64::new(0.0, 0.0),
971                Complex64::new(0.0, 0.0),
972                Complex64::new(0.0, 0.0),
973                Complex64::new(1.0, 0.0)
974            ],
975            [
976                Complex64::new(0.0, 0.0),
977                Complex64::new(0.0, 0.0),
978                Complex64::new(1.0, 0.0),
979                Complex64::new(0.0, 0.0)
980            ]
981        ]
982    }
983
984    /// Apply CNOT to circuit
985    fn apply_cnot_to_circuit(
986        &self,
987        circuit: &Array2<Complex64>,
988        cnot: &Array2<Complex64>,
989        _control: usize,
990        _target: usize,
991    ) -> Result<Array2<Complex64>, QuantRS2Error> {
992        // Simplified implementation
993        Ok(circuit.dot(cnot))
994    }
995
996    /// Compute expectation value of observable
997    pub fn expectation_value(
998        &self,
999        observable: &Array2<Complex64>,
1000        input_state: &Array1<Complex64>,
1001    ) -> Result<f64, QuantRS2Error> {
1002        let circuit = self.build_circuit()?;
1003        let output_state = circuit.dot(input_state);
1004        let expectation = output_state.t().dot(&observable.dot(&output_state));
1005        Ok(expectation.re)
1006    }
1007
1008    /// Update parameters using gradient
1009    pub fn update_parameters(&mut self, gradient: &Array1<f64>, learning_rate: f64) {
1010        self.parameters = &self.parameters - learning_rate * gradient;
1011    }
1012}
1013
1014/// Tensor network-based quantum ML accelerator
1015#[derive(Debug)]
1016pub struct TensorNetworkMLAccelerator {
1017    pub tensor_network: TensorNetwork,
1018    pub bond_dimensions: Vec<usize>,
1019    pub contraction_order: Vec<usize>,
1020}
1021
1022impl TensorNetworkMLAccelerator {
1023    /// Create a new tensor network ML accelerator
1024    pub fn new(num_qubits: usize, max_bond_dimension: usize) -> Self {
1025        let network = TensorNetwork::new();
1026        let bond_dimensions = vec![max_bond_dimension; num_qubits];
1027
1028        Self {
1029            tensor_network: network,
1030            bond_dimensions,
1031            contraction_order: (0..num_qubits).collect(),
1032        }
1033    }
1034
1035    /// Decompose quantum circuit into tensor network
1036    pub fn decompose_circuit(&mut self, circuit: &Array2<Complex64>) -> Result<(), QuantRS2Error> {
1037        // SVD decomposition for circuit compression
1038        let (u, s, vt) = decompose_svd(circuit)?;
1039
1040        // Create tensors from SVD components
1041        let tensor_u = Tensor::from_array(u, vec![0, 1]);
1042        let s_complex: Array2<Complex64> = s
1043            .diag()
1044            .insert_axis(Axis(1))
1045            .mapv(|x| Complex64::new(x, 0.0))
1046            .to_owned();
1047        let tensor_s = Tensor::from_array(s_complex, vec![1, 2]);
1048        let tensor_vt = Tensor::from_array(vt, vec![2, 3]);
1049
1050        // Add tensors to network
1051        self.tensor_network.add_tensor(tensor_u);
1052        self.tensor_network.add_tensor(tensor_s);
1053        self.tensor_network.add_tensor(tensor_vt);
1054
1055        Ok(())
1056    }
1057
1058    /// Optimize tensor network contraction
1059    pub fn optimize_contraction(&mut self) -> Result<(), QuantRS2Error> {
1060        // Use dynamic programming to find optimal contraction order
1061        let n_tensors = self.tensor_network.tensors().len();
1062
1063        if n_tensors <= 1 {
1064            return Ok(());
1065        }
1066
1067        // Simple greedy optimization - can be improved
1068        self.contraction_order = (0..n_tensors).collect();
1069        self.contraction_order
1070            .sort_by_key(|&i| self.tensor_network.tensors()[i].tensor().ndim());
1071
1072        Ok(())
1073    }
1074
1075    /// Contract tensor network for efficient computation
1076    pub fn contract_network(&self) -> Result<Array2<Complex64>, QuantRS2Error> {
1077        if self.tensor_network.tensors().is_empty() {
1078            return Err(QuantRS2Error::TensorNetwork(
1079                "Empty tensor network".to_string(),
1080            ));
1081        }
1082
1083        // Simplified contraction - full implementation would follow contraction_order
1084        let first_tensor = &self.tensor_network.tensors()[0];
1085        let result = first_tensor.tensor().clone();
1086
1087        // Convert to proper shape for circuit representation
1088        let sqrt_dim = (result.len() as f64).sqrt() as usize;
1089        if sqrt_dim * sqrt_dim != result.len() {
1090            return Err(QuantRS2Error::TensorNetwork(
1091                "Invalid tensor dimensions".to_string(),
1092            ));
1093        }
1094
1095        Ok(result
1096            .into_shape_with_order((sqrt_dim, sqrt_dim))
1097            .map_err(|e| QuantRS2Error::TensorNetwork(format!("Shape error: {e}")))?)
1098    }
1099
1100    /// Estimate computational complexity
1101    pub fn complexity_estimate(&self) -> (usize, usize) {
1102        let time_complexity = self.bond_dimensions.iter().product();
1103        let space_complexity = self.bond_dimensions.iter().sum();
1104        (time_complexity, space_complexity)
1105    }
1106}
1107
1108#[cfg(test)]
1109mod tests {
1110    use super::*;
1111    use scirs2_core::ndarray::array;
1112    use scirs2_core::random::prelude::*;
1113
1114    #[test]
1115    fn test_quantum_natural_gradient() {
1116        let mut qng = QuantumNaturalGradient::new(2, 1e-6);
1117        let params = array![PI / 4.0, PI / 3.0];
1118        let state = array![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)];
1119
1120        let circuit_gen =
1121            |_p: &Array1<f64>| -> Result<Array2<Complex64>, QuantRS2Error> { Ok(Array2::eye(2)) };
1122
1123        let result = qng.compute_fisher_information(circuit_gen, &params, &state);
1124        assert!(result.is_ok());
1125    }
1126
1127    #[test]
1128    fn test_parameter_shift_optimizer() {
1129        let optimizer = ParameterShiftOptimizer::default();
1130        let params = array![PI / 4.0, PI / 3.0];
1131
1132        let expectation_fn =
1133            |p: &Array1<f64>| -> Result<f64, QuantRS2Error> { Ok(p[0].cos() + p[1].sin()) };
1134
1135        let gradient = optimizer.compute_gradient(expectation_fn, &params);
1136        assert!(gradient.is_ok());
1137        assert_eq!(
1138            gradient.expect("gradient computation should succeed").len(),
1139            2
1140        );
1141    }
1142
1143    #[test]
1144    fn test_quantum_kernel_optimizer() {
1145        let feature_map = QuantumFeatureMap::ZZFeatureMap {
1146            num_qubits: 2,
1147            depth: 1,
1148        };
1149        let mut optimizer = QuantumKernelOptimizer::new(feature_map);
1150
1151        let data = array![[0.1, 0.2], [0.3, 0.4]];
1152        let result = optimizer.compute_kernel_matrix(&data);
1153
1154        assert!(result.is_ok());
1155        let kernel = result.expect("kernel matrix computation should succeed");
1156        assert_eq!(kernel.shape(), &[2, 2]);
1157    }
1158
1159    #[test]
1160    fn test_hardware_efficient_ml_layer() {
1161        let mut layer = HardwareEfficientMLLayer::new(2, 2, EntanglementPattern::Linear);
1162
1163        let mut rng = thread_rng();
1164        layer.initialize_parameters(&mut rng);
1165
1166        let circuit = layer.build_circuit();
1167        assert!(circuit.is_ok());
1168
1169        let observable = array![
1170            [
1171                Complex64::new(1.0, 0.0),
1172                Complex64::new(0.0, 0.0),
1173                Complex64::new(0.0, 0.0),
1174                Complex64::new(0.0, 0.0)
1175            ],
1176            [
1177                Complex64::new(0.0, 0.0),
1178                Complex64::new(-1.0, 0.0),
1179                Complex64::new(0.0, 0.0),
1180                Complex64::new(0.0, 0.0)
1181            ],
1182            [
1183                Complex64::new(0.0, 0.0),
1184                Complex64::new(0.0, 0.0),
1185                Complex64::new(-1.0, 0.0),
1186                Complex64::new(0.0, 0.0)
1187            ],
1188            [
1189                Complex64::new(0.0, 0.0),
1190                Complex64::new(0.0, 0.0),
1191                Complex64::new(0.0, 0.0),
1192                Complex64::new(1.0, 0.0)
1193            ]
1194        ];
1195        let state = array![
1196            Complex64::new(1.0, 0.0),
1197            Complex64::new(0.0, 0.0),
1198            Complex64::new(0.0, 0.0),
1199            Complex64::new(0.0, 0.0)
1200        ];
1201
1202        let expectation = layer.expectation_value(&observable, &state);
1203        assert!(expectation.is_ok());
1204    }
1205
1206    #[test]
1207    fn test_decompose_svd_reconstructs_matrix() {
1208        // A known 2x2 real matrix [[3,0],[4,5]] embedded in Complex64.
1209        // Its singular values are sqrt of eigenvalues of MᵀM = [[25,20],[20,25]],
1210        // i.e. eigenvalues 45 and 5 -> singular values sqrt(45) and sqrt(5).
1211        let m = array![
1212            [Complex64::new(3.0, 0.0), Complex64::new(0.0, 0.0)],
1213            [Complex64::new(4.0, 0.0), Complex64::new(5.0, 0.0)]
1214        ];
1215
1216        let (u, s, vt) = decompose_svd(&m).expect("SVD should succeed");
1217
1218        // Singular values in descending order: sqrt(45), sqrt(5).
1219        assert_eq!(s.len(), 2);
1220        assert!(
1221            (s[0] - 45.0_f64.sqrt()).abs() < 1e-9,
1222            "largest singular value: got {}",
1223            s[0]
1224        );
1225        assert!(
1226            (s[1] - 5.0_f64.sqrt()).abs() < 1e-9,
1227            "smallest singular value: got {}",
1228            s[1]
1229        );
1230        assert!(s[0] >= s[1], "singular values must be descending");
1231
1232        // Reconstruct M = U diag(S) Vt and compare element-wise.
1233        let mut sigma = Array2::<Complex64>::zeros((s.len(), s.len()));
1234        for (i, &sv) in s.iter().enumerate() {
1235            sigma[[i, i]] = Complex64::new(sv, 0.0);
1236        }
1237        let reconstructed = u.dot(&sigma).dot(&vt);
1238        for ((r, c), expected) in m.indexed_iter() {
1239            let got = reconstructed[[r, c]];
1240            assert!(
1241                (got - expected).norm() < 1e-9,
1242                "reconstruction[{r},{c}] = {got:?}, expected {expected:?}"
1243            );
1244        }
1245
1246        // U must NOT be the identity for this non-diagonal matrix (guards against
1247        // the previous fabrication that returned eye()).
1248        let identity = Array2::<Complex64>::eye(2);
1249        let diff_from_identity: f64 = u
1250            .iter()
1251            .zip(identity.iter())
1252            .map(|(a, b)| (a - b).norm_sqr())
1253            .sum();
1254        assert!(
1255            diff_from_identity > 1e-6,
1256            "U must not be the identity for a non-diagonal input"
1257        );
1258
1259        // U should have orthonormal columns: UᴴU = I.
1260        let u_dag = u.t().mapv(|z| z.conj());
1261        let utu = u_dag.dot(&u);
1262        for i in 0..2 {
1263            for j in 0..2 {
1264                let expected = if i == j { 1.0 } else { 0.0 };
1265                assert!(
1266                    (utu[[i, j]].re - expected).abs() < 1e-9 && utu[[i, j]].im.abs() < 1e-9,
1267                    "UᴴU not identity at [{i},{j}]: {:?}",
1268                    utu[[i, j]]
1269                );
1270            }
1271        }
1272    }
1273
1274    #[test]
1275    fn test_minimize_actually_reduces_cost() {
1276        // A real optimiser must lower a quadratic bowl, not echo the input.
1277        let objective =
1278            |p: &Array1<f64>| -> Result<f64, String> { Ok(p.iter().map(|x| x * x).sum::<f64>()) };
1279        let initial = array![1.5, -2.0, 0.75];
1280        let initial_cost = objective(&initial).expect("eval");
1281        let config = OptimizationConfig::default();
1282
1283        let result = minimize(objective, &initial, &config).expect("minimize");
1284
1285        assert!(
1286            result.cost < initial_cost,
1287            "optimiser must reduce cost: {} !< {}",
1288            result.cost,
1289            initial_cost
1290        );
1291        // It should approach the true minimum (origin) reasonably well.
1292        assert!(
1293            result.cost < 1e-2,
1294            "optimiser should converge near the minimum, got cost {}",
1295            result.cost
1296        );
1297        // It must have done more than a single no-op iteration.
1298        assert!(result.iterations > 1, "optimiser must actually iterate");
1299        // The returned parameters must differ from the initial ones.
1300        let moved: f64 = result
1301            .parameters
1302            .iter()
1303            .zip(initial.iter())
1304            .map(|(a, b)| (a - b).abs())
1305            .sum();
1306        assert!(
1307            moved > 1e-6,
1308            "parameters must move away from the initial guess"
1309        );
1310    }
1311
1312    #[test]
1313    fn test_tensor_network_ml_accelerator() {
1314        let mut accelerator = TensorNetworkMLAccelerator::new(2, 4);
1315
1316        let circuit = array![
1317            [Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)],
1318            [Complex64::new(0.0, 0.0), Complex64::new(-1.0, 0.0)]
1319        ];
1320
1321        let result = accelerator.decompose_circuit(&circuit);
1322        assert!(result.is_ok());
1323
1324        let optimization = accelerator.optimize_contraction();
1325        assert!(optimization.is_ok());
1326
1327        let (time_comp, space_comp) = accelerator.complexity_estimate();
1328        assert!(time_comp > 0);
1329        assert!(space_comp > 0);
1330    }
1331}