Skip to main content

quantrs2_core/qml/
layers.rs

1//! Common quantum machine learning layers
2//!
3//! This module provides implementations of common QML layers including
4//! rotation layers, entangling layers, and composite layers.
5
6use super::{create_entangling_gates, EntanglementPattern, QMLLayer};
7use crate::{
8    error::{QuantRS2Error, QuantRS2Result},
9    gate::GateOp,
10    parametric::{ParametricRotationX, ParametricRotationY, ParametricRotationZ},
11    qubit::QubitId,
12};
13use scirs2_core::ndarray::Array1;
14use scirs2_core::Complex64;
15use std::f64::consts::PI;
16
17// Parameter type for QML
18#[derive(Debug, Clone)]
19pub struct Parameter {
20    pub name: String,
21    pub value: f64,
22    pub bounds: Option<(f64, f64)>,
23}
24
25// Simple wrapper types for the QML module
26type RXGate = ParametricRotationX;
27type RYGate = ParametricRotationY;
28type RZGate = ParametricRotationZ;
29
30// Simple CNOT gate for QML usage
31#[derive(Debug, Clone, Copy)]
32struct CNOT {
33    control: QubitId,
34    target: QubitId,
35}
36
37impl GateOp for CNOT {
38    fn name(&self) -> &'static str {
39        "CNOT"
40    }
41
42    fn qubits(&self) -> Vec<QubitId> {
43        vec![self.control, self.target]
44    }
45
46    fn matrix(&self) -> crate::error::QuantRS2Result<Vec<Complex64>> {
47        Ok(vec![
48            Complex64::new(1.0, 0.0),
49            Complex64::new(0.0, 0.0),
50            Complex64::new(0.0, 0.0),
51            Complex64::new(0.0, 0.0),
52            Complex64::new(0.0, 0.0),
53            Complex64::new(1.0, 0.0),
54            Complex64::new(0.0, 0.0),
55            Complex64::new(0.0, 0.0),
56            Complex64::new(0.0, 0.0),
57            Complex64::new(0.0, 0.0),
58            Complex64::new(0.0, 0.0),
59            Complex64::new(1.0, 0.0),
60            Complex64::new(0.0, 0.0),
61            Complex64::new(0.0, 0.0),
62            Complex64::new(1.0, 0.0),
63            Complex64::new(0.0, 0.0),
64        ])
65    }
66
67    fn as_any(&self) -> &dyn std::any::Any {
68        self
69    }
70
71    fn clone_gate(&self) -> Box<dyn GateOp> {
72        Box::new(*self)
73    }
74}
75
76/// A layer of rotation gates on all qubits
77#[derive(Debug, Clone)]
78pub struct RotationLayer {
79    /// Number of qubits
80    num_qubits: usize,
81    /// Rotation axes (X, Y, or Z for each qubit)
82    axes: Vec<char>,
83    /// Parameters for each rotation
84    parameters: Vec<Parameter>,
85    /// Layer name
86    name: String,
87}
88
89impl RotationLayer {
90    /// Create a new rotation layer
91    pub fn new(num_qubits: usize, axes: Vec<char>) -> QuantRS2Result<Self> {
92        if axes.len() != num_qubits {
93            return Err(QuantRS2Error::InvalidInput(format!(
94                "Expected {} axes, got {}",
95                num_qubits,
96                axes.len()
97            )));
98        }
99
100        for &axis in &axes {
101            if !['X', 'Y', 'Z'].contains(&axis) {
102                return Err(QuantRS2Error::InvalidInput(format!(
103                    "Invalid rotation axis: {axis}"
104                )));
105            }
106        }
107
108        let parameters = (0..num_qubits)
109            .map(|i| Parameter {
110                name: format!("rot_{}_{}", axes[i], i),
111                value: 0.0,
112                bounds: Some((-2.0 * PI, 2.0 * PI)),
113            })
114            .collect();
115
116        let name = format!("RotationLayer_{}", axes.iter().collect::<String>());
117
118        Ok(Self {
119            num_qubits,
120            axes,
121            parameters,
122            name,
123        })
124    }
125
126    /// Create a layer with all rotations on the same axis
127    pub fn uniform(num_qubits: usize, axis: char) -> QuantRS2Result<Self> {
128        Self::new(num_qubits, vec![axis; num_qubits])
129    }
130}
131
132impl QMLLayer for RotationLayer {
133    fn num_qubits(&self) -> usize {
134        self.num_qubits
135    }
136
137    fn parameters(&self) -> &[Parameter] {
138        &self.parameters
139    }
140
141    fn parameters_mut(&mut self) -> &mut [Parameter] {
142        &mut self.parameters
143    }
144
145    fn gates(&self) -> Vec<Box<dyn GateOp>> {
146        self.parameters
147            .iter()
148            .enumerate()
149            .map(|(i, param)| {
150                let qubit = QubitId(i as u32);
151                let gate: Box<dyn GateOp> = match self.axes[i] {
152                    'X' => Box::new(ParametricRotationX::new(qubit, param.value)),
153                    'Y' => Box::new(ParametricRotationY::new(qubit, param.value)),
154                    'Z' => Box::new(ParametricRotationZ::new(qubit, param.value)),
155                    _ => unreachable!(),
156                };
157                gate
158            })
159            .collect()
160    }
161
162    fn compute_gradients(
163        &self,
164        state: &Array1<Complex64>,
165        loss_gradient: &Array1<Complex64>,
166    ) -> QuantRS2Result<Vec<f64>> {
167        // Parameter shift rule: ∂⟨O⟩/∂θ_i = ½[⟨O⟩(θ_i + π/2) - ⟨O⟩(θ_i - π/2)]
168        //
169        // For a rotation gate R_P(θ)|ψ⟩ with Pauli P, the generator is P/2.
170        // The gradient of the expectation value equals the difference of
171        // two expectation values evaluated at θ ± π/2.
172        //
173        // Here we approximate the expectation value shift using the inner product
174        // of the current state with the loss_gradient direction, modulated by the
175        // derivative of the rotation: d/dθ [cos θ + i·sin θ·P] = -sin θ + i·cos θ·P.
176        // We use cos(θ + π/2) = -sin θ and sin(θ + π/2) = cos θ.
177        let shift = std::f64::consts::PI / 2.0;
178        let mut gradients = Vec::with_capacity(self.parameters.len());
179
180        for (i, param) in self.parameters.iter().enumerate() {
181            let theta = param.value;
182            // Derivative of the i-th qubit rotation w.r.t. θ_i:
183            // d/dθ R_P(θ) = ½ R_P(θ + π/2) - ½ R_P(θ - π/2)
184            // Using the chain rule with the state vector:
185            //   grad_i ≈ Re⟨loss_gradient | d/dθ_i |ψ⟩
186            // For a single-qubit rotation on qubit i:
187            //   d/dθ R_P(θ)|ψ_i⟩ = ½(R_P(θ+π/2) - R_P(θ-π/2))|ψ_i⟩
188            // We represent this as a scalar via the projection onto loss_gradient.
189            let qubit_idx = i;
190            if qubit_idx >= state.len() {
191                gradients.push(0.0);
192                continue;
193            }
194
195            // Rotation angle derivative: d/dθ e^{-iθ/2 P} acts as multiplication by
196            // ±i/2 in the diagonal basis; for a pure rotation cos(θ/2)|0⟩ - i·sin(θ/2)|1⟩
197            // the amplitude gradient on the affected component is -sin(θ/2) or cos(θ/2).
198            // We use the parameter-shift scalar approximation for real-valued cost functions.
199            let cos_shift = (theta + shift).cos() - (theta - shift).cos(); // = -2 sin θ
200            let sin_shift = (theta + shift).sin() - (theta - shift).sin(); // =  2 cos θ
201
202            // Compute gradient as Re[⟨loss_gradient_i | dψ_i⟩]
203            let amp = state[qubit_idx];
204            let lg = loss_gradient[qubit_idx];
205            // d|ψ_i⟩/dθ ≈ (cos_shift/2) + i·(sin_shift/2) applied to amp
206            let d_amp = Complex64::new(
207                cos_shift / 2.0 * amp.re - sin_shift / 2.0 * amp.im,
208                cos_shift / 2.0 * amp.im + sin_shift / 2.0 * amp.re,
209            );
210            let grad = lg.re * d_amp.re + lg.im * d_amp.im;
211            gradients.push(grad);
212        }
213
214        Ok(gradients)
215    }
216
217    fn name(&self) -> &str {
218        &self.name
219    }
220}
221
222/// A layer of entangling gates
223#[derive(Debug, Clone)]
224pub struct EntanglingLayer {
225    /// Number of qubits
226    num_qubits: usize,
227    /// Entanglement pattern
228    pattern: EntanglementPattern,
229    /// Parameters for parameterized entangling gates (if any)
230    parameters: Vec<Parameter>,
231    /// Whether to use parameterized gates
232    parameterized: bool,
233    /// Layer name
234    name: String,
235}
236
237impl EntanglingLayer {
238    /// Create a new entangling layer with CNOT gates
239    pub fn new(num_qubits: usize, pattern: EntanglementPattern) -> Self {
240        let name = format!("EntanglingLayer_{pattern:?}");
241
242        Self {
243            num_qubits,
244            pattern,
245            parameters: vec![],
246            parameterized: false,
247            name,
248        }
249    }
250
251    /// Create a parameterized entangling layer (e.g., with CRZ gates)
252    pub fn parameterized(num_qubits: usize, pattern: EntanglementPattern) -> Self {
253        let pairs = create_entangling_gates(num_qubits, pattern);
254        let parameters = pairs
255            .iter()
256            .enumerate()
257            .map(|(_i, (ctrl, tgt))| Parameter {
258                name: format!("entangle_{}_{}", ctrl.0, tgt.0),
259                value: 0.0,
260                bounds: Some((-PI, PI)),
261            })
262            .collect();
263
264        let name = format!("ParameterizedEntanglingLayer_{pattern:?}");
265
266        Self {
267            num_qubits,
268            pattern,
269            parameters,
270            parameterized: true,
271            name,
272        }
273    }
274}
275
276impl QMLLayer for EntanglingLayer {
277    fn num_qubits(&self) -> usize {
278        self.num_qubits
279    }
280
281    fn parameters(&self) -> &[Parameter] {
282        &self.parameters
283    }
284
285    fn parameters_mut(&mut self) -> &mut [Parameter] {
286        &mut self.parameters
287    }
288
289    fn gates(&self) -> Vec<Box<dyn GateOp>> {
290        let pairs = create_entangling_gates(self.num_qubits, self.pattern);
291
292        if self.parameterized {
293            // Use parameterized entangling gates (CRZ)
294            pairs
295                .iter()
296                .zip(self.parameters.iter())
297                .map(|((ctrl, tgt), _param)| {
298                    // For now, use CNOT - would implement CRZ
299                    Box::new(CNOT {
300                        control: *ctrl,
301                        target: *tgt,
302                    }) as Box<dyn GateOp>
303                })
304                .collect()
305        } else {
306            // Use fixed CNOT gates
307            pairs
308                .iter()
309                .map(|(ctrl, tgt)| {
310                    Box::new(CNOT {
311                        control: *ctrl,
312                        target: *tgt,
313                    }) as Box<dyn GateOp>
314                })
315                .collect()
316        }
317    }
318
319    fn compute_gradients(
320        &self,
321        _state: &Array1<Complex64>,
322        _loss_gradient: &Array1<Complex64>,
323    ) -> QuantRS2Result<Vec<f64>> {
324        if self.parameterized {
325            // Would compute gradients for parameterized gates
326            Ok(vec![0.0; self.parameters.len()])
327        } else {
328            // No parameters, no gradients
329            Ok(vec![])
330        }
331    }
332
333    fn name(&self) -> &str {
334        &self.name
335    }
336}
337
338/// A composite layer combining rotations and entanglement
339#[derive(Debug, Clone)]
340pub struct StronglyEntanglingLayer {
341    /// Number of qubits
342    num_qubits: usize,
343    /// Rotation layers (one for each axis)
344    rotation_layers: Vec<RotationLayer>,
345    /// Entangling layer
346    entangling_layer: EntanglingLayer,
347    /// Total parameters
348    total_parameters: usize,
349    /// Layer name
350    name: String,
351}
352
353impl StronglyEntanglingLayer {
354    /// Create a new strongly entangling layer
355    pub fn new(num_qubits: usize, pattern: EntanglementPattern) -> QuantRS2Result<Self> {
356        let rotation_layers = vec![
357            RotationLayer::uniform(num_qubits, 'X')?,
358            RotationLayer::uniform(num_qubits, 'Y')?,
359            RotationLayer::uniform(num_qubits, 'Z')?,
360        ];
361
362        let entangling_layer = EntanglingLayer::new(num_qubits, pattern);
363
364        let total_parameters = rotation_layers
365            .iter()
366            .map(|layer| layer.parameters().len())
367            .sum::<usize>()
368            + entangling_layer.parameters().len();
369
370        let name = format!("StronglyEntanglingLayer_{pattern:?}");
371
372        Ok(Self {
373            num_qubits,
374            rotation_layers,
375            entangling_layer,
376            total_parameters,
377            name,
378        })
379    }
380}
381
382impl QMLLayer for StronglyEntanglingLayer {
383    fn num_qubits(&self) -> usize {
384        self.num_qubits
385    }
386
387    fn parameters(&self) -> &[Parameter] {
388        // This is a simplified implementation
389        // In practice, we'd need to return a combined view
390        &[]
391    }
392
393    fn parameters_mut(&mut self) -> &mut [Parameter] {
394        // This is a simplified implementation
395        &mut []
396    }
397
398    fn set_parameters(&mut self, values: &[f64]) -> QuantRS2Result<()> {
399        if values.len() != self.total_parameters {
400            return Err(QuantRS2Error::InvalidInput(format!(
401                "Expected {} parameters, got {}",
402                self.total_parameters,
403                values.len()
404            )));
405        }
406
407        let mut offset = 0;
408        for layer in &mut self.rotation_layers {
409            let n = layer.parameters().len();
410            layer.set_parameters(&values[offset..offset + n])?;
411            offset += n;
412        }
413
414        if self.entangling_layer.parameterized {
415            self.entangling_layer.set_parameters(&values[offset..])?;
416        }
417
418        Ok(())
419    }
420
421    fn gates(&self) -> Vec<Box<dyn GateOp>> {
422        let mut gates = Vec::new();
423
424        // Apply rotation gates
425        for layer in &self.rotation_layers {
426            gates.extend(layer.gates());
427        }
428
429        // Apply entangling gates
430        gates.extend(self.entangling_layer.gates());
431
432        gates
433    }
434
435    fn compute_gradients(
436        &self,
437        state: &Array1<Complex64>,
438        loss_gradient: &Array1<Complex64>,
439    ) -> QuantRS2Result<Vec<f64>> {
440        let mut gradients = Vec::new();
441
442        for layer in &self.rotation_layers {
443            gradients.extend(layer.compute_gradients(state, loss_gradient)?);
444        }
445
446        if self.entangling_layer.parameterized {
447            gradients.extend(
448                self.entangling_layer
449                    .compute_gradients(state, loss_gradient)?,
450            );
451        }
452
453        Ok(gradients)
454    }
455
456    fn name(&self) -> &str {
457        &self.name
458    }
459}
460
461/// Hardware-efficient ansatz layer
462#[derive(Debug, Clone)]
463pub struct HardwareEfficientLayer {
464    /// Number of qubits
465    num_qubits: usize,
466    /// Single-qubit rotations
467    single_qubit_gates: Vec<RotationLayer>,
468    /// Two-qubit gates
469    entangling_gates: EntanglingLayer,
470    /// Layer name
471    name: String,
472}
473
474impl HardwareEfficientLayer {
475    /// Create a new hardware-efficient layer
476    pub fn new(num_qubits: usize) -> QuantRS2Result<Self> {
477        // Use RY and RZ rotations (common on hardware)
478        let single_qubit_gates = vec![
479            RotationLayer::uniform(num_qubits, 'Y')?,
480            RotationLayer::uniform(num_qubits, 'Z')?,
481        ];
482
483        // Use linear entanglement (nearest-neighbor)
484        let entangling_gates = EntanglingLayer::new(num_qubits, EntanglementPattern::Linear);
485
486        Ok(Self {
487            num_qubits,
488            single_qubit_gates,
489            entangling_gates,
490            name: "HardwareEfficientLayer".to_string(),
491        })
492    }
493}
494
495impl QMLLayer for HardwareEfficientLayer {
496    fn num_qubits(&self) -> usize {
497        self.num_qubits
498    }
499
500    fn parameters(&self) -> &[Parameter] {
501        // Simplified - would need proper implementation
502        &[]
503    }
504
505    fn parameters_mut(&mut self) -> &mut [Parameter] {
506        &mut []
507    }
508
509    fn gates(&self) -> Vec<Box<dyn GateOp>> {
510        let mut gates = Vec::new();
511
512        for layer in &self.single_qubit_gates {
513            gates.extend(layer.gates());
514        }
515
516        gates.extend(self.entangling_gates.gates());
517
518        gates
519    }
520
521    fn compute_gradients(
522        &self,
523        state: &Array1<Complex64>,
524        loss_gradient: &Array1<Complex64>,
525    ) -> QuantRS2Result<Vec<f64>> {
526        let mut gradients = Vec::new();
527
528        for layer in &self.single_qubit_gates {
529            gradients.extend(layer.compute_gradients(state, loss_gradient)?);
530        }
531
532        Ok(gradients)
533    }
534
535    fn name(&self) -> &str {
536        &self.name
537    }
538}
539
540/// Pooling layer for quantum convolutional neural networks
541#[derive(Debug, Clone)]
542pub struct QuantumPoolingLayer {
543    /// Number of input qubits
544    input_qubits: usize,
545    /// Number of output qubits (after pooling)
546    output_qubits: usize,
547    /// Pooling strategy
548    strategy: PoolingStrategy,
549    /// Layer name
550    name: String,
551}
552
553#[derive(Debug, Clone, Copy)]
554pub enum PoolingStrategy {
555    /// Trace out every other qubit
556    TraceOut,
557    /// Measure and condition
558    MeasureCondition,
559    /// Parameterized pooling
560    Parameterized,
561}
562
563impl QuantumPoolingLayer {
564    /// Create a new pooling layer
565    pub fn new(input_qubits: usize, strategy: PoolingStrategy) -> Self {
566        let output_qubits = input_qubits / 2;
567
568        Self {
569            input_qubits,
570            output_qubits,
571            strategy,
572            name: format!("QuantumPoolingLayer_{strategy:?}"),
573        }
574    }
575}
576
577impl QMLLayer for QuantumPoolingLayer {
578    fn num_qubits(&self) -> usize {
579        self.input_qubits
580    }
581
582    fn parameters(&self) -> &[Parameter] {
583        &[]
584    }
585
586    fn parameters_mut(&mut self) -> &mut [Parameter] {
587        &mut []
588    }
589
590    fn gates(&self) -> Vec<Box<dyn GateOp>> {
591        // Pooling typically involves measurements or partial traces
592        // For now, return empty
593        vec![]
594    }
595
596    fn compute_gradients(
597        &self,
598        _state: &Array1<Complex64>,
599        _loss_gradient: &Array1<Complex64>,
600    ) -> QuantRS2Result<Vec<f64>> {
601        Ok(vec![])
602    }
603
604    fn name(&self) -> &str {
605        &self.name
606    }
607}
608
609#[cfg(test)]
610mod tests {
611    use super::*;
612
613    #[test]
614    fn test_rotation_layer() {
615        let layer = RotationLayer::uniform(3, 'X').expect("rotation layer creation should succeed");
616        assert_eq!(layer.num_qubits(), 3);
617        assert_eq!(layer.parameters().len(), 3);
618
619        let gates = layer.gates();
620        assert_eq!(gates.len(), 3);
621    }
622
623    #[test]
624    fn test_entangling_layer() {
625        let layer = EntanglingLayer::new(4, EntanglementPattern::Linear);
626        assert_eq!(layer.num_qubits(), 4);
627
628        let gates = layer.gates();
629        assert_eq!(gates.len(), 3); // 3 CNOT gates for linear pattern
630    }
631
632    #[test]
633    fn test_strongly_entangling_layer() {
634        let layer = StronglyEntanglingLayer::new(2, EntanglementPattern::Full)
635            .expect("strongly entangling layer creation should succeed");
636        assert_eq!(layer.num_qubits(), 2);
637
638        let gates = layer.gates();
639        assert_eq!(gates.len(), 7); // 6 rotation gates + 1 CNOT
640    }
641
642    #[test]
643    fn test_hardware_efficient_layer() {
644        let layer = HardwareEfficientLayer::new(3)
645            .expect("hardware efficient layer creation should succeed");
646        assert_eq!(layer.num_qubits(), 3);
647
648        let gates = layer.gates();
649        assert_eq!(gates.len(), 8); // 6 rotation gates + 2 CNOTs
650    }
651}