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        // 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 const fn new(num_qubits: usize, map_type: FeatureMapType, reps: usize) -> Self {
258        let num_features = num_qubits; // All feature map types use one feature per qubit
259
260        Self {
261            num_qubits,
262            num_features,
263            map_type,
264            reps,
265        }
266    }
267
268    /// Create gates for the feature map
269    pub fn create_gates(&self, features: &[f64]) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
270        if features.len() != self.num_features {
271            return Err(QuantRS2Error::InvalidInput(format!(
272                "Expected {} features, got {}",
273                self.num_features,
274                features.len()
275            )));
276        }
277
278        let mut gates = vec![];
279
280        for _ in 0..self.reps {
281            match self.map_type {
282                FeatureMapType::Pauli => {
283                    gates.extend(self.pauli_feature_map(features)?);
284                }
285                FeatureMapType::ZFeature => {
286                    gates.extend(self.z_feature_map(features)?);
287                }
288                FeatureMapType::ZZFeature => {
289                    gates.extend(self.zz_feature_map(features)?);
290                }
291                FeatureMapType::Custom => {
292                    // Custom implementation would go here
293                }
294            }
295        }
296
297        Ok(gates)
298    }
299
300    /// Pauli feature map
301    fn pauli_feature_map(&self, features: &[f64]) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
302        let mut gates: Vec<Box<dyn GateOp>> = vec![];
303
304        // Apply Hadamard gates
305        for i in 0..self.num_qubits {
306            gates.push(Box::new(Hadamard {
307                target: QubitId(i as u32),
308            }));
309        }
310
311        // Apply rotations based on features
312        for (i, &feature) in features.iter().enumerate() {
313            gates.push(Box::new(ParametricRotationZ::new(
314                QubitId(i as u32),
315                2.0 * feature,
316            )));
317        }
318
319        Ok(gates)
320    }
321
322    /// Z feature map
323    fn z_feature_map(&self, features: &[f64]) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
324        let mut gates: Vec<Box<dyn GateOp>> = vec![];
325
326        // First layer: Hadamard gates
327        for i in 0..self.num_qubits {
328            gates.push(Box::new(Hadamard {
329                target: QubitId(i as u32),
330            }));
331        }
332
333        // Second layer: RZ rotations
334        for (i, &feature) in features.iter().enumerate() {
335            gates.push(Box::new(ParametricRotationZ::new(
336                QubitId(i as u32),
337                2.0 * feature,
338            )));
339        }
340
341        Ok(gates)
342    }
343
344    /// ZZ feature map
345    fn zz_feature_map(&self, features: &[f64]) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
346        let mut gates = self.z_feature_map(features)?;
347
348        // Add entangling gates
349        for i in 0..self.num_qubits - 1 {
350            gates.push(Box::new(CNOT {
351                control: QubitId(i as u32),
352                target: QubitId((i + 1) as u32),
353            }));
354
355            // Two-qubit rotation
356            let angle = (PI - features[i]) * (PI - features[i + 1]);
357            gates.push(Box::new(ParametricRotationZ::new(
358                QubitId((i + 1) as u32),
359                angle,
360            )));
361
362            gates.push(Box::new(CNOT {
363                control: QubitId(i as u32),
364                target: QubitId((i + 1) as u32),
365            }));
366        }
367
368        Ok(gates)
369    }
370}
371
372/// Data re-uploading strategy
373pub struct DataReuploader {
374    /// Base encoder
375    encoder: DataEncoder,
376    /// Number of layers to repeat encoding
377    num_layers: usize,
378    /// Whether to use different parameters per layer
379    trainable_scaling: bool,
380}
381
382impl DataReuploader {
383    /// Create a new data re-uploader
384    pub const fn new(encoder: DataEncoder, num_layers: usize, trainable_scaling: bool) -> Self {
385        Self {
386            encoder,
387            num_layers,
388            trainable_scaling,
389        }
390    }
391
392    /// Create gates with data re-uploading
393    pub fn create_gates(
394        &self,
395        data: &[f64],
396        scaling_params: Option<&[f64]>,
397    ) -> QuantRS2Result<Vec<Vec<Box<dyn GateOp>>>> {
398        let mut layers = vec![];
399
400        for layer in 0..self.num_layers {
401            let scaled_data = if self.trainable_scaling {
402                if let Some(params) = scaling_params {
403                    let offset = layer * data.len();
404                    if offset + data.len() > params.len() {
405                        return Err(QuantRS2Error::InvalidInput(
406                            "Not enough scaling parameters".to_string(),
407                        ));
408                    }
409
410                    data.iter()
411                        .zip(&params[offset..offset + data.len()])
412                        .map(|(d, p)| d * p)
413                        .collect()
414                } else {
415                    data.to_vec()
416                }
417            } else {
418                data.to_vec()
419            };
420
421            layers.push(self.encoder.encode(&scaled_data)?);
422        }
423
424        Ok(layers)
425    }
426}
427
428#[cfg(test)]
429mod tests {
430    use super::*;
431
432    #[test]
433    fn test_angle_encoding() {
434        let encoder = DataEncoder::new(EncodingStrategy::Angle, 3);
435        assert_eq!(encoder.num_features(), 3);
436
437        let data = vec![0.5, 1.0, 0.0];
438        let gates = encoder.encode(&data).expect("Failed to encode angle data");
439
440        // Should have 3 Hadamards + 3 RY gates
441        assert_eq!(gates.len(), 6);
442    }
443
444    #[test]
445    fn test_basis_encoding() {
446        let encoder = DataEncoder::new(EncodingStrategy::Basis, 4);
447        assert_eq!(encoder.num_features(), 4);
448
449        let data = vec![1.0, 0.0, 1.0, 0.0];
450        let gates = encoder.encode(&data).expect("Failed to encode basis data");
451
452        // Should have 2 X gates (for the 1.0 values)
453        assert_eq!(gates.len(), 2);
454    }
455
456    #[test]
457    fn test_feature_map() {
458        let feature_map = FeatureMap::new(2, FeatureMapType::ZFeature, 1);
459        let features = vec![0.5, 0.7];
460
461        let gates = feature_map
462            .create_gates(&features)
463            .expect("Failed to create feature map gates");
464
465        // Should have 2 Hadamards + 2 RZ gates
466        assert_eq!(gates.len(), 4);
467    }
468
469    #[test]
470    fn test_data_reuploader() {
471        let encoder = DataEncoder::new(EncodingStrategy::Angle, 2);
472        let reuploader = DataReuploader::new(encoder, 3, false);
473
474        let data = vec![0.5, 0.5];
475        let layers = reuploader
476            .create_gates(&data, None)
477            .expect("Failed to create reuploader gates");
478
479        assert_eq!(layers.len(), 3); // 3 layers
480        assert_eq!(layers[0].len(), 4); // Each layer has 2 H + 2 RY
481    }
482}