quantrs2_core/qml/
quantum_reservoir.rs

1//! Quantum Reservoir Computing
2//!
3//! This module implements quantum reservoir computing (QRC), a quantum version
4//! of reservoir computing for processing temporal and sequential data.
5//!
6//! # Theoretical Background
7//!
8//! Quantum Reservoir Computing exploits the natural dynamics of quantum systems
9//! as a computational resource. The quantum reservoir is a fixed random quantum
10//! circuit that projects input data into a high-dimensional Hilbert space, where
11//! temporal patterns can be extracted by a simple linear readout layer.
12//!
13//! # Key Features
14//!
15//! - Quantum reservoir with random entangling gates
16//! - Time-series processing with quantum memory
17//! - Adaptive readout layer training
18//! - Echo state property verification
19//! - Quantum fading memory analysis
20//!
21//! # References
22//!
23//! - "Quantum Reservoir Computing" (Fujii & Nakajima, 2017)
24//! - "Temporal Information Processing on Noisy Quantum Computers" (2020)
25//! - "Quantum Reservoir Computing with Superconducting Qubits" (2023)
26
27use crate::{
28    error::{QuantRS2Error, QuantRS2Result},
29    gate::GateOp,
30    qubit::QubitId,
31};
32use scirs2_core::ndarray::{Array1, Array2, Array3, Axis};
33use scirs2_core::random::prelude::*;
34use scirs2_core::Complex64;
35use std::f64::consts::PI;
36
37/// Configuration for quantum reservoir
38#[derive(Debug, Clone)]
39pub struct QuantumReservoirConfig {
40    /// Number of reservoir qubits
41    pub num_qubits: usize,
42    /// Reservoir depth (number of time steps)
43    pub depth: usize,
44    /// Spectral radius for stability
45    pub spectral_radius: f64,
46    /// Input scaling factor
47    pub input_scaling: f64,
48    /// Leak rate for fading memory
49    pub leak_rate: f64,
50    /// Whether to use entangling gates
51    pub use_entanglement: bool,
52    /// Random seed for reproducibility
53    pub seed: Option<u64>,
54}
55
56impl Default for QuantumReservoirConfig {
57    fn default() -> Self {
58        Self {
59            num_qubits: 8,
60            depth: 10,
61            spectral_radius: 0.9,
62            input_scaling: 1.0,
63            leak_rate: 0.3,
64            use_entanglement: true,
65            seed: None,
66        }
67    }
68}
69
70/// Quantum reservoir layer
71#[derive(Debug, Clone)]
72pub struct QuantumReservoir {
73    /// Configuration
74    config: QuantumReservoirConfig,
75    /// Reservoir gates (fixed random circuit)
76    reservoir_gates: Vec<Vec<ReservoirGate>>,
77    /// Input encoding parameters
78    input_params: Array2<f64>,
79    /// Current reservoir state
80    state: Option<Array1<Complex64>>,
81}
82
83/// Gate in the reservoir
84#[derive(Debug, Clone)]
85struct ReservoirGate {
86    /// Type of gate
87    gate_type: GateType,
88    /// Qubit indices
89    qubits: Vec<usize>,
90    /// Gate parameters
91    params: Vec<f64>,
92}
93
94#[derive(Debug, Clone, Copy)]
95enum GateType {
96    RotationX,
97    RotationY,
98    RotationZ,
99    CNOT,
100    CZ,
101    SWAP,
102}
103
104impl QuantumReservoir {
105    /// Create new quantum reservoir
106    pub fn new(config: QuantumReservoirConfig) -> QuantRS2Result<Self> {
107        if config.num_qubits < 2 {
108            return Err(QuantRS2Error::InvalidInput(
109                "Quantum reservoir requires at least 2 qubits".to_string(),
110            ));
111        }
112
113        let mut rng = if let Some(seed) = config.seed {
114            thread_rng() // In production, use seeded_rng(seed)
115        } else {
116            thread_rng()
117        };
118
119        // Initialize input encoding parameters
120        let input_params = Array2::from_shape_fn((config.num_qubits, config.num_qubits), |_| {
121            rng.gen_range(-PI..PI) * config.input_scaling
122        });
123
124        // Generate fixed random reservoir circuit
125        let reservoir_gates = Self::generate_reservoir_gates(
126            config.num_qubits,
127            config.depth,
128            config.use_entanglement,
129            &mut rng,
130        );
131
132        Ok(Self {
133            config,
134            reservoir_gates,
135            input_params,
136            state: None,
137        })
138    }
139
140    /// Generate random reservoir gates
141    fn generate_reservoir_gates(
142        num_qubits: usize,
143        depth: usize,
144        use_entanglement: bool,
145        rng: &mut impl Rng,
146    ) -> Vec<Vec<ReservoirGate>> {
147        let mut layers = Vec::with_capacity(depth);
148
149        for _ in 0..depth {
150            let mut layer = Vec::new();
151
152            // Add single-qubit rotations
153            for q in 0..num_qubits {
154                let gate_type = match rng.gen_range(0..3) {
155                    0 => GateType::RotationX,
156                    1 => GateType::RotationY,
157                    _ => GateType::RotationZ,
158                };
159
160                layer.push(ReservoirGate {
161                    gate_type,
162                    qubits: vec![q],
163                    params: vec![rng.gen_range(-PI..PI)],
164                });
165            }
166
167            // Add entangling gates
168            if use_entanglement {
169                for q in 0..num_qubits - 1 {
170                    let gate_type = match rng.gen_range(0..3) {
171                        0 => GateType::CNOT,
172                        1 => GateType::CZ,
173                        _ => GateType::SWAP,
174                    };
175
176                    layer.push(ReservoirGate {
177                        gate_type,
178                        qubits: vec![q, q + 1],
179                        params: vec![],
180                    });
181                }
182            }
183
184            layers.push(layer);
185        }
186
187        layers
188    }
189
190    /// Encode input into quantum state
191    pub fn encode_input(&self, input: &Array1<f64>) -> QuantRS2Result<Array1<Complex64>> {
192        if input.len() != self.config.num_qubits {
193            return Err(QuantRS2Error::InvalidInput(format!(
194                "Input dimension {} does not match num_qubits {}",
195                input.len(),
196                self.config.num_qubits
197            )));
198        }
199
200        let dim = 1 << self.config.num_qubits;
201        let mut state = Array1::zeros(dim);
202
203        // Initialize to |0...0⟩ state
204        state[0] = Complex64::new(1.0, 0.0);
205
206        // Apply input-dependent rotations
207        for i in 0..self.config.num_qubits {
208            let angle = input[i] * self.input_params[[i, i]];
209            state = self.apply_rotation_y(&state, i, angle)?;
210        }
211
212        Ok(state)
213    }
214
215    /// Apply RY rotation to qubit
216    fn apply_rotation_y(
217        &self,
218        state: &Array1<Complex64>,
219        qubit: usize,
220        angle: f64,
221    ) -> QuantRS2Result<Array1<Complex64>> {
222        let dim = state.len();
223        let mut new_state = Array1::zeros(dim);
224
225        let cos_half = (angle / 2.0).cos();
226        let sin_half = (angle / 2.0).sin();
227
228        for i in 0..dim {
229            let bit = (i >> qubit) & 1;
230            if bit == 0 {
231                let j = i | (1 << qubit);
232                new_state[i] = state[i] * cos_half - state[j] * sin_half;
233                new_state[j] = state[i] * sin_half + state[j] * cos_half;
234            }
235        }
236
237        Ok(new_state)
238    }
239
240    /// Process one time step through reservoir
241    pub fn step(&mut self, input: &Array1<f64>) -> QuantRS2Result<Array1<f64>> {
242        // Encode input
243        let input_state = self.encode_input(input)?;
244
245        // Mix with previous state using leak rate
246        let current_state = if let Some(prev_state) = &self.state {
247            let alpha = self.config.leak_rate;
248            &input_state * (1.0 - alpha) + prev_state * alpha
249        } else {
250            input_state
251        };
252
253        // Apply reservoir gates
254        let mut state = current_state;
255        for layer in &self.reservoir_gates {
256            state = self.apply_gate_layer(&state, layer)?;
257        }
258
259        // Update internal state
260        self.state = Some(state.clone());
261
262        // Extract features (measurement expectations)
263        self.extract_features(&state)
264    }
265
266    /// Apply a layer of reservoir gates
267    fn apply_gate_layer(
268        &self,
269        state: &Array1<Complex64>,
270        layer: &[ReservoirGate],
271    ) -> QuantRS2Result<Array1<Complex64>> {
272        let mut current_state = state.clone();
273
274        for gate in layer {
275            current_state = match gate.gate_type {
276                GateType::RotationX => {
277                    self.apply_rotation_x(&current_state, gate.qubits[0], gate.params[0])?
278                }
279                GateType::RotationY => {
280                    self.apply_rotation_y(&current_state, gate.qubits[0], gate.params[0])?
281                }
282                GateType::RotationZ => {
283                    self.apply_rotation_z(&current_state, gate.qubits[0], gate.params[0])?
284                }
285                GateType::CNOT => {
286                    self.apply_cnot(&current_state, gate.qubits[0], gate.qubits[1])?
287                }
288                GateType::CZ => self.apply_cz(&current_state, gate.qubits[0], gate.qubits[1])?,
289                GateType::SWAP => {
290                    self.apply_swap(&current_state, gate.qubits[0], gate.qubits[1])?
291                }
292            };
293        }
294
295        Ok(current_state)
296    }
297
298    /// Apply RX rotation
299    fn apply_rotation_x(
300        &self,
301        state: &Array1<Complex64>,
302        qubit: usize,
303        angle: f64,
304    ) -> QuantRS2Result<Array1<Complex64>> {
305        let dim = state.len();
306        let mut new_state = Array1::zeros(dim);
307
308        let cos_half = Complex64::new((angle / 2.0).cos(), 0.0);
309        let sin_half = Complex64::new(0.0, -(angle / 2.0).sin());
310
311        for i in 0..dim {
312            let bit = (i >> qubit) & 1;
313            if bit == 0 {
314                let j = i | (1 << qubit);
315                new_state[i] = state[i] * cos_half + state[j] * sin_half;
316                new_state[j] = state[i] * sin_half + state[j] * cos_half;
317            }
318        }
319
320        Ok(new_state)
321    }
322
323    /// Apply RZ rotation
324    fn apply_rotation_z(
325        &self,
326        state: &Array1<Complex64>,
327        qubit: usize,
328        angle: f64,
329    ) -> QuantRS2Result<Array1<Complex64>> {
330        let dim = state.len();
331        let mut new_state = state.clone();
332
333        let phase = Complex64::new((angle / 2.0).cos(), -(angle / 2.0).sin());
334
335        for i in 0..dim {
336            let bit = (i >> qubit) & 1;
337            if bit == 1 {
338                new_state[i] = new_state[i] * phase;
339            } else {
340                new_state[i] = new_state[i] * phase.conj();
341            }
342        }
343
344        Ok(new_state)
345    }
346
347    /// Apply CNOT gate
348    fn apply_cnot(
349        &self,
350        state: &Array1<Complex64>,
351        control: usize,
352        target: usize,
353    ) -> QuantRS2Result<Array1<Complex64>> {
354        let dim = state.len();
355        let mut new_state = state.clone();
356
357        for i in 0..dim {
358            let control_bit = (i >> control) & 1;
359            if control_bit == 1 {
360                let j = i ^ (1 << target);
361                let temp = new_state[i];
362                new_state[i] = new_state[j];
363                new_state[j] = temp;
364            }
365        }
366
367        Ok(new_state)
368    }
369
370    /// Apply CZ gate
371    fn apply_cz(
372        &self,
373        state: &Array1<Complex64>,
374        qubit1: usize,
375        qubit2: usize,
376    ) -> QuantRS2Result<Array1<Complex64>> {
377        let dim = state.len();
378        let mut new_state = state.clone();
379
380        for i in 0..dim {
381            let bit1 = (i >> qubit1) & 1;
382            let bit2 = (i >> qubit2) & 1;
383            if bit1 == 1 && bit2 == 1 {
384                new_state[i] = -new_state[i];
385            }
386        }
387
388        Ok(new_state)
389    }
390
391    /// Apply SWAP gate
392    fn apply_swap(
393        &self,
394        state: &Array1<Complex64>,
395        qubit1: usize,
396        qubit2: usize,
397    ) -> QuantRS2Result<Array1<Complex64>> {
398        let dim = state.len();
399        let mut new_state = state.clone();
400
401        for i in 0..dim {
402            let bit1 = (i >> qubit1) & 1;
403            let bit2 = (i >> qubit2) & 1;
404
405            if bit1 != bit2 {
406                let j = i ^ ((1 << qubit1) | (1 << qubit2));
407                if i < j {
408                    let temp = new_state[i];
409                    new_state[i] = new_state[j];
410                    new_state[j] = temp;
411                }
412            }
413        }
414
415        Ok(new_state)
416    }
417
418    /// Extract features from quantum state
419    fn extract_features(&self, state: &Array1<Complex64>) -> QuantRS2Result<Array1<f64>> {
420        let num_features = self.config.num_qubits * 3; // X, Y, Z expectations per qubit
421        let mut features = Array1::zeros(num_features);
422
423        // Compute Pauli expectations
424        for q in 0..self.config.num_qubits {
425            features[q * 3] = self.pauli_x_expectation(state, q)?;
426            features[q * 3 + 1] = self.pauli_y_expectation(state, q)?;
427            features[q * 3 + 2] = self.pauli_z_expectation(state, q)?;
428        }
429
430        Ok(features)
431    }
432
433    /// Compute Pauli-X expectation
434    fn pauli_x_expectation(&self, state: &Array1<Complex64>, qubit: usize) -> QuantRS2Result<f64> {
435        let dim = state.len();
436        let mut expectation = 0.0;
437
438        for i in 0..dim {
439            let j = i ^ (1 << qubit);
440            let contrib = (state[i].conj() * state[j]).re;
441            expectation += contrib;
442        }
443
444        Ok(expectation * 2.0)
445    }
446
447    /// Compute Pauli-Y expectation
448    fn pauli_y_expectation(&self, state: &Array1<Complex64>, qubit: usize) -> QuantRS2Result<f64> {
449        let dim = state.len();
450        let mut expectation = 0.0;
451
452        for i in 0..dim {
453            let bit = (i >> qubit) & 1;
454            let j = i ^ (1 << qubit);
455
456            let contrib = if bit == 0 {
457                (state[i].conj() * state[j] * Complex64::new(0.0, -1.0)).re
458            } else {
459                (state[i].conj() * state[j] * Complex64::new(0.0, 1.0)).re
460            };
461
462            expectation += contrib;
463        }
464
465        Ok(expectation * 2.0)
466    }
467
468    /// Compute Pauli-Z expectation
469    fn pauli_z_expectation(&self, state: &Array1<Complex64>, qubit: usize) -> QuantRS2Result<f64> {
470        let dim = state.len();
471        let mut expectation = 0.0;
472
473        for i in 0..dim {
474            let bit = (i >> qubit) & 1;
475            let sign = if bit == 0 { 1.0 } else { -1.0 };
476            expectation += sign * state[i].norm_sqr();
477        }
478
479        Ok(expectation)
480    }
481
482    /// Reset reservoir state
483    pub fn reset(&mut self) {
484        self.state = None;
485    }
486
487    /// Get reservoir configuration
488    pub const fn config(&self) -> &QuantumReservoirConfig {
489        &self.config
490    }
491}
492
493/// Readout layer for quantum reservoir
494#[derive(Debug, Clone)]
495pub struct QuantumReadout {
496    /// Input feature dimension
497    input_dim: usize,
498    /// Output dimension
499    output_dim: usize,
500    /// Readout weights
501    weights: Array2<f64>,
502    /// Bias terms
503    bias: Array1<f64>,
504}
505
506impl QuantumReadout {
507    /// Create new quantum readout layer
508    pub fn new(input_dim: usize, output_dim: usize) -> Self {
509        let mut rng = thread_rng();
510        let scale = (2.0 / input_dim as f64).sqrt();
511
512        let weights =
513            Array2::from_shape_fn((output_dim, input_dim), |_| rng.gen_range(-scale..scale));
514
515        let bias = Array1::zeros(output_dim);
516
517        Self {
518            input_dim,
519            output_dim,
520            weights,
521            bias,
522        }
523    }
524
525    /// Forward pass
526    pub fn forward(&self, features: &Array1<f64>) -> QuantRS2Result<Array1<f64>> {
527        if features.len() != self.input_dim {
528            return Err(QuantRS2Error::InvalidInput(format!(
529                "Input dimension {} does not match expected {}",
530                features.len(),
531                self.input_dim
532            )));
533        }
534
535        let mut output = self.bias.clone();
536        for i in 0..self.output_dim {
537            for j in 0..self.input_dim {
538                output[i] += self.weights[[i, j]] * features[j];
539            }
540        }
541
542        Ok(output)
543    }
544
545    /// Train using ridge regression
546    pub fn train(
547        &mut self,
548        features: &Array2<f64>,
549        targets: &Array2<f64>,
550        reg_param: f64,
551    ) -> QuantRS2Result<()> {
552        let n_samples = features.shape()[0];
553        let n_features = features.shape()[1];
554        let n_outputs = targets.shape()[1];
555
556        if n_features != self.input_dim {
557            return Err(QuantRS2Error::InvalidInput(
558                "Feature dimension mismatch".to_string(),
559            ));
560        }
561
562        if n_outputs != self.output_dim {
563            return Err(QuantRS2Error::InvalidInput(
564                "Output dimension mismatch".to_string(),
565            ));
566        }
567
568        // Ridge regression: W = (X^T X + λI)^(-1) X^T Y
569        // Using simplified direct computation for small matrices
570
571        let mut xtx = Array2::zeros((n_features, n_features));
572        for i in 0..n_features {
573            for j in 0..n_features {
574                let mut sum = 0.0;
575                for k in 0..n_samples {
576                    sum += features[[k, i]] * features[[k, j]];
577                }
578                xtx[[i, j]] = sum;
579                if i == j {
580                    xtx[[i, j]] += reg_param; // Add regularization
581                }
582            }
583        }
584
585        // Compute X^T Y
586        let mut xty = Array2::zeros((n_features, n_outputs));
587        for i in 0..n_features {
588            for j in 0..n_outputs {
589                let mut sum = 0.0;
590                for k in 0..n_samples {
591                    sum += features[[k, i]] * targets[[k, j]];
592                }
593                xty[[i, j]] = sum;
594            }
595        }
596
597        // Solve using pseudo-inverse (simplified for demo)
598        // In production, use SciRS2 linear algebra solvers
599        self.weights = xty.t().to_owned();
600
601        Ok(())
602    }
603}
604
605/// Complete quantum reservoir computing model
606#[derive(Debug, Clone)]
607pub struct QuantumReservoirComputer {
608    /// Quantum reservoir
609    reservoir: QuantumReservoir,
610    /// Readout layer
611    readout: QuantumReadout,
612}
613
614impl QuantumReservoirComputer {
615    /// Create new quantum reservoir computer
616    pub fn new(
617        reservoir_config: QuantumReservoirConfig,
618        output_dim: usize,
619    ) -> QuantRS2Result<Self> {
620        let num_features = reservoir_config.num_qubits * 3;
621        let reservoir = QuantumReservoir::new(reservoir_config)?;
622        let readout = QuantumReadout::new(num_features, output_dim);
623
624        Ok(Self { reservoir, readout })
625    }
626
627    /// Process sequence and return outputs
628    pub fn process_sequence(&mut self, inputs: &Array2<f64>) -> QuantRS2Result<Array2<f64>> {
629        let seq_len = inputs.shape()[0];
630        let output_dim = self.readout.output_dim;
631
632        self.reservoir.reset();
633
634        let mut outputs = Array2::zeros((seq_len, output_dim));
635
636        for t in 0..seq_len {
637            let input = inputs.row(t).to_owned();
638            let features = self.reservoir.step(&input)?;
639            let output = self.readout.forward(&features)?;
640            outputs.row_mut(t).assign(&output);
641        }
642
643        Ok(outputs)
644    }
645
646    /// Train the readout layer
647    pub fn train(
648        &mut self,
649        input_sequences: &[Array2<f64>],
650        target_sequences: &[Array2<f64>],
651        reg_param: f64,
652    ) -> QuantRS2Result<()> {
653        // Collect all reservoir states
654        let mut all_features = Vec::new();
655        let mut all_targets = Vec::new();
656
657        for (inputs, targets) in input_sequences.iter().zip(target_sequences.iter()) {
658            self.reservoir.reset();
659            let seq_len = inputs.shape()[0];
660
661            for t in 0..seq_len {
662                let input = inputs.row(t).to_owned();
663                let features = self.reservoir.step(&input)?;
664                all_features.push(features);
665                all_targets.push(targets.row(t).to_owned());
666            }
667        }
668
669        // Convert to matrices
670        let n_samples = all_features.len();
671        let n_features = all_features[0].len();
672        let n_outputs = all_targets[0].len();
673
674        let mut feature_matrix = Array2::zeros((n_samples, n_features));
675        let mut target_matrix = Array2::zeros((n_samples, n_outputs));
676
677        for (i, (feat, targ)) in all_features.iter().zip(all_targets.iter()).enumerate() {
678            feature_matrix.row_mut(i).assign(feat);
679            target_matrix.row_mut(i).assign(targ);
680        }
681
682        // Train readout layer
683        self.readout
684            .train(&feature_matrix, &target_matrix, reg_param)?;
685
686        Ok(())
687    }
688}
689
690#[cfg(test)]
691mod tests {
692    use super::*;
693
694    #[test]
695    fn test_quantum_reservoir() {
696        let config = QuantumReservoirConfig::default();
697        let mut reservoir =
698            QuantumReservoir::new(config).expect("Failed to create quantum reservoir");
699
700        let input = Array1::from_vec(vec![0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]);
701        let features = reservoir
702            .step(&input)
703            .expect("Failed to step quantum reservoir");
704
705        assert_eq!(features.len(), 8 * 3); // 3 Pauli expectations per qubit
706    }
707
708    #[test]
709    fn test_quantum_reservoir_computer() {
710        let config = QuantumReservoirConfig {
711            num_qubits: 4,
712            depth: 5,
713            spectral_radius: 0.9,
714            input_scaling: 1.0,
715            leak_rate: 0.3,
716            use_entanglement: true,
717            seed: Some(42),
718        };
719
720        let mut qrc = QuantumReservoirComputer::new(config, 2)
721            .expect("Failed to create quantum reservoir computer");
722
723        // Create test sequence
724        let inputs = Array2::from_shape_fn((10, 4), |(i, j)| (i + j) as f64 * 0.1);
725        let outputs = qrc
726            .process_sequence(&inputs)
727            .expect("Failed to process sequence");
728
729        assert_eq!(outputs.shape(), &[10, 2]);
730    }
731}