1use scirs2_core::ndarray::Array2;
8use scirs2_core::Complex64;
9use std::collections::{HashMap, HashSet};
10
11use crate::error::{Result, SimulatorError};
12use crate::sparse::{CSRMatrix, SparseMatrixBuilder};
13use quantrs2_core::gate::GateOp;
14use quantrs2_core::qubit::QubitId;
15
16#[derive(Debug)]
18struct SciRS2MatrixMultiplier;
19
20impl SciRS2MatrixMultiplier {
21 fn multiply_sparse(a: &CSRMatrix, b: &CSRMatrix) -> Result<CSRMatrix> {
22 if a.num_cols != b.num_rows {
24 return Err(SimulatorError::DimensionMismatch(format!(
25 "Cannot multiply {}x{} with {}x{}",
26 a.num_rows, a.num_cols, b.num_rows, b.num_cols
27 )));
28 }
29
30 let mut builder = SparseMatrixBuilder::new(a.num_rows, b.num_cols);
31
32 for i in 0..a.num_rows {
34 for k in a.row_ptr[i]..a.row_ptr[i + 1] {
35 let a_val = a.values[k];
36 let a_col = a.col_indices[k];
37
38 for j_idx in b.row_ptr[a_col]..b.row_ptr[a_col + 1] {
39 let b_val = b.values[j_idx];
40 let b_col = b.col_indices[j_idx];
41
42 builder.add(i, b_col, a_val * b_val);
43 }
44 }
45 }
46
47 Ok(builder.build())
48 }
49
50 #[must_use]
51 fn multiply_dense(a: &Array2<Complex64>, b: &Array2<Complex64>) -> Result<Array2<Complex64>> {
52 if a.ncols() != b.nrows() {
54 return Err(SimulatorError::DimensionMismatch(format!(
55 "Cannot multiply {}x{} with {}x{}",
56 a.nrows(),
57 a.ncols(),
58 b.nrows(),
59 b.ncols()
60 )));
61 }
62
63 Ok(a.dot(b))
64 }
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum FusionStrategy {
70 Aggressive,
72 Conservative,
74 DepthOptimized,
76 Custom,
78}
79
80#[derive(Debug, Clone)]
82pub struct GateGroup {
83 pub gate_indices: Vec<usize>,
85 pub qubits: Vec<QubitId>,
87 pub fusable: bool,
89 pub fusion_cost: f64,
91}
92
93pub struct GateFusion {
95 strategy: FusionStrategy,
97 max_fusion_qubits: usize,
99 min_fusion_gates: usize,
101 cost_threshold: f64,
103}
104
105impl GateFusion {
106 #[must_use]
108 pub const fn new(strategy: FusionStrategy) -> Self {
109 Self {
110 strategy,
111 max_fusion_qubits: 4,
112 min_fusion_gates: 2,
113 cost_threshold: 0.8,
114 }
115 }
116
117 #[must_use]
119 pub const fn with_params(
120 mut self,
121 max_qubits: usize,
122 min_gates: usize,
123 threshold: f64,
124 ) -> Self {
125 self.max_fusion_qubits = max_qubits;
126 self.min_fusion_gates = min_gates;
127 self.cost_threshold = threshold;
128 self
129 }
130
131 pub fn analyze_circuit(&self, gates: &[Box<dyn GateOp>]) -> Result<Vec<GateGroup>> {
133 let mut groups = Vec::new();
134 let mut processed = vec![false; gates.len()];
135
136 for i in 0..gates.len() {
137 if processed[i] {
138 continue;
139 }
140
141 let mut group = GateGroup {
143 gate_indices: vec![i],
144 qubits: gates[i].qubits().clone(),
145 fusable: false,
146 fusion_cost: 0.0,
147 };
148
149 for j in i + 1..gates.len() {
151 if processed[j] {
152 continue;
153 }
154
155 if self.can_fuse_with_group(&group, gates[j].as_ref()) {
157 group.gate_indices.push(j);
158
159 for qubit in gates[j].qubits() {
161 if !group.qubits.contains(&qubit) {
162 group.qubits.push(qubit);
163 }
164 }
165
166 if group.qubits.len() > self.max_fusion_qubits {
168 group.gate_indices.pop();
169 break;
170 }
171 } else if self.blocks_fusion(&group, gates[j].as_ref()) {
172 break;
174 }
175 }
176
177 if group.gate_indices.len() >= self.min_fusion_gates {
179 group.fusion_cost = self.compute_fusion_cost(&group, gates)?;
180 group.fusable = self.should_fuse(&group);
181
182 if group.fusable {
184 for &idx in &group.gate_indices {
185 processed[idx] = true;
186 }
187 }
188 }
189
190 groups.push(group);
191 }
192
193 Ok(groups)
194 }
195
196 fn can_fuse_with_group(&self, group: &GateGroup, gate: &dyn GateOp) -> bool {
198 let gate_qubits: HashSet<_> = gate.qubits().iter().copied().collect();
200 let group_qubits: HashSet<_> = group.qubits.iter().copied().collect();
201
202 match self.strategy {
203 FusionStrategy::Aggressive => {
204 !gate_qubits.is_disjoint(&group_qubits)
206 }
207 FusionStrategy::Conservative => {
208 gate_qubits.is_subset(&group_qubits) || group_qubits.is_subset(&gate_qubits)
210 }
211 FusionStrategy::DepthOptimized => {
212 let combined_qubits: HashSet<_> =
214 gate_qubits.union(&group_qubits).copied().collect();
215 combined_qubits.len() <= self.max_fusion_qubits
216 }
217 FusionStrategy::Custom => {
218 !gate_qubits.is_disjoint(&group_qubits)
220 }
221 }
222 }
223
224 fn blocks_fusion(&self, group: &GateGroup, gate: &dyn GateOp) -> bool {
226 let gate_qubits: HashSet<_> = gate.qubits().iter().copied().collect();
228 let group_qubits: HashSet<_> = group.qubits.iter().copied().collect();
229
230 let intersection = gate_qubits.intersection(&group_qubits).count();
231 intersection > 0 && intersection < group_qubits.len()
232 }
233
234 fn compute_fusion_cost(&self, group: &GateGroup, gates: &[Box<dyn GateOp>]) -> Result<f64> {
236 let num_qubits = group.qubits.len();
237 let num_gates = group.gate_indices.len();
238
239 let matrix_size_cost = f64::from(1 << num_qubits);
242
243 let ops_saved = (num_gates - 1) as f64;
245
246 let memory_cost = matrix_size_cost * matrix_size_cost * 16.0; let cost = matrix_size_cost / (ops_saved + 1.0) + memory_cost / 1e9;
251
252 Ok(cost)
253 }
254
255 fn should_fuse(&self, group: &GateGroup) -> bool {
257 match self.strategy {
258 FusionStrategy::Aggressive => true,
259 FusionStrategy::Conservative => group.fusion_cost < self.cost_threshold,
260 FusionStrategy::DepthOptimized => group.gate_indices.len() > 2,
261 FusionStrategy::Custom => group.fusion_cost < self.cost_threshold,
262 }
263 }
264
265 pub fn fuse_group(
267 &self,
268 group: &GateGroup,
269 gates: &[Box<dyn GateOp>],
270 num_qubits: usize,
271 ) -> Result<FusedGate> {
272 let group_qubits = &group.qubits;
273 let group_size = group_qubits.len();
274
275 let dim = 1 << group_size;
277 let mut fused_matrix = Array2::eye(dim);
278
279 for &gate_idx in &group.gate_indices {
281 let gate = &gates[gate_idx];
282 let gate_matrix = self.get_gate_matrix(gate.as_ref())?;
283
284 let gate_qubits = gate.qubits();
286 let qubit_map: HashMap<QubitId, usize> = group_qubits
287 .iter()
288 .enumerate()
289 .map(|(i, &q)| (q, i))
290 .collect();
291
292 let expanded =
294 self.expand_gate_matrix(&gate_matrix, &gate_qubits, &qubit_map, group_size)?;
295
296 fused_matrix = SciRS2MatrixMultiplier::multiply_dense(&expanded, &fused_matrix)?;
298 }
299
300 Ok(FusedGate {
301 matrix: fused_matrix,
302 qubits: group_qubits.clone(),
303 original_gates: group.gate_indices.clone(),
304 })
305 }
306
307 fn get_gate_matrix(&self, gate: &dyn GateOp) -> Result<Array2<Complex64>> {
316 let n = gate.qubits().len();
317 let dim = 1 << n;
318 let flat = gate.matrix().map_err(|e| {
319 SimulatorError::InvalidInput(format!(
320 "failed to get matrix for gate '{}': {e}",
321 gate.name()
322 ))
323 })?;
324 Array2::from_shape_vec((dim, dim), flat).map_err(|e| {
325 SimulatorError::InvalidInput(format!(
326 "gate '{}' returned a matrix with the wrong shape for {n} qubit(s): {e}",
327 gate.name()
328 ))
329 })
330 }
331
332 fn expand_gate_matrix(
334 &self,
335 gate_matrix: &Array2<Complex64>,
336 gate_qubits: &[QubitId],
337 qubit_map: &HashMap<QubitId, usize>,
338 total_qubits: usize,
339 ) -> Result<Array2<Complex64>> {
340 let dim = 1 << total_qubits;
341 let mut expanded = Array2::zeros((dim, dim));
342
343 let gate_positions: Vec<usize> = gate_qubits
345 .iter()
346 .map(|q| qubit_map.get(q).copied().unwrap_or(0))
347 .collect();
348
349 for i in 0..dim {
351 for j in 0..dim {
352 let mut gate_i = 0;
354 let mut gate_j = 0;
355 let mut other_bits_match = true;
356
357 for (k, &pos) in gate_positions.iter().enumerate() {
358 if (i >> pos) & 1 == 1 {
359 gate_i |= 1 << k;
360 }
361 if (j >> pos) & 1 == 1 {
362 gate_j |= 1 << k;
363 }
364 }
365
366 for k in 0..total_qubits {
368 if !gate_positions.contains(&k) && ((i >> k) & 1) != ((j >> k) & 1) {
369 other_bits_match = false;
370 break;
371 }
372 }
373
374 if other_bits_match {
375 expanded[[i, j]] = gate_matrix[[gate_i, gate_j]];
376 }
377 }
378 }
379
380 Ok(expanded)
381 }
382
383 pub fn optimize_circuit(
385 &self,
386 gates: Vec<Box<dyn GateOp>>,
387 num_qubits: usize,
388 ) -> Result<OptimizedCircuit> {
389 let groups = self.analyze_circuit(&gates)?;
390 let mut optimized_gates = Vec::new();
391 let mut fusion_map = HashMap::new();
392
393 let mut processed = vec![false; gates.len()];
394
395 for group in &groups {
396 if group.fusable && group.gate_indices.len() > 1 {
397 let fused = self.fuse_group(group, &gates, num_qubits)?;
399 let fused_idx = optimized_gates.len();
400 optimized_gates.push(OptimizedGate::Fused(fused));
401
402 for &gate_idx in &group.gate_indices {
404 fusion_map.insert(gate_idx, fused_idx);
405 processed[gate_idx] = true;
406 }
407 } else {
408 for &gate_idx in &group.gate_indices {
410 if !processed[gate_idx] {
411 optimized_gates.push(OptimizedGate::Original(gate_idx));
412 processed[gate_idx] = true;
413 }
414 }
415 }
416 }
417
418 for (i, &p) in processed.iter().enumerate() {
420 if !p {
421 optimized_gates.push(OptimizedGate::Original(i));
422 }
423 }
424
425 Ok(OptimizedCircuit {
426 gates: optimized_gates,
427 original_gates: gates,
428 fusion_map,
429 stats: self.compute_stats(&groups),
430 })
431 }
432
433 fn compute_stats(&self, groups: &[GateGroup]) -> FusionStats {
435 let total_groups = groups.len();
436 let fused_groups = groups.iter().filter(|g| g.fusable).count();
437 let total_gates: usize = groups.iter().map(|g| g.gate_indices.len()).sum();
438 let fused_gates: usize = groups
439 .iter()
440 .filter(|g| g.fusable)
441 .map(|g| g.gate_indices.len())
442 .sum();
443
444 FusionStats {
445 total_gates,
446 fused_gates,
447 fusion_ratio: fused_gates as f64 / total_gates.max(1) as f64,
448 groups_analyzed: total_groups,
449 groups_fused: fused_groups,
450 }
451 }
452}
453
454#[derive(Debug)]
456pub struct FusedGate {
457 pub matrix: Array2<Complex64>,
459 pub qubits: Vec<QubitId>,
461 pub original_gates: Vec<usize>,
463}
464
465impl FusedGate {
466 pub fn to_sparse(&self) -> Result<CSRMatrix> {
468 let mut builder = SparseMatrixBuilder::new(self.matrix.nrows(), self.matrix.ncols());
469
470 for ((i, j), &val) in self.matrix.indexed_iter() {
471 if val.norm() > 1e-12 {
472 builder.set_value(i, j, val);
473 }
474 }
475
476 Ok(builder.build())
477 }
478
479 #[must_use]
481 pub fn dimension(&self) -> usize {
482 self.matrix.nrows()
483 }
484}
485
486#[derive(Debug)]
488pub enum OptimizedGate {
489 Original(usize),
491 Fused(FusedGate),
493}
494
495#[derive(Debug)]
497pub struct OptimizedCircuit {
498 pub gates: Vec<OptimizedGate>,
500 pub original_gates: Vec<Box<dyn GateOp>>,
502 pub fusion_map: HashMap<usize, usize>,
504 pub stats: FusionStats,
506}
507
508impl OptimizedCircuit {
509 #[must_use]
511 pub fn gate_count(&self) -> usize {
512 self.gates.len()
513 }
514
515 #[must_use]
517 pub fn memory_usage(&self) -> usize {
518 self.gates
519 .iter()
520 .map(|g| match g {
521 OptimizedGate::Original(_) => 64, OptimizedGate::Fused(f) => f.dimension() * f.dimension() * 16,
523 })
524 .sum()
525 }
526}
527
528#[derive(Debug)]
530pub struct FusionStats {
531 pub total_gates: usize,
533 pub fused_gates: usize,
535 pub fusion_ratio: f64,
537 pub groups_analyzed: usize,
539 pub groups_fused: usize,
541}
542
543pub fn benchmark_fusion_strategies(gates: Vec<Box<dyn GateOp>>, num_qubits: usize) -> Result<()> {
545 println!("\nGate Fusion Benchmark");
546 println!("Original circuit: {} gates", gates.len());
547 println!("{:-<60}", "");
548
549 for strategy in [
550 FusionStrategy::Conservative,
551 FusionStrategy::Aggressive,
552 FusionStrategy::DepthOptimized,
553 ] {
554 let fusion = GateFusion::new(strategy);
555 let start = std::time::Instant::now();
556
557 let optimized = fusion.optimize_circuit(gates.clone(), num_qubits)?;
558 let elapsed = start.elapsed();
559
560 println!("\n{strategy:?} Strategy:");
561 println!(" Gates after fusion: {}", optimized.gate_count());
562 println!(
563 " Fusion ratio: {:.2}%",
564 optimized.stats.fusion_ratio * 100.0
565 );
566 println!(
567 " Groups fused: {}/{}",
568 optimized.stats.groups_fused, optimized.stats.groups_analyzed
569 );
570 println!(
571 " Memory usage: {:.2} MB",
572 optimized.memory_usage() as f64 / 1e6
573 );
574 println!(" Optimization time: {elapsed:?}");
575 }
576
577 Ok(())
578}
579
580#[cfg(test)]
581mod tests {
582 use super::*;
583 use quantrs2_core::gate::multi::CNOT;
584 use quantrs2_core::gate::single::{Hadamard, PauliX, RotationX};
585
586 #[test]
587 fn test_gate_group_creation() {
588 let group = GateGroup {
589 gate_indices: vec![0, 1, 2],
590 qubits: vec![QubitId::new(0), QubitId::new(1)],
591 fusable: true,
592 fusion_cost: 0.5,
593 };
594
595 assert_eq!(group.gate_indices.len(), 3);
596 assert_eq!(group.qubits.len(), 2);
597 }
598
599 #[test]
600 fn test_fusion_strategy() {
601 let fusion = GateFusion::new(FusionStrategy::Conservative);
602 assert_eq!(fusion.max_fusion_qubits, 4);
603 assert_eq!(fusion.min_fusion_gates, 2);
604 }
605
606 #[test]
607 fn test_sparse_matrix_multiplication() {
608 let mut builder1 = SparseMatrixBuilder::new(2, 2);
609 builder1.set_value(0, 0, Complex64::new(1.0, 0.0));
610 builder1.set_value(1, 1, Complex64::new(1.0, 0.0));
611 let m1 = builder1.build();
612
613 let mut builder2 = SparseMatrixBuilder::new(2, 2);
614 builder2.set_value(0, 1, Complex64::new(1.0, 0.0));
615 builder2.set_value(1, 0, Complex64::new(1.0, 0.0));
616 let m2 = builder2.build();
617
618 let result = SciRS2MatrixMultiplier::multiply_sparse(&m1, &m2)
619 .expect("sparse matrix multiplication should succeed");
620 assert_eq!(result.num_rows, 2);
621 assert_eq!(result.num_cols, 2);
622 }
623
624 #[test]
625 fn test_fused_gate() {
626 let matrix = Array2::eye(4);
627 let fused = FusedGate {
628 matrix,
629 qubits: vec![QubitId::new(0), QubitId::new(1)],
630 original_gates: vec![0, 1],
631 };
632
633 assert_eq!(fused.dimension(), 4);
634 let sparse = fused
635 .to_sparse()
636 .expect("conversion to sparse should succeed");
637 assert_eq!(sparse.num_rows, 4);
638 }
639
640 #[test]
641 fn test_fusion_cost() {
642 let fusion = GateFusion::new(FusionStrategy::Conservative);
643 let group = GateGroup {
644 gate_indices: vec![0, 1],
645 qubits: vec![QubitId::new(0), QubitId::new(1)],
646 fusable: false,
647 fusion_cost: 0.0,
648 };
649
650 let gates: Vec<Box<dyn GateOp>> = vec![
651 Box::new(Hadamard {
652 target: QubitId::new(0),
653 }),
654 Box::new(CNOT {
655 control: QubitId::new(0),
656 target: QubitId::new(1),
657 }),
658 ];
659
660 let cost = fusion
661 .compute_fusion_cost(&group, &gates)
662 .expect("fusion cost computation should succeed");
663 assert!(cost > 0.0);
664 }
665
666 #[test]
673 fn test_fuse_group_uses_real_rotation_gate_matrix() {
674 let fusion = GateFusion::new(FusionStrategy::Aggressive);
675 let theta = std::f64::consts::PI;
676 let rx_gate = RotationX {
677 target: QubitId::new(0),
678 theta,
679 };
680 let expected_matrix = rx_gate
681 .matrix()
682 .expect("RX matrix computation should succeed");
683
684 let group = GateGroup {
685 gate_indices: vec![0],
686 qubits: vec![QubitId::new(0)],
687 fusable: true,
688 fusion_cost: 0.0,
689 };
690 let gates: Vec<Box<dyn GateOp>> = vec![Box::new(rx_gate)];
691
692 let fused = fusion
693 .fuse_group(&group, &gates, 1)
694 .expect("fusing a single rotation gate should succeed");
695
696 assert!(
699 (fused.matrix[[0, 0]] - expected_matrix[0]).norm() < 1e-10,
700 "fused matrix[0,0] should match RX(pi)[0,0], got {:?} vs {:?}",
701 fused.matrix[[0, 0]],
702 expected_matrix[0]
703 );
704 assert!(
705 (fused.matrix[[0, 1]] - expected_matrix[1]).norm() < 1e-10,
706 "fused matrix[0,1] should match RX(pi)[0,1], got {:?} vs {:?}",
707 fused.matrix[[0, 1]],
708 expected_matrix[1]
709 );
710 assert!(
712 fused.matrix[[0, 1]].norm() > 0.9,
713 "RX(pi) off-diagonal should be near -i, got {:?}",
714 fused.matrix[[0, 1]]
715 );
716 }
717}