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().cloned().collect();
413
414            // Inline the evaluation logic since we can't access self
415            let gate_matrix = match gate_type_clone.as_str() {
416                "rotation" => Array2::eye(target_matrix_clone.dim().0), // Placeholder
417                "phase" => {
418                    let mut matrix = Array2::eye(target_matrix_clone.dim().0);
419                    if !params.is_empty() {
420                        let phase = Complex64::from_polar(1.0, params[0]);
421                        let n = matrix.dim().0;
422                        matrix[(n - 1, n - 1)] = phase;
423                    }
424                    matrix
425                }
426                _ => Array2::eye(target_matrix_clone.dim().0), // Placeholder
427            };
428
429            // Compute Frobenius norm of difference
430            let diff = &target_matrix_clone - &gate_matrix;
431            diff.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt()
432        };
433
434        // Use differential evolution for global optimization
435        let mut options = DifferentialEvolutionOptions::default();
436        options.popsize = 50;
437        options.maxiter = self.config.max_iterations;
438        options.tol = self.config.tolerance;
439
440        let de_bounds: Vec<(f64, f64)> = bounds
441            .into_iter()
442            .map(|(low, high)| {
443                (
444                    low.unwrap_or(-std::f64::consts::PI),
445                    high.unwrap_or(std::f64::consts::PI),
446                )
447            })
448            .collect();
449
450        let result =
451            differential_evolution(objective, &de_bounds, Some(options), None).map_err(|e| {
452                QuantRS2Error::InvalidInput(format!("Parameter optimization failed: {:?}", e))
453            })?;
454
455        if result.fun > self.config.tolerance {
456            // Optimization didn't converge well enough
457            return Ok(None);
458        }
459
460        Ok(Some(CompressedGate::Parameterized {
461            gate_type,
462            parameters: result.x.to_vec(),
463            qubits: vec![], // Would need to extract from gate
464        }))
465    }
466
467    /// Merge adjacent gates that can be combined
468    fn merge_adjacent_gates(
469        &self,
470        gates: &[Box<dyn GateOp>],
471    ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
472        let mut merged = Vec::new();
473        let mut i = 0;
474
475        while i < gates.len() {
476            if i + 1 < gates.len() {
477                // Check if gates can be merged
478                if self.can_merge(gates[i].as_ref(), gates[i + 1].as_ref()) {
479                    // Merge the gates
480                    let combined =
481                        self.merge_two_gates(gates[i].as_ref(), gates[i + 1].as_ref())?;
482                    merged.push(combined);
483                    i += 2;
484                } else {
485                    merged.push(gates[i].clone_gate());
486                    i += 1;
487                }
488            } else {
489                merged.push(gates[i].clone_gate());
490                i += 1;
491            }
492        }
493
494        Ok(merged)
495    }
496
497    /// Check if two gates can be merged
498    fn can_merge(&self, gate1: &dyn GateOp, gate2: &dyn GateOp) -> bool {
499        // Gates can be merged if they:
500        // 1. Act on the same qubits
501        // 2. Are both unitary
502        // 3. Their product is simpler than the individual gates
503
504        // For now, simple check - same type gates on same qubits
505        gate1.name() == gate2.name()
506    }
507
508    /// Merge two gates into one
509    fn merge_two_gates(
510        &self,
511        gate1: &dyn GateOp,
512        gate2: &dyn GateOp,
513    ) -> QuantRS2Result<Box<dyn GateOp>> {
514        // Get matrices
515        let matrix1_vec = gate1.matrix()?;
516        let matrix2_vec = gate2.matrix()?;
517
518        // Convert to 2D arrays
519        let n = (matrix1_vec.len() as f64).sqrt() as usize;
520        let mut matrix1 = Array2::zeros((n, n));
521        let mut matrix2 = Array2::zeros((n, n));
522
523        for j in 0..n {
524            for i in 0..n {
525                matrix1[(i, j)] = matrix1_vec[j * n + i];
526                matrix2[(i, j)] = matrix2_vec[j * n + i];
527            }
528        }
529
530        // Matrix multiplication
531        let combined_matrix = matrix2.dot(&matrix1);
532
533        // Create a custom gate with the combined matrix
534        Ok(Box::new(CustomGate::new(
535            format!("{}_{}_merged", gate1.name(), gate2.name()),
536            combined_matrix,
537        )))
538    }
539
540    /// Compute hash of matrix for caching
541    fn compute_matrix_hash(&self, matrix: &ArrayView2<Complex64>) -> u64 {
542        use std::collections::hash_map::DefaultHasher;
543        use std::hash::{Hash, Hasher};
544
545        let mut hasher = DefaultHasher::new();
546        for elem in matrix.iter() {
547            // Hash real and imaginary parts
548            elem.re.to_bits().hash(&mut hasher);
549            elem.im.to_bits().hash(&mut hasher);
550        }
551        hasher.finish()
552    }
553
554    /// Find effective rank based on singular values
555    fn find_effective_rank(
556        &self,
557        s_real: &scirs2_core::ndarray::Array1<f64>,
558        s_imag: &scirs2_core::ndarray::Array1<f64>,
559    ) -> QuantRS2Result<usize> {
560        let max_singular = s_real
561            .iter()
562            .chain(s_imag.iter())
563            .map(|s| s.abs())
564            .fold(0.0, f64::max);
565
566        let threshold = max_singular * self.config.tolerance;
567
568        let rank = s_real
569            .iter()
570            .zip(s_imag.iter())
571            .take_while(|(sr, si)| sr.abs() > threshold || si.abs() > threshold)
572            .count();
573
574        Ok(rank.max(1))
575    }
576
577    /// Combine real and imaginary parts into complex matrix
578    fn combine_complex(
579        &self,
580        real: &Array2<f64>,
581        imag: &Array2<f64>,
582        rank: usize,
583    ) -> QuantRS2Result<Array2<Complex64>> {
584        let (rows, _) = real.dim();
585        let result = Array2::from_shape_fn((rows, rank), |(i, j)| {
586            Complex64::new(real[(i, j)], imag[(i, j)])
587        });
588        Ok(result)
589    }
590
591    /// Combine with singular values
592    fn combine_complex_with_singular(
593        &self,
594        vt_real: &Array2<f64>,
595        vt_imag: &Array2<f64>,
596        s_real: &scirs2_core::ndarray::Array1<f64>,
597        s_imag: &scirs2_core::ndarray::Array1<f64>,
598        rank: usize,
599    ) -> QuantRS2Result<Array2<Complex64>> {
600        let (_, cols) = vt_real.dim();
601        let result = Array2::from_shape_fn((rank, cols), |(i, j)| {
602            let s = Complex64::new(s_real[i], s_imag[i]);
603            let v = Complex64::new(vt_real[(i, j)], vt_imag[(i, j)]);
604            s * v
605        });
606        Ok(result)
607    }
608
609    /// Convert tensor data to complex matrix
610    #[allow(dead_code)]
611    fn tensor_to_complex_matrix(&self, tensor: &[f64]) -> QuantRS2Result<Array2<Complex64>> {
612        let size = (tensor.len() / 2) as f64;
613        let dim = size.sqrt() as usize;
614
615        let mut matrix = Array2::zeros((dim, dim));
616        for i in 0..dim {
617            for j in 0..dim {
618                let idx = (i * dim + j) * 2;
619                matrix[(i, j)] = Complex64::new(tensor[idx], tensor[idx + 1]);
620            }
621        }
622
623        Ok(matrix)
624    }
625
626    /// Convert ArrayD to complex matrix
627    #[allow(dead_code)]
628    fn tensor_to_complex_matrix_from_array(
629        &self,
630        tensor: &scirs2_core::ndarray::ArrayD<f64>,
631    ) -> QuantRS2Result<Array2<Complex64>> {
632        // For now, just flatten the tensor and reshape to square matrix
633        let elements: Vec<f64> = tensor.iter().cloned().collect();
634        let size = elements.len() as f64;
635        let dim = size.sqrt() as usize;
636
637        if dim * dim != elements.len() {
638            // If not square, pad with zeros
639            let dim = (size.sqrt().ceil()) as usize;
640            let mut matrix = Array2::zeros((dim, dim));
641            for (idx, &val) in elements.iter().enumerate() {
642                let i = idx / dim;
643                let j = idx % dim;
644                if i < dim && j < dim {
645                    matrix[(i, j)] = Complex64::new(val, 0.0);
646                }
647            }
648            Ok(matrix)
649        } else {
650            let mut matrix = Array2::zeros((dim, dim));
651            for i in 0..dim {
652                for j in 0..dim {
653                    let idx = i * dim + j;
654                    matrix[(i, j)] = Complex64::new(elements[idx], 0.0);
655                }
656            }
657            Ok(matrix)
658        }
659    }
660
661    /// Identify gate type for parameterization
662    fn identify_gate_type(&self, gate: &dyn GateOp) -> String {
663        // Simple heuristic based on gate name
664        let name = gate.name();
665        if name.contains("rot") || name.contains("Rot") {
666            "rotation".to_string()
667        } else if name.contains("phase") || name.contains("Phase") {
668            "phase".to_string()
669        } else {
670            "general".to_string()
671        }
672    }
673
674    /// Evaluate gate parameters for optimization
675    #[allow(dead_code)]
676    fn evaluate_gate_parameters(
677        &self,
678        target: &Array2<Complex64>,
679        gate_type: &str,
680        params: &[f64],
681    ) -> f64 {
682        // Construct gate from parameters
683        let gate_matrix = match gate_type {
684            "rotation" => self.rotation_matrix_from_params(params, target.dim().0),
685            "phase" => self.phase_matrix_from_params(params, target.dim().0),
686            _ => self.general_matrix_from_params(params, target.dim().0),
687        };
688
689        // Compute Frobenius norm of difference
690        let diff = target - &gate_matrix;
691        diff.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt()
692    }
693
694    fn rotation_matrix_from_params(&self, _params: &[f64], dim: usize) -> Array2<Complex64> {
695        // Construct rotation matrix from Euler angles
696        // This is a placeholder - would need proper implementation
697        Array2::eye(dim)
698    }
699
700    fn phase_matrix_from_params(&self, params: &[f64], dim: usize) -> Array2<Complex64> {
701        let mut matrix = Array2::eye(dim);
702        if !params.is_empty() {
703            let phase = Complex64::from_polar(1.0, params[0]);
704            matrix[(dim - 1, dim - 1)] = phase;
705        }
706        matrix
707    }
708
709    #[allow(dead_code)]
710    fn general_matrix_from_params(&self, _params: &[f64], dim: usize) -> Array2<Complex64> {
711        // General parameterization - would need proper implementation
712        Array2::eye(dim)
713    }
714
715    /// Try runtime compression using various algorithms
716    fn try_runtime_compression(&self, gate: &dyn GateOp) -> QuantRS2Result<Option<CompressedGate>> {
717        let matrix_vec = gate.matrix()?;
718        let n = (matrix_vec.len() as f64).sqrt() as usize;
719
720        // Create gate metadata
721        let metadata = GateMetadata {
722            name: gate.name().to_string(),
723            num_qubits: gate.num_qubits(),
724            qubits: gate.qubits(),
725            matrix_dims: (n, n),
726            sparsity_ratio: self.calculate_sparsity_ratio(&matrix_vec),
727            is_unitary: self.check_unitary(&matrix_vec, n),
728        };
729
730        // Serialize the matrix to bytes
731        let matrix_bytes = self.serialize_matrix(&matrix_vec)?;
732
733        // Try different compression algorithms
734        let best_compression = self.find_best_compression(&matrix_bytes, &metadata)?;
735
736        // Only compress if we achieve significant compression ratio
737        if best_compression.compression_ratio < 0.8 {
738            Ok(Some(CompressedGate::RuntimeCompressed {
739                compressed_data: best_compression.data,
740                compression_type: best_compression.compression_type,
741                original_size: matrix_bytes.len(),
742                gate_metadata: metadata,
743            }))
744        } else {
745            Ok(None)
746        }
747    }
748
749    /// Calculate sparsity ratio of a matrix
750    fn calculate_sparsity_ratio(&self, matrix: &[Complex64]) -> f64 {
751        let zero_count = matrix
752            .iter()
753            .filter(|&c| c.norm() < self.config.tolerance)
754            .count();
755        zero_count as f64 / matrix.len() as f64
756    }
757
758    /// Check if matrix is unitary
759    fn check_unitary(&self, matrix: &[Complex64], n: usize) -> bool {
760        // Simple check: ||U†U - I||_F < tolerance
761        // This is a simplified implementation
762        if n > 8 {
763            return false; // Skip check for large matrices
764        }
765
766        // Convert to Array2 for easier computation
767        let mut u = Array2::zeros((n, n));
768        for j in 0..n {
769            for i in 0..n {
770                u[(i, j)] = matrix[j * n + i];
771            }
772        }
773
774        // Compute U†U
775        let u_dagger = u.t().mapv(|c| c.conj());
776        let product = u_dagger.dot(&u);
777
778        // Check if close to identity
779        let identity = Array2::<Complex64>::eye(n);
780        let diff = &product - &identity;
781        let frobenius_norm: f64 = diff.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
782
783        frobenius_norm < self.config.tolerance
784    }
785
786    /// Serialize matrix to bytes
787    fn serialize_matrix(&self, matrix: &[Complex64]) -> QuantRS2Result<Vec<u8>> {
788        let mut bytes = Vec::with_capacity(matrix.len() * 16); // 16 bytes per Complex64
789
790        for &complex in matrix {
791            bytes.extend_from_slice(&complex.re.to_le_bytes());
792            bytes.extend_from_slice(&complex.im.to_le_bytes());
793        }
794
795        Ok(bytes)
796    }
797
798    /// Find the best compression algorithm for the data
799    fn find_best_compression(
800        &self,
801        data: &[u8],
802        metadata: &GateMetadata,
803    ) -> QuantRS2Result<CompressionResult> {
804        let mut best_compression = CompressionResult {
805            data: data.to_vec(),
806            compression_type: CompressionType::None,
807            compression_ratio: 1.0,
808        };
809
810        // Try Zlib compression
811        if let Ok(zlib_compressed) = self.compress_zlib(data) {
812            let ratio = zlib_compressed.len() as f64 / data.len() as f64;
813            if ratio < best_compression.compression_ratio {
814                best_compression = CompressionResult {
815                    data: zlib_compressed,
816                    compression_type: CompressionType::Zlib,
817                    compression_ratio: ratio,
818                };
819            }
820        }
821
822        // Try LZ4 compression (simulated - would use actual LZ4 in real implementation)
823        if let Ok(lz4_compressed) = self.compress_lz4(data) {
824            let ratio = lz4_compressed.len() as f64 / data.len() as f64;
825            if ratio < best_compression.compression_ratio {
826                best_compression = CompressionResult {
827                    data: lz4_compressed,
828                    compression_type: CompressionType::LZ4,
829                    compression_ratio: ratio,
830                };
831            }
832        }
833
834        // Try quantum-optimized compression for sparse matrices
835        if metadata.sparsity_ratio > 0.3 {
836            if let Ok(quantum_compressed) = self.compress_quantum_optimized(data, metadata) {
837                let ratio = quantum_compressed.len() as f64 / data.len() as f64;
838                if ratio < best_compression.compression_ratio {
839                    best_compression = CompressionResult {
840                        data: quantum_compressed,
841                        compression_type: CompressionType::QuantumOptimized,
842                        compression_ratio: ratio,
843                    };
844                }
845            }
846        }
847
848        Ok(best_compression)
849    }
850
851    /// Compress using Zlib
852    fn compress_zlib(&self, _data: &[u8]) -> QuantRS2Result<Vec<u8>> {
853        #[cfg(feature = "compression")]
854        {
855            use std::io::Write;
856            let mut encoder =
857                flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::default());
858            encoder.write_all(_data).map_err(|e| {
859                QuantRS2Error::RuntimeError(format!("Zlib compression failed: {}", e))
860            })?;
861
862            encoder
863                .finish()
864                .map_err(|e| QuantRS2Error::RuntimeError(format!("Zlib compression failed: {}", e)))
865        }
866
867        #[cfg(not(feature = "compression"))]
868        {
869            // Fallback: return uncompressed data
870            Ok(_data.to_vec())
871        }
872    }
873
874    /// Compress using LZ4 (simulated)
875    fn compress_lz4(&self, data: &[u8]) -> QuantRS2Result<Vec<u8>> {
876        // This is a simplified simulation of LZ4 compression
877        // In a real implementation, you would use the lz4 crate
878
879        // Simple run-length encoding as a placeholder
880        let mut compressed = Vec::new();
881        let mut i = 0;
882
883        while i < data.len() {
884            let byte = data[i];
885            let mut count = 1;
886
887            while i + count < data.len() && data[i + count] == byte && count < 255 {
888                count += 1;
889            }
890
891            if count > 3 {
892                // Run-length encode
893                compressed.push(0xFF); // Marker for run-length
894                compressed.push(count as u8);
895                compressed.push(byte);
896            } else {
897                // Just copy the bytes
898                for _ in 0..count {
899                    compressed.push(byte);
900                }
901            }
902
903            i += count;
904        }
905
906        Ok(compressed)
907    }
908
909    /// Quantum-optimized compression for sparse matrices
910    fn compress_quantum_optimized(
911        &self,
912        data: &[u8],
913        metadata: &GateMetadata,
914    ) -> QuantRS2Result<Vec<u8>> {
915        // Custom compression for quantum gate matrices
916        let mut compressed = Vec::new();
917
918        // Add metadata header
919        compressed.extend_from_slice(&(metadata.num_qubits as u32).to_le_bytes());
920        compressed.extend_from_slice(&metadata.sparsity_ratio.to_le_bytes());
921
922        // For sparse matrices, store only non-zero elements with their indices
923        if metadata.sparsity_ratio > 0.5 {
924            let complex_data = self.deserialize_matrix_from_bytes(data)?;
925            let _n = metadata.matrix_dims.0;
926
927            // Store as (index, real, imag) triples for non-zero elements
928            let mut non_zero_count = 0u32;
929            let mut non_zero_data = Vec::new();
930
931            for (idx, &complex) in complex_data.iter().enumerate() {
932                if complex.norm() >= self.config.tolerance {
933                    non_zero_data.extend_from_slice(&(idx as u32).to_le_bytes());
934                    non_zero_data.extend_from_slice(&complex.re.to_le_bytes());
935                    non_zero_data.extend_from_slice(&complex.im.to_le_bytes());
936                    non_zero_count += 1;
937                }
938            }
939
940            compressed.extend_from_slice(&non_zero_count.to_le_bytes());
941            compressed.extend_from_slice(&non_zero_data);
942        } else {
943            // For dense matrices, use delta encoding
944            compressed.extend_from_slice(data);
945        }
946
947        Ok(compressed)
948    }
949
950    /// Deserialize matrix from bytes
951    fn deserialize_matrix_from_bytes(&self, bytes: &[u8]) -> QuantRS2Result<Vec<Complex64>> {
952        if bytes.len() % 16 != 0 {
953            return Err(QuantRS2Error::InvalidInput(
954                "Invalid byte length for Complex64 array".to_string(),
955            ));
956        }
957
958        let mut matrix = Vec::with_capacity(bytes.len() / 16);
959
960        for chunk in bytes.chunks_exact(16) {
961            let re = f64::from_le_bytes([
962                chunk[0], chunk[1], chunk[2], chunk[3], chunk[4], chunk[5], chunk[6], chunk[7],
963            ]);
964            let im = f64::from_le_bytes([
965                chunk[8], chunk[9], chunk[10], chunk[11], chunk[12], chunk[13], chunk[14],
966                chunk[15],
967            ]);
968
969            matrix.push(Complex64::new(re, im));
970        }
971
972        Ok(matrix)
973    }
974
975    /// Decompress runtime-compressed gate
976    pub fn decompress_gate(&self, compressed: &CompressedGate) -> QuantRS2Result<Box<dyn GateOp>> {
977        match compressed {
978            CompressedGate::RuntimeCompressed {
979                compressed_data,
980                compression_type,
981                original_size,
982                gate_metadata,
983            } => {
984                // Decompress the data
985                let decompressed_bytes = self.decompress_data(
986                    compressed_data,
987                    *compression_type,
988                    *original_size,
989                    gate_metadata,
990                )?;
991
992                // Deserialize back to matrix
993                let matrix = self.deserialize_matrix_from_bytes(&decompressed_bytes)?;
994
995                // Create a custom gate with the decompressed matrix
996                let n = gate_metadata.matrix_dims.0;
997                let mut matrix_2d = Array2::zeros((n, n));
998                for j in 0..n {
999                    for i in 0..n {
1000                        matrix_2d[(i, j)] = matrix[j * n + i];
1001                    }
1002                }
1003
1004                Ok(Box::new(CustomGate::with_qubits(
1005                    gate_metadata.name.clone(),
1006                    matrix_2d,
1007                    gate_metadata.qubits.clone(),
1008                )))
1009            }
1010            CompressedGate::LowRank { left, right, .. } => {
1011                // Reconstruct from low-rank approximation
1012                let reconstructed = left.dot(&right.t());
1013                Ok(Box::new(CustomGate::new(
1014                    "LowRank".to_string(),
1015                    reconstructed,
1016                )))
1017            }
1018            CompressedGate::Tucker { core, factors } => {
1019                // Reconstruct from Tucker decomposition using proper tensor contraction
1020                let reconstructed = self.reconstruct_tucker(core, factors)?;
1021                Ok(Box::new(CustomGate::new(
1022                    "Tucker".to_string(),
1023                    reconstructed,
1024                )))
1025            }
1026            CompressedGate::Parameterized {
1027                gate_type,
1028                parameters,
1029                qubits,
1030            } => {
1031                // Reconstruct parameterized gate
1032                self.reconstruct_parameterized_gate(gate_type, parameters, qubits)
1033            }
1034            CompressedGate::Original(gate) => Ok(gate.clone_gate()),
1035        }
1036    }
1037
1038    /// Decompress data based on compression type
1039    fn decompress_data(
1040        &self,
1041        compressed_data: &[u8],
1042        compression_type: CompressionType,
1043        original_size: usize,
1044        metadata: &GateMetadata,
1045    ) -> QuantRS2Result<Vec<u8>> {
1046        match compression_type {
1047            CompressionType::None => Ok(compressed_data.to_vec()),
1048            CompressionType::Zlib => self.decompress_zlib(compressed_data),
1049            CompressionType::LZ4 => self.decompress_lz4(compressed_data, original_size),
1050            CompressionType::QuantumOptimized => {
1051                self.decompress_quantum_optimized(compressed_data, metadata)
1052            }
1053            CompressionType::Huffman => {
1054                // Placeholder for Huffman decompression
1055                Ok(compressed_data.to_vec())
1056            }
1057        }
1058    }
1059
1060    /// Decompress Zlib data
1061    fn decompress_zlib(&self, compressed_data: &[u8]) -> QuantRS2Result<Vec<u8>> {
1062        #[cfg(feature = "compression")]
1063        {
1064            use std::io::Read;
1065
1066            let mut decoder = flate2::read::ZlibDecoder::new(compressed_data);
1067            let mut decompressed = Vec::new();
1068
1069            decoder.read_to_end(&mut decompressed).map_err(|e| {
1070                QuantRS2Error::RuntimeError(format!("Zlib decompression failed: {}", e))
1071            })?;
1072
1073            Ok(decompressed)
1074        }
1075
1076        #[cfg(not(feature = "compression"))]
1077        {
1078            // Fallback: return compressed data as-is (since we didn't really compress it)
1079            Ok(compressed_data.to_vec())
1080        }
1081    }
1082
1083    /// Decompress LZ4 data (simulated)
1084    fn decompress_lz4(
1085        &self,
1086        compressed_data: &[u8],
1087        original_size: usize,
1088    ) -> QuantRS2Result<Vec<u8>> {
1089        // Reverse of the simple run-length encoding
1090        let mut decompressed = Vec::with_capacity(original_size);
1091        let mut i = 0;
1092
1093        while i < compressed_data.len() {
1094            if compressed_data[i] == 0xFF && i + 2 < compressed_data.len() {
1095                // Run-length encoded
1096                let count = compressed_data[i + 1] as usize;
1097                let byte = compressed_data[i + 2];
1098
1099                for _ in 0..count {
1100                    decompressed.push(byte);
1101                }
1102
1103                i += 3;
1104            } else {
1105                // Regular byte
1106                decompressed.push(compressed_data[i]);
1107                i += 1;
1108            }
1109        }
1110
1111        Ok(decompressed)
1112    }
1113
1114    /// Decompress quantum-optimized data
1115    fn decompress_quantum_optimized(
1116        &self,
1117        compressed_data: &[u8],
1118        metadata: &GateMetadata,
1119    ) -> QuantRS2Result<Vec<u8>> {
1120        if compressed_data.len() < 12 {
1121            return Err(QuantRS2Error::InvalidInput(
1122                "Invalid quantum-optimized compressed data".to_string(),
1123            ));
1124        }
1125
1126        let mut cursor = 0;
1127
1128        // Read header
1129        let _num_qubits = u32::from_le_bytes([
1130            compressed_data[cursor],
1131            compressed_data[cursor + 1],
1132            compressed_data[cursor + 2],
1133            compressed_data[cursor + 3],
1134        ]);
1135        cursor += 4;
1136
1137        let sparsity_ratio = f64::from_le_bytes([
1138            compressed_data[cursor],
1139            compressed_data[cursor + 1],
1140            compressed_data[cursor + 2],
1141            compressed_data[cursor + 3],
1142            compressed_data[cursor + 4],
1143            compressed_data[cursor + 5],
1144            compressed_data[cursor + 6],
1145            compressed_data[cursor + 7],
1146        ]);
1147        cursor += 8;
1148
1149        if sparsity_ratio > 0.5 {
1150            // Sparse format: reconstruct from (index, real, imag) triples
1151            let non_zero_count = u32::from_le_bytes([
1152                compressed_data[cursor],
1153                compressed_data[cursor + 1],
1154                compressed_data[cursor + 2],
1155                compressed_data[cursor + 3],
1156            ]);
1157            cursor += 4;
1158
1159            let matrix_size = metadata.matrix_dims.0 * metadata.matrix_dims.1;
1160            let mut matrix = vec![Complex64::new(0.0, 0.0); matrix_size];
1161
1162            for _ in 0..non_zero_count {
1163                let index = u32::from_le_bytes([
1164                    compressed_data[cursor],
1165                    compressed_data[cursor + 1],
1166                    compressed_data[cursor + 2],
1167                    compressed_data[cursor + 3],
1168                ]) as usize;
1169                cursor += 4;
1170
1171                let re = f64::from_le_bytes([
1172                    compressed_data[cursor],
1173                    compressed_data[cursor + 1],
1174                    compressed_data[cursor + 2],
1175                    compressed_data[cursor + 3],
1176                    compressed_data[cursor + 4],
1177                    compressed_data[cursor + 5],
1178                    compressed_data[cursor + 6],
1179                    compressed_data[cursor + 7],
1180                ]);
1181                cursor += 8;
1182
1183                let im = f64::from_le_bytes([
1184                    compressed_data[cursor],
1185                    compressed_data[cursor + 1],
1186                    compressed_data[cursor + 2],
1187                    compressed_data[cursor + 3],
1188                    compressed_data[cursor + 4],
1189                    compressed_data[cursor + 5],
1190                    compressed_data[cursor + 6],
1191                    compressed_data[cursor + 7],
1192                ]);
1193                cursor += 8;
1194
1195                if index < matrix_size {
1196                    matrix[index] = Complex64::new(re, im);
1197                }
1198            }
1199
1200            // Serialize back to bytes
1201            self.serialize_matrix(&matrix)
1202        } else {
1203            // Dense format: just return the remaining data
1204            Ok(compressed_data[cursor..].to_vec())
1205        }
1206    }
1207
1208    /// Reconstruct parameterized gate
1209    fn reconstruct_parameterized_gate(
1210        &self,
1211        gate_type: &str,
1212        parameters: &[f64],
1213        qubits: &[QubitId],
1214    ) -> QuantRS2Result<Box<dyn GateOp>> {
1215        match gate_type {
1216            "rotation" => {
1217                if parameters.len() >= 3 && !qubits.is_empty() {
1218                    // Create a rotation gate from Euler angles
1219                    let matrix = self.rotation_matrix_from_params(parameters, 2);
1220                    Ok(Box::new(CustomGate::with_qubits(
1221                        "Rotation".to_string(),
1222                        matrix,
1223                        qubits.to_vec(),
1224                    )))
1225                } else {
1226                    Err(QuantRS2Error::InvalidInput(
1227                        "Invalid rotation parameters".to_string(),
1228                    ))
1229                }
1230            }
1231            "phase" => {
1232                if !parameters.is_empty() && !qubits.is_empty() {
1233                    let matrix = self.phase_matrix_from_params(parameters, 2);
1234                    Ok(Box::new(CustomGate::with_qubits(
1235                        "Phase".to_string(),
1236                        matrix,
1237                        qubits.to_vec(),
1238                    )))
1239                } else {
1240                    Err(QuantRS2Error::InvalidInput(
1241                        "Invalid phase parameters".to_string(),
1242                    ))
1243                }
1244            }
1245            _ => {
1246                // General case - create identity for now
1247                let dim = 1 << qubits.len();
1248                let matrix = Array2::eye(dim);
1249                Ok(Box::new(CustomGate::with_qubits(
1250                    gate_type.to_string(),
1251                    matrix,
1252                    qubits.to_vec(),
1253                )))
1254            }
1255        }
1256    }
1257}
1258
1259/// Result of compression operation
1260struct CompressionResult {
1261    data: Vec<u8>,
1262    compression_type: CompressionType,
1263    compression_ratio: f64,
1264}
1265
1266/// Custom gate implementation for compressed gates
1267#[derive(Debug, Clone)]
1268pub struct CustomGate {
1269    name: String,
1270    matrix: Array2<Complex64>,
1271    qubits: Vec<QubitId>,
1272}
1273
1274impl CustomGate {
1275    pub fn new(name: String, matrix: Array2<Complex64>) -> Self {
1276        // Determine number of qubits from matrix size
1277        let n_qubits = (matrix.dim().0 as f64).log2() as usize;
1278        let qubits = (0..n_qubits).map(|i| QubitId::new(i as u32)).collect();
1279        Self {
1280            name,
1281            matrix,
1282            qubits,
1283        }
1284    }
1285
1286    pub fn with_qubits(name: String, matrix: Array2<Complex64>, qubits: Vec<QubitId>) -> Self {
1287        Self {
1288            name,
1289            matrix,
1290            qubits,
1291        }
1292    }
1293}
1294
1295impl GateOp for CustomGate {
1296    fn name(&self) -> &'static str {
1297        // Since we need 'static, we leak the string
1298        Box::leak(self.name.clone().into_boxed_str())
1299    }
1300
1301    fn qubits(&self) -> Vec<QubitId> {
1302        self.qubits.clone()
1303    }
1304
1305    fn matrix(&self) -> QuantRS2Result<Vec<Complex64>> {
1306        // Flatten the matrix to a vector in column-major order
1307        let mut result = Vec::with_capacity(self.matrix.len());
1308        let (rows, cols) = self.matrix.dim();
1309        for j in 0..cols {
1310            for i in 0..rows {
1311                result.push(self.matrix[(i, j)]);
1312            }
1313        }
1314        Ok(result)
1315    }
1316
1317    fn as_any(&self) -> &dyn Any {
1318        self
1319    }
1320
1321    fn clone_gate(&self) -> Box<dyn GateOp> {
1322        Box::new(self.clone())
1323    }
1324}
1325
1326/// Compression statistics
1327#[derive(Debug, Clone, Default)]
1328pub struct CompressionStats {
1329    pub original_gates: usize,
1330    pub compressed_gates: usize,
1331    pub low_rank_compressions: usize,
1332    pub tucker_compressions: usize,
1333    pub parameterized_compressions: usize,
1334    pub compression_ratio: f64,
1335    pub total_parameters_before: usize,
1336    pub total_parameters_after: usize,
1337}
1338
1339impl GateSequenceCompressor {
1340    /// Get compression statistics
1341    pub fn get_stats(
1342        &self,
1343        original: &[Box<dyn GateOp>],
1344        compressed: &[CompressedGate],
1345    ) -> CompressionStats {
1346        let mut stats = CompressionStats::default();
1347        stats.original_gates = original.len();
1348        stats.compressed_gates = compressed.len();
1349
1350        for gate in compressed {
1351            match gate {
1352                CompressedGate::LowRank { left, right, .. } => {
1353                    stats.low_rank_compressions += 1;
1354                    stats.total_parameters_after += (left.len() + right.len()) * 2;
1355                }
1356                CompressedGate::Tucker { core, factors } => {
1357                    stats.tucker_compressions += 1;
1358                    stats.total_parameters_after += core.len() * 2;
1359                    stats.total_parameters_after +=
1360                        factors.iter().map(|f| f.len() * 2).sum::<usize>();
1361                }
1362                CompressedGate::Parameterized { parameters, .. } => {
1363                    stats.parameterized_compressions += 1;
1364                    stats.total_parameters_after += parameters.len();
1365                }
1366                CompressedGate::RuntimeCompressed {
1367                    compressed_data, ..
1368                } => {
1369                    // For runtime compressed gates, count the compressed data size
1370                    stats.total_parameters_after += compressed_data.len();
1371                }
1372                CompressedGate::Original(gate) => {
1373                    if let Ok(matrix_vec) = gate.matrix() {
1374                        let size = (matrix_vec.len() as f64).sqrt() as usize;
1375                        stats.total_parameters_after += size * size * 2;
1376                    }
1377                }
1378            }
1379        }
1380
1381        for gate in original {
1382            if let Ok(matrix_vec) = gate.matrix() {
1383                let size = (matrix_vec.len() as f64).sqrt() as usize;
1384                stats.total_parameters_before += size * size * 2;
1385            }
1386        }
1387
1388        stats.compression_ratio = if stats.total_parameters_before > 0 {
1389            stats.total_parameters_after as f64 / stats.total_parameters_before as f64
1390        } else {
1391            1.0
1392        };
1393
1394        stats
1395    }
1396}
1397
1398#[cfg(test)]
1399mod tests {
1400    use super::*;
1401    use crate::gate::single::{Hadamard, PauliX, PauliZ};
1402    use crate::qubit::QubitId;
1403
1404    #[test]
1405    fn test_gate_compression() {
1406        let config = CompressionConfig::default();
1407        let mut compressor = GateSequenceCompressor::new(config);
1408
1409        // Test single gate compression
1410        let h_gate = Hadamard {
1411            target: QubitId::new(0),
1412        };
1413        let compressed = compressor.compress_gate(&h_gate).unwrap();
1414
1415        match compressed {
1416            CompressedGate::Original(_) => {
1417                // H gate is already minimal, shouldn't compress
1418            }
1419            CompressedGate::RuntimeCompressed { .. } => {
1420                // H gate might be runtime compressed, which is acceptable
1421            }
1422            _ => panic!("H gate shouldn't be significantly compressed"),
1423        }
1424    }
1425
1426    #[test]
1427    fn test_sequence_compression() {
1428        let config = CompressionConfig::default();
1429        let mut compressor = GateSequenceCompressor::new(config);
1430
1431        // Create a sequence of gates
1432        let gates: Vec<Box<dyn GateOp>> = vec![
1433            Box::new(Hadamard {
1434                target: QubitId::new(0),
1435            }),
1436            Box::new(PauliX {
1437                target: QubitId::new(0),
1438            }),
1439            Box::new(Hadamard {
1440                target: QubitId::new(0),
1441            }),
1442        ];
1443
1444        let compressed = compressor.compress_sequence(&gates).unwrap();
1445        assert!(compressed.len() <= gates.len());
1446    }
1447
1448    #[test]
1449    fn test_compression_stats() {
1450        let config = CompressionConfig::default();
1451        let mut compressor = GateSequenceCompressor::new(config);
1452
1453        let gates: Vec<Box<dyn GateOp>> = vec![
1454            Box::new(Hadamard {
1455                target: QubitId::new(0),
1456            }),
1457            Box::new(PauliZ {
1458                target: QubitId::new(0),
1459            }),
1460        ];
1461
1462        let compressed = compressor.compress_sequence(&gates).unwrap();
1463        let stats = compressor.get_stats(&gates, &compressed);
1464
1465        assert_eq!(stats.original_gates, 2);
1466        // Compression ratio can be > 1.0 for small gates due to overhead
1467        assert!(stats.compression_ratio >= 0.0);
1468    }
1469}