1use crate::builder::Circuit;
6use crate::scirs2_integration::{AnalyzerConfig, SciRS2CircuitAnalyzer};
7use quantrs2_core::{
8 error::{QuantRS2Error, QuantRS2Result},
9 gate::{
10 multi::{CRX, CRY, CRZ},
11 single::{RotationX, RotationY, RotationZ},
12 GateOp,
13 },
14 qubit::QubitId,
15};
16use scirs2_core::ndarray::{array, Array2, ArrayView2};
17use scirs2_core::Complex64;
18use serde::{Deserialize, Serialize};
19use std::collections::HashMap;
20
21use super::functions::SCIRS2_DEFAULT_TOLERANCE;
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct NumericalAnalysis {
26 pub condition_number: Option<f64>,
28 pub numerical_rank: Option<usize>,
30 pub frobenius_norm: f64,
32 pub spectral_norm: Option<f64>,
34 pub adaptive_tolerance: f64,
36 pub stability_indicator: f64,
38}
39pub struct EquivalenceChecker {
41 options: EquivalenceOptions,
42 scirs2_analyzer: Option<SciRS2CircuitAnalyzer>,
43 numerical_cache: HashMap<String, NumericalAnalysis>,
44}
45impl EquivalenceChecker {
46 #[must_use]
48 pub fn new(options: EquivalenceOptions) -> Self {
49 let scirs2_analyzer = if options.enable_graph_comparison
50 || options.enable_statistical_analysis
51 || options.enable_stability_analysis
52 {
53 Some(SciRS2CircuitAnalyzer::new())
54 } else {
55 None
56 };
57 Self {
58 options,
59 scirs2_analyzer,
60 numerical_cache: HashMap::new(),
61 }
62 }
63 #[must_use]
65 pub fn default() -> Self {
66 Self::new(EquivalenceOptions::default())
67 }
68 #[must_use]
70 pub fn with_scirs2_config(config: AnalyzerConfig) -> Self {
71 let scirs2_analyzer = Some(SciRS2CircuitAnalyzer::with_config(config.clone()));
72 Self {
73 options: EquivalenceOptions {
74 scirs2_config: Some(config),
75 enable_graph_comparison: true,
76 ..Default::default()
77 },
78 scirs2_analyzer,
79 numerical_cache: HashMap::new(),
80 }
81 }
82 pub fn check_equivalence<const N: usize>(
84 &mut self,
85 circuit1: &Circuit<N>,
86 circuit2: &Circuit<N>,
87 ) -> QuantRS2Result<EquivalenceResult> {
88 if self.options.enable_graph_comparison {
89 if let Ok(result) = self.check_scirs2_graph_equivalence(circuit1, circuit2) {
90 if result.equivalent {
91 return Ok(result);
92 }
93 }
94 }
95 if let Ok(result) = self.check_structural_equivalence(circuit1, circuit2) {
96 if result.equivalent {
97 return Ok(result);
98 }
99 }
100 if (self.options.enable_adaptive_tolerance || self.options.enable_statistical_analysis)
101 && N <= self.options.max_unitary_qubits
102 {
103 return self.check_scirs2_numerical_equivalence(circuit1, circuit2);
104 }
105 if N <= self.options.max_unitary_qubits {
106 return self.check_unitary_equivalence(circuit1, circuit2);
107 }
108 self.check_state_vector_equivalence(circuit1, circuit2)
109 }
110 pub fn check_scirs2_numerical_equivalence<const N: usize>(
112 &mut self,
113 circuit1: &Circuit<N>,
114 circuit2: &Circuit<N>,
115 ) -> QuantRS2Result<EquivalenceResult> {
116 if N > self.options.max_unitary_qubits {
117 return Err(QuantRS2Error::InvalidInput(format!(
118 "Circuit too large for SciRS2 numerical analysis: {} qubits (max: {})",
119 N, self.options.max_unitary_qubits
120 )));
121 }
122 let unitary1 = self.get_circuit_unitary(circuit1)?;
123 let unitary2 = self.get_circuit_unitary(circuit2)?;
124 let numerical_analysis = self.perform_scirs2_numerical_analysis(&unitary1, &unitary2)?;
125 let adaptive_tolerance = self.calculate_adaptive_tolerance::<N>(N, &numerical_analysis);
126 let (equivalent, max_diff, confidence_score, error_bounds) =
127 self.scirs2_unitaries_equal(&unitary1, &unitary2, adaptive_tolerance)?;
128 let statistical_significance = if self.options.enable_statistical_analysis {
129 Some(self.calculate_statistical_significance(&unitary1, &unitary2, max_diff)?)
130 } else {
131 None
132 };
133 Ok(EquivalenceResult {
134 equivalent,
135 check_type: EquivalenceType::SciRS2NumericalEquivalence,
136 max_difference: Some(max_diff),
137 details: format!(
138 "SciRS2 numerical analysis: tolerance={:.2e}, confidence={:.3}, condition_number={:.2e}",
139 adaptive_tolerance, confidence_score, numerical_analysis.condition_number
140 .unwrap_or(0.0)
141 ),
142 numerical_analysis: Some(numerical_analysis),
143 confidence_score,
144 statistical_significance,
145 error_bounds: Some(error_bounds),
146 })
147 }
148 pub fn check_scirs2_graph_equivalence<const N: usize>(
150 &mut self,
151 circuit1: &Circuit<N>,
152 circuit2: &Circuit<N>,
153 ) -> QuantRS2Result<EquivalenceResult> {
154 let analyzer = self.scirs2_analyzer.as_mut().ok_or_else(|| {
155 QuantRS2Error::InvalidInput("SciRS2 analyzer not initialized".to_string())
156 })?;
157 let graph1 = analyzer.circuit_to_scirs2_graph(circuit1)?;
158 let graph2 = analyzer.circuit_to_scirs2_graph(circuit2)?;
159 let (equivalent, similarity_score, graph_details) =
160 self.compare_scirs2_graphs(&graph1, &graph2)?;
161 Ok(EquivalenceResult {
162 equivalent,
163 check_type: EquivalenceType::SciRS2GraphEquivalence,
164 max_difference: Some(1.0 - similarity_score),
165 details: graph_details,
166 numerical_analysis: None,
167 confidence_score: similarity_score,
168 statistical_significance: None,
169 error_bounds: None,
170 })
171 }
172 pub fn check_structural_equivalence<const N: usize>(
174 &self,
175 circuit1: &Circuit<N>,
176 circuit2: &Circuit<N>,
177 ) -> QuantRS2Result<EquivalenceResult> {
178 if circuit1.num_gates() != circuit2.num_gates() {
179 return Ok(EquivalenceResult {
180 equivalent: false,
181 check_type: EquivalenceType::StructuralEquivalence,
182 max_difference: None,
183 details: format!(
184 "Different number of gates: {} vs {}",
185 circuit1.num_gates(),
186 circuit2.num_gates()
187 ),
188 numerical_analysis: None,
189 confidence_score: 0.0,
190 statistical_significance: None,
191 error_bounds: None,
192 });
193 }
194 let gates1 = circuit1.gates();
195 let gates2 = circuit2.gates();
196 for (i, (gate1, gate2)) in gates1.iter().zip(gates2.iter()).enumerate() {
197 if !self.gates_equal(gate1.as_ref(), gate2.as_ref()) {
198 return Ok(EquivalenceResult {
199 equivalent: false,
200 check_type: EquivalenceType::StructuralEquivalence,
201 max_difference: None,
202 details: format!(
203 "Gates differ at position {}: {} vs {}",
204 i,
205 gate1.name(),
206 gate2.name()
207 ),
208 numerical_analysis: None,
209 confidence_score: 0.0,
210 statistical_significance: None,
211 error_bounds: None,
212 });
213 }
214 }
215 Ok(EquivalenceResult {
216 equivalent: true,
217 check_type: EquivalenceType::StructuralEquivalence,
218 max_difference: Some(0.0),
219 details: "Circuits are structurally identical".to_string(),
220 numerical_analysis: None,
221 confidence_score: 1.0,
222 statistical_significance: None,
223 error_bounds: None,
224 })
225 }
226 fn gates_equal(&self, gate1: &dyn GateOp, gate2: &dyn GateOp) -> bool {
231 if gate1.name() != gate2.name() {
232 return false;
233 }
234 let qubits1 = gate1.qubits();
235 let qubits2 = gate2.qubits();
236 if qubits1.len() != qubits2.len() {
237 return false;
238 }
239 for (q1, q2) in qubits1.iter().zip(qubits2.iter()) {
240 if q1 != q2 {
241 return false;
242 }
243 }
244 if !self.check_gate_parameters(gate1, gate2) {
245 return false;
246 }
247 true
248 }
249 fn check_gate_parameters(&self, gate1: &dyn GateOp, gate2: &dyn GateOp) -> bool {
251 if let Some(rx1) = gate1.as_any().downcast_ref::<RotationX>() {
252 if let Some(rx2) = gate2.as_any().downcast_ref::<RotationX>() {
253 return (rx1.theta - rx2.theta).abs() < self.options.tolerance;
254 }
255 }
256 if let Some(ry1) = gate1.as_any().downcast_ref::<RotationY>() {
257 if let Some(ry2) = gate2.as_any().downcast_ref::<RotationY>() {
258 return (ry1.theta - ry2.theta).abs() < self.options.tolerance;
259 }
260 }
261 if let Some(rz1) = gate1.as_any().downcast_ref::<RotationZ>() {
262 if let Some(rz2) = gate2.as_any().downcast_ref::<RotationZ>() {
263 return (rz1.theta - rz2.theta).abs() < self.options.tolerance;
264 }
265 }
266 if let Some(crx1) = gate1.as_any().downcast_ref::<CRX>() {
267 if let Some(crx2) = gate2.as_any().downcast_ref::<CRX>() {
268 return (crx1.theta - crx2.theta).abs() < self.options.tolerance;
269 }
270 }
271 if let Some(cry1) = gate1.as_any().downcast_ref::<CRY>() {
272 if let Some(cry2) = gate2.as_any().downcast_ref::<CRY>() {
273 return (cry1.theta - cry2.theta).abs() < self.options.tolerance;
274 }
275 }
276 if let Some(crz1) = gate1.as_any().downcast_ref::<CRZ>() {
277 if let Some(crz2) = gate2.as_any().downcast_ref::<CRZ>() {
278 return (crz1.theta - crz2.theta).abs() < self.options.tolerance;
279 }
280 }
281 true
282 }
283 pub fn check_unitary_equivalence<const N: usize>(
285 &self,
286 circuit1: &Circuit<N>,
287 circuit2: &Circuit<N>,
288 ) -> QuantRS2Result<EquivalenceResult> {
289 if N > self.options.max_unitary_qubits {
290 return Err(QuantRS2Error::InvalidInput(format!(
291 "Circuit too large for unitary construction: {} qubits (max: {})",
292 N, self.options.max_unitary_qubits
293 )));
294 }
295 let unitary1 = self.get_circuit_unitary(circuit1)?;
296 let unitary2 = self.get_circuit_unitary(circuit2)?;
297 let (equivalent, max_diff) = if self.options.ignore_global_phase {
298 self.unitaries_equal_up_to_phase(&unitary1, &unitary2)
299 } else {
300 self.unitaries_equal(&unitary1, &unitary2)
301 };
302 Ok(EquivalenceResult {
303 equivalent,
304 check_type: if self.options.ignore_global_phase {
305 EquivalenceType::GlobalPhaseEquivalence
306 } else {
307 EquivalenceType::UnitaryEquivalence
308 },
309 max_difference: Some(max_diff),
310 details: if equivalent {
311 "Unitaries are equivalent".to_string()
312 } else {
313 format!("Maximum unitary difference: {max_diff:.2e}")
314 },
315 numerical_analysis: None,
316 confidence_score: if equivalent {
317 1.0 - (max_diff / self.options.tolerance)
318 } else {
319 0.0
320 },
321 statistical_significance: None,
322 error_bounds: None,
323 })
324 }
325 fn get_circuit_unitary<const N: usize>(
327 &self,
328 circuit: &Circuit<N>,
329 ) -> QuantRS2Result<Array2<Complex64>> {
330 let dim = 1 << N;
331 let mut unitary = Array2::eye(dim);
332 for gate in circuit.gates() {
333 self.apply_gate_to_unitary(&mut unitary, gate.as_ref(), N)?;
334 }
335 Ok(unitary)
336 }
337 fn apply_gate_to_unitary(
339 &self,
340 unitary: &mut Array2<Complex64>,
341 gate: &dyn GateOp,
342 num_qubits: usize,
343 ) -> QuantRS2Result<()> {
344 let gate_matrix = self.get_gate_matrix(gate)?;
345 let qubits = gate.qubits();
346 match qubits.len() {
347 1 => {
348 let qubit_idx = qubits[0].id() as usize;
349 self.apply_single_qubit_gate(unitary, &gate_matrix, qubit_idx, num_qubits)?;
350 }
351 2 => {
352 let control_idx = qubits[0].id() as usize;
353 let target_idx = qubits[1].id() as usize;
354 self.apply_two_qubit_gate(
355 unitary,
356 &gate_matrix,
357 control_idx,
358 target_idx,
359 num_qubits,
360 )?;
361 }
362 _ => {
363 return Err(QuantRS2Error::UnsupportedOperation(format!(
364 "Gates with {} qubits not yet supported",
365 qubits.len()
366 )));
367 }
368 }
369 Ok(())
370 }
371 fn get_gate_matrix(&self, gate: &dyn GateOp) -> QuantRS2Result<Array2<Complex64>> {
373 let c0 = Complex64::new(0.0, 0.0);
374 let c1 = Complex64::new(1.0, 0.0);
375 let ci = Complex64::new(0.0, 1.0);
376 match gate.name() {
377 "H" => {
378 let sqrt2_inv = 1.0 / std::f64::consts::SQRT_2;
379 Ok(array![
380 [c1 * sqrt2_inv, c1 * sqrt2_inv],
381 [c1 * sqrt2_inv, -c1 * sqrt2_inv]
382 ])
383 }
384 "X" => Ok(array![[c0, c1], [c1, c0]]),
385 "Y" => Ok(array![[c0, -ci], [ci, c0]]),
386 "Z" => Ok(array![[c1, c0], [c0, -c1]]),
387 "S" => Ok(array![[c1, c0], [c0, ci]]),
388 "T" => Ok(array![
389 [c1, c0],
390 [
391 c0,
392 Complex64::new(
393 1.0 / std::f64::consts::SQRT_2,
394 1.0 / std::f64::consts::SQRT_2
395 )
396 ]
397 ]),
398 "CNOT" | "CX" => Ok(array![
399 [c1, c0, c0, c0],
400 [c0, c1, c0, c0],
401 [c0, c0, c0, c1],
402 [c0, c0, c1, c0]
403 ]),
404 "CZ" => Ok(array![
405 [c1, c0, c0, c0],
406 [c0, c1, c0, c0],
407 [c0, c0, c1, c0],
408 [c0, c0, c0, -c1]
409 ]),
410 "SWAP" => Ok(array![
411 [c1, c0, c0, c0],
412 [c0, c0, c1, c0],
413 [c0, c1, c0, c0],
414 [c0, c0, c0, c1]
415 ]),
416 _ => Err(QuantRS2Error::UnsupportedOperation(format!(
417 "Gate '{}' matrix not yet implemented",
418 gate.name()
419 ))),
420 }
421 }
422 fn apply_single_qubit_gate(
424 &self,
425 unitary: &mut Array2<Complex64>,
426 gate_matrix: &Array2<Complex64>,
427 qubit_idx: usize,
428 num_qubits: usize,
429 ) -> QuantRS2Result<()> {
430 let dim = 1 << num_qubits;
431 let mut new_unitary = Array2::zeros((dim, dim));
432 for col in 0..dim {
433 for row in 0..dim {
434 let mut sum = Complex64::new(0.0, 0.0);
435 let row_bit = (row >> qubit_idx) & 1;
436 let col_bit = (col >> qubit_idx) & 1;
437 for k in 0..dim {
438 let k_bit = (k >> qubit_idx) & 1;
439 if (row ^ k) == ((row_bit ^ k_bit) << qubit_idx) {
440 sum += gate_matrix[[row_bit, k_bit]] * unitary[[k, col]];
441 }
442 }
443 new_unitary[[row, col]] = sum;
444 }
445 }
446 *unitary = new_unitary;
447 Ok(())
448 }
449 fn apply_two_qubit_gate(
451 &self,
452 unitary: &mut Array2<Complex64>,
453 gate_matrix: &Array2<Complex64>,
454 qubit1_idx: usize,
455 qubit2_idx: usize,
456 num_qubits: usize,
457 ) -> QuantRS2Result<()> {
458 let dim = 1 << num_qubits;
459 let mut new_unitary = Array2::zeros((dim, dim));
460 for col in 0..dim {
461 for row in 0..dim {
462 let mut sum = Complex64::new(0.0, 0.0);
463 let row_q1 = (row >> qubit1_idx) & 1;
464 let row_q2 = (row >> qubit2_idx) & 1;
465 let row_gate_idx = (row_q1 << 1) | row_q2;
466 let col_q1 = (col >> qubit1_idx) & 1;
467 let col_q2 = (col >> qubit2_idx) & 1;
468 for k in 0..dim {
469 let k_q1 = (k >> qubit1_idx) & 1;
470 let k_q2 = (k >> qubit2_idx) & 1;
471 let k_gate_idx = (k_q1 << 1) | k_q2;
472 let diff = row ^ k;
473 let expected_diff =
474 ((row_q1 ^ k_q1) << qubit1_idx) | ((row_q2 ^ k_q2) << qubit2_idx);
475 if diff == expected_diff {
476 sum += gate_matrix[[row_gate_idx, k_gate_idx]] * unitary[[k, col]];
477 }
478 }
479 new_unitary[[row, col]] = sum;
480 }
481 }
482 *unitary = new_unitary;
483 Ok(())
484 }
485 fn unitaries_equal(&self, u1: &Array2<Complex64>, u2: &Array2<Complex64>) -> (bool, f64) {
487 if u1.shape() != u2.shape() {
488 return (false, f64::INFINITY);
489 }
490 let mut max_diff = 0.0;
491 for (a, b) in u1.iter().zip(u2.iter()) {
492 let diff = (a - b).norm();
493 if diff > max_diff {
494 max_diff = diff;
495 }
496 if diff > self.options.tolerance {
497 return (false, max_diff);
498 }
499 }
500 (true, max_diff)
501 }
502 fn unitaries_equal_up_to_phase(
504 &self,
505 u1: &Array2<Complex64>,
506 u2: &Array2<Complex64>,
507 ) -> (bool, f64) {
508 if u1.shape() != u2.shape() {
509 return (false, f64::INFINITY);
510 }
511 let mut phase = None;
512 for (a, b) in u1.iter().zip(u2.iter()) {
513 if a.norm() > self.options.tolerance && b.norm() > self.options.tolerance {
514 phase = Some(b / a);
515 break;
516 }
517 }
518 let phase = match phase {
519 Some(p) => p,
520 None => return (false, f64::INFINITY),
521 };
522 let mut max_diff = 0.0;
523 for (a, b) in u1.iter().zip(u2.iter()) {
524 let adjusted = a * phase;
525 let diff = (adjusted - b).norm();
526 if diff > max_diff {
527 max_diff = diff;
528 }
529 if diff > self.options.tolerance {
530 return (false, max_diff);
531 }
532 }
533 (true, max_diff)
534 }
535 pub fn check_state_vector_equivalence<const N: usize>(
537 &self,
538 circuit1: &Circuit<N>,
539 circuit2: &Circuit<N>,
540 ) -> QuantRS2Result<EquivalenceResult> {
541 let mut max_diff = 0.0;
542 let num_states = if self.options.check_all_states {
543 1 << N
544 } else {
545 std::cmp::min(1 << N, 100)
546 };
547 for state_idx in 0..num_states {
548 let state1 = self.apply_circuit_to_state(circuit1, state_idx, N)?;
549 let state2 = self.apply_circuit_to_state(circuit2, state_idx, N)?;
550 let (equal, diff) = if self.options.ignore_global_phase {
551 self.states_equal_up_to_phase(&state1, &state2)
552 } else {
553 self.states_equal(&state1, &state2)
554 };
555 if diff > max_diff {
556 max_diff = diff;
557 }
558 if !equal {
559 return Ok(EquivalenceResult {
560 equivalent: false,
561 check_type: EquivalenceType::StateVectorEquivalence,
562 max_difference: Some(max_diff),
563 details: format!(
564 "States differ for input |{state_idx:0b}>: max difference {max_diff:.2e}"
565 ),
566 numerical_analysis: None,
567 confidence_score: 0.0,
568 statistical_significance: None,
569 error_bounds: None,
570 });
571 }
572 }
573 Ok(EquivalenceResult {
574 equivalent: true,
575 check_type: EquivalenceType::StateVectorEquivalence,
576 max_difference: Some(max_diff),
577 details: format!("Checked {num_states} computational basis states"),
578 numerical_analysis: None,
579 confidence_score: 1.0 - (max_diff / self.options.tolerance).min(1.0),
580 statistical_significance: None,
581 error_bounds: None,
582 })
583 }
584 fn apply_circuit_to_state<const N: usize>(
586 &self,
587 circuit: &Circuit<N>,
588 state_idx: usize,
589 num_qubits: usize,
590 ) -> QuantRS2Result<Vec<Complex64>> {
591 let dim = 1 << num_qubits;
592 let mut state = vec![Complex64::new(0.0, 0.0); dim];
593 state[state_idx] = Complex64::new(1.0, 0.0);
594 for gate in circuit.gates() {
595 self.apply_gate_to_state(&mut state, gate.as_ref(), num_qubits)?;
596 }
597 Ok(state)
598 }
599 fn apply_gate_to_state(
601 &self,
602 state: &mut Vec<Complex64>,
603 gate: &dyn GateOp,
604 num_qubits: usize,
605 ) -> QuantRS2Result<()> {
606 let gate_matrix = self.get_gate_matrix(gate)?;
607 let qubits = gate.qubits();
608 match qubits.len() {
609 1 => {
610 let qubit_idx = qubits[0].id() as usize;
611 self.apply_single_qubit_gate_to_state(state, &gate_matrix, qubit_idx, num_qubits)?;
612 }
613 2 => {
614 let control_idx = qubits[0].id() as usize;
615 let target_idx = qubits[1].id() as usize;
616 self.apply_two_qubit_gate_to_state(
617 state,
618 &gate_matrix,
619 control_idx,
620 target_idx,
621 num_qubits,
622 )?;
623 }
624 _ => {
625 return Err(QuantRS2Error::UnsupportedOperation(format!(
626 "Gates with {} qubits not yet supported",
627 qubits.len()
628 )));
629 }
630 }
631 Ok(())
632 }
633 fn apply_single_qubit_gate_to_state(
635 &self,
636 state: &mut Vec<Complex64>,
637 gate_matrix: &Array2<Complex64>,
638 qubit_idx: usize,
639 num_qubits: usize,
640 ) -> QuantRS2Result<()> {
641 let dim = 1 << num_qubits;
642 let mut new_state = vec![Complex64::new(0.0, 0.0); dim];
643 for i in 0..dim {
644 let bit = (i >> qubit_idx) & 1;
645 for j in 0..2 {
646 let other_idx = i ^ ((bit ^ j) << qubit_idx);
647 new_state[i] += gate_matrix[[bit, j]] * state[other_idx];
648 }
649 }
650 *state = new_state;
651 Ok(())
652 }
653 fn apply_two_qubit_gate_to_state(
655 &self,
656 state: &mut Vec<Complex64>,
657 gate_matrix: &Array2<Complex64>,
658 qubit1_idx: usize,
659 qubit2_idx: usize,
660 num_qubits: usize,
661 ) -> QuantRS2Result<()> {
662 let dim = 1 << num_qubits;
663 let mut new_state = vec![Complex64::new(0.0, 0.0); dim];
664 for i in 0..dim {
665 let bit1 = (i >> qubit1_idx) & 1;
666 let bit2 = (i >> qubit2_idx) & 1;
667 let gate_row = (bit1 << 1) | bit2;
668 for gate_col in 0..4 {
669 let new_bit1 = (gate_col >> 1) & 1;
670 let new_bit2 = gate_col & 1;
671 let j = i ^ ((bit1 ^ new_bit1) << qubit1_idx) ^ ((bit2 ^ new_bit2) << qubit2_idx);
672 new_state[i] += gate_matrix[[gate_row, gate_col]] * state[j];
673 }
674 }
675 *state = new_state;
676 Ok(())
677 }
678 fn states_equal(&self, s1: &[Complex64], s2: &[Complex64]) -> (bool, f64) {
680 if s1.len() != s2.len() {
681 return (false, f64::INFINITY);
682 }
683 let mut max_diff = 0.0;
684 for (a, b) in s1.iter().zip(s2.iter()) {
685 let diff = (a - b).norm();
686 if diff > max_diff {
687 max_diff = diff;
688 }
689 if diff > self.options.tolerance {
690 return (false, max_diff);
691 }
692 }
693 (true, max_diff)
694 }
695 fn states_equal_up_to_phase(&self, s1: &[Complex64], s2: &[Complex64]) -> (bool, f64) {
697 if s1.len() != s2.len() {
698 return (false, f64::INFINITY);
699 }
700 let mut phase = None;
701 for (a, b) in s1.iter().zip(s2.iter()) {
702 if a.norm() > self.options.tolerance && b.norm() > self.options.tolerance {
703 phase = Some(b / a);
704 break;
705 }
706 }
707 let phase = match phase {
708 Some(p) => p,
709 None => return (false, f64::INFINITY),
710 };
711 let mut max_diff = 0.0;
712 for (a, b) in s1.iter().zip(s2.iter()) {
713 let adjusted = a * phase;
714 let diff = (adjusted - b).norm();
715 if diff > max_diff {
716 max_diff = diff;
717 }
718 if diff > self.options.tolerance {
719 return (false, max_diff);
720 }
721 }
722 (true, max_diff)
723 }
724 pub fn check_probabilistic_equivalence<const N: usize>(
726 &self,
727 circuit1: &Circuit<N>,
728 circuit2: &Circuit<N>,
729 ) -> QuantRS2Result<EquivalenceResult> {
730 let mut max_diff = 0.0;
731 for state_idx in 0..(1 << N) {
732 let probs1 = self.get_measurement_probabilities(circuit1, state_idx, N)?;
733 let probs2 = self.get_measurement_probabilities(circuit2, state_idx, N)?;
734 for (p1, p2) in probs1.iter().zip(probs2.iter()) {
735 let diff = (p1 - p2).abs();
736 if diff > max_diff {
737 max_diff = diff;
738 }
739 if diff > self.options.tolerance {
740 return Ok(EquivalenceResult {
741 equivalent: false,
742 check_type: EquivalenceType::ProbabilisticEquivalence,
743 max_difference: Some(max_diff),
744 details: format!(
745 "Measurement probabilities differ for input |{state_idx:0b}>"
746 ),
747 numerical_analysis: None,
748 confidence_score: 0.0,
749 statistical_significance: None,
750 error_bounds: None,
751 });
752 }
753 }
754 }
755 Ok(EquivalenceResult {
756 equivalent: true,
757 check_type: EquivalenceType::ProbabilisticEquivalence,
758 max_difference: Some(max_diff),
759 details: "Measurement probabilities match for all inputs".to_string(),
760 numerical_analysis: None,
761 confidence_score: 1.0 - (max_diff / self.options.tolerance).min(1.0),
762 statistical_significance: None,
763 error_bounds: None,
764 })
765 }
766 fn get_measurement_probabilities<const N: usize>(
768 &self,
769 circuit: &Circuit<N>,
770 state_idx: usize,
771 num_qubits: usize,
772 ) -> QuantRS2Result<Vec<f64>> {
773 let final_state = self.apply_circuit_to_state(circuit, state_idx, num_qubits)?;
774 let probs: Vec<f64> = final_state
775 .iter()
776 .map(scirs2_core::Complex::norm_sqr)
777 .collect();
778 Ok(probs)
779 }
780 fn perform_scirs2_numerical_analysis(
782 &self,
783 unitary1: &Array2<Complex64>,
784 unitary2: &Array2<Complex64>,
785 ) -> QuantRS2Result<NumericalAnalysis> {
786 let diff_matrix = unitary1 - unitary2;
787 let frobenius_norm = diff_matrix
788 .iter()
789 .map(scirs2_core::Complex::norm_sqr)
790 .sum::<f64>()
791 .sqrt();
792 let condition_number = if self.options.enable_stability_analysis {
793 Some(self.estimate_condition_number(unitary1)?)
794 } else {
795 None
796 };
797 let spectral_norm = if self.options.enable_stability_analysis {
798 Some(self.calculate_spectral_norm(&diff_matrix)?)
799 } else {
800 None
801 };
802 let numerical_rank = self.estimate_numerical_rank(&diff_matrix);
803 let stability_indicator = if let Some(cond_num) = condition_number {
804 1.0 / (1.0 + (cond_num / self.options.max_condition_number).log10())
805 } else {
806 1.0
807 };
808 let adaptive_tolerance = self.calculate_adaptive_tolerance_internal(
809 unitary1.nrows(),
810 frobenius_norm,
811 condition_number.unwrap_or(1.0),
812 );
813 Ok(NumericalAnalysis {
814 condition_number,
815 numerical_rank: Some(numerical_rank),
816 frobenius_norm,
817 spectral_norm,
818 adaptive_tolerance,
819 stability_indicator,
820 })
821 }
822 fn calculate_adaptive_tolerance<const N: usize>(
824 &self,
825 num_qubits: usize,
826 analysis: &NumericalAnalysis,
827 ) -> f64 {
828 let base_tolerance = if self.options.enable_adaptive_tolerance {
829 SCIRS2_DEFAULT_TOLERANCE
830 } else {
831 self.options.tolerance
832 };
833 let size_factor = (num_qubits as f64).powf(1.5).mul_add(1e-15, 1.0);
834 let condition_factor = if let Some(cond_num) = analysis.condition_number {
835 (cond_num / 1e12).log10().max(0.0).mul_add(1e-2, 1.0)
836 } else {
837 1.0
838 };
839 let norm_factor = analysis.frobenius_norm.mul_add(1e-3, 1.0);
840 base_tolerance * size_factor * condition_factor * norm_factor
841 }
842 fn calculate_adaptive_tolerance_internal(
844 &self,
845 matrix_size: usize,
846 frobenius_norm: f64,
847 condition_number: f64,
848 ) -> f64 {
849 let base_tolerance = SCIRS2_DEFAULT_TOLERANCE;
850 let size_factor = (matrix_size as f64).sqrt().mul_add(1e-15, 1.0);
851 let condition_factor = (condition_number / 1e12)
852 .log10()
853 .max(0.0)
854 .mul_add(1e-2, 1.0);
855 let norm_factor = frobenius_norm.mul_add(1e-3, 1.0);
856 base_tolerance * size_factor * condition_factor * norm_factor
857 }
858 fn scirs2_unitaries_equal(
860 &self,
861 u1: &Array2<Complex64>,
862 u2: &Array2<Complex64>,
863 adaptive_tolerance: f64,
864 ) -> QuantRS2Result<(bool, f64, f64, ErrorBounds)> {
865 if u1.shape() != u2.shape() {
866 return Ok((
867 false,
868 f64::INFINITY,
869 0.0,
870 ErrorBounds {
871 lower_bound: f64::INFINITY,
872 upper_bound: f64::INFINITY,
873 confidence_level: 0.0,
874 standard_deviation: None,
875 },
876 ));
877 }
878 let mut max_diff = 0.0;
879 let mut differences = Vec::new();
880 for (a, b) in u1.iter().zip(u2.iter()) {
881 let diff = if self.options.ignore_global_phase {
882 let phase = if a.norm() > adaptive_tolerance && b.norm() > adaptive_tolerance {
883 b / a
884 } else {
885 Complex64::new(1.0, 0.0)
886 };
887 (a * phase - b).norm()
888 } else {
889 (a - b).norm()
890 };
891 differences.push(diff);
892 if diff > max_diff {
893 max_diff = diff;
894 }
895 }
896 let mean_diff = differences.iter().sum::<f64>() / differences.len() as f64;
897 let variance = differences
898 .iter()
899 .map(|d| (d - mean_diff).powi(2))
900 .sum::<f64>()
901 / differences.len() as f64;
902 let std_dev = variance.sqrt();
903 let confidence_score = if max_diff <= adaptive_tolerance {
904 1.0 - (max_diff / adaptive_tolerance).min(1.0)
905 } else {
906 0.0
907 };
908 let error_bounds = ErrorBounds {
909 lower_bound: 2.0f64.mul_add(-std_dev, mean_diff).max(0.0),
910 upper_bound: 2.0f64.mul_add(std_dev, mean_diff),
911 confidence_level: self.options.confidence_level,
912 standard_deviation: Some(std_dev),
913 };
914 let equivalent = max_diff <= adaptive_tolerance;
915 Ok((equivalent, max_diff, confidence_score, error_bounds))
916 }
917 fn compare_scirs2_graphs(
919 &self,
920 graph1: &crate::scirs2_integration::SciRS2CircuitGraph,
921 graph2: &crate::scirs2_integration::SciRS2CircuitGraph,
922 ) -> QuantRS2Result<(bool, f64, String)> {
923 if graph1.nodes.len() != graph2.nodes.len() {
924 return Ok((
925 false,
926 0.0,
927 format!(
928 "Different number of nodes: {} vs {}",
929 graph1.nodes.len(),
930 graph2.nodes.len()
931 ),
932 ));
933 }
934 if graph1.edges.len() != graph2.edges.len() {
935 return Ok((
936 false,
937 0.0,
938 format!(
939 "Different number of edges: {} vs {}",
940 graph1.edges.len(),
941 graph2.edges.len()
942 ),
943 ));
944 }
945 let node_similarity = self.calculate_node_similarity(graph1, graph2);
946 let edge_similarity = self.calculate_edge_similarity(graph1, graph2);
947 let topology_similarity = self.calculate_topology_similarity(graph1, graph2);
948 let overall_similarity = (node_similarity + edge_similarity + topology_similarity) / 3.0;
949 let equivalent = overall_similarity > 0.95;
950 let details = format!(
951 "Graph similarity analysis: nodes={node_similarity:.3}, edges={edge_similarity:.3}, topology={topology_similarity:.3}, overall={overall_similarity:.3}"
952 );
953 Ok((equivalent, overall_similarity, details))
954 }
955 fn calculate_node_similarity(
957 &self,
958 graph1: &crate::scirs2_integration::SciRS2CircuitGraph,
959 graph2: &crate::scirs2_integration::SciRS2CircuitGraph,
960 ) -> f64 {
961 if graph1.nodes.is_empty() && graph2.nodes.is_empty() {
962 return 1.0;
963 }
964 let total_nodes = graph1.nodes.len().max(graph2.nodes.len());
965 let mut matching_nodes = 0;
966 for node1 in graph1.nodes.values() {
967 for node2 in graph2.nodes.values() {
968 if node1.node_type == node2.node_type {
969 matching_nodes += 1;
970 break;
971 }
972 }
973 }
974 f64::from(matching_nodes) / total_nodes as f64
975 }
976 fn calculate_edge_similarity(
978 &self,
979 graph1: &crate::scirs2_integration::SciRS2CircuitGraph,
980 graph2: &crate::scirs2_integration::SciRS2CircuitGraph,
981 ) -> f64 {
982 if graph1.edges.is_empty() && graph2.edges.is_empty() {
983 return 1.0;
984 }
985 let total_edges = graph1.edges.len().max(graph2.edges.len());
986 let mut matching_edges = 0;
987 for edge1 in graph1.edges.values() {
988 for edge2 in graph2.edges.values() {
989 if edge1.edge_type == edge2.edge_type {
990 matching_edges += 1;
991 break;
992 }
993 }
994 }
995 f64::from(matching_edges) / total_edges as f64
996 }
997 fn calculate_topology_similarity(
999 &self,
1000 graph1: &crate::scirs2_integration::SciRS2CircuitGraph,
1001 graph2: &crate::scirs2_integration::SciRS2CircuitGraph,
1002 ) -> f64 {
1003 if graph1.adjacency_matrix.len() != graph2.adjacency_matrix.len() {
1004 return 0.0;
1005 }
1006 let mut total_elements = 0;
1007 let mut matching_elements = 0;
1008 for (row1, row2) in graph1
1009 .adjacency_matrix
1010 .iter()
1011 .zip(graph2.adjacency_matrix.iter())
1012 {
1013 if row1.len() != row2.len() {
1014 return 0.0;
1015 }
1016 for (elem1, elem2) in row1.iter().zip(row2.iter()) {
1017 total_elements += 1;
1018 if elem1 == elem2 {
1019 matching_elements += 1;
1020 }
1021 }
1022 }
1023 if total_elements == 0 {
1024 1.0
1025 } else {
1026 f64::from(matching_elements) / f64::from(total_elements)
1027 }
1028 }
1029 fn estimate_condition_number(&self, matrix: &Array2<Complex64>) -> QuantRS2Result<f64> {
1031 let n = matrix.nrows();
1032 if n == 0 {
1033 return Ok(1.0);
1034 }
1035 let mut v = vec![Complex64::new(1.0, 0.0); n];
1036 for _ in 0..10 {
1037 let mut new_v = vec![Complex64::new(0.0, 0.0); n];
1038 for i in 0..n {
1039 for j in 0..n {
1040 for k in 0..n {
1041 new_v[i] += matrix[[k, i]].conj() * matrix[[k, j]] * v[j];
1042 }
1043 }
1044 }
1045 let norm = new_v
1046 .iter()
1047 .map(scirs2_core::Complex::norm_sqr)
1048 .sum::<f64>()
1049 .sqrt();
1050 if norm > 0.0 {
1051 for x in &mut new_v {
1052 *x /= norm;
1053 }
1054 }
1055 v = new_v;
1056 }
1057 let estimated_largest_sv = v.iter().map(|x| x.norm()).sum::<f64>() / n as f64;
1058 let estimated_smallest_sv = 1.0 / estimated_largest_sv;
1059 Ok((estimated_largest_sv / estimated_smallest_sv.max(1e-16)).min(1e16))
1060 }
1061 fn calculate_spectral_norm(&self, matrix: &Array2<Complex64>) -> QuantRS2Result<f64> {
1063 Ok(matrix.iter().map(|x| x.norm()).fold(0.0, f64::max))
1064 }
1065 fn estimate_numerical_rank(&self, matrix: &Array2<Complex64>) -> usize {
1067 let tolerance = self.options.tolerance;
1068 let mut rank = 0;
1069 for row in matrix.rows() {
1070 let row_norm = row
1071 .iter()
1072 .map(scirs2_core::Complex::norm_sqr)
1073 .sum::<f64>()
1074 .sqrt();
1075 if row_norm > tolerance {
1076 rank += 1;
1077 }
1078 }
1079 rank
1080 }
1081 fn calculate_statistical_significance(
1083 &self,
1084 u1: &Array2<Complex64>,
1085 u2: &Array2<Complex64>,
1086 max_difference: f64,
1087 ) -> QuantRS2Result<f64> {
1088 let n = u1.len();
1089 let degrees_of_freedom = n - 1;
1090 let differences: Vec<f64> = u1
1091 .iter()
1092 .zip(u2.iter())
1093 .map(|(a, b)| (a - b).norm())
1094 .collect();
1095 let mean_diff = differences.iter().sum::<f64>() / n as f64;
1096 let variance = differences
1097 .iter()
1098 .map(|d| (d - mean_diff).powi(2))
1099 .sum::<f64>()
1100 / degrees_of_freedom as f64;
1101 let std_error = (variance / n as f64).sqrt();
1102 let t_stat = if std_error > 0.0 {
1103 mean_diff / std_error
1104 } else {
1105 0.0
1106 };
1107 let p_value = 2.0 * (1.0 - (t_stat.abs() / (1.0 + t_stat.abs())));
1108 Ok(p_value.clamp(0.0, 1.0))
1109 }
1110}
1111#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1113pub enum EquivalenceType {
1114 UnitaryEquivalence,
1116 StateVectorEquivalence,
1118 ProbabilisticEquivalence,
1120 StructuralEquivalence,
1122 GlobalPhaseEquivalence,
1124 SciRS2NumericalEquivalence,
1126 SciRS2StatisticalEquivalence,
1128 SciRS2GraphEquivalence,
1130}
1131#[derive(Debug, Clone, Serialize, Deserialize)]
1133pub struct ErrorBounds {
1134 pub lower_bound: f64,
1136 pub upper_bound: f64,
1138 pub confidence_level: f64,
1140 pub standard_deviation: Option<f64>,
1142}
1143#[derive(Debug, Clone, Serialize, Deserialize)]
1145pub struct EquivalenceOptions {
1146 pub tolerance: f64,
1148 pub ignore_global_phase: bool,
1150 pub check_all_states: bool,
1152 pub max_unitary_qubits: usize,
1154 pub enable_adaptive_tolerance: bool,
1156 pub enable_statistical_analysis: bool,
1158 pub enable_stability_analysis: bool,
1160 pub enable_graph_comparison: bool,
1162 pub confidence_level: f64,
1164 pub max_condition_number: f64,
1166 pub scirs2_config: Option<AnalyzerConfig>,
1168 pub complex_tolerance: f64,
1170 pub enable_parallel_computation: bool,
1172}
1173#[derive(Debug, Clone, Serialize, Deserialize)]
1175pub struct EquivalenceResult {
1176 pub equivalent: bool,
1178 pub check_type: EquivalenceType,
1180 pub max_difference: Option<f64>,
1182 pub details: String,
1184 pub numerical_analysis: Option<NumericalAnalysis>,
1186 pub confidence_score: f64,
1188 pub statistical_significance: Option<f64>,
1190 pub error_bounds: Option<ErrorBounds>,
1192}