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, DenseMatrix},
10    qubit::QubitId,
11};
12use ndarray::{Array2, ArrayView2};
13use num_complex::Complex64;
14use scirs2_linalg::lowrank::{randomized_svd, truncated_svd};
15// Tucker decomposition temporarily disabled due to scirs2-linalg compilation issues
16// use scirs2_linalg::tensor_contraction::tucker::{tucker_decomposition, Tucker};
17use scirs2_optimize::{
18    differential_evolution,
19    error::OptimizeError,
20    global::DifferentialEvolutionOptions,
21    unconstrained::{minimize, Method as OptMethod, Options as OptOptions},
22};
23use std::any::Any;
24use std::collections::HashMap;
25
26/// Configuration for gate sequence compression
27#[derive(Debug, Clone)]
28pub struct CompressionConfig {
29    /// Maximum allowed error in gate approximation
30    pub tolerance: f64,
31    /// Maximum rank for low-rank approximations
32    pub max_rank: Option<usize>,
33    /// Whether to use randomized algorithms for speed
34    pub use_randomized: bool,
35    /// Maximum optimization iterations
36    pub max_iterations: usize,
37    /// Parallel execution threads
38    pub num_threads: Option<usize>,
39}
40
41impl Default for CompressionConfig {
42    fn default() -> Self {
43        Self {
44            tolerance: 1e-10,
45            max_rank: None,
46            use_randomized: true,
47            max_iterations: 1000,
48            num_threads: None,
49        }
50    }
51}
52
53/// Gate sequence compression optimizer
54pub struct GateSequenceCompressor {
55    config: CompressionConfig,
56    /// Cache of compressed gates
57    compression_cache: HashMap<u64, CompressedGate>,
58}
59
60/// Compressed representation of a gate
61#[derive(Debug, Clone)]
62pub enum CompressedGate {
63    /// Low-rank approximation U ≈ AB†
64    LowRank {
65        left: Array2<Complex64>,
66        right: Array2<Complex64>,
67        rank: usize,
68    },
69    /// Tucker decomposition for multi-qubit gates
70    Tucker {
71        core: Array2<Complex64>,
72        factors: Vec<Array2<Complex64>>,
73    },
74    /// Parameterized gate with optimized parameters
75    Parameterized {
76        gate_type: String,
77        parameters: Vec<f64>,
78        qubits: Vec<QubitId>,
79    },
80    /// Original gate (no compression possible)
81    Original(Box<dyn GateOp>),
82}
83
84impl GateSequenceCompressor {
85    /// Create a new gate sequence compressor
86    pub fn new(config: CompressionConfig) -> Self {
87        Self {
88            config,
89            compression_cache: HashMap::new(),
90        }
91    }
92
93    /// Compress a single gate using various techniques
94    pub fn compress_gate(&mut self, gate: &dyn GateOp) -> QuantRS2Result<CompressedGate> {
95        let matrix_vec = gate.matrix()?;
96
97        // Convert vector to 2D array
98        let n = (matrix_vec.len() as f64).sqrt() as usize;
99        let mut matrix = Array2::zeros((n, n));
100        for j in 0..n {
101            for i in 0..n {
102                matrix[(i, j)] = matrix_vec[j * n + i];
103            }
104        }
105
106        let matrix_view = matrix.view();
107        let hash = self.compute_matrix_hash(&matrix_view);
108
109        // Check cache
110        if let Some(compressed) = self.compression_cache.get(&hash) {
111            return Ok(compressed.clone());
112        }
113
114        // Try different compression strategies
115        let compressed = if let Some(low_rank) = self.try_low_rank_approximation(&matrix_view)? {
116            low_rank
117        } else if let Some(tucker) = self.try_tucker_decomposition(&matrix_view)? {
118            tucker
119        } else if let Some(param) = self.try_parameterized_compression(gate)? {
120            param
121        } else {
122            CompressedGate::Original(gate.clone_gate())
123        };
124
125        // Cache the result
126        self.compression_cache.insert(hash, compressed.clone());
127
128        Ok(compressed)
129    }
130
131    /// Compress a sequence of gates
132    pub fn compress_sequence(
133        &mut self,
134        gates: &[Box<dyn GateOp>],
135    ) -> QuantRS2Result<Vec<CompressedGate>> {
136        // Set up parallel execution if configured
137        if let Some(threads) = self.config.num_threads {
138            rayon::ThreadPoolBuilder::new()
139                .num_threads(threads)
140                .build_global()
141                .map_err(|e| QuantRS2Error::InvalidInput(e.to_string()))?;
142        }
143
144        // First, try to merge adjacent gates
145        let merged = self.merge_adjacent_gates(gates)?;
146
147        // Then compress each gate individually
148        let compressed: Result<Vec<_>, _> = merged
149            .iter()
150            .map(|gate| self.compress_gate(gate.as_ref()))
151            .collect();
152
153        compressed
154    }
155
156    /// Try low-rank approximation using SVD
157    fn try_low_rank_approximation(
158        &self,
159        matrix: &ArrayView2<Complex64>,
160    ) -> QuantRS2Result<Option<CompressedGate>> {
161        let (rows, cols) = matrix.dim();
162        if rows != cols || rows < 4 {
163            // Only try for larger gates
164            return Ok(None);
165        }
166
167        // Convert to SciRS2 matrix format
168        let real_part = Array2::from_shape_fn((rows, cols), |(i, j)| matrix[(i, j)].re);
169        let imag_part = Array2::from_shape_fn((rows, cols), |(i, j)| matrix[(i, j)].im);
170
171        // Try SVD-based compression
172        let target_rank = self.config.max_rank.unwrap_or(rows / 2);
173
174        // Apply SVD to real and imaginary parts separately
175        let (u_real, s_real, vt_real) = if self.config.use_randomized {
176            randomized_svd(&real_part.view(), target_rank, Some(10), Some(2))
177                .map_err(|e| QuantRS2Error::InvalidInput(format!("SVD failed: {}", e)))?
178        } else {
179            truncated_svd(&real_part.view(), target_rank)
180                .map_err(|e| QuantRS2Error::InvalidInput(format!("SVD failed: {}", e)))?
181        };
182
183        let (u_imag, s_imag, vt_imag) = if self.config.use_randomized {
184            randomized_svd(&imag_part.view(), target_rank, Some(10), Some(2))
185                .map_err(|e| QuantRS2Error::InvalidInput(format!("SVD failed: {}", e)))?
186        } else {
187            truncated_svd(&imag_part.view(), target_rank)
188                .map_err(|e| QuantRS2Error::InvalidInput(format!("SVD failed: {}", e)))?
189        };
190
191        // Find effective rank based on singular values
192        let effective_rank = self.find_effective_rank(&s_real, &s_imag)?;
193
194        if effective_rank >= rows * 3 / 4 {
195            // Not worth compressing
196            return Ok(None);
197        }
198
199        // Reconstruct low-rank approximation
200        let left = self.combine_complex(&u_real, &u_imag, effective_rank)?;
201        let right = self.combine_complex_with_singular(
202            &vt_real,
203            &vt_imag,
204            &s_real,
205            &s_imag,
206            effective_rank,
207        )?;
208
209        // Verify approximation quality
210        let approx = left.dot(&right.t());
211        if !matrices_approx_equal(&approx.view(), matrix, self.config.tolerance) {
212            return Ok(None);
213        }
214
215        Ok(Some(CompressedGate::LowRank {
216            left,
217            right,
218            rank: effective_rank,
219        }))
220    }
221
222    /// Try Tucker decomposition for multi-qubit gates
223    fn try_tucker_decomposition(
224        &self,
225        matrix: &ArrayView2<Complex64>,
226    ) -> QuantRS2Result<Option<CompressedGate>> {
227        // Tucker decomposition temporarily disabled due to scirs2-linalg compilation issues
228        // TODO: Re-enable when scirs2-linalg tensor_contraction feature is fixed
229        Ok(None)
230    }
231
232    /// Try to find parameterized representation
233    fn try_parameterized_compression(
234        &self,
235        gate: &dyn GateOp,
236    ) -> QuantRS2Result<Option<CompressedGate>> {
237        // This would identify if the gate can be represented
238        // as a parameterized gate (e.g., rotation gates)
239
240        // For now, we'll use global optimization to find parameters
241        let matrix_vec = gate.matrix()?;
242        let n = (matrix_vec.len() as f64).sqrt() as usize;
243
244        if n > 4 {
245            // Only try for single and two-qubit gates
246            return Ok(None);
247        }
248
249        // Convert vector to 2D array
250        let mut target_matrix = Array2::zeros((n, n));
251        for j in 0..n {
252            for i in 0..n {
253                target_matrix[(i, j)] = matrix_vec[j * n + i];
254            }
255        }
256
257        let gate_type = self.identify_gate_type(gate);
258
259        // Set up bounds for optimization
260        let dim = match gate_type.as_str() {
261            "rotation" => 3, // Three Euler angles
262            "phase" => 1,    // One phase parameter
263            _ => 6,          // General parameterization
264        };
265        let bounds = vec![(Some(-std::f64::consts::PI), Some(std::f64::consts::PI)); dim];
266
267        // Clone values needed for the closure to avoid borrowing self
268        let target_matrix_clone = target_matrix.clone();
269        let gate_type_clone = gate_type.clone();
270        let tolerance = self.config.tolerance;
271
272        // Create objective function
273        let objective = move |x: &ndarray::ArrayView1<f64>| -> f64 {
274            let params: Vec<f64> = x.iter().cloned().collect();
275
276            // Inline the evaluation logic since we can't access self
277            let gate_matrix = match gate_type_clone.as_str() {
278                "rotation" => Array2::eye(target_matrix_clone.dim().0), // Placeholder
279                "phase" => {
280                    let mut matrix = Array2::eye(target_matrix_clone.dim().0);
281                    if !params.is_empty() {
282                        let phase = Complex64::from_polar(1.0, params[0]);
283                        let n = matrix.dim().0;
284                        matrix[(n - 1, n - 1)] = phase;
285                    }
286                    matrix
287                }
288                _ => Array2::eye(target_matrix_clone.dim().0), // Placeholder
289            };
290
291            // Compute Frobenius norm of difference
292            let diff = &target_matrix_clone - &gate_matrix;
293            diff.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt()
294        };
295
296        // Use differential evolution for global optimization
297        let mut options = DifferentialEvolutionOptions::default();
298        options.popsize = 50;
299        options.maxiter = self.config.max_iterations;
300        options.tol = self.config.tolerance;
301
302        let de_bounds: Vec<(f64, f64)> = bounds
303            .into_iter()
304            .map(|(low, high)| {
305                (
306                    low.unwrap_or(-std::f64::consts::PI),
307                    high.unwrap_or(std::f64::consts::PI),
308                )
309            })
310            .collect();
311
312        let result =
313            differential_evolution(objective, de_bounds, Some(options), None).map_err(|e| {
314                QuantRS2Error::InvalidInput(format!("Parameter optimization failed: {:?}", e))
315            })?;
316
317        if result.fun > self.config.tolerance {
318            // Optimization didn't converge well enough
319            return Ok(None);
320        }
321
322        Ok(Some(CompressedGate::Parameterized {
323            gate_type,
324            parameters: result.x.to_vec(),
325            qubits: vec![], // Would need to extract from gate
326        }))
327    }
328
329    /// Merge adjacent gates that can be combined
330    fn merge_adjacent_gates(
331        &self,
332        gates: &[Box<dyn GateOp>],
333    ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
334        let mut merged = Vec::new();
335        let mut i = 0;
336
337        while i < gates.len() {
338            if i + 1 < gates.len() {
339                // Check if gates can be merged
340                if self.can_merge(gates[i].as_ref(), gates[i + 1].as_ref()) {
341                    // Merge the gates
342                    let combined =
343                        self.merge_two_gates(gates[i].as_ref(), gates[i + 1].as_ref())?;
344                    merged.push(combined);
345                    i += 2;
346                } else {
347                    merged.push(gates[i].clone_gate());
348                    i += 1;
349                }
350            } else {
351                merged.push(gates[i].clone_gate());
352                i += 1;
353            }
354        }
355
356        Ok(merged)
357    }
358
359    /// Check if two gates can be merged
360    fn can_merge(&self, gate1: &dyn GateOp, gate2: &dyn GateOp) -> bool {
361        // Gates can be merged if they:
362        // 1. Act on the same qubits
363        // 2. Are both unitary
364        // 3. Their product is simpler than the individual gates
365
366        // For now, simple check - same type gates on same qubits
367        gate1.name() == gate2.name()
368    }
369
370    /// Merge two gates into one
371    fn merge_two_gates(
372        &self,
373        gate1: &dyn GateOp,
374        gate2: &dyn GateOp,
375    ) -> QuantRS2Result<Box<dyn GateOp>> {
376        // Get matrices
377        let matrix1_vec = gate1.matrix()?;
378        let matrix2_vec = gate2.matrix()?;
379
380        // Convert to 2D arrays
381        let n = (matrix1_vec.len() as f64).sqrt() as usize;
382        let mut matrix1 = Array2::zeros((n, n));
383        let mut matrix2 = Array2::zeros((n, n));
384
385        for j in 0..n {
386            for i in 0..n {
387                matrix1[(i, j)] = matrix1_vec[j * n + i];
388                matrix2[(i, j)] = matrix2_vec[j * n + i];
389            }
390        }
391
392        // Matrix multiplication
393        let combined_matrix = matrix2.dot(&matrix1);
394
395        // Create a custom gate with the combined matrix
396        Ok(Box::new(CustomGate::new(
397            format!("{}_{}_merged", gate1.name(), gate2.name()),
398            combined_matrix,
399        )))
400    }
401
402    /// Compute hash of matrix for caching
403    fn compute_matrix_hash(&self, matrix: &ArrayView2<Complex64>) -> u64 {
404        use std::collections::hash_map::DefaultHasher;
405        use std::hash::{Hash, Hasher};
406
407        let mut hasher = DefaultHasher::new();
408        for elem in matrix.iter() {
409            // Hash real and imaginary parts
410            elem.re.to_bits().hash(&mut hasher);
411            elem.im.to_bits().hash(&mut hasher);
412        }
413        hasher.finish()
414    }
415
416    /// Find effective rank based on singular values
417    fn find_effective_rank(
418        &self,
419        s_real: &ndarray::Array1<f64>,
420        s_imag: &ndarray::Array1<f64>,
421    ) -> QuantRS2Result<usize> {
422        let max_singular = s_real
423            .iter()
424            .chain(s_imag.iter())
425            .map(|s| s.abs())
426            .fold(0.0, f64::max);
427
428        let threshold = max_singular * self.config.tolerance;
429
430        let rank = s_real
431            .iter()
432            .zip(s_imag.iter())
433            .take_while(|(sr, si)| sr.abs() > threshold || si.abs() > threshold)
434            .count();
435
436        Ok(rank.max(1))
437    }
438
439    /// Combine real and imaginary parts into complex matrix
440    fn combine_complex(
441        &self,
442        real: &Array2<f64>,
443        imag: &Array2<f64>,
444        rank: usize,
445    ) -> QuantRS2Result<Array2<Complex64>> {
446        let (rows, _) = real.dim();
447        let result = Array2::from_shape_fn((rows, rank), |(i, j)| {
448            Complex64::new(real[(i, j)], imag[(i, j)])
449        });
450        Ok(result)
451    }
452
453    /// Combine with singular values
454    fn combine_complex_with_singular(
455        &self,
456        vt_real: &Array2<f64>,
457        vt_imag: &Array2<f64>,
458        s_real: &ndarray::Array1<f64>,
459        s_imag: &ndarray::Array1<f64>,
460        rank: usize,
461    ) -> QuantRS2Result<Array2<Complex64>> {
462        let (_, cols) = vt_real.dim();
463        let result = Array2::from_shape_fn((rank, cols), |(i, j)| {
464            let s = Complex64::new(s_real[i], s_imag[i]);
465            let v = Complex64::new(vt_real[(i, j)], vt_imag[(i, j)]);
466            s * v
467        });
468        Ok(result)
469    }
470
471    /// Convert tensor data to complex matrix
472    fn tensor_to_complex_matrix(&self, tensor: &[f64]) -> QuantRS2Result<Array2<Complex64>> {
473        let size = (tensor.len() / 2) as f64;
474        let dim = size.sqrt() as usize;
475
476        let mut matrix = Array2::zeros((dim, dim));
477        for i in 0..dim {
478            for j in 0..dim {
479                let idx = (i * dim + j) * 2;
480                matrix[(i, j)] = Complex64::new(tensor[idx], tensor[idx + 1]);
481            }
482        }
483
484        Ok(matrix)
485    }
486
487    /// Convert ArrayD to complex matrix
488    fn tensor_to_complex_matrix_from_array(
489        &self,
490        tensor: &ndarray::ArrayD<f64>,
491    ) -> QuantRS2Result<Array2<Complex64>> {
492        // For now, just flatten the tensor and reshape to square matrix
493        let elements: Vec<f64> = tensor.iter().cloned().collect();
494        let size = elements.len() as f64;
495        let dim = size.sqrt() as usize;
496
497        if dim * dim != elements.len() {
498            // If not square, pad with zeros
499            let dim = (size.sqrt().ceil()) as usize;
500            let mut matrix = Array2::zeros((dim, dim));
501            for (idx, &val) in elements.iter().enumerate() {
502                let i = idx / dim;
503                let j = idx % dim;
504                if i < dim && j < dim {
505                    matrix[(i, j)] = Complex64::new(val, 0.0);
506                }
507            }
508            Ok(matrix)
509        } else {
510            let mut matrix = Array2::zeros((dim, dim));
511            for i in 0..dim {
512                for j in 0..dim {
513                    let idx = i * dim + j;
514                    matrix[(i, j)] = Complex64::new(elements[idx], 0.0);
515                }
516            }
517            Ok(matrix)
518        }
519    }
520
521    /// Identify gate type for parameterization
522    fn identify_gate_type(&self, gate: &dyn GateOp) -> String {
523        // Simple heuristic based on gate name
524        let name = gate.name();
525        if name.contains("rot") || name.contains("Rot") {
526            "rotation".to_string()
527        } else if name.contains("phase") || name.contains("Phase") {
528            "phase".to_string()
529        } else {
530            "general".to_string()
531        }
532    }
533
534    /// Evaluate gate parameters for optimization
535    fn evaluate_gate_parameters(
536        &self,
537        target: &Array2<Complex64>,
538        gate_type: &str,
539        params: &[f64],
540    ) -> f64 {
541        // Construct gate from parameters
542        let gate_matrix = match gate_type {
543            "rotation" => self.rotation_matrix_from_params(params, target.dim().0),
544            "phase" => self.phase_matrix_from_params(params, target.dim().0),
545            _ => self.general_matrix_from_params(params, target.dim().0),
546        };
547
548        // Compute Frobenius norm of difference
549        let diff = target - &gate_matrix;
550        diff.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt()
551    }
552
553    fn rotation_matrix_from_params(&self, params: &[f64], dim: usize) -> Array2<Complex64> {
554        // Construct rotation matrix from Euler angles
555        // This is a placeholder - would need proper implementation
556        Array2::eye(dim)
557    }
558
559    fn phase_matrix_from_params(&self, params: &[f64], dim: usize) -> Array2<Complex64> {
560        let mut matrix = Array2::eye(dim);
561        if !params.is_empty() {
562            let phase = Complex64::from_polar(1.0, params[0]);
563            matrix[(dim - 1, dim - 1)] = phase;
564        }
565        matrix
566    }
567
568    fn general_matrix_from_params(&self, _params: &[f64], dim: usize) -> Array2<Complex64> {
569        // General parameterization - would need proper implementation
570        Array2::eye(dim)
571    }
572}
573
574/// Custom gate implementation for compressed gates
575#[derive(Debug, Clone)]
576pub struct CustomGate {
577    name: String,
578    matrix: Array2<Complex64>,
579    qubits: Vec<QubitId>,
580}
581
582impl CustomGate {
583    pub fn new(name: String, matrix: Array2<Complex64>) -> Self {
584        // Determine number of qubits from matrix size
585        let n_qubits = (matrix.dim().0 as f64).log2() as usize;
586        let qubits = (0..n_qubits).map(|i| QubitId::new(i as u32)).collect();
587        Self {
588            name,
589            matrix,
590            qubits,
591        }
592    }
593
594    pub fn with_qubits(name: String, matrix: Array2<Complex64>, qubits: Vec<QubitId>) -> Self {
595        Self {
596            name,
597            matrix,
598            qubits,
599        }
600    }
601}
602
603impl GateOp for CustomGate {
604    fn name(&self) -> &'static str {
605        // Since we need 'static, we leak the string
606        Box::leak(self.name.clone().into_boxed_str())
607    }
608
609    fn qubits(&self) -> Vec<QubitId> {
610        self.qubits.clone()
611    }
612
613    fn matrix(&self) -> QuantRS2Result<Vec<Complex64>> {
614        // Flatten the matrix to a vector in column-major order
615        let mut result = Vec::with_capacity(self.matrix.len());
616        let (rows, cols) = self.matrix.dim();
617        for j in 0..cols {
618            for i in 0..rows {
619                result.push(self.matrix[(i, j)]);
620            }
621        }
622        Ok(result)
623    }
624
625    fn as_any(&self) -> &dyn Any {
626        self
627    }
628
629    fn clone_gate(&self) -> Box<dyn GateOp> {
630        Box::new(self.clone())
631    }
632}
633
634/// Compression statistics
635#[derive(Debug, Clone, Default)]
636pub struct CompressionStats {
637    pub original_gates: usize,
638    pub compressed_gates: usize,
639    pub low_rank_compressions: usize,
640    pub tucker_compressions: usize,
641    pub parameterized_compressions: usize,
642    pub compression_ratio: f64,
643    pub total_parameters_before: usize,
644    pub total_parameters_after: usize,
645}
646
647impl GateSequenceCompressor {
648    /// Get compression statistics
649    pub fn get_stats(
650        &self,
651        original: &[Box<dyn GateOp>],
652        compressed: &[CompressedGate],
653    ) -> CompressionStats {
654        let mut stats = CompressionStats::default();
655        stats.original_gates = original.len();
656        stats.compressed_gates = compressed.len();
657
658        for gate in compressed {
659            match gate {
660                CompressedGate::LowRank { left, right, .. } => {
661                    stats.low_rank_compressions += 1;
662                    stats.total_parameters_after += (left.len() + right.len()) * 2;
663                }
664                CompressedGate::Tucker { core, factors } => {
665                    stats.tucker_compressions += 1;
666                    stats.total_parameters_after += core.len() * 2;
667                    stats.total_parameters_after +=
668                        factors.iter().map(|f| f.len() * 2).sum::<usize>();
669                }
670                CompressedGate::Parameterized { parameters, .. } => {
671                    stats.parameterized_compressions += 1;
672                    stats.total_parameters_after += parameters.len();
673                }
674                CompressedGate::Original(gate) => {
675                    if let Ok(matrix_vec) = gate.matrix() {
676                        let size = (matrix_vec.len() as f64).sqrt() as usize;
677                        stats.total_parameters_after += size * size * 2;
678                    }
679                }
680            }
681        }
682
683        for gate in original {
684            if let Ok(matrix_vec) = gate.matrix() {
685                let size = (matrix_vec.len() as f64).sqrt() as usize;
686                stats.total_parameters_before += size * size * 2;
687            }
688        }
689
690        stats.compression_ratio = if stats.total_parameters_before > 0 {
691            stats.total_parameters_after as f64 / stats.total_parameters_before as f64
692        } else {
693            1.0
694        };
695
696        stats
697    }
698}
699
700#[cfg(test)]
701mod tests {
702    use super::*;
703    use crate::gate::single::{Hadamard, PauliX, PauliZ};
704    use crate::qubit::QubitId;
705
706    #[test]
707    fn test_gate_compression() {
708        let config = CompressionConfig::default();
709        let mut compressor = GateSequenceCompressor::new(config);
710
711        // Test single gate compression
712        let h_gate = Hadamard {
713            target: QubitId::new(0),
714        };
715        let compressed = compressor.compress_gate(&h_gate).unwrap();
716
717        match compressed {
718            CompressedGate::Original(_) => {
719                // H gate is already minimal, shouldn't compress
720            }
721            _ => panic!("H gate shouldn't be compressed"),
722        }
723    }
724
725    #[test]
726    fn test_sequence_compression() {
727        let config = CompressionConfig::default();
728        let mut compressor = GateSequenceCompressor::new(config);
729
730        // Create a sequence of gates
731        let gates: Vec<Box<dyn GateOp>> = vec![
732            Box::new(Hadamard {
733                target: QubitId::new(0),
734            }),
735            Box::new(PauliX {
736                target: QubitId::new(0),
737            }),
738            Box::new(Hadamard {
739                target: QubitId::new(0),
740            }),
741        ];
742
743        let compressed = compressor.compress_sequence(&gates).unwrap();
744        assert!(compressed.len() <= gates.len());
745    }
746
747    #[test]
748    fn test_compression_stats() {
749        let config = CompressionConfig::default();
750        let mut compressor = GateSequenceCompressor::new(config);
751
752        let gates: Vec<Box<dyn GateOp>> = vec![
753            Box::new(Hadamard {
754                target: QubitId::new(0),
755            }),
756            Box::new(PauliZ {
757                target: QubitId::new(0),
758            }),
759        ];
760
761        let compressed = compressor.compress_sequence(&gates).unwrap();
762        let stats = compressor.get_stats(&gates, &compressed);
763
764        assert_eq!(stats.original_gates, 2);
765        assert!(stats.compression_ratio <= 1.0);
766    }
767}