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 ndarray::{Array1, Array2};
14use num_complex::Complex64;
15use std::f64::consts::PI;
16
17// Type aliases for convenience
18type RYGate = ParametricRotationY;
19type RZGate = ParametricRotationZ;
20
21// Simple CNOT gate for encoding usage
22#[derive(Debug, Clone, Copy)]
23struct CNOT {
24    control: QubitId,
25    target: QubitId,
26}
27
28impl GateOp for CNOT {
29    fn name(&self) -> &'static str {
30        "CNOT"
31    }
32
33    fn qubits(&self) -> Vec<QubitId> {
34        vec![self.control, self.target]
35    }
36
37    fn matrix(&self) -> crate::error::QuantRS2Result<Vec<Complex64>> {
38        Ok(vec![
39            Complex64::new(1.0, 0.0),
40            Complex64::new(0.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(1.0, 0.0),
45            Complex64::new(0.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(1.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        ])
56    }
57
58    fn as_any(&self) -> &dyn std::any::Any {
59        self
60    }
61
62    fn clone_gate(&self) -> Box<dyn GateOp> {
63        Box::new(*self)
64    }
65}
66
67/// Data encoder for quantum circuits
68pub struct DataEncoder {
69    /// Encoding strategy
70    strategy: EncodingStrategy,
71    /// Number of qubits
72    num_qubits: usize,
73    /// Number of features that can be encoded
74    num_features: usize,
75}
76
77impl DataEncoder {
78    /// Create a new data encoder
79    pub fn new(strategy: EncodingStrategy, num_qubits: usize) -> Self {
80        let num_features = match strategy {
81            EncodingStrategy::Amplitude => 1 << num_qubits, // 2^n amplitudes
82            EncodingStrategy::Angle => num_qubits,          // One angle per qubit
83            EncodingStrategy::IQP => num_qubits * (num_qubits + 1) / 2, // All pairs
84            EncodingStrategy::Basis => num_qubits,          // One bit per qubit
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 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        // For amplitude encoding, we need to prepare a state with given amplitudes
130        // This is complex and typically requires decomposition into gates
131        // For now, return a placeholder
132
133        let mut gates: Vec<Box<dyn GateOp>> = vec![];
134
135        // Start with uniform superposition
136        for i in 0..self.num_qubits {
137            gates.push(Box::new(Hadamard {
138                target: QubitId(i as u32),
139            }));
140        }
141
142        // Would need to implement state preparation algorithm here
143        // This is a non-trivial operation requiring careful decomposition
144
145        Ok(gates)
146    }
147
148    /// Angle encoding: encode data as rotation angles
149    fn angle_encoding(&self, data: &[f64]) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
150        let mut gates: Vec<Box<dyn GateOp>> = vec![];
151
152        // Apply Hadamard gates first for superposition
153        for i in 0..self.num_qubits {
154            gates.push(Box::new(Hadamard {
155                target: QubitId(i as u32),
156            }));
157        }
158
159        // Encode each feature as a rotation angle
160        for (i, &value) in data.iter().enumerate() {
161            let qubit = QubitId(i as u32);
162            // Scale data to [0, 2π]
163            let angle = value * PI;
164            gates.push(Box::new(ParametricRotationY::new(qubit, angle)));
165        }
166
167        Ok(gates)
168    }
169
170    /// IQP (Instantaneous Quantum Polynomial) encoding
171    fn iqp_encoding(&self, data: &[f64]) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
172        let mut gates: Vec<Box<dyn GateOp>> = vec![];
173
174        // Apply Hadamard gates
175        for i in 0..self.num_qubits {
176            gates.push(Box::new(Hadamard {
177                target: QubitId(i as u32),
178            }));
179        }
180
181        // Single-qubit rotations
182        for i in 0..self.num_qubits {
183            let angle = data[i] * PI;
184            gates.push(Box::new(ParametricRotationZ::new(QubitId(i as u32), angle)));
185        }
186
187        // Two-qubit interactions
188        let mut idx = self.num_qubits;
189        for i in 0..self.num_qubits {
190            for j in i + 1..self.num_qubits {
191                if idx < data.len() {
192                    let angle = data[idx] * PI;
193                    // Would implement RZZ gate here
194                    // For now, use two RZ gates as placeholder
195                    gates.push(Box::new(ParametricRotationZ::new(
196                        QubitId(i as u32),
197                        angle / 2.0,
198                    )));
199                    gates.push(Box::new(ParametricRotationZ::new(
200                        QubitId(j as u32),
201                        angle / 2.0,
202                    )));
203                    idx += 1;
204                }
205            }
206        }
207
208        Ok(gates)
209    }
210
211    /// Basis encoding: encode binary data in computational basis
212    fn basis_encoding(&self, data: &[f64]) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
213        use crate::gate::single::PauliX;
214
215        let mut gates: Vec<Box<dyn GateOp>> = vec![];
216
217        // Encode each bit
218        for (i, &value) in data.iter().enumerate() {
219            if value.abs() > 0.5 {
220                // Threshold at 0.5
221                gates.push(Box::new(PauliX {
222                    target: QubitId(i as u32),
223                }));
224            }
225        }
226
227        Ok(gates)
228    }
229}
230
231/// Feature map for kernel methods
232pub struct FeatureMap {
233    /// Number of qubits
234    num_qubits: usize,
235    /// Number of features
236    num_features: usize,
237    /// Feature map type
238    map_type: FeatureMapType,
239    /// Number of repetitions
240    reps: usize,
241}
242
243#[derive(Debug, Clone, Copy)]
244pub enum FeatureMapType {
245    /// Pauli feature map
246    Pauli,
247    /// Z feature map
248    ZFeature,
249    /// ZZ feature map
250    ZZFeature,
251    /// Custom feature map
252    Custom,
253}
254
255impl FeatureMap {
256    /// Create a new feature map
257    pub fn new(num_qubits: usize, map_type: FeatureMapType, reps: usize) -> Self {
258        let num_features = match map_type {
259            FeatureMapType::Pauli => num_qubits,
260            FeatureMapType::ZFeature => num_qubits,
261            FeatureMapType::ZZFeature => num_qubits,
262            FeatureMapType::Custom => num_qubits,
263        };
264
265        Self {
266            num_qubits,
267            num_features,
268            map_type,
269            reps,
270        }
271    }
272
273    /// Create gates for the feature map
274    pub fn create_gates(&self, features: &[f64]) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
275        if features.len() != self.num_features {
276            return Err(QuantRS2Error::InvalidInput(format!(
277                "Expected {} features, got {}",
278                self.num_features,
279                features.len()
280            )));
281        }
282
283        let mut gates = vec![];
284
285        for _ in 0..self.reps {
286            match self.map_type {
287                FeatureMapType::Pauli => {
288                    gates.extend(self.pauli_feature_map(features)?);
289                }
290                FeatureMapType::ZFeature => {
291                    gates.extend(self.z_feature_map(features)?);
292                }
293                FeatureMapType::ZZFeature => {
294                    gates.extend(self.zz_feature_map(features)?);
295                }
296                FeatureMapType::Custom => {
297                    // Custom implementation would go here
298                }
299            }
300        }
301
302        Ok(gates)
303    }
304
305    /// Pauli feature map
306    fn pauli_feature_map(&self, features: &[f64]) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
307        let mut gates: Vec<Box<dyn GateOp>> = vec![];
308
309        // Apply Hadamard gates
310        for i in 0..self.num_qubits {
311            gates.push(Box::new(Hadamard {
312                target: QubitId(i as u32),
313            }));
314        }
315
316        // Apply rotations based on features
317        for (i, &feature) in features.iter().enumerate() {
318            gates.push(Box::new(ParametricRotationZ::new(
319                QubitId(i as u32),
320                2.0 * feature,
321            )));
322        }
323
324        Ok(gates)
325    }
326
327    /// Z feature map
328    fn z_feature_map(&self, features: &[f64]) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
329        let mut gates: Vec<Box<dyn GateOp>> = vec![];
330
331        // First layer: Hadamard gates
332        for i in 0..self.num_qubits {
333            gates.push(Box::new(Hadamard {
334                target: QubitId(i as u32),
335            }));
336        }
337
338        // Second layer: RZ rotations
339        for (i, &feature) in features.iter().enumerate() {
340            gates.push(Box::new(ParametricRotationZ::new(
341                QubitId(i as u32),
342                2.0 * feature,
343            )));
344        }
345
346        Ok(gates)
347    }
348
349    /// ZZ feature map
350    fn zz_feature_map(&self, features: &[f64]) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
351        let mut gates = self.z_feature_map(features)?;
352
353        // Add entangling gates
354        for i in 0..self.num_qubits - 1 {
355            gates.push(Box::new(CNOT {
356                control: QubitId(i as u32),
357                target: QubitId((i + 1) as u32),
358            }));
359
360            // Two-qubit rotation
361            let angle = (PI - features[i]) * (PI - features[i + 1]);
362            gates.push(Box::new(ParametricRotationZ::new(
363                QubitId((i + 1) as u32),
364                angle,
365            )));
366
367            gates.push(Box::new(CNOT {
368                control: QubitId(i as u32),
369                target: QubitId((i + 1) as u32),
370            }));
371        }
372
373        Ok(gates)
374    }
375}
376
377/// Data re-uploading strategy
378pub struct DataReuploader {
379    /// Base encoder
380    encoder: DataEncoder,
381    /// Number of layers to repeat encoding
382    num_layers: usize,
383    /// Whether to use different parameters per layer
384    trainable_scaling: bool,
385}
386
387impl DataReuploader {
388    /// Create a new data re-uploader
389    pub fn new(encoder: DataEncoder, num_layers: usize, trainable_scaling: bool) -> Self {
390        Self {
391            encoder,
392            num_layers,
393            trainable_scaling,
394        }
395    }
396
397    /// Create gates with data re-uploading
398    pub fn create_gates(
399        &self,
400        data: &[f64],
401        scaling_params: Option<&[f64]>,
402    ) -> QuantRS2Result<Vec<Vec<Box<dyn GateOp>>>> {
403        let mut layers = vec![];
404
405        for layer in 0..self.num_layers {
406            let scaled_data = if self.trainable_scaling {
407                if let Some(params) = scaling_params {
408                    let offset = layer * data.len();
409                    if offset + data.len() > params.len() {
410                        return Err(QuantRS2Error::InvalidInput(
411                            "Not enough scaling parameters".to_string(),
412                        ));
413                    }
414
415                    data.iter()
416                        .zip(&params[offset..offset + data.len()])
417                        .map(|(d, p)| d * p)
418                        .collect()
419                } else {
420                    data.to_vec()
421                }
422            } else {
423                data.to_vec()
424            };
425
426            layers.push(self.encoder.encode(&scaled_data)?);
427        }
428
429        Ok(layers)
430    }
431}
432
433#[cfg(test)]
434mod tests {
435    use super::*;
436
437    #[test]
438    fn test_angle_encoding() {
439        let encoder = DataEncoder::new(EncodingStrategy::Angle, 3);
440        assert_eq!(encoder.num_features(), 3);
441
442        let data = vec![0.5, 1.0, 0.0];
443        let gates = encoder.encode(&data).unwrap();
444
445        // Should have 3 Hadamards + 3 RY gates
446        assert_eq!(gates.len(), 6);
447    }
448
449    #[test]
450    fn test_basis_encoding() {
451        let encoder = DataEncoder::new(EncodingStrategy::Basis, 4);
452        assert_eq!(encoder.num_features(), 4);
453
454        let data = vec![1.0, 0.0, 1.0, 0.0];
455        let gates = encoder.encode(&data).unwrap();
456
457        // Should have 2 X gates (for the 1.0 values)
458        assert_eq!(gates.len(), 2);
459    }
460
461    #[test]
462    fn test_feature_map() {
463        let feature_map = FeatureMap::new(2, FeatureMapType::ZFeature, 1);
464        let features = vec![0.5, 0.7];
465
466        let gates = feature_map.create_gates(&features).unwrap();
467
468        // Should have 2 Hadamards + 2 RZ gates
469        assert_eq!(gates.len(), 4);
470    }
471
472    #[test]
473    fn test_data_reuploader() {
474        let encoder = DataEncoder::new(EncodingStrategy::Angle, 2);
475        let reuploader = DataReuploader::new(encoder, 3, false);
476
477        let data = vec![0.5, 0.5];
478        let layers = reuploader.create_gates(&data, None).unwrap();
479
480        assert_eq!(layers.len(), 3); // 3 layers
481        assert_eq!(layers[0].len(), 4); // Each layer has 2 H + 2 RY
482    }
483}