1use 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};
15use 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#[derive(Debug, Clone)]
28pub struct CompressionConfig {
29 pub tolerance: f64,
31 pub max_rank: Option<usize>,
33 pub use_randomized: bool,
35 pub max_iterations: usize,
37 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
53pub struct GateSequenceCompressor {
55 config: CompressionConfig,
56 compression_cache: HashMap<u64, CompressedGate>,
58}
59
60#[derive(Debug, Clone)]
62pub enum CompressedGate {
63 LowRank {
65 left: Array2<Complex64>,
66 right: Array2<Complex64>,
67 rank: usize,
68 },
69 Tucker {
71 core: Array2<Complex64>,
72 factors: Vec<Array2<Complex64>>,
73 },
74 Parameterized {
76 gate_type: String,
77 parameters: Vec<f64>,
78 qubits: Vec<QubitId>,
79 },
80 Original(Box<dyn GateOp>),
82}
83
84impl GateSequenceCompressor {
85 pub fn new(config: CompressionConfig) -> Self {
87 Self {
88 config,
89 compression_cache: HashMap::new(),
90 }
91 }
92
93 pub fn compress_gate(&mut self, gate: &dyn GateOp) -> QuantRS2Result<CompressedGate> {
95 let matrix_vec = gate.matrix()?;
96
97 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 if let Some(compressed) = self.compression_cache.get(&hash) {
111 return Ok(compressed.clone());
112 }
113
114 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 self.compression_cache.insert(hash, compressed.clone());
127
128 Ok(compressed)
129 }
130
131 pub fn compress_sequence(
133 &mut self,
134 gates: &[Box<dyn GateOp>],
135 ) -> QuantRS2Result<Vec<CompressedGate>> {
136 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 let merged = self.merge_adjacent_gates(gates)?;
146
147 let compressed: Result<Vec<_>, _> = merged
149 .iter()
150 .map(|gate| self.compress_gate(gate.as_ref()))
151 .collect();
152
153 compressed
154 }
155
156 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 return Ok(None);
165 }
166
167 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 let target_rank = self.config.max_rank.unwrap_or(rows / 2);
173
174 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 let effective_rank = self.find_effective_rank(&s_real, &s_imag)?;
193
194 if effective_rank >= rows * 3 / 4 {
195 return Ok(None);
197 }
198
199 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 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 fn try_tucker_decomposition(
224 &self,
225 matrix: &ArrayView2<Complex64>,
226 ) -> QuantRS2Result<Option<CompressedGate>> {
227 Ok(None)
230 }
231
232 fn try_parameterized_compression(
234 &self,
235 gate: &dyn GateOp,
236 ) -> QuantRS2Result<Option<CompressedGate>> {
237 let matrix_vec = gate.matrix()?;
242 let n = (matrix_vec.len() as f64).sqrt() as usize;
243
244 if n > 4 {
245 return Ok(None);
247 }
248
249 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 let dim = match gate_type.as_str() {
261 "rotation" => 3, "phase" => 1, _ => 6, };
265 let bounds = vec![(Some(-std::f64::consts::PI), Some(std::f64::consts::PI)); dim];
266
267 let target_matrix_clone = target_matrix.clone();
269 let gate_type_clone = gate_type.clone();
270 let tolerance = self.config.tolerance;
271
272 let objective = move |x: &ndarray::ArrayView1<f64>| -> f64 {
274 let params: Vec<f64> = x.iter().cloned().collect();
275
276 let gate_matrix = match gate_type_clone.as_str() {
278 "rotation" => Array2::eye(target_matrix_clone.dim().0), "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), };
290
291 let diff = &target_matrix_clone - &gate_matrix;
293 diff.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt()
294 };
295
296 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 return Ok(None);
320 }
321
322 Ok(Some(CompressedGate::Parameterized {
323 gate_type,
324 parameters: result.x.to_vec(),
325 qubits: vec![], }))
327 }
328
329 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 if self.can_merge(gates[i].as_ref(), gates[i + 1].as_ref()) {
341 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 fn can_merge(&self, gate1: &dyn GateOp, gate2: &dyn GateOp) -> bool {
361 gate1.name() == gate2.name()
368 }
369
370 fn merge_two_gates(
372 &self,
373 gate1: &dyn GateOp,
374 gate2: &dyn GateOp,
375 ) -> QuantRS2Result<Box<dyn GateOp>> {
376 let matrix1_vec = gate1.matrix()?;
378 let matrix2_vec = gate2.matrix()?;
379
380 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 let combined_matrix = matrix2.dot(&matrix1);
394
395 Ok(Box::new(CustomGate::new(
397 format!("{}_{}_merged", gate1.name(), gate2.name()),
398 combined_matrix,
399 )))
400 }
401
402 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 elem.re.to_bits().hash(&mut hasher);
411 elem.im.to_bits().hash(&mut hasher);
412 }
413 hasher.finish()
414 }
415
416 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 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 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 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 fn tensor_to_complex_matrix_from_array(
489 &self,
490 tensor: &ndarray::ArrayD<f64>,
491 ) -> QuantRS2Result<Array2<Complex64>> {
492 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 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 fn identify_gate_type(&self, gate: &dyn GateOp) -> String {
523 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 fn evaluate_gate_parameters(
536 &self,
537 target: &Array2<Complex64>,
538 gate_type: &str,
539 params: &[f64],
540 ) -> f64 {
541 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 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 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 Array2::eye(dim)
571 }
572}
573
574#[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 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 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 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#[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 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 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 }
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 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}