quantrs2_core/optimization/
compression.rs

1//! Gate sequence compression using SciRS2 optimization
2//!
3//! This module provides advanced gate sequence compression techniques
4//! leveraging SciRS2's optimization and linear algebra capabilities.
5
6use crate::{
7    error::{QuantRS2Error, QuantRS2Result},
8    gate::GateOp,
9    matrix_ops::matrices_approx_equal,
10    qubit::QubitId,
11};
12use scirs2_core::ndarray::{Array2, ArrayView2};
13use scirs2_core::Complex64;
14// use scirs2_linalg::lowrank::{randomized_svd, truncated_svd};
15// use scirs2_optimize::prelude::DifferentialEvolutionOptions;
16// Tucker decomposition enabled for beta.3
17use scirs2_linalg::tensor_contraction::tucker::tucker_decomposition;
18// use scirs2_optimize::differential_evolution;
19use crate::linalg_stubs::{randomized_svd, truncated_svd};
20use crate::optimization_stubs::{differential_evolution, DifferentialEvolutionOptions};
21use std::any::Any;
22use std::collections::HashMap;
23
24/// Configuration for gate sequence compression
25#[derive(Debug, Clone)]
26pub struct CompressionConfig {
27    /// Maximum allowed error in gate approximation
28    pub tolerance: f64,
29    /// Maximum rank for low-rank approximations
30    pub max_rank: Option<usize>,
31    /// Whether to use randomized algorithms for speed
32    pub use_randomized: bool,
33    /// Maximum optimization iterations
34    pub max_iterations: usize,
35    /// Parallel execution threads
36    pub num_threads: Option<usize>,
37}
38
39impl Default for CompressionConfig {
40    fn default() -> Self {
41        Self {
42            tolerance: 1e-10,
43            max_rank: None,
44            use_randomized: true,
45            max_iterations: 1000,
46            num_threads: None,
47        }
48    }
49}
50
51/// Gate sequence compression optimizer
52pub struct GateSequenceCompressor {
53    config: CompressionConfig,
54    /// Cache of compressed gates
55    compression_cache: HashMap<u64, CompressedGate>,
56}
57
58/// Compressed representation of a gate
59#[derive(Debug, Clone)]
60pub enum CompressedGate {
61    /// Low-rank approximation U ≈ AB†
62    LowRank {
63        left: Array2<Complex64>,
64        right: Array2<Complex64>,
65        rank: usize,
66    },
67    /// Tucker decomposition for multi-qubit gates
68    Tucker {
69        core: Array2<Complex64>,
70        factors: Vec<Array2<Complex64>>,
71    },
72    /// Parameterized gate with optimized parameters
73    Parameterized {
74        gate_type: String,
75        parameters: Vec<f64>,
76        qubits: Vec<QubitId>,
77    },
78    /// Runtime-compressed storage with decompression function
79    RuntimeCompressed {
80        compressed_data: Vec<u8>,
81        compression_type: CompressionType,
82        original_size: usize,
83        gate_metadata: GateMetadata,
84    },
85    /// Original gate (no compression possible)
86    Original(Box<dyn GateOp>),
87}
88
89/// Type of compression used for runtime storage
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub enum CompressionType {
92    /// No compression
93    None,
94    /// Zlib compression
95    Zlib,
96    /// LZ4 compression (fast)
97    LZ4,
98    /// Huffman coding for sparse matrices
99    Huffman,
100    /// Custom quantum-specific compression
101    QuantumOptimized,
102}
103
104/// Metadata for compressed gates
105#[derive(Debug, Clone)]
106pub struct GateMetadata {
107    /// Gate name
108    pub name: String,
109    /// Number of qubits
110    pub num_qubits: usize,
111    /// Target qubits
112    pub qubits: Vec<QubitId>,
113    /// Matrix dimensions
114    pub matrix_dims: (usize, usize),
115    /// Sparsity ratio (0.0 = dense, 1.0 = all zeros)
116    pub sparsity_ratio: f64,
117    /// Whether the gate is unitary
118    pub is_unitary: bool,
119}
120
121impl GateSequenceCompressor {
122    /// Create a new gate sequence compressor
123    pub fn new(config: CompressionConfig) -> Self {
124        Self {
125            config,
126            compression_cache: HashMap::new(),
127        }
128    }
129
130    /// Compress a single gate using various techniques
131    pub fn compress_gate(&mut self, gate: &dyn GateOp) -> QuantRS2Result<CompressedGate> {
132        let matrix_vec = gate.matrix()?;
133
134        // Convert vector to 2D array
135        let n = (matrix_vec.len() as f64).sqrt() as usize;
136        let mut matrix = Array2::zeros((n, n));
137        for j in 0..n {
138            for i in 0..n {
139                matrix[(i, j)] = matrix_vec[j * n + i];
140            }
141        }
142
143        let matrix_view = matrix.view();
144        let hash = self.compute_matrix_hash(&matrix_view);
145
146        // Check cache
147        if let Some(compressed) = self.compression_cache.get(&hash) {
148            return Ok(compressed.clone());
149        }
150
151        // Try different compression strategies
152        let compressed = if let Some(low_rank) = self.try_low_rank_approximation(&matrix_view)? {
153            low_rank
154        } else if let Some(tucker) = self.try_tucker_decomposition(&matrix_view)? {
155            tucker
156        } else if let Some(param) = self.try_parameterized_compression(gate)? {
157            param
158        } else if let Some(runtime_compressed) = self.try_runtime_compression(gate)? {
159            runtime_compressed
160        } else {
161            CompressedGate::Original(gate.clone_gate())
162        };
163
164        // Cache the result
165        self.compression_cache.insert(hash, compressed.clone());
166
167        Ok(compressed)
168    }
169
170    /// Compress a sequence of gates
171    pub fn compress_sequence(
172        &mut self,
173        gates: &[Box<dyn GateOp>],
174    ) -> QuantRS2Result<Vec<CompressedGate>> {
175        // Set up parallel execution if configured (SciRS2 POLICY compliant)
176        if let Some(threads) = self.config.num_threads {
177            use scirs2_core::parallel_ops::ThreadPoolBuilder;
178            ThreadPoolBuilder::new()
179                .num_threads(threads)
180                .build_global()
181                .map_err(|e| QuantRS2Error::InvalidInput(e.to_string()))?;
182        }
183
184        // First, try to merge adjacent gates
185        let merged = self.merge_adjacent_gates(gates)?;
186
187        // Then compress each gate individually
188        let compressed: Result<Vec<_>, _> = merged
189            .iter()
190            .map(|gate| self.compress_gate(gate.as_ref()))
191            .collect();
192
193        compressed
194    }
195
196    /// Try low-rank approximation using SVD
197    fn try_low_rank_approximation(
198        &self,
199        matrix: &ArrayView2<Complex64>,
200    ) -> QuantRS2Result<Option<CompressedGate>> {
201        let (rows, cols) = matrix.dim();
202        if rows != cols || rows < 4 {
203            // Only try for larger gates
204            return Ok(None);
205        }
206
207        // Convert to SciRS2 matrix format
208        let real_part = Array2::from_shape_fn((rows, cols), |(i, j)| matrix[(i, j)].re);
209        let imag_part = Array2::from_shape_fn((rows, cols), |(i, j)| matrix[(i, j)].im);
210
211        // Try SVD-based compression
212        let target_rank = self.config.max_rank.unwrap_or(rows / 2);
213
214        // Apply SVD to real and imaginary parts separately
215        let (u_real, s_real, vt_real) = if self.config.use_randomized {
216            randomized_svd(&real_part.view(), target_rank, Some(10), Some(2), None)
217                .map_err(|e| QuantRS2Error::InvalidInput(format!("SVD failed: {e}")))?
218        } else {
219            truncated_svd(&real_part.view(), target_rank, None)
220                .map_err(|e| QuantRS2Error::InvalidInput(format!("SVD failed: {e}")))?
221        };
222
223        let (u_imag, s_imag, vt_imag) = if self.config.use_randomized {
224            randomized_svd(&imag_part.view(), target_rank, Some(10), Some(2), None)
225                .map_err(|e| QuantRS2Error::InvalidInput(format!("SVD failed: {e}")))?
226        } else {
227            truncated_svd(&imag_part.view(), target_rank, None)
228                .map_err(|e| QuantRS2Error::InvalidInput(format!("SVD failed: {e}")))?
229        };
230
231        // Find effective rank based on singular values
232        let effective_rank = self.find_effective_rank(&s_real, &s_imag)?;
233
234        if effective_rank >= rows * 3 / 4 {
235            // Not worth compressing
236            return Ok(None);
237        }
238
239        // Reconstruct low-rank approximation
240        let left = self.combine_complex(&u_real, &u_imag, effective_rank)?;
241        let right = self.combine_complex_with_singular(
242            &vt_real,
243            &vt_imag,
244            &s_real,
245            &s_imag,
246            effective_rank,
247        )?;
248
249        // Verify approximation quality
250        let approx = left.dot(&right.t());
251        if !matrices_approx_equal(&approx.view(), matrix, self.config.tolerance) {
252            return Ok(None);
253        }
254
255        Ok(Some(CompressedGate::LowRank {
256            left,
257            right,
258            rank: effective_rank,
259        }))
260    }
261
262    /// Try Tucker decomposition for multi-qubit gates
263    ///
264    /// Note: Current implementation applies Tucker separately to real and imaginary parts
265    /// since scirs2-linalg's Tucker only supports Float types (not Complex).
266    /// TODO: Request Complex support in scirs2-linalg Tucker decomposition for better performance
267    fn try_tucker_decomposition(
268        &self,
269        matrix: &ArrayView2<Complex64>,
270    ) -> QuantRS2Result<Option<CompressedGate>> {
271        let (rows, cols) = matrix.dim();
272
273        // Only apply Tucker to larger multi-qubit gates (4x4 or larger)
274        if rows < 4 || cols < 4 || rows != cols {
275            return Ok(None);
276        }
277
278        // Determine target ranks for compression
279        let target_rank = self.config.max_rank.unwrap_or(rows / 2).min(rows - 1);
280        let ranks = vec![target_rank, target_rank];
281
282        // Separate real and imaginary parts (scirs2-linalg Tucker only supports Float, not Complex)
283        let real_part = Array2::from_shape_fn((rows, cols), |(i, j)| matrix[(i, j)].re);
284        let imag_part = Array2::from_shape_fn((rows, cols), |(i, j)| matrix[(i, j)].im);
285
286        // Apply Tucker decomposition to real part
287        let tucker_real = tucker_decomposition(&real_part.view(), &ranks).map_err(|e| {
288            QuantRS2Error::InvalidInput(format!("Tucker decomposition (real) failed: {e}"))
289        })?;
290
291        // Apply Tucker decomposition to imaginary part
292        let tucker_imag = tucker_decomposition(&imag_part.view(), &ranks).map_err(|e| {
293            QuantRS2Error::InvalidInput(format!("Tucker decomposition (imag) failed: {e}"))
294        })?;
295
296        // Convert cores to 2D
297        let core_real = tucker_real
298            .core
299            .into_dimensionality::<scirs2_core::ndarray::Ix2>()
300            .map_err(|e| {
301                QuantRS2Error::InvalidInput(format!("Failed to convert real core: {e}"))
302            })?;
303        let core_imag = tucker_imag
304            .core
305            .into_dimensionality::<scirs2_core::ndarray::Ix2>()
306            .map_err(|e| {
307                QuantRS2Error::InvalidInput(format!("Failed to convert imag core: {e}"))
308            })?;
309
310        // Combine real and imaginary cores into complex core
311        let core_2d = Array2::from_shape_fn(core_real.dim(), |(i, j)| {
312            Complex64::new(core_real[(i, j)], core_imag[(i, j)])
313        });
314
315        // Combine real and imaginary factors into complex factors
316        // Average the factors from real and imag decompositions (since they should be similar for unitary matrices)
317        let mut factors = Vec::new();
318        for i in 0..tucker_real.factors.len() {
319            let factor_real = &tucker_real.factors[i];
320            let factor_imag = &tucker_imag.factors[i];
321            let combined_factor = Array2::from_shape_fn(factor_real.dim(), |(r, c)| {
322                Complex64::new(factor_real[(r, c)], factor_imag[(r, c)])
323            });
324            factors.push(combined_factor);
325        }
326
327        // Check if compression is worthwhile
328        let original_params = rows * cols * 2; // Complex numbers have 2 components
329        let compressed_params =
330            (core_2d.len() * 2) + factors.iter().map(|f| f.len() * 2).sum::<usize>();
331
332        if compressed_params >= original_params * 3 / 4 {
333            // Not enough compression benefit
334            return Ok(None);
335        }
336
337        // Verify approximation quality by reconstructing and comparing
338        let reconstructed = self.reconstruct_tucker(&core_2d, &factors)?;
339        if !matrices_approx_equal(&reconstructed.view(), matrix, self.config.tolerance) {
340            return Ok(None);
341        }
342
343        Ok(Some(CompressedGate::Tucker {
344            core: core_2d,
345            factors,
346        }))
347    }
348
349    /// Reconstruct matrix from Tucker decomposition
350    fn reconstruct_tucker(
351        &self,
352        core: &Array2<Complex64>,
353        factors: &[Array2<Complex64>],
354    ) -> QuantRS2Result<Array2<Complex64>> {
355        if factors.len() != 2 {
356            return Err(QuantRS2Error::InvalidInput(
357                "Tucker decomposition requires exactly 2 factors for 2D matrices".to_string(),
358            ));
359        }
360
361        // Reconstruct: M ≈ U_1 × core × U_2^T
362        // First: temp = core × U_2^T
363        let temp = core.dot(&factors[1].t());
364        // Second: result = U_1 × temp
365        let result = factors[0].dot(&temp);
366
367        Ok(result)
368    }
369
370    /// Try to find parameterized representation
371    fn try_parameterized_compression(
372        &self,
373        gate: &dyn GateOp,
374    ) -> QuantRS2Result<Option<CompressedGate>> {
375        // This would identify if the gate can be represented
376        // as a parameterized gate (e.g., rotation gates)
377
378        // For now, we'll use global optimization to find parameters
379        let matrix_vec = gate.matrix()?;
380        let n = (matrix_vec.len() as f64).sqrt() as usize;
381
382        if n > 4 {
383            // Only try for single and two-qubit gates
384            return Ok(None);
385        }
386
387        // Convert vector to 2D array
388        let mut target_matrix = Array2::zeros((n, n));
389        for j in 0..n {
390            for i in 0..n {
391                target_matrix[(i, j)] = matrix_vec[j * n + i];
392            }
393        }
394
395        let gate_type = self.identify_gate_type(gate);
396
397        // Set up bounds for optimization
398        let dim = match gate_type.as_str() {
399            "rotation" => 3, // Three Euler angles
400            "phase" => 1,    // One phase parameter
401            _ => 6,          // General parameterization
402        };
403        let bounds = vec![(Some(-std::f64::consts::PI), Some(std::f64::consts::PI)); dim];
404
405        // Clone values needed for the closure to avoid borrowing self
406        let target_matrix_clone = target_matrix.clone();
407        let gate_type_clone = gate_type.clone();
408        // let _tolerance = self.config.tolerance;
409
410        // Create objective function
411        let objective = move |x: &scirs2_core::ndarray::ArrayView1<f64>| -> f64 {
412            let params: Vec<f64> = x.iter().copied().collect();
413
414            // Inline the evaluation logic since we can't access self
415            let gate_matrix = match gate_type_clone.as_str() {
416                "phase" => {
417                    let mut matrix = Array2::eye(target_matrix_clone.dim().0);
418                    if !params.is_empty() {
419                        let phase = Complex64::from_polar(1.0, params[0]);
420                        let n = matrix.dim().0;
421                        matrix[(n - 1, n - 1)] = phase;
422                    }
423                    matrix
424                }
425                "rotation" | _ => Array2::eye(target_matrix_clone.dim().0), // Placeholder
426            };
427
428            // Compute Frobenius norm of difference
429            let diff = &target_matrix_clone - &gate_matrix;
430            diff.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt()
431        };
432
433        // Use differential evolution for global optimization
434        let mut options = DifferentialEvolutionOptions::default();
435        options.popsize = 50;
436        options.maxiter = self.config.max_iterations;
437        options.tol = self.config.tolerance;
438
439        let de_bounds: Vec<(f64, f64)> = bounds
440            .into_iter()
441            .map(|(low, high)| {
442                (
443                    low.unwrap_or(-std::f64::consts::PI),
444                    high.unwrap_or(std::f64::consts::PI),
445                )
446            })
447            .collect();
448
449        let result =
450            differential_evolution(objective, &de_bounds, Some(options), None).map_err(|e| {
451                QuantRS2Error::InvalidInput(format!("Parameter optimization failed: {e:?}"))
452            })?;
453
454        if result.fun > self.config.tolerance {
455            // Optimization didn't converge well enough
456            return Ok(None);
457        }
458
459        Ok(Some(CompressedGate::Parameterized {
460            gate_type,
461            parameters: result.x.to_vec(),
462            qubits: vec![], // Would need to extract from gate
463        }))
464    }
465
466    /// Merge adjacent gates that can be combined
467    fn merge_adjacent_gates(
468        &self,
469        gates: &[Box<dyn GateOp>],
470    ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
471        let mut merged = Vec::new();
472        let mut i = 0;
473
474        while i < gates.len() {
475            if i + 1 < gates.len() {
476                // Check if gates can be merged
477                if self.can_merge(gates[i].as_ref(), gates[i + 1].as_ref()) {
478                    // Merge the gates
479                    let combined =
480                        self.merge_two_gates(gates[i].as_ref(), gates[i + 1].as_ref())?;
481                    merged.push(combined);
482                    i += 2;
483                } else {
484                    merged.push(gates[i].clone_gate());
485                    i += 1;
486                }
487            } else {
488                merged.push(gates[i].clone_gate());
489                i += 1;
490            }
491        }
492
493        Ok(merged)
494    }
495
496    /// Check if two gates can be merged
497    fn can_merge(&self, gate1: &dyn GateOp, gate2: &dyn GateOp) -> bool {
498        // Gates can be merged if they:
499        // 1. Act on the same qubits
500        // 2. Are both unitary
501        // 3. Their product is simpler than the individual gates
502
503        // For now, simple check - same type gates on same qubits
504        gate1.name() == gate2.name()
505    }
506
507    /// Merge two gates into one
508    fn merge_two_gates(
509        &self,
510        gate1: &dyn GateOp,
511        gate2: &dyn GateOp,
512    ) -> QuantRS2Result<Box<dyn GateOp>> {
513        // Get matrices
514        let matrix1_vec = gate1.matrix()?;
515        let matrix2_vec = gate2.matrix()?;
516
517        // Convert to 2D arrays
518        let n = (matrix1_vec.len() as f64).sqrt() as usize;
519        let mut matrix1 = Array2::zeros((n, n));
520        let mut matrix2 = Array2::zeros((n, n));
521
522        for j in 0..n {
523            for i in 0..n {
524                matrix1[(i, j)] = matrix1_vec[j * n + i];
525                matrix2[(i, j)] = matrix2_vec[j * n + i];
526            }
527        }
528
529        // Matrix multiplication
530        let combined_matrix = matrix2.dot(&matrix1);
531
532        // Create a custom gate with the combined matrix
533        Ok(Box::new(CustomGate::new(
534            format!("{}_{}_merged", gate1.name(), gate2.name()),
535            combined_matrix,
536        )))
537    }
538
539    /// Compute hash of matrix for caching
540    fn compute_matrix_hash(&self, matrix: &ArrayView2<Complex64>) -> u64 {
541        use std::collections::hash_map::DefaultHasher;
542        use std::hash::{Hash, Hasher};
543
544        let mut hasher = DefaultHasher::new();
545        for elem in matrix {
546            // Hash real and imaginary parts
547            elem.re.to_bits().hash(&mut hasher);
548            elem.im.to_bits().hash(&mut hasher);
549        }
550        hasher.finish()
551    }
552
553    /// Find effective rank based on singular values
554    fn find_effective_rank(
555        &self,
556        s_real: &scirs2_core::ndarray::Array1<f64>,
557        s_imag: &scirs2_core::ndarray::Array1<f64>,
558    ) -> QuantRS2Result<usize> {
559        let max_singular = s_real
560            .iter()
561            .chain(s_imag.iter())
562            .map(|s| s.abs())
563            .fold(0.0, f64::max);
564
565        let threshold = max_singular * self.config.tolerance;
566
567        let rank = s_real
568            .iter()
569            .zip(s_imag.iter())
570            .take_while(|(sr, si)| sr.abs() > threshold || si.abs() > threshold)
571            .count();
572
573        Ok(rank.max(1))
574    }
575
576    /// Combine real and imaginary parts into complex matrix
577    fn combine_complex(
578        &self,
579        real: &Array2<f64>,
580        imag: &Array2<f64>,
581        rank: usize,
582    ) -> QuantRS2Result<Array2<Complex64>> {
583        let (rows, _) = real.dim();
584        let result = Array2::from_shape_fn((rows, rank), |(i, j)| {
585            Complex64::new(real[(i, j)], imag[(i, j)])
586        });
587        Ok(result)
588    }
589
590    /// Combine with singular values
591    fn combine_complex_with_singular(
592        &self,
593        vt_real: &Array2<f64>,
594        vt_imag: &Array2<f64>,
595        s_real: &scirs2_core::ndarray::Array1<f64>,
596        s_imag: &scirs2_core::ndarray::Array1<f64>,
597        rank: usize,
598    ) -> QuantRS2Result<Array2<Complex64>> {
599        let (_, cols) = vt_real.dim();
600        let result = Array2::from_shape_fn((rank, cols), |(i, j)| {
601            let s = Complex64::new(s_real[i], s_imag[i]);
602            let v = Complex64::new(vt_real[(i, j)], vt_imag[(i, j)]);
603            s * v
604        });
605        Ok(result)
606    }
607
608    /// Convert tensor data to complex matrix
609    #[allow(dead_code)]
610    fn tensor_to_complex_matrix(&self, tensor: &[f64]) -> QuantRS2Result<Array2<Complex64>> {
611        let size = (tensor.len() / 2) as f64;
612        let dim = size.sqrt() as usize;
613
614        let mut matrix = Array2::zeros((dim, dim));
615        for i in 0..dim {
616            for j in 0..dim {
617                let idx = (i * dim + j) * 2;
618                matrix[(i, j)] = Complex64::new(tensor[idx], tensor[idx + 1]);
619            }
620        }
621
622        Ok(matrix)
623    }
624
625    /// Convert ArrayD to complex matrix
626    #[allow(dead_code)]
627    fn tensor_to_complex_matrix_from_array(
628        &self,
629        tensor: &scirs2_core::ndarray::ArrayD<f64>,
630    ) -> QuantRS2Result<Array2<Complex64>> {
631        // For now, just flatten the tensor and reshape to square matrix
632        let elements: Vec<f64> = tensor.iter().copied().collect();
633        let size = elements.len() as f64;
634        let dim = size.sqrt() as usize;
635
636        if dim * dim == elements.len() {
637            let mut matrix = Array2::zeros((dim, dim));
638            for i in 0..dim {
639                for j in 0..dim {
640                    let idx = i * dim + j;
641                    matrix[(i, j)] = Complex64::new(elements[idx], 0.0);
642                }
643            }
644            Ok(matrix)
645        } else {
646            // If not square, pad with zeros
647            let dim = (size.sqrt().ceil()) as usize;
648            let mut matrix = Array2::zeros((dim, dim));
649            for (idx, &val) in elements.iter().enumerate() {
650                let i = idx / dim;
651                let j = idx % dim;
652                if i < dim && j < dim {
653                    matrix[(i, j)] = Complex64::new(val, 0.0);
654                }
655            }
656            Ok(matrix)
657        }
658    }
659
660    /// Identify gate type for parameterization
661    fn identify_gate_type(&self, gate: &dyn GateOp) -> String {
662        // Simple heuristic based on gate name
663        let name = gate.name();
664        if name.contains("rot") || name.contains("Rot") {
665            "rotation".to_string()
666        } else if name.contains("phase") || name.contains("Phase") {
667            "phase".to_string()
668        } else {
669            "general".to_string()
670        }
671    }
672
673    /// Evaluate gate parameters for optimization
674    #[allow(dead_code)]
675    fn evaluate_gate_parameters(
676        &self,
677        target: &Array2<Complex64>,
678        gate_type: &str,
679        params: &[f64],
680    ) -> f64 {
681        // Construct gate from parameters
682        let gate_matrix = match gate_type {
683            "rotation" => self.rotation_matrix_from_params(params, target.dim().0),
684            "phase" => self.phase_matrix_from_params(params, target.dim().0),
685            _ => self.general_matrix_from_params(params, target.dim().0),
686        };
687
688        // Compute Frobenius norm of difference
689        let diff = target - &gate_matrix;
690        diff.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt()
691    }
692
693    fn rotation_matrix_from_params(&self, _params: &[f64], dim: usize) -> Array2<Complex64> {
694        // Construct rotation matrix from Euler angles
695        // This is a placeholder - would need proper implementation
696        Array2::eye(dim)
697    }
698
699    fn phase_matrix_from_params(&self, params: &[f64], dim: usize) -> Array2<Complex64> {
700        let mut matrix = Array2::eye(dim);
701        if !params.is_empty() {
702            let phase = Complex64::from_polar(1.0, params[0]);
703            matrix[(dim - 1, dim - 1)] = phase;
704        }
705        matrix
706    }
707
708    #[allow(dead_code)]
709    fn general_matrix_from_params(&self, _params: &[f64], dim: usize) -> Array2<Complex64> {
710        // General parameterization - would need proper implementation
711        Array2::eye(dim)
712    }
713
714    /// Try runtime compression using various algorithms
715    fn try_runtime_compression(&self, gate: &dyn GateOp) -> QuantRS2Result<Option<CompressedGate>> {
716        let matrix_vec = gate.matrix()?;
717        let n = (matrix_vec.len() as f64).sqrt() as usize;
718
719        // Create gate metadata
720        let metadata = GateMetadata {
721            name: gate.name().to_string(),
722            num_qubits: gate.num_qubits(),
723            qubits: gate.qubits(),
724            matrix_dims: (n, n),
725            sparsity_ratio: self.calculate_sparsity_ratio(&matrix_vec),
726            is_unitary: self.check_unitary(&matrix_vec, n),
727        };
728
729        // Serialize the matrix to bytes
730        let matrix_bytes = self.serialize_matrix(&matrix_vec)?;
731
732        // Try different compression algorithms
733        let best_compression = self.find_best_compression(&matrix_bytes, &metadata)?;
734
735        // Only compress if we achieve significant compression ratio
736        if best_compression.compression_ratio < 0.8 {
737            Ok(Some(CompressedGate::RuntimeCompressed {
738                compressed_data: best_compression.data,
739                compression_type: best_compression.compression_type,
740                original_size: matrix_bytes.len(),
741                gate_metadata: metadata,
742            }))
743        } else {
744            Ok(None)
745        }
746    }
747
748    /// Calculate sparsity ratio of a matrix
749    fn calculate_sparsity_ratio(&self, matrix: &[Complex64]) -> f64 {
750        let zero_count = matrix
751            .iter()
752            .filter(|&c| c.norm() < self.config.tolerance)
753            .count();
754        zero_count as f64 / matrix.len() as f64
755    }
756
757    /// Check if matrix is unitary
758    fn check_unitary(&self, matrix: &[Complex64], n: usize) -> bool {
759        // Simple check: ||U†U - I||_F < tolerance
760        // This is a simplified implementation
761        if n > 8 {
762            return false; // Skip check for large matrices
763        }
764
765        // Convert to Array2 for easier computation
766        let mut u = Array2::zeros((n, n));
767        for j in 0..n {
768            for i in 0..n {
769                u[(i, j)] = matrix[j * n + i];
770            }
771        }
772
773        // Compute U†U
774        let u_dagger = u.t().mapv(|c| c.conj());
775        let product = u_dagger.dot(&u);
776
777        // Check if close to identity
778        let identity = Array2::<Complex64>::eye(n);
779        let diff = &product - &identity;
780        let frobenius_norm: f64 = diff.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
781
782        frobenius_norm < self.config.tolerance
783    }
784
785    /// Serialize matrix to bytes
786    fn serialize_matrix(&self, matrix: &[Complex64]) -> QuantRS2Result<Vec<u8>> {
787        let mut bytes = Vec::with_capacity(matrix.len() * 16); // 16 bytes per Complex64
788
789        for &complex in matrix {
790            bytes.extend_from_slice(&complex.re.to_le_bytes());
791            bytes.extend_from_slice(&complex.im.to_le_bytes());
792        }
793
794        Ok(bytes)
795    }
796
797    /// Find the best compression algorithm for the data
798    fn find_best_compression(
799        &self,
800        data: &[u8],
801        metadata: &GateMetadata,
802    ) -> QuantRS2Result<CompressionResult> {
803        let mut best_compression = CompressionResult {
804            data: data.to_vec(),
805            compression_type: CompressionType::None,
806            compression_ratio: 1.0,
807        };
808
809        // Try Zlib compression
810        if let Ok(zlib_compressed) = self.compress_zlib(data) {
811            let ratio = zlib_compressed.len() as f64 / data.len() as f64;
812            if ratio < best_compression.compression_ratio {
813                best_compression = CompressionResult {
814                    data: zlib_compressed,
815                    compression_type: CompressionType::Zlib,
816                    compression_ratio: ratio,
817                };
818            }
819        }
820
821        // Try LZ4 compression (simulated - would use actual LZ4 in real implementation)
822        if let Ok(lz4_compressed) = self.compress_lz4(data) {
823            let ratio = lz4_compressed.len() as f64 / data.len() as f64;
824            if ratio < best_compression.compression_ratio {
825                best_compression = CompressionResult {
826                    data: lz4_compressed,
827                    compression_type: CompressionType::LZ4,
828                    compression_ratio: ratio,
829                };
830            }
831        }
832
833        // Try quantum-optimized compression for sparse matrices
834        if metadata.sparsity_ratio > 0.3 {
835            if let Ok(quantum_compressed) = self.compress_quantum_optimized(data, metadata) {
836                let ratio = quantum_compressed.len() as f64 / data.len() as f64;
837                if ratio < best_compression.compression_ratio {
838                    best_compression = CompressionResult {
839                        data: quantum_compressed,
840                        compression_type: CompressionType::QuantumOptimized,
841                        compression_ratio: ratio,
842                    };
843                }
844            }
845        }
846
847        Ok(best_compression)
848    }
849
850    /// Compress using Zlib
851    fn compress_zlib(&self, data: &[u8]) -> QuantRS2Result<Vec<u8>> {
852        #[cfg(feature = "compression")]
853        {
854            use std::io::Write;
855            let mut encoder =
856                flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::default());
857            encoder.write_all(data).map_err(|e| {
858                QuantRS2Error::RuntimeError(format!("Zlib compression failed: {e}"))
859            })?;
860
861            encoder
862                .finish()
863                .map_err(|e| QuantRS2Error::RuntimeError(format!("Zlib compression failed: {e}")))
864        }
865
866        #[cfg(not(feature = "compression"))]
867        {
868            // Fallback: return uncompressed data
869            Ok(data.to_vec())
870        }
871    }
872
873    /// Compress using LZ4 (simulated)
874    fn compress_lz4(&self, data: &[u8]) -> QuantRS2Result<Vec<u8>> {
875        // This is a simplified simulation of LZ4 compression
876        // In a real implementation, you would use the lz4 crate
877
878        // Simple run-length encoding as a placeholder
879        let mut compressed = Vec::new();
880        let mut i = 0;
881
882        while i < data.len() {
883            let byte = data[i];
884            let mut count = 1;
885
886            while i + count < data.len() && data[i + count] == byte && count < 255 {
887                count += 1;
888            }
889
890            if count > 3 {
891                // Run-length encode
892                compressed.push(0xFF); // Marker for run-length
893                compressed.push(count as u8);
894                compressed.push(byte);
895            } else {
896                // Just copy the bytes
897                for _ in 0..count {
898                    compressed.push(byte);
899                }
900            }
901
902            i += count;
903        }
904
905        Ok(compressed)
906    }
907
908    /// Quantum-optimized compression for sparse matrices
909    fn compress_quantum_optimized(
910        &self,
911        data: &[u8],
912        metadata: &GateMetadata,
913    ) -> QuantRS2Result<Vec<u8>> {
914        // Custom compression for quantum gate matrices
915        let mut compressed = Vec::new();
916
917        // Add metadata header
918        compressed.extend_from_slice(&(metadata.num_qubits as u32).to_le_bytes());
919        compressed.extend_from_slice(&metadata.sparsity_ratio.to_le_bytes());
920
921        // For sparse matrices, store only non-zero elements with their indices
922        if metadata.sparsity_ratio > 0.5 {
923            let complex_data = self.deserialize_matrix_from_bytes(data)?;
924            // let _n = metadata.matrix_dims.0;
925
926            // Store as (index, real, imag) triples for non-zero elements
927            let mut non_zero_count = 0u32;
928            let mut non_zero_data = Vec::new();
929
930            for (idx, &complex) in complex_data.iter().enumerate() {
931                if complex.norm() >= self.config.tolerance {
932                    non_zero_data.extend_from_slice(&(idx as u32).to_le_bytes());
933                    non_zero_data.extend_from_slice(&complex.re.to_le_bytes());
934                    non_zero_data.extend_from_slice(&complex.im.to_le_bytes());
935                    non_zero_count += 1;
936                }
937            }
938
939            compressed.extend_from_slice(&non_zero_count.to_le_bytes());
940            compressed.extend_from_slice(&non_zero_data);
941        } else {
942            // For dense matrices, use delta encoding
943            compressed.extend_from_slice(data);
944        }
945
946        Ok(compressed)
947    }
948
949    /// Deserialize matrix from bytes
950    fn deserialize_matrix_from_bytes(&self, bytes: &[u8]) -> QuantRS2Result<Vec<Complex64>> {
951        if bytes.len() % 16 != 0 {
952            return Err(QuantRS2Error::InvalidInput(
953                "Invalid byte length for Complex64 array".to_string(),
954            ));
955        }
956
957        let mut matrix = Vec::with_capacity(bytes.len() / 16);
958
959        for chunk in bytes.chunks_exact(16) {
960            let re = f64::from_le_bytes([
961                chunk[0], chunk[1], chunk[2], chunk[3], chunk[4], chunk[5], chunk[6], chunk[7],
962            ]);
963            let im = f64::from_le_bytes([
964                chunk[8], chunk[9], chunk[10], chunk[11], chunk[12], chunk[13], chunk[14],
965                chunk[15],
966            ]);
967
968            matrix.push(Complex64::new(re, im));
969        }
970
971        Ok(matrix)
972    }
973
974    /// Decompress runtime-compressed gate
975    pub fn decompress_gate(&self, compressed: &CompressedGate) -> QuantRS2Result<Box<dyn GateOp>> {
976        match compressed {
977            CompressedGate::RuntimeCompressed {
978                compressed_data,
979                compression_type,
980                original_size,
981                gate_metadata,
982            } => {
983                // Decompress the data
984                let decompressed_bytes = self.decompress_data(
985                    compressed_data,
986                    *compression_type,
987                    *original_size,
988                    gate_metadata,
989                )?;
990
991                // Deserialize back to matrix
992                let matrix = self.deserialize_matrix_from_bytes(&decompressed_bytes)?;
993
994                // Create a custom gate with the decompressed matrix
995                let n = gate_metadata.matrix_dims.0;
996                let mut matrix_2d = Array2::zeros((n, n));
997                for j in 0..n {
998                    for i in 0..n {
999                        matrix_2d[(i, j)] = matrix[j * n + i];
1000                    }
1001                }
1002
1003                Ok(Box::new(CustomGate::with_qubits(
1004                    gate_metadata.name.clone(),
1005                    matrix_2d,
1006                    gate_metadata.qubits.clone(),
1007                )))
1008            }
1009            CompressedGate::LowRank { left, right, .. } => {
1010                // Reconstruct from low-rank approximation
1011                let reconstructed = left.dot(&right.t());
1012                Ok(Box::new(CustomGate::new(
1013                    "LowRank".to_string(),
1014                    reconstructed,
1015                )))
1016            }
1017            CompressedGate::Tucker { core, factors } => {
1018                // Reconstruct from Tucker decomposition using proper tensor contraction
1019                let reconstructed = self.reconstruct_tucker(core, factors)?;
1020                Ok(Box::new(CustomGate::new(
1021                    "Tucker".to_string(),
1022                    reconstructed,
1023                )))
1024            }
1025            CompressedGate::Parameterized {
1026                gate_type,
1027                parameters,
1028                qubits,
1029            } => {
1030                // Reconstruct parameterized gate
1031                self.reconstruct_parameterized_gate(gate_type, parameters, qubits)
1032            }
1033            CompressedGate::Original(gate) => Ok(gate.clone_gate()),
1034        }
1035    }
1036
1037    /// Decompress data based on compression type
1038    fn decompress_data(
1039        &self,
1040        compressed_data: &[u8],
1041        compression_type: CompressionType,
1042        original_size: usize,
1043        metadata: &GateMetadata,
1044    ) -> QuantRS2Result<Vec<u8>> {
1045        match compression_type {
1046            CompressionType::None => Ok(compressed_data.to_vec()),
1047            CompressionType::Zlib => self.decompress_zlib(compressed_data),
1048            CompressionType::LZ4 => self.decompress_lz4(compressed_data, original_size),
1049            CompressionType::QuantumOptimized => {
1050                self.decompress_quantum_optimized(compressed_data, metadata)
1051            }
1052            CompressionType::Huffman => {
1053                // Placeholder for Huffman decompression
1054                Ok(compressed_data.to_vec())
1055            }
1056        }
1057    }
1058
1059    /// Decompress Zlib data
1060    fn decompress_zlib(&self, compressed_data: &[u8]) -> QuantRS2Result<Vec<u8>> {
1061        #[cfg(feature = "compression")]
1062        {
1063            use std::io::Read;
1064
1065            let mut decoder = flate2::read::ZlibDecoder::new(compressed_data);
1066            let mut decompressed = Vec::new();
1067
1068            decoder.read_to_end(&mut decompressed).map_err(|e| {
1069                QuantRS2Error::RuntimeError(format!("Zlib decompression failed: {e}"))
1070            })?;
1071
1072            Ok(decompressed)
1073        }
1074
1075        #[cfg(not(feature = "compression"))]
1076        {
1077            // Fallback: return compressed data as-is (since we didn't really compress it)
1078            Ok(compressed_data.to_vec())
1079        }
1080    }
1081
1082    /// Decompress LZ4 data (simulated)
1083    fn decompress_lz4(
1084        &self,
1085        compressed_data: &[u8],
1086        original_size: usize,
1087    ) -> QuantRS2Result<Vec<u8>> {
1088        // Reverse of the simple run-length encoding
1089        let mut decompressed = Vec::with_capacity(original_size);
1090        let mut i = 0;
1091
1092        while i < compressed_data.len() {
1093            if compressed_data[i] == 0xFF && i + 2 < compressed_data.len() {
1094                // Run-length encoded
1095                let count = compressed_data[i + 1] as usize;
1096                let byte = compressed_data[i + 2];
1097
1098                for _ in 0..count {
1099                    decompressed.push(byte);
1100                }
1101
1102                i += 3;
1103            } else {
1104                // Regular byte
1105                decompressed.push(compressed_data[i]);
1106                i += 1;
1107            }
1108        }
1109
1110        Ok(decompressed)
1111    }
1112
1113    /// Decompress quantum-optimized data
1114    fn decompress_quantum_optimized(
1115        &self,
1116        compressed_data: &[u8],
1117        metadata: &GateMetadata,
1118    ) -> QuantRS2Result<Vec<u8>> {
1119        if compressed_data.len() < 12 {
1120            return Err(QuantRS2Error::InvalidInput(
1121                "Invalid quantum-optimized compressed data".to_string(),
1122            ));
1123        }
1124
1125        let mut cursor = 0;
1126
1127        // Read header
1128        let _num_qubits = u32::from_le_bytes([
1129            compressed_data[cursor],
1130            compressed_data[cursor + 1],
1131            compressed_data[cursor + 2],
1132            compressed_data[cursor + 3],
1133        ]);
1134        cursor += 4;
1135
1136        let sparsity_ratio = f64::from_le_bytes([
1137            compressed_data[cursor],
1138            compressed_data[cursor + 1],
1139            compressed_data[cursor + 2],
1140            compressed_data[cursor + 3],
1141            compressed_data[cursor + 4],
1142            compressed_data[cursor + 5],
1143            compressed_data[cursor + 6],
1144            compressed_data[cursor + 7],
1145        ]);
1146        cursor += 8;
1147
1148        if sparsity_ratio > 0.5 {
1149            // Sparse format: reconstruct from (index, real, imag) triples
1150            let non_zero_count = u32::from_le_bytes([
1151                compressed_data[cursor],
1152                compressed_data[cursor + 1],
1153                compressed_data[cursor + 2],
1154                compressed_data[cursor + 3],
1155            ]);
1156            cursor += 4;
1157
1158            let matrix_size = metadata.matrix_dims.0 * metadata.matrix_dims.1;
1159            let mut matrix = vec![Complex64::new(0.0, 0.0); matrix_size];
1160
1161            for _ in 0..non_zero_count {
1162                let index = u32::from_le_bytes([
1163                    compressed_data[cursor],
1164                    compressed_data[cursor + 1],
1165                    compressed_data[cursor + 2],
1166                    compressed_data[cursor + 3],
1167                ]) as usize;
1168                cursor += 4;
1169
1170                let re = f64::from_le_bytes([
1171                    compressed_data[cursor],
1172                    compressed_data[cursor + 1],
1173                    compressed_data[cursor + 2],
1174                    compressed_data[cursor + 3],
1175                    compressed_data[cursor + 4],
1176                    compressed_data[cursor + 5],
1177                    compressed_data[cursor + 6],
1178                    compressed_data[cursor + 7],
1179                ]);
1180                cursor += 8;
1181
1182                let im = f64::from_le_bytes([
1183                    compressed_data[cursor],
1184                    compressed_data[cursor + 1],
1185                    compressed_data[cursor + 2],
1186                    compressed_data[cursor + 3],
1187                    compressed_data[cursor + 4],
1188                    compressed_data[cursor + 5],
1189                    compressed_data[cursor + 6],
1190                    compressed_data[cursor + 7],
1191                ]);
1192                cursor += 8;
1193
1194                if index < matrix_size {
1195                    matrix[index] = Complex64::new(re, im);
1196                }
1197            }
1198
1199            // Serialize back to bytes
1200            self.serialize_matrix(&matrix)
1201        } else {
1202            // Dense format: just return the remaining data
1203            Ok(compressed_data[cursor..].to_vec())
1204        }
1205    }
1206
1207    /// Reconstruct parameterized gate
1208    fn reconstruct_parameterized_gate(
1209        &self,
1210        gate_type: &str,
1211        parameters: &[f64],
1212        qubits: &[QubitId],
1213    ) -> QuantRS2Result<Box<dyn GateOp>> {
1214        match gate_type {
1215            "rotation" => {
1216                if parameters.len() >= 3 && !qubits.is_empty() {
1217                    // Create a rotation gate from Euler angles
1218                    let matrix = self.rotation_matrix_from_params(parameters, 2);
1219                    Ok(Box::new(CustomGate::with_qubits(
1220                        "Rotation".to_string(),
1221                        matrix,
1222                        qubits.to_vec(),
1223                    )))
1224                } else {
1225                    Err(QuantRS2Error::InvalidInput(
1226                        "Invalid rotation parameters".to_string(),
1227                    ))
1228                }
1229            }
1230            "phase" => {
1231                if !parameters.is_empty() && !qubits.is_empty() {
1232                    let matrix = self.phase_matrix_from_params(parameters, 2);
1233                    Ok(Box::new(CustomGate::with_qubits(
1234                        "Phase".to_string(),
1235                        matrix,
1236                        qubits.to_vec(),
1237                    )))
1238                } else {
1239                    Err(QuantRS2Error::InvalidInput(
1240                        "Invalid phase parameters".to_string(),
1241                    ))
1242                }
1243            }
1244            _ => {
1245                // General case - create identity for now
1246                let dim = 1 << qubits.len();
1247                let matrix = Array2::eye(dim);
1248                Ok(Box::new(CustomGate::with_qubits(
1249                    gate_type.to_string(),
1250                    matrix,
1251                    qubits.to_vec(),
1252                )))
1253            }
1254        }
1255    }
1256}
1257
1258/// Result of compression operation
1259struct CompressionResult {
1260    data: Vec<u8>,
1261    compression_type: CompressionType,
1262    compression_ratio: f64,
1263}
1264
1265/// Custom gate implementation for compressed gates
1266#[derive(Debug, Clone)]
1267pub struct CustomGate {
1268    name: String,
1269    matrix: Array2<Complex64>,
1270    qubits: Vec<QubitId>,
1271}
1272
1273impl CustomGate {
1274    pub fn new(name: String, matrix: Array2<Complex64>) -> Self {
1275        // Determine number of qubits from matrix size
1276        let n_qubits = (matrix.dim().0 as f64).log2() as usize;
1277        let qubits = (0..n_qubits).map(|i| QubitId::new(i as u32)).collect();
1278        Self {
1279            name,
1280            matrix,
1281            qubits,
1282        }
1283    }
1284
1285    pub const fn with_qubits(
1286        name: String,
1287        matrix: Array2<Complex64>,
1288        qubits: Vec<QubitId>,
1289    ) -> Self {
1290        Self {
1291            name,
1292            matrix,
1293            qubits,
1294        }
1295    }
1296}
1297
1298impl GateOp for CustomGate {
1299    fn name(&self) -> &'static str {
1300        // Since we need 'static, we leak the string
1301        Box::leak(self.name.clone().into_boxed_str())
1302    }
1303
1304    fn qubits(&self) -> Vec<QubitId> {
1305        self.qubits.clone()
1306    }
1307
1308    fn matrix(&self) -> QuantRS2Result<Vec<Complex64>> {
1309        // Flatten the matrix to a vector in column-major order
1310        let mut result = Vec::with_capacity(self.matrix.len());
1311        let (rows, cols) = self.matrix.dim();
1312        for j in 0..cols {
1313            for i in 0..rows {
1314                result.push(self.matrix[(i, j)]);
1315            }
1316        }
1317        Ok(result)
1318    }
1319
1320    fn as_any(&self) -> &dyn Any {
1321        self
1322    }
1323
1324    fn clone_gate(&self) -> Box<dyn GateOp> {
1325        Box::new(self.clone())
1326    }
1327}
1328
1329/// Compression statistics
1330#[derive(Debug, Clone, Default)]
1331pub struct CompressionStats {
1332    pub original_gates: usize,
1333    pub compressed_gates: usize,
1334    pub low_rank_compressions: usize,
1335    pub tucker_compressions: usize,
1336    pub parameterized_compressions: usize,
1337    pub compression_ratio: f64,
1338    pub total_parameters_before: usize,
1339    pub total_parameters_after: usize,
1340}
1341
1342impl GateSequenceCompressor {
1343    /// Get compression statistics
1344    pub fn get_stats(
1345        &self,
1346        original: &[Box<dyn GateOp>],
1347        compressed: &[CompressedGate],
1348    ) -> CompressionStats {
1349        let mut stats = CompressionStats::default();
1350        stats.original_gates = original.len();
1351        stats.compressed_gates = compressed.len();
1352
1353        for gate in compressed {
1354            match gate {
1355                CompressedGate::LowRank { left, right, .. } => {
1356                    stats.low_rank_compressions += 1;
1357                    stats.total_parameters_after += (left.len() + right.len()) * 2;
1358                }
1359                CompressedGate::Tucker { core, factors } => {
1360                    stats.tucker_compressions += 1;
1361                    stats.total_parameters_after += core.len() * 2;
1362                    stats.total_parameters_after +=
1363                        factors.iter().map(|f| f.len() * 2).sum::<usize>();
1364                }
1365                CompressedGate::Parameterized { parameters, .. } => {
1366                    stats.parameterized_compressions += 1;
1367                    stats.total_parameters_after += parameters.len();
1368                }
1369                CompressedGate::RuntimeCompressed {
1370                    compressed_data, ..
1371                } => {
1372                    // For runtime compressed gates, count the compressed data size
1373                    stats.total_parameters_after += compressed_data.len();
1374                }
1375                CompressedGate::Original(gate) => {
1376                    if let Ok(matrix_vec) = gate.matrix() {
1377                        let size = (matrix_vec.len() as f64).sqrt() as usize;
1378                        stats.total_parameters_after += size * size * 2;
1379                    }
1380                }
1381            }
1382        }
1383
1384        for gate in original {
1385            if let Ok(matrix_vec) = gate.matrix() {
1386                let size = (matrix_vec.len() as f64).sqrt() as usize;
1387                stats.total_parameters_before += size * size * 2;
1388            }
1389        }
1390
1391        stats.compression_ratio = if stats.total_parameters_before > 0 {
1392            stats.total_parameters_after as f64 / stats.total_parameters_before as f64
1393        } else {
1394            1.0
1395        };
1396
1397        stats
1398    }
1399}
1400
1401#[cfg(test)]
1402mod tests {
1403    use super::*;
1404    use crate::gate::single::{Hadamard, PauliX, PauliZ};
1405    use crate::qubit::QubitId;
1406
1407    #[test]
1408    fn test_gate_compression() {
1409        let config = CompressionConfig::default();
1410        let mut compressor = GateSequenceCompressor::new(config);
1411
1412        // Test single gate compression
1413        let h_gate = Hadamard {
1414            target: QubitId::new(0),
1415        };
1416        let compressed = compressor
1417            .compress_gate(&h_gate)
1418            .expect("Failed to compress Hadamard gate");
1419
1420        match compressed {
1421            CompressedGate::Original(_) => {
1422                // H gate is already minimal, shouldn't compress
1423            }
1424            CompressedGate::RuntimeCompressed { .. } => {
1425                // H gate might be runtime compressed, which is acceptable
1426            }
1427            _ => panic!("H gate shouldn't be significantly compressed"),
1428        }
1429    }
1430
1431    #[test]
1432    fn test_sequence_compression() {
1433        let config = CompressionConfig::default();
1434        let mut compressor = GateSequenceCompressor::new(config);
1435
1436        // Create a sequence of gates
1437        let gates: Vec<Box<dyn GateOp>> = vec![
1438            Box::new(Hadamard {
1439                target: QubitId::new(0),
1440            }),
1441            Box::new(PauliX {
1442                target: QubitId::new(0),
1443            }),
1444            Box::new(Hadamard {
1445                target: QubitId::new(0),
1446            }),
1447        ];
1448
1449        let compressed = compressor
1450            .compress_sequence(&gates)
1451            .expect("Failed to compress gate sequence");
1452        assert!(compressed.len() <= gates.len());
1453    }
1454
1455    #[test]
1456    fn test_compression_stats() {
1457        let config = CompressionConfig::default();
1458        let mut compressor = GateSequenceCompressor::new(config);
1459
1460        let gates: Vec<Box<dyn GateOp>> = vec![
1461            Box::new(Hadamard {
1462                target: QubitId::new(0),
1463            }),
1464            Box::new(PauliZ {
1465                target: QubitId::new(0),
1466            }),
1467        ];
1468
1469        let compressed = compressor
1470            .compress_sequence(&gates)
1471            .expect("Failed to compress gate sequence for stats");
1472        let stats = compressor.get_stats(&gates, &compressed);
1473
1474        assert_eq!(stats.original_gates, 2);
1475        // Compression ratio can be > 1.0 for small gates due to overhead
1476        assert!(stats.compression_ratio >= 0.0);
1477    }
1478}