Skip to main content

quantrs2_core/qml/
encoding.rs

1//! Data encoding strategies for quantum machine learning
2//!
3//! This module provides various methods to encode classical data
4//! into quantum states for processing by quantum circuits.
5
6use super::EncodingStrategy;
7use crate::{
8    error::{QuantRS2Error, QuantRS2Result},
9    gate::{single::Hadamard, GateOp},
10    parametric::{ParametricRotationY, ParametricRotationZ},
11    qubit::QubitId,
12};
13use scirs2_core::Complex64;
14use std::f64::consts::PI;
15
16// Type aliases for convenience
17#[allow(dead_code)]
18type RYGate = ParametricRotationY;
19#[allow(dead_code)]
20type RZGate = ParametricRotationZ;
21
22// Simple CNOT gate for encoding usage
23#[derive(Debug, Clone, Copy)]
24struct CNOT {
25    control: QubitId,
26    target: QubitId,
27}
28
29impl GateOp for CNOT {
30    fn name(&self) -> &'static str {
31        "CNOT"
32    }
33
34    fn qubits(&self) -> Vec<QubitId> {
35        vec![self.control, self.target]
36    }
37
38    fn matrix(&self) -> crate::error::QuantRS2Result<Vec<Complex64>> {
39        Ok(vec![
40            Complex64::new(1.0, 0.0),
41            Complex64::new(0.0, 0.0),
42            Complex64::new(0.0, 0.0),
43            Complex64::new(0.0, 0.0),
44            Complex64::new(0.0, 0.0),
45            Complex64::new(1.0, 0.0),
46            Complex64::new(0.0, 0.0),
47            Complex64::new(0.0, 0.0),
48            Complex64::new(0.0, 0.0),
49            Complex64::new(0.0, 0.0),
50            Complex64::new(0.0, 0.0),
51            Complex64::new(1.0, 0.0),
52            Complex64::new(0.0, 0.0),
53            Complex64::new(0.0, 0.0),
54            Complex64::new(1.0, 0.0),
55            Complex64::new(0.0, 0.0),
56        ])
57    }
58
59    fn as_any(&self) -> &dyn std::any::Any {
60        self
61    }
62
63    fn clone_gate(&self) -> Box<dyn GateOp> {
64        Box::new(*self)
65    }
66}
67
68/// Data encoder for quantum circuits
69pub struct DataEncoder {
70    /// Encoding strategy
71    strategy: EncodingStrategy,
72    /// Number of qubits
73    num_qubits: usize,
74    /// Number of features that can be encoded
75    num_features: usize,
76}
77
78impl DataEncoder {
79    /// Create a new data encoder
80    pub const fn new(strategy: EncodingStrategy, num_qubits: usize) -> Self {
81        let num_features = match strategy {
82            EncodingStrategy::Amplitude => 1 << num_qubits, // 2^n amplitudes
83            EncodingStrategy::Angle | EncodingStrategy::Basis => num_qubits, // One per qubit
84            EncodingStrategy::IQP => num_qubits * (num_qubits + 1) / 2, // All pairs
85        };
86
87        Self {
88            strategy,
89            num_qubits,
90            num_features,
91        }
92    }
93
94    /// Get the number of features this encoder can handle
95    pub const fn num_features(&self) -> usize {
96        self.num_features
97    }
98
99    /// Encode classical data into quantum gates
100    pub fn encode(&self, data: &[f64]) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
101        if data.len() != self.num_features {
102            return Err(QuantRS2Error::InvalidInput(format!(
103                "Expected {} features, got {}",
104                self.num_features,
105                data.len()
106            )));
107        }
108
109        match self.strategy {
110            EncodingStrategy::Amplitude => self.amplitude_encoding(data),
111            EncodingStrategy::Angle => self.angle_encoding(data),
112            EncodingStrategy::IQP => self.iqp_encoding(data),
113            EncodingStrategy::Basis => self.basis_encoding(data),
114        }
115    }
116
117    /// Amplitude encoding: encode data in state amplitudes
118    fn amplitude_encoding(&self, data: &[f64]) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
119        // Normalize data
120        let norm: f64 = data.iter().map(|x| x * x).sum::<f64>().sqrt();
121        if norm < 1e-10 {
122            return Err(QuantRS2Error::InvalidInput(
123                "Cannot encode zero vector".to_string(),
124            ));
125        }
126
127        let normalized: Vec<f64> = data.iter().map(|x| x / norm).collect();
128
129        // Amplitude encoding via recursive Mottonen-style state preparation.
130        //
131        // Algorithm: build a binary-tree of uniformly-controlled RY rotations.
132        // For a 2^n amplitude vector, the decomposition has depth n and requires
133        // 2^n - 1 RY gates interleaved with CNOT entanglers.
134        //
135        // We compute the "multiplexor angles" recursively:
136        //   θ_k = 2·arctan2(‖right_k‖, ‖left_k‖)
137        // where left_k / right_k are the two child sub-vectors at each node.
138        // Then we apply a uniformly-controlled rotation layer for each level of
139        // the binary tree, controlled on the higher-order qubits.
140        let mut gates: Vec<Box<dyn GateOp>> = vec![];
141
142        // Compute recursive angles for the full binary tree
143        let angles = compute_amplitude_angles(&normalized);
144
145        // Level 0: single RY on qubit 0 (no control)
146        if let Some(&angle) = angles.first() {
147            gates.push(Box::new(ParametricRotationY::new(QubitId(0), angle)));
148        }
149
150        // Level l (l = 1..num_qubits): uniformly-controlled RY on qubit l
151        // controlled by qubits 0..l using a Gray-code decomposition.
152        for level in 1..self.num_qubits {
153            let num_angles_at_level = 1usize << level;
154            let level_start = num_angles_at_level - 1; // index into `angles` slice
155            let level_end = level_start + num_angles_at_level;
156            let level_angles = if level_end <= angles.len() {
157                &angles[level_start..level_end]
158            } else {
159                continue;
160            };
161
162            // Gray-code-based uniformly-controlled rotation decomposition:
163            // Transforms level_angles into a sequence of plain RY + CNOT pairs.
164            let ucry_angles = ucry_decompose(level_angles);
165            let target = QubitId(level as u32);
166
167            for (k, &theta) in ucry_angles.iter().enumerate() {
168                gates.push(Box::new(ParametricRotationY::new(target, theta)));
169                if k < ucry_angles.len().saturating_sub(1) {
170                    // Control qubit follows Gray-code ordering
171                    let gray_k = k ^ (k >> 1);
172                    let gray_k1 = (k + 1) ^ ((k + 1) >> 1);
173                    let diff_bit = gray_k ^ gray_k1;
174                    // find position of differing bit
175                    let ctrl_qubit = diff_bit.trailing_zeros();
176                    if (ctrl_qubit as usize) < level {
177                        gates.push(Box::new(CNOT {
178                            control: QubitId(ctrl_qubit),
179                            target,
180                        }));
181                    }
182                }
183            }
184        }
185
186        Ok(gates)
187    }
188
189    /// Angle encoding: encode data as rotation angles
190    fn angle_encoding(&self, data: &[f64]) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
191        let mut gates: Vec<Box<dyn GateOp>> = vec![];
192
193        // Apply Hadamard gates first for superposition
194        for i in 0..self.num_qubits {
195            gates.push(Box::new(Hadamard {
196                target: QubitId(i as u32),
197            }));
198        }
199
200        // Encode each feature as a rotation angle
201        for (i, &value) in data.iter().enumerate() {
202            let qubit = QubitId(i as u32);
203            // Scale data to [0, 2π]
204            let angle = value * PI;
205            gates.push(Box::new(ParametricRotationY::new(qubit, angle)));
206        }
207
208        Ok(gates)
209    }
210
211    /// IQP (Instantaneous Quantum Polynomial) encoding
212    fn iqp_encoding(&self, data: &[f64]) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
213        let mut gates: Vec<Box<dyn GateOp>> = vec![];
214
215        // Apply Hadamard gates
216        for i in 0..self.num_qubits {
217            gates.push(Box::new(Hadamard {
218                target: QubitId(i as u32),
219            }));
220        }
221
222        // Single-qubit rotations
223        for i in 0..self.num_qubits {
224            let angle = data[i] * PI;
225            gates.push(Box::new(ParametricRotationZ::new(QubitId(i as u32), angle)));
226        }
227
228        // Two-qubit interactions
229        let mut idx = self.num_qubits;
230        for i in 0..self.num_qubits {
231            for j in i + 1..self.num_qubits {
232                if idx < data.len() {
233                    let angle = data[idx] * PI;
234                    // ZZ interaction: RZZ(θ) = CNOT · (I ⊗ RZ(θ)) · CNOT
235                    // This implements e^{-i θ/2 Z⊗Z} up to a global phase.
236                    gates.push(Box::new(CNOT {
237                        control: QubitId(i as u32),
238                        target: QubitId(j as u32),
239                    }));
240                    gates.push(Box::new(ParametricRotationZ::new(QubitId(j as u32), angle)));
241                    gates.push(Box::new(CNOT {
242                        control: QubitId(i as u32),
243                        target: QubitId(j as u32),
244                    }));
245                    idx += 1;
246                }
247            }
248        }
249
250        Ok(gates)
251    }
252
253    /// Basis encoding: encode binary data in computational basis
254    fn basis_encoding(&self, data: &[f64]) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
255        use crate::gate::single::PauliX;
256
257        let mut gates: Vec<Box<dyn GateOp>> = vec![];
258
259        // Encode each bit
260        for (i, &value) in data.iter().enumerate() {
261            if value.abs() > 0.5 {
262                // Threshold at 0.5
263                gates.push(Box::new(PauliX {
264                    target: QubitId(i as u32),
265                }));
266            }
267        }
268
269        Ok(gates)
270    }
271}
272
273/// Feature map for kernel methods
274pub struct FeatureMap {
275    /// Number of qubits
276    num_qubits: usize,
277    /// Number of features
278    num_features: usize,
279    /// Feature map type
280    map_type: FeatureMapType,
281    /// Number of repetitions
282    reps: usize,
283}
284
285#[derive(Debug, Clone, Copy)]
286pub enum FeatureMapType {
287    /// Pauli feature map
288    Pauli,
289    /// Z feature map
290    ZFeature,
291    /// ZZ feature map
292    ZZFeature,
293    /// Custom feature map
294    Custom,
295}
296
297impl FeatureMap {
298    /// Create a new feature map
299    pub const fn new(num_qubits: usize, map_type: FeatureMapType, reps: usize) -> Self {
300        let num_features = num_qubits; // All feature map types use one feature per qubit
301
302        Self {
303            num_qubits,
304            num_features,
305            map_type,
306            reps,
307        }
308    }
309
310    /// Create gates for the feature map
311    pub fn create_gates(&self, features: &[f64]) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
312        if features.len() != self.num_features {
313            return Err(QuantRS2Error::InvalidInput(format!(
314                "Expected {} features, got {}",
315                self.num_features,
316                features.len()
317            )));
318        }
319
320        let mut gates = vec![];
321
322        for _ in 0..self.reps {
323            match self.map_type {
324                FeatureMapType::Pauli => {
325                    gates.extend(self.pauli_feature_map(features)?);
326                }
327                FeatureMapType::ZFeature => {
328                    gates.extend(self.z_feature_map(features)?);
329                }
330                FeatureMapType::ZZFeature => {
331                    gates.extend(self.zz_feature_map(features)?);
332                }
333                FeatureMapType::Custom => {
334                    // Custom implementation would go here
335                }
336            }
337        }
338
339        Ok(gates)
340    }
341
342    /// Pauli feature map
343    fn pauli_feature_map(&self, features: &[f64]) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
344        let mut gates: Vec<Box<dyn GateOp>> = vec![];
345
346        // Apply Hadamard gates
347        for i in 0..self.num_qubits {
348            gates.push(Box::new(Hadamard {
349                target: QubitId(i as u32),
350            }));
351        }
352
353        // Apply rotations based on features
354        for (i, &feature) in features.iter().enumerate() {
355            gates.push(Box::new(ParametricRotationZ::new(
356                QubitId(i as u32),
357                2.0 * feature,
358            )));
359        }
360
361        Ok(gates)
362    }
363
364    /// Z feature map
365    fn z_feature_map(&self, features: &[f64]) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
366        let mut gates: Vec<Box<dyn GateOp>> = vec![];
367
368        // First layer: Hadamard gates
369        for i in 0..self.num_qubits {
370            gates.push(Box::new(Hadamard {
371                target: QubitId(i as u32),
372            }));
373        }
374
375        // Second layer: RZ rotations
376        for (i, &feature) in features.iter().enumerate() {
377            gates.push(Box::new(ParametricRotationZ::new(
378                QubitId(i as u32),
379                2.0 * feature,
380            )));
381        }
382
383        Ok(gates)
384    }
385
386    /// ZZ feature map
387    fn zz_feature_map(&self, features: &[f64]) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
388        let mut gates = self.z_feature_map(features)?;
389
390        // Add entangling gates
391        for i in 0..self.num_qubits - 1 {
392            gates.push(Box::new(CNOT {
393                control: QubitId(i as u32),
394                target: QubitId((i + 1) as u32),
395            }));
396
397            // Two-qubit rotation
398            let angle = (PI - features[i]) * (PI - features[i + 1]);
399            gates.push(Box::new(ParametricRotationZ::new(
400                QubitId((i + 1) as u32),
401                angle,
402            )));
403
404            gates.push(Box::new(CNOT {
405                control: QubitId(i as u32),
406                target: QubitId((i + 1) as u32),
407            }));
408        }
409
410        Ok(gates)
411    }
412}
413
414/// Data re-uploading strategy
415pub struct DataReuploader {
416    /// Base encoder
417    encoder: DataEncoder,
418    /// Number of layers to repeat encoding
419    num_layers: usize,
420    /// Whether to use different parameters per layer
421    trainable_scaling: bool,
422}
423
424impl DataReuploader {
425    /// Create a new data re-uploader
426    pub const fn new(encoder: DataEncoder, num_layers: usize, trainable_scaling: bool) -> Self {
427        Self {
428            encoder,
429            num_layers,
430            trainable_scaling,
431        }
432    }
433
434    /// Create gates with data re-uploading
435    pub fn create_gates(
436        &self,
437        data: &[f64],
438        scaling_params: Option<&[f64]>,
439    ) -> QuantRS2Result<Vec<Vec<Box<dyn GateOp>>>> {
440        let mut layers = vec![];
441
442        for layer in 0..self.num_layers {
443            let scaled_data = if self.trainable_scaling {
444                if let Some(params) = scaling_params {
445                    let offset = layer * data.len();
446                    if offset + data.len() > params.len() {
447                        return Err(QuantRS2Error::InvalidInput(
448                            "Not enough scaling parameters".to_string(),
449                        ));
450                    }
451
452                    data.iter()
453                        .zip(&params[offset..offset + data.len()])
454                        .map(|(d, p)| d * p)
455                        .collect()
456                } else {
457                    data.to_vec()
458                }
459            } else {
460                data.to_vec()
461            };
462
463            layers.push(self.encoder.encode(&scaled_data)?);
464        }
465
466        Ok(layers)
467    }
468}
469
470// ---------------------------------------------------------------------------
471// Helper functions for amplitude encoding
472// ---------------------------------------------------------------------------
473
474/// Compute the recursive multiplexor angles for Mottonen-style amplitude encoding.
475///
476/// Given a unit-norm amplitude vector `amplitudes` of length 2^n, returns a flat
477/// list of angles organised level by level (root first).  The number of angles
478/// per level l is 2^l, and angles are defined by
479///
480///   θ = 2 · atan2(‖right‖₂, ‖left‖₂)
481///
482/// where `left` and `right` are the left and right halves of the current
483/// sub-vector at each tree node.
484fn compute_amplitude_angles(amplitudes: &[f64]) -> Vec<f64> {
485    let n = amplitudes.len();
486    if n == 0 {
487        return vec![];
488    }
489
490    let mut angles: Vec<f64> = Vec::new();
491    // Work with a mutable copy; the recursive tree processes each level
492    let mut current_level: Vec<f64> = amplitudes.to_vec();
493
494    while current_level.len() > 1 {
495        let mut next_level: Vec<f64> = Vec::with_capacity(current_level.len() / 2);
496        let mut level_angles: Vec<f64> = Vec::with_capacity(current_level.len() / 2);
497
498        let chunks = current_level.chunks(2);
499        for chunk in chunks {
500            let left = chunk[0];
501            let right = if chunk.len() > 1 { chunk[1] } else { 0.0 };
502            let norm = (left * left + right * right).sqrt();
503            next_level.push(norm);
504            let angle = 2.0 * right.atan2(left);
505            level_angles.push(angle);
506        }
507
508        angles.extend(level_angles);
509        current_level = next_level;
510    }
511
512    // We built the angles bottom-up; reverse so that the root level comes first.
513    angles.reverse();
514    angles
515}
516
517/// Decompose a uniformly-controlled rotation with `angles` into a sequence of
518/// plain rotation angles using the Walsh–Hadamard (Gray-code) transform.
519///
520/// This converts the 2^k uniformly-controlled angles into 2^k plain RY rotation
521/// angles that, when interleaved with CNOT gates in Gray-code order, implement
522/// the uniformly-controlled operation.
523fn ucry_decompose(angles: &[f64]) -> Vec<f64> {
524    let n = angles.len();
525    if n == 0 {
526        return vec![];
527    }
528
529    // Apply the Walsh–Hadamard-like transform for UCRy decomposition
530    let mut result = angles.to_vec();
531    let mut step = 1usize;
532
533    while step < n {
534        let mut k = 0usize;
535        while k < n {
536            for j in 0..step {
537                let a = result[k + j];
538                let b = result[k + j + step];
539                result[k + j] = (a + b) / 2.0;
540                result[k + j + step] = (a - b) / 2.0;
541            }
542            k += 2 * step;
543        }
544        step *= 2;
545    }
546
547    result
548}
549
550#[cfg(test)]
551mod tests {
552    use super::*;
553
554    #[test]
555    fn test_angle_encoding() {
556        let encoder = DataEncoder::new(EncodingStrategy::Angle, 3);
557        assert_eq!(encoder.num_features(), 3);
558
559        let data = vec![0.5, 1.0, 0.0];
560        let gates = encoder.encode(&data).expect("Failed to encode angle data");
561
562        // Should have 3 Hadamards + 3 RY gates
563        assert_eq!(gates.len(), 6);
564    }
565
566    #[test]
567    fn test_basis_encoding() {
568        let encoder = DataEncoder::new(EncodingStrategy::Basis, 4);
569        assert_eq!(encoder.num_features(), 4);
570
571        let data = vec![1.0, 0.0, 1.0, 0.0];
572        let gates = encoder.encode(&data).expect("Failed to encode basis data");
573
574        // Should have 2 X gates (for the 1.0 values)
575        assert_eq!(gates.len(), 2);
576    }
577
578    #[test]
579    fn test_feature_map() {
580        let feature_map = FeatureMap::new(2, FeatureMapType::ZFeature, 1);
581        let features = vec![0.5, 0.7];
582
583        let gates = feature_map
584            .create_gates(&features)
585            .expect("Failed to create feature map gates");
586
587        // Should have 2 Hadamards + 2 RZ gates
588        assert_eq!(gates.len(), 4);
589    }
590
591    #[test]
592    fn test_data_reuploader() {
593        let encoder = DataEncoder::new(EncodingStrategy::Angle, 2);
594        let reuploader = DataReuploader::new(encoder, 3, false);
595
596        let data = vec![0.5, 0.5];
597        let layers = reuploader
598            .create_gates(&data, None)
599            .expect("Failed to create reuploader gates");
600
601        assert_eq!(layers.len(), 3); // 3 layers
602        assert_eq!(layers[0].len(), 4); // Each layer has 2 H + 2 RY
603    }
604}