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