Skip to main content

quantrs2_core/qml/
mod.rs

1//! Quantum Machine Learning (QML) primitives and layers
2//!
3//! This module provides building blocks for quantum machine learning,
4//! including parameterized quantum circuits, data encoding strategies,
5//! and common QML layer patterns.
6
7pub mod advanced_algorithms;
8pub mod encoding;
9pub mod generative_adversarial;
10pub mod layers;
11pub mod nlp;
12pub mod reinforcement_learning;
13pub mod simulator;
14pub mod training;
15
16// New cutting-edge quantum ML modules
17pub mod quantum_contrastive;
18pub mod quantum_memory_networks;
19pub mod quantum_meta_learning;
20pub mod quantum_reservoir;
21pub mod quantum_transformer;
22
23// Advanced quantum ML: Privacy, Security, and Distributed Learning
24pub mod quantum_boltzmann;
25pub mod quantum_federated;
26
27// Re-export advanced QML algorithms
28pub use advanced_algorithms::{
29    FeatureMapType, QMLMetrics, QuantumEnsemble, QuantumKernel, QuantumKernelConfig, QuantumSVM,
30    QuantumTransferLearning, TransferLearningConfig, VotingStrategy,
31};
32
33// Re-export new modules
34pub use quantum_contrastive::{
35    QuantumAugmentation, QuantumContrastiveConfig, QuantumContrastiveLearner,
36};
37pub use quantum_memory_networks::{MemoryInitStrategy, QuantumMemoryConfig, QuantumMemoryNetwork};
38pub use quantum_meta_learning::{
39    QuantumMAML, QuantumMetaLearningConfig, QuantumReptile, QuantumTask,
40};
41pub use quantum_reservoir::{QuantumReservoirComputer, QuantumReservoirConfig};
42pub use quantum_transformer::{QuantumAttention, QuantumTransformer, QuantumTransformerConfig};
43
44// Re-export advanced quantum ML modules
45pub use quantum_boltzmann::{DeepQuantumBoltzmannMachine, QRBMConfig, QuantumRBM};
46pub use quantum_federated::{AggregationStrategy, QuantumFederatedConfig, QuantumFederatedServer};
47
48use crate::{
49    error::{QuantRS2Error, QuantRS2Result},
50    gate::GateOp,
51    qubit::QubitId,
52};
53use scirs2_core::ndarray::{Array1, Array2};
54use scirs2_core::Complex64;
55
56// Re-export Parameter from layers module
57pub use layers::Parameter;
58
59/// Trait for quantum machine learning layers
60pub trait QMLLayer: Send + Sync {
61    /// Get the number of qubits this layer acts on
62    fn num_qubits(&self) -> usize;
63
64    /// Get the parameters of this layer
65    fn parameters(&self) -> &[Parameter];
66
67    /// Get mutable access to parameters
68    fn parameters_mut(&mut self) -> &mut [Parameter];
69
70    /// Set parameter values
71    fn set_parameters(&mut self, values: &[f64]) -> QuantRS2Result<()> {
72        if values.len() != self.parameters().len() {
73            return Err(QuantRS2Error::InvalidInput(format!(
74                "Expected {} parameters, got {}",
75                self.parameters().len(),
76                values.len()
77            )));
78        }
79
80        for (param, &value) in self.parameters_mut().iter_mut().zip(values.iter()) {
81            param.value = value;
82        }
83
84        Ok(())
85    }
86
87    /// Get the gates that make up this layer
88    fn gates(&self) -> Vec<Box<dyn GateOp>>;
89
90    /// Compute gradients with respect to parameters
91    fn compute_gradients(
92        &self,
93        state: &Array1<Complex64>,
94        loss_gradient: &Array1<Complex64>,
95    ) -> QuantRS2Result<Vec<f64>>;
96
97    /// Get layer name
98    fn name(&self) -> &str;
99}
100
101/// Data encoding strategies for QML
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub enum EncodingStrategy {
104    /// Amplitude encoding: data encoded in state amplitudes
105    Amplitude,
106    /// Angle encoding: data encoded as rotation angles
107    Angle,
108    /// IQP encoding: data encoded in diagonal gates
109    IQP,
110    /// Basis encoding: data encoded in computational basis
111    Basis,
112}
113
114/// Configuration for QML circuits
115#[derive(Debug, Clone)]
116pub struct QMLConfig {
117    /// Number of qubits
118    pub num_qubits: usize,
119    /// Number of layers
120    pub num_layers: usize,
121    /// Data encoding strategy
122    pub encoding: EncodingStrategy,
123    /// Entanglement pattern
124    pub entanglement: EntanglementPattern,
125    /// Whether to reupload data in each layer
126    pub data_reuploading: bool,
127}
128
129impl Default for QMLConfig {
130    fn default() -> Self {
131        Self {
132            num_qubits: 4,
133            num_layers: 2,
134            encoding: EncodingStrategy::Angle,
135            entanglement: EntanglementPattern::Full,
136            data_reuploading: false,
137        }
138    }
139}
140
141/// Entanglement patterns for QML layers
142#[derive(Debug, Clone, Copy, PartialEq, Eq)]
143pub enum EntanglementPattern {
144    /// No entanglement
145    None,
146    /// Linear nearest-neighbor entanglement
147    Linear,
148    /// Circular nearest-neighbor entanglement
149    Circular,
150    /// All-to-all entanglement
151    Full,
152    /// Alternating pairs
153    Alternating,
154}
155
156/// A parameterized quantum circuit for QML
157pub struct QMLCircuit {
158    /// Configuration
159    config: QMLConfig,
160    /// The layers in the circuit
161    layers: Vec<Box<dyn QMLLayer>>,
162    /// Parameter count
163    num_parameters: usize,
164}
165
166impl QMLCircuit {
167    /// Create a new QML circuit
168    pub fn new(config: QMLConfig) -> Self {
169        Self {
170            config,
171            layers: Vec::new(),
172            num_parameters: 0,
173        }
174    }
175
176    /// Add a layer to the circuit
177    pub fn add_layer(&mut self, layer: Box<dyn QMLLayer>) -> QuantRS2Result<()> {
178        if layer.num_qubits() != self.config.num_qubits {
179            return Err(QuantRS2Error::InvalidInput(format!(
180                "Layer has {} qubits, circuit has {}",
181                layer.num_qubits(),
182                self.config.num_qubits
183            )));
184        }
185
186        self.num_parameters += layer.parameters().len();
187        self.layers.push(layer);
188        Ok(())
189    }
190
191    /// Get all parameters in the circuit
192    pub fn parameters(&self) -> Vec<&Parameter> {
193        self.layers
194            .iter()
195            .flat_map(|layer| layer.parameters().iter())
196            .collect()
197    }
198
199    /// Set all parameters in the circuit
200    pub fn set_parameters(&mut self, values: &[f64]) -> QuantRS2Result<()> {
201        if values.len() != self.num_parameters {
202            return Err(QuantRS2Error::InvalidInput(format!(
203                "Expected {} parameters, got {}",
204                self.num_parameters,
205                values.len()
206            )));
207        }
208
209        let mut offset = 0;
210        for layer in &mut self.layers {
211            let layer_params = layer.parameters().len();
212            layer.set_parameters(&values[offset..offset + layer_params])?;
213            offset += layer_params;
214        }
215
216        Ok(())
217    }
218
219    /// Get all gates in the circuit
220    pub fn gates(&self) -> Vec<Box<dyn GateOp>> {
221        self.layers.iter().flat_map(|layer| layer.gates()).collect()
222    }
223
224    /// Compute gradients for all parameters
225    pub fn compute_gradients(
226        &self,
227        state: &Array1<Complex64>,
228        loss_gradient: &Array1<Complex64>,
229    ) -> QuantRS2Result<Vec<f64>> {
230        let mut all_gradients = Vec::new();
231
232        for layer in &self.layers {
233            let layer_grads = layer.compute_gradients(state, loss_gradient)?;
234            all_gradients.extend(layer_grads);
235        }
236
237        Ok(all_gradients)
238    }
239}
240
241/// Helper function to create entangling gates based on pattern
242pub fn create_entangling_gates(
243    num_qubits: usize,
244    pattern: EntanglementPattern,
245) -> Vec<(QubitId, QubitId)> {
246    match pattern {
247        EntanglementPattern::None => vec![],
248
249        EntanglementPattern::Linear => (0..num_qubits - 1)
250            .map(|i| (QubitId(i as u32), QubitId((i + 1) as u32)))
251            .collect(),
252
253        EntanglementPattern::Circular => {
254            let mut gates = vec![];
255            for i in 0..num_qubits {
256                gates.push((QubitId(i as u32), QubitId(((i + 1) % num_qubits) as u32)));
257            }
258            gates
259        }
260
261        EntanglementPattern::Full => {
262            let mut gates = vec![];
263            for i in 0..num_qubits {
264                for j in i + 1..num_qubits {
265                    gates.push((QubitId(i as u32), QubitId(j as u32)));
266                }
267            }
268            gates
269        }
270
271        EntanglementPattern::Alternating => {
272            let mut gates = vec![];
273            // Even pairs
274            for i in (0..num_qubits - 1).step_by(2) {
275                gates.push((QubitId(i as u32), QubitId((i + 1) as u32)));
276            }
277            // Odd pairs
278            for i in (1..num_qubits - 1).step_by(2) {
279                gates.push((QubitId(i as u32), QubitId((i + 1) as u32)));
280            }
281            gates
282        }
283    }
284}
285
286/// Compute the quantum Fisher information matrix (real part of the quantum
287/// geometric tensor) of a parameterized circuit.
288///
289/// `F_ij = 4 · Re(⟨∂_i ψ | ∂_j ψ⟩ − ⟨∂_i ψ | ψ⟩⟨ψ | ∂_j ψ⟩)`
290///
291/// The state derivatives `|∂_i ψ⟩` are computed by central finite differences:
292/// the circuit's `i`-th parameter is shifted by `±ε`, the resulting state is
293/// simulated exactly, and `|∂_i ψ⟩ ≈ (|ψ(θ+ε)⟩ − |ψ(θ−ε)⟩) / (2ε)`. The
294/// circuit's original parameters are restored on return.
295///
296/// The circuit is borrowed mutably because computing the derivatives requires
297/// temporarily perturbing its parameters; this is an exact, non-fabricated
298/// computation.
299pub fn quantum_fisher_information(circuit: &mut QMLCircuit) -> QuantRS2Result<Array2<f64>> {
300    let num_params = circuit.num_parameters;
301    let mut fisher = Array2::zeros((num_params, num_params));
302    if num_params == 0 {
303        return Ok(fisher);
304    }
305
306    let num_qubits = circuit.config.num_qubits;
307    let base_params: Vec<f64> = circuit.parameters().iter().map(|p| p.value).collect();
308
309    // Helper: simulate the state for a given parameter assignment.
310    let epsilon = 1e-6;
311    let dim = 1usize << num_qubits;
312
313    // Reference state |ψ(θ)⟩.
314    circuit.set_parameters(&base_params)?;
315    let psi = simulator::simulate(num_qubits, &circuit.gates())?;
316
317    // State derivatives |∂_i ψ⟩ via central differences.
318    let mut derivatives: Vec<Array1<Complex64>> = Vec::with_capacity(num_params);
319    for i in 0..num_params {
320        let mut plus = base_params.clone();
321        plus[i] += epsilon;
322        circuit.set_parameters(&plus)?;
323        let psi_plus = simulator::simulate(num_qubits, &circuit.gates())?;
324
325        let mut minus = base_params.clone();
326        minus[i] -= epsilon;
327        circuit.set_parameters(&minus)?;
328        let psi_minus = simulator::simulate(num_qubits, &circuit.gates())?;
329
330        let mut deriv = Array1::zeros(dim);
331        for k in 0..dim {
332            deriv[k] = (psi_plus[k] - psi_minus[k]) / Complex64::new(2.0 * epsilon, 0.0);
333        }
334        derivatives.push(deriv);
335    }
336
337    // Restore original parameters.
338    circuit.set_parameters(&base_params)?;
339
340    // ⟨ψ | ∂_i ψ⟩ for each i.
341    let psi_dot_deriv: Vec<Complex64> = derivatives
342        .iter()
343        .map(|d| {
344            psi.iter()
345                .zip(d.iter())
346                .map(|(p, di)| p.conj() * di)
347                .sum::<Complex64>()
348        })
349        .collect();
350
351    // Assemble the symmetric Fisher matrix.
352    for i in 0..num_params {
353        for j in i..num_params {
354            let overlap: Complex64 = derivatives[i]
355                .iter()
356                .zip(derivatives[j].iter())
357                .map(|(di, dj)| di.conj() * dj)
358                .sum();
359            let correction = psi_dot_deriv[i].conj() * psi_dot_deriv[j];
360            let value = 4.0 * (overlap - correction).re;
361            fisher[(i, j)] = value;
362            fisher[(j, i)] = value;
363        }
364    }
365
366    Ok(fisher)
367}
368
369/// Natural gradient for quantum optimization.
370///
371/// Solves the regularized linear system `(F + λI) · g_nat = g` for the natural
372/// gradient `g_nat`, where `F` is the quantum Fisher information matrix, `λ` the
373/// Tikhonov regularization, and `g` the Euclidean gradient. The system is
374/// solved by inverting the (symmetric positive-definite after regularization)
375/// matrix via `scirs2_linalg`.
376pub fn natural_gradient(
377    gradients: &[f64],
378    fisher: &Array2<f64>,
379    regularization: f64,
380) -> QuantRS2Result<Vec<f64>> {
381    let n = gradients.len();
382    if n == 0 {
383        return Ok(Vec::new());
384    }
385    if fisher.nrows() != n || fisher.ncols() != n {
386        return Err(QuantRS2Error::InvalidInput(format!(
387            "Fisher matrix is {}×{} but gradient has length {n}",
388            fisher.nrows(),
389            fisher.ncols()
390        )));
391    }
392
393    // Regularize the diagonal: (F + λI).
394    let mut regularized = fisher.clone();
395    for i in 0..n {
396        regularized[(i, i)] += regularization;
397    }
398
399    let grad = Array1::from_vec(gradients.to_vec());
400
401    // g_nat = (F + λI)^{-1} g. Invert via scirs2_linalg (SciRS2 POLICY).
402    if n == 1 {
403        let denom = regularized[(0, 0)];
404        if denom.abs() < 1e-14 {
405            return Err(QuantRS2Error::InvalidInput(
406                "regularized Fisher matrix is singular".to_string(),
407            ));
408        }
409        return Ok(vec![grad[0] / denom]);
410    }
411
412    let inverse = scirs2_linalg::inv(&regularized.view(), None).map_err(|e| {
413        QuantRS2Error::ComputationError(format!("natural gradient solve failed: {e:?}"))
414    })?;
415    let natural = inverse.dot(&grad);
416    Ok(natural.to_vec())
417}
418
419#[cfg(test)]
420mod tests {
421    use super::*;
422
423    #[test]
424    fn test_entanglement_patterns() {
425        let linear = create_entangling_gates(4, EntanglementPattern::Linear);
426        assert_eq!(linear.len(), 3);
427        assert_eq!(linear[0], (QubitId(0), QubitId(1)));
428
429        let circular = create_entangling_gates(4, EntanglementPattern::Circular);
430        assert_eq!(circular.len(), 4);
431        assert_eq!(circular[3], (QubitId(3), QubitId(0)));
432
433        let full = create_entangling_gates(3, EntanglementPattern::Full);
434        assert_eq!(full.len(), 3); // 3 choose 2
435
436        let none = create_entangling_gates(4, EntanglementPattern::None);
437        assert_eq!(none.len(), 0);
438    }
439
440    #[test]
441    fn test_qml_circuit() {
442        let config = QMLConfig {
443            num_qubits: 2,
444            num_layers: 1,
445            ..Default::default()
446        };
447
448        let circuit = QMLCircuit::new(config);
449        assert_eq!(circuit.num_parameters, 0);
450    }
451
452    #[test]
453    fn test_natural_gradient_solves_system_not_passthrough() {
454        // For a non-identity Fisher matrix the natural gradient must DIFFER from
455        // the raw gradient (the old fabrication returned the gradient verbatim).
456        // (F + λI) g_nat = g  =>  g_nat = (F + λI)^{-1} g.
457        let fisher =
458            Array2::from_shape_vec((2, 2), vec![2.0, 0.5, 0.5, 3.0]).expect("fisher matrix");
459        let gradients = vec![1.0, 1.0];
460        let reg = 0.0;
461
462        let g_nat = natural_gradient(&gradients, &fisher, reg).expect("natural gradient");
463
464        // Verify it is the true solution: (F) g_nat ≈ g.
465        let recon0 = fisher[(0, 0)] * g_nat[0] + fisher[(0, 1)] * g_nat[1];
466        let recon1 = fisher[(1, 0)] * g_nat[0] + fisher[(1, 1)] * g_nat[1];
467        assert!((recon0 - gradients[0]).abs() < 1e-9);
468        assert!((recon1 - gradients[1]).abs() < 1e-9);
469
470        // And it must NOT be the raw gradient (proves real solve, not passthrough).
471        assert!(
472            (g_nat[0] - gradients[0]).abs() > 1e-6 || (g_nat[1] - gradients[1]).abs() > 1e-6,
473            "natural gradient must differ from raw gradient for non-identity Fisher"
474        );
475    }
476
477    #[test]
478    fn test_natural_gradient_identity_fisher_is_passthrough() {
479        // With F = 0 and λ = 1, (F+λI)=I so g_nat == g. This is the
480        // mathematically-correct identity case (not a fabrication).
481        let fisher = Array2::zeros((3, 3));
482        let gradients = vec![0.4, -1.2, 0.7];
483        let g_nat = natural_gradient(&gradients, &fisher, 1.0).expect("natural gradient");
484        for (a, b) in g_nat.iter().zip(gradients.iter()) {
485            assert!((a - b).abs() < 1e-12);
486        }
487    }
488
489    #[test]
490    fn test_quantum_fisher_information_is_nonzero_for_parameterized_circuit() {
491        // A parameterized rotation circuit has a non-trivial quantum geometric
492        // tensor; the old placeholder returned all zeros.
493        let config = QMLConfig {
494            num_qubits: 2,
495            num_layers: 1,
496            ..Default::default()
497        };
498        let mut circuit = QMLCircuit::new(config);
499        let layer = layers::RotationLayer::uniform(2, 'Y').expect("rotation layer");
500        circuit.add_layer(Box::new(layer)).expect("add layer");
501        // Set non-trivial parameters.
502        circuit.set_parameters(&[0.5, 1.1]).expect("set parameters");
503
504        let fisher = quantum_fisher_information(&mut circuit).expect("fisher");
505        assert_eq!(fisher.shape(), &[2, 2]);
506        let max_abs = fisher.iter().fold(0.0_f64, |acc, &v| acc.max(v.abs()));
507        assert!(
508            max_abs > 1e-6,
509            "Fisher information must be non-zero for a parameterized circuit, got max {max_abs}"
510        );
511        // For independent RY rotations on the |0> state the QFI is ~I (diagonal).
512        assert!(fisher[(0, 0)] > 1e-3 && fisher[(1, 1)] > 1e-3);
513    }
514}