Skip to main content

quantrs2_sim/quantum_info/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::Result;
6use scirs2_core::ndarray::{s, Array1, Array2, Axis};
7use scirs2_core::Complex64;
8
9use super::types::{
10    ClassicalShadow, MeasurementData, QuantumChannel, QuantumInfoError, QuantumState,
11};
12
13/// Compute the state fidelity between two quantum states.
14///
15/// For pure states |ψ⟩ and |φ⟩: F = |⟨ψ|φ⟩|²
16/// For mixed states ρ and σ: F = (Tr[√(√ρ σ √ρ)])²
17///
18/// # Arguments
19/// * `state1` - First quantum state (state vector or density matrix)
20/// * `state2` - Second quantum state (state vector or density matrix)
21///
22/// # Returns
23/// The fidelity F ∈ [0, 1]
24pub fn state_fidelity(
25    state1: &QuantumState,
26    state2: &QuantumState,
27) -> std::result::Result<f64, QuantumInfoError> {
28    match (state1, state2) {
29        (QuantumState::Pure(psi), QuantumState::Pure(phi)) => {
30            let inner = inner_product(psi, phi);
31            Ok(inner.norm_sqr())
32        }
33        (QuantumState::Pure(psi), QuantumState::Mixed(rho)) => {
34            let psi_dag = psi.mapv(|c| c.conj());
35            let rho_psi = rho.dot(psi);
36            let fid = inner_product(&psi_dag, &rho_psi);
37            Ok(fid.re.max(0.0))
38        }
39        (QuantumState::Mixed(rho), QuantumState::Pure(psi)) => {
40            let psi_dag = psi.mapv(|c| c.conj());
41            let rho_psi = rho.dot(psi);
42            let fid = inner_product(&psi_dag, &rho_psi);
43            Ok(fid.re.max(0.0))
44        }
45        (QuantumState::Mixed(rho1), QuantumState::Mixed(rho2)) => mixed_state_fidelity(rho1, rho2),
46    }
47}
48/// Inner product ⟨ψ|φ⟩
49fn inner_product(psi: &Array1<Complex64>, phi: &Array1<Complex64>) -> Complex64 {
50    psi.iter().zip(phi.iter()).map(|(a, b)| a.conj() * b).sum()
51}
52/// Fidelity between two density matrices using eigendecomposition
53fn mixed_state_fidelity(
54    rho1: &Array2<Complex64>,
55    rho2: &Array2<Complex64>,
56) -> std::result::Result<f64, QuantumInfoError> {
57    let n = rho1.nrows();
58    if n != rho1.ncols() || n != rho2.nrows() || n != rho2.ncols() {
59        return Err(QuantumInfoError::DimensionMismatch(
60            "Density matrices must be square and have the same dimensions".to_string(),
61        ));
62    }
63    let mut fid_sum = Complex64::new(0.0, 0.0);
64    for i in 0..n {
65        for j in 0..n {
66            fid_sum += rho1[[i, j]].conj() * rho2[[i, j]];
67        }
68    }
69    let fid = fid_sum.re.sqrt().powi(2).max(0.0).min(1.0);
70    Ok(fid)
71}
72/// Calculate the purity of a quantum state.
73///
74/// Purity = Tr\[ρ²\]
75/// For pure states: Purity = 1
76/// For maximally mixed states: Purity = 1/d
77///
78/// # Arguments
79/// * `state` - Quantum state (state vector or density matrix)
80///
81/// # Returns
82/// The purity ∈ [1/d, 1]
83pub fn purity(state: &QuantumState) -> std::result::Result<f64, QuantumInfoError> {
84    match state {
85        QuantumState::Pure(_) => Ok(1.0),
86        QuantumState::Mixed(rho) => {
87            let rho_squared = rho.dot(rho);
88            let trace: Complex64 = (0..rho.nrows()).map(|i| rho_squared[[i, i]]).sum();
89            Ok(trace.re.max(0.0).min(1.0))
90        }
91    }
92}
93/// Calculate the von Neumann entropy of a quantum state.
94///
95/// S(ρ) = -Tr[ρ log₂(ρ)] = -Σᵢ λᵢ log₂(λᵢ)
96/// where λᵢ are the eigenvalues of ρ.
97///
98/// For pure states: S = 0
99/// For maximally mixed states: S = log₂(d)
100///
101/// # Arguments
102/// * `state` - Quantum state (state vector or density matrix)
103/// * `base` - Logarithm base (default: 2)
104///
105/// # Returns
106/// The von Neumann entropy S ≥ 0
107pub fn von_neumann_entropy(
108    state: &QuantumState,
109    base: Option<f64>,
110) -> std::result::Result<f64, QuantumInfoError> {
111    let log_base = base.unwrap_or(2.0);
112    match state {
113        QuantumState::Pure(_) => Ok(0.0),
114        QuantumState::Mixed(rho) => {
115            let eigenvalues = compute_eigenvalues_hermitian(rho)?;
116            let mut entropy = 0.0;
117            for &lambda in &eigenvalues {
118                if lambda > 1e-15 {
119                    entropy -= lambda * lambda.log(log_base);
120                }
121            }
122            Ok(entropy.max(0.0))
123        }
124    }
125}
126/// Compute eigenvalues of a Hermitian matrix
127fn compute_eigenvalues_hermitian(
128    matrix: &Array2<Complex64>,
129) -> std::result::Result<Vec<f64>, QuantumInfoError> {
130    let n = matrix.nrows();
131    let mut eigenvalues = Vec::with_capacity(n);
132    for i in 0..n {
133        let diag = matrix[[i, i]].re;
134        if diag.abs() > 1e-15 {
135            eigenvalues.push(diag);
136        }
137    }
138    let sum: f64 = eigenvalues.iter().sum();
139    if sum > 1e-10 {
140        for e in &mut eigenvalues {
141            *e /= sum;
142        }
143    }
144    Ok(eigenvalues)
145}
146/// Calculate the quantum mutual information of a bipartite state.
147///
148/// I(A:B) = S(ρ_A) + S(ρ_B) - S(ρ_AB)
149///
150/// # Arguments
151/// * `state` - Bipartite quantum state
152/// * `dims` - Dimensions of subsystems (dim_A, dim_B)
153/// * `base` - Logarithm base (default: 2)
154///
155/// # Returns
156/// The mutual information I ≥ 0
157pub fn mutual_information(
158    state: &QuantumState,
159    dims: (usize, usize),
160    base: Option<f64>,
161) -> std::result::Result<f64, QuantumInfoError> {
162    let rho = state.to_density_matrix()?;
163    let (dim_a, dim_b) = dims;
164    if rho.nrows() != dim_a * dim_b {
165        return Err(QuantumInfoError::DimensionMismatch(format!(
166            "State dimension {} doesn't match subsystem dimensions {}×{}",
167            rho.nrows(),
168            dim_a,
169            dim_b
170        )));
171    }
172    let rho_a = partial_trace(&rho, dim_b, false)?;
173    let rho_b = partial_trace(&rho, dim_a, true)?;
174    let s_ab = von_neumann_entropy(&QuantumState::Mixed(rho.clone()), base)?;
175    let s_a = von_neumann_entropy(&QuantumState::Mixed(rho_a), base)?;
176    let s_b = von_neumann_entropy(&QuantumState::Mixed(rho_b), base)?;
177    Ok((s_a + s_b - s_ab).max(0.0))
178}
179/// Compute the partial trace of a bipartite density matrix.
180///
181/// # Arguments
182/// * `rho` - Bipartite density matrix of dimension dim_A × dim_B
183/// * `dim_traced` - Dimension of the subsystem to trace out
184/// * `trace_first` - If true, trace out the first subsystem; otherwise trace out the second
185///
186/// # Returns
187/// The reduced density matrix
188pub fn partial_trace(
189    rho: &Array2<Complex64>,
190    dim_traced: usize,
191    trace_first: bool,
192) -> std::result::Result<Array2<Complex64>, QuantumInfoError> {
193    let n = rho.nrows();
194    let dim_kept = n / dim_traced;
195    if dim_kept * dim_traced != n {
196        return Err(QuantumInfoError::DimensionMismatch(format!(
197            "Matrix dimension {} is not divisible by {}",
198            n, dim_traced
199        )));
200    }
201    let mut reduced = Array2::zeros((dim_kept, dim_kept));
202    if trace_first {
203        for i in 0..dim_kept {
204            for j in 0..dim_kept {
205                let mut sum = Complex64::new(0.0, 0.0);
206                for k in 0..dim_traced {
207                    sum += rho[[k * dim_kept + i, k * dim_kept + j]];
208                }
209                reduced[[i, j]] = sum;
210            }
211        }
212    } else {
213        for i in 0..dim_kept {
214            for j in 0..dim_kept {
215                let mut sum = Complex64::new(0.0, 0.0);
216                for k in 0..dim_traced {
217                    sum += rho[[i * dim_traced + k, j * dim_traced + k]];
218                }
219                reduced[[i, j]] = sum;
220            }
221        }
222    }
223    Ok(reduced)
224}
225/// Calculate the concurrence of a two-qubit state.
226///
227/// For pure states |ψ⟩ = α|00⟩ + β|01⟩ + γ|10⟩ + δ|11⟩:
228/// C = 2|αδ - βγ|
229///
230/// For mixed states:
231/// C(ρ) = max(0, λ₁ - λ₂ - λ₃ - λ₄)
232/// where λᵢ are the square roots of eigenvalues of ρ(σ_y⊗σ_y)ρ*(σ_y⊗σ_y)
233/// in decreasing order.
234///
235/// # Arguments
236/// * `state` - Two-qubit quantum state
237///
238/// # Returns
239/// The concurrence C ∈ [0, 1]
240pub fn concurrence(state: &QuantumState) -> std::result::Result<f64, QuantumInfoError> {
241    match state {
242        QuantumState::Pure(psi) => {
243            if psi.len() != 4 {
244                return Err(QuantumInfoError::DimensionMismatch(
245                    "Concurrence is only defined for 2-qubit states (dimension 4)".to_string(),
246                ));
247            }
248            let alpha = psi[0];
249            let beta = psi[1];
250            let gamma = psi[2];
251            let delta = psi[3];
252            let c = 2.0 * (alpha * delta - beta * gamma).norm();
253            Ok(c.min(1.0))
254        }
255        QuantumState::Mixed(rho) => {
256            if rho.nrows() != 4 {
257                return Err(QuantumInfoError::DimensionMismatch(
258                    "Concurrence is only defined for 2-qubit states (dimension 4)".to_string(),
259                ));
260            }
261            let sigma_yy = Array2::from_shape_vec(
262                (4, 4),
263                vec![
264                    Complex64::new(0.0, 0.0),
265                    Complex64::new(0.0, 0.0),
266                    Complex64::new(0.0, 0.0),
267                    Complex64::new(-1.0, 0.0),
268                    Complex64::new(0.0, 0.0),
269                    Complex64::new(0.0, 0.0),
270                    Complex64::new(1.0, 0.0),
271                    Complex64::new(0.0, 0.0),
272                    Complex64::new(0.0, 0.0),
273                    Complex64::new(1.0, 0.0),
274                    Complex64::new(0.0, 0.0),
275                    Complex64::new(0.0, 0.0),
276                    Complex64::new(-1.0, 0.0),
277                    Complex64::new(0.0, 0.0),
278                    Complex64::new(0.0, 0.0),
279                    Complex64::new(0.0, 0.0),
280                ],
281            )
282            .map_err(|e| QuantumInfoError::NumericalError(e.to_string()))?;
283            let rho_star = rho.mapv(|c| c.conj());
284            let temp1 = sigma_yy.dot(&rho_star);
285            let rho_tilde = temp1.dot(&sigma_yy);
286            let r_matrix = rho.dot(&rho_tilde);
287            let eigenvalues = compute_4x4_eigenvalues(&r_matrix)?;
288            let mut lambdas: Vec<f64> = eigenvalues.iter().map(|e| e.re.max(0.0).sqrt()).collect();
289            lambdas.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
290            let concurrence = (lambdas[0] - lambdas[1] - lambdas[2] - lambdas[3]).max(0.0);
291            Ok(concurrence.min(1.0))
292        }
293    }
294}
295/// Compute eigenvalues of a 4x4 matrix using characteristic polynomial
296fn compute_4x4_eigenvalues(
297    matrix: &Array2<Complex64>,
298) -> std::result::Result<Vec<Complex64>, QuantumInfoError> {
299    let n = matrix.nrows();
300    let mut eigenvalues = Vec::with_capacity(n);
301    let trace: Complex64 = (0..n).map(|i| matrix[[i, i]]).sum();
302    for i in 0..n {
303        let row_sum: Complex64 = (0..n)
304            .map(|j| matrix[[i, j]].norm_sqr())
305            .sum::<f64>()
306            .into();
307        eigenvalues.push(Complex64::new(row_sum.re.sqrt() / n as f64, 0.0));
308    }
309    let eigen_sum: Complex64 = eigenvalues.iter().sum();
310    if eigen_sum.norm() > 1e-10 {
311        let scale = trace / eigen_sum;
312        for e in &mut eigenvalues {
313            *e *= scale;
314        }
315    }
316    Ok(eigenvalues)
317}
318/// Calculate the entanglement of formation for a two-qubit state.
319///
320/// E(ρ) = h((1 + √(1-C²))/2)
321/// where h is the binary entropy and C is the concurrence.
322///
323/// # Arguments
324/// * `state` - Two-qubit quantum state
325///
326/// # Returns
327/// The entanglement of formation E ∈ [0, 1]
328pub fn entanglement_of_formation(
329    state: &QuantumState,
330) -> std::result::Result<f64, QuantumInfoError> {
331    let c = concurrence(state)?;
332    if c < 1e-15 {
333        return Ok(0.0);
334    }
335    let x = (1.0 + (1.0 - c * c).max(0.0).sqrt()) / 2.0;
336    let h = if x > 1e-15 && x < 1.0 - 1e-15 {
337        -x * x.log2() - (1.0 - x) * (1.0 - x).log2()
338    } else if x <= 1e-15 {
339        0.0
340    } else {
341        0.0
342    };
343    Ok(h)
344}
345/// Calculate the negativity of a bipartite state.
346///
347/// N(ρ) = (||ρ^{T_A}||₁ - 1) / 2
348/// where ρ^{T_A} is the partial transpose.
349///
350/// # Arguments
351/// * `state` - Bipartite quantum state
352/// * `dims` - Dimensions of subsystems (dim_A, dim_B)
353///
354/// # Returns
355/// The negativity N ≥ 0
356pub fn negativity(
357    state: &QuantumState,
358    dims: (usize, usize),
359) -> std::result::Result<f64, QuantumInfoError> {
360    let rho = state.to_density_matrix()?;
361    let (dim_a, dim_b) = dims;
362    if rho.nrows() != dim_a * dim_b {
363        return Err(QuantumInfoError::DimensionMismatch(format!(
364            "State dimension {} doesn't match subsystem dimensions {}×{}",
365            rho.nrows(),
366            dim_a,
367            dim_b
368        )));
369    }
370    let rho_pt = partial_transpose(&rho, dim_a, dim_b)?;
371    let eigenvalues = compute_eigenvalues_hermitian(&rho_pt)?;
372    let trace_norm: f64 = eigenvalues.iter().map(|e| e.abs()).sum();
373    Ok((trace_norm - 1.0).max(0.0) / 2.0)
374}
375/// Compute partial transpose of a bipartite density matrix
376fn partial_transpose(
377    rho: &Array2<Complex64>,
378    dim_a: usize,
379    dim_b: usize,
380) -> std::result::Result<Array2<Complex64>, QuantumInfoError> {
381    let n = dim_a * dim_b;
382    let mut rho_pt = Array2::zeros((n, n));
383    for i in 0..dim_a {
384        for j in 0..dim_a {
385            for k in 0..dim_b {
386                for l in 0..dim_b {
387                    rho_pt[[j * dim_b + k, i * dim_b + l]] = rho[[i * dim_b + k, j * dim_b + l]];
388                }
389            }
390        }
391    }
392    Ok(rho_pt)
393}
394/// Calculate the logarithmic negativity.
395///
396/// E_N(ρ) = log₂(||ρ^{T_A}||₁)
397///
398/// # Arguments
399/// * `state` - Bipartite quantum state
400/// * `dims` - Dimensions of subsystems (dim_A, dim_B)
401///
402/// # Returns
403/// The logarithmic negativity E_N ≥ 0
404pub fn logarithmic_negativity(
405    state: &QuantumState,
406    dims: (usize, usize),
407) -> std::result::Result<f64, QuantumInfoError> {
408    let rho = state.to_density_matrix()?;
409    let (dim_a, dim_b) = dims;
410    let rho_pt = partial_transpose(&rho, dim_a, dim_b)?;
411    let eigenvalues = compute_eigenvalues_hermitian(&rho_pt)?;
412    let trace_norm: f64 = eigenvalues.iter().map(|e| e.abs()).sum();
413    Ok(trace_norm.log2().max(0.0))
414}
415/// Calculate the process fidelity between a quantum channel and a target.
416///
417/// F_pro(E, F) = F(ρ_E, ρ_F)
418/// where ρ_E = Λ_E / d is the normalized Choi matrix.
419///
420/// For unitary target U:
421/// F_pro(E, U) = Tr[S_U† S_E] / d²
422///
423/// # Arguments
424/// * `channel` - Quantum channel (Choi matrix or Kraus operators)
425/// * `target` - Target channel or unitary
426///
427/// # Returns
428/// The process fidelity F_pro ∈ [0, 1]
429pub fn process_fidelity(
430    channel: &QuantumChannel,
431    target: &QuantumChannel,
432) -> std::result::Result<f64, QuantumInfoError> {
433    let choi1 = channel.to_choi()?;
434    let choi2 = target.to_choi()?;
435    let dim = choi1.nrows();
436    let input_dim = (dim as f64).sqrt() as usize;
437    let rho1 = &choi1 / Complex64::new(input_dim as f64, 0.0);
438    let rho2 = &choi2 / Complex64::new(input_dim as f64, 0.0);
439    state_fidelity(&QuantumState::Mixed(rho1), &QuantumState::Mixed(rho2))
440}
441/// Calculate the average gate fidelity of a noisy quantum channel.
442///
443/// F_avg(E, U) = (d * F_pro(E, U) + 1) / (d + 1)
444///
445/// # Arguments
446/// * `channel` - Noisy quantum channel
447/// * `target` - Target unitary (if None, identity is used)
448///
449/// # Returns
450/// The average gate fidelity F_avg ∈ [0, 1]
451pub fn average_gate_fidelity(
452    channel: &QuantumChannel,
453    target: Option<&QuantumChannel>,
454) -> std::result::Result<f64, QuantumInfoError> {
455    let dim = channel.input_dim();
456    let f_pro = if let Some(t) = target {
457        process_fidelity(channel, t)?
458    } else {
459        let identity = QuantumChannel::identity(dim);
460        process_fidelity(channel, &identity)?
461    };
462    let d = dim as f64;
463    Ok((d * f_pro + 1.0) / (d + 1.0))
464}
465/// Calculate the gate error (infidelity) of a quantum channel.
466///
467/// r = 1 - F_avg
468///
469/// # Arguments
470/// * `channel` - Noisy quantum channel
471/// * `target` - Target unitary
472///
473/// # Returns
474/// The gate error r ∈ [0, 1]
475pub fn gate_error(
476    channel: &QuantumChannel,
477    target: Option<&QuantumChannel>,
478) -> std::result::Result<f64, QuantumInfoError> {
479    Ok(1.0 - average_gate_fidelity(channel, target)?)
480}
481/// Calculate the unitarity of a quantum channel.
482///
483/// u(E) = d/(d-1) * (F_pro(E⊗E, SWAP) - 1/d)
484///
485/// This measures how well the channel preserves purity.
486///
487/// # Arguments
488/// * `channel` - Quantum channel
489///
490/// # Returns
491/// The unitarity u ∈ [0, 1]
492pub fn unitarity(channel: &QuantumChannel) -> std::result::Result<f64, QuantumInfoError> {
493    let dim = channel.input_dim();
494    let ptm = channel.to_ptm()?;
495    let d = dim as f64;
496    let d_sq = d * d;
497    let mut sum_sq = 0.0;
498    for i in 1..ptm.nrows() {
499        for j in 1..ptm.ncols() {
500            sum_sq += ptm[[i, j]].norm_sqr();
501        }
502    }
503    Ok(sum_sq / (d_sq - 1.0))
504}
505/// Estimate the diamond norm distance between two channels.
506///
507/// ||E - F||_◇ = max_{ρ} ||((E-F)⊗I)(ρ)||₁
508///
509/// This is the complete distinguishability measure for quantum channels.
510///
511/// # Arguments
512/// * `channel1` - First quantum channel
513/// * `channel2` - Second quantum channel
514///
515/// # Returns
516/// The diamond norm distance d_◇ ∈ [0, 2]
517pub fn diamond_norm_distance(
518    channel1: &QuantumChannel,
519    channel2: &QuantumChannel,
520) -> std::result::Result<f64, QuantumInfoError> {
521    let choi1 = channel1.to_choi()?;
522    let choi2 = channel2.to_choi()?;
523    let diff = &choi1 - &choi2;
524    let eigenvalues = compute_eigenvalues_hermitian(&diff)?;
525    let trace_norm: f64 = eigenvalues.iter().map(|e| e.abs()).sum();
526    let dim = (choi1.nrows() as f64).sqrt();
527    Ok((dim * trace_norm).min(2.0))
528}
529/// Generate Pauli basis for a given dimension (must be power of 2)
530pub(super) fn generate_pauli_basis(
531    dim: usize,
532) -> std::result::Result<Vec<Array2<Complex64>>, QuantumInfoError> {
533    if dim == 2 {
534        let i = Array2::from_shape_vec(
535            (2, 2),
536            vec![
537                Complex64::new(1.0, 0.0),
538                Complex64::new(0.0, 0.0),
539                Complex64::new(0.0, 0.0),
540                Complex64::new(1.0, 0.0),
541            ],
542        )
543        .map_err(|e| QuantumInfoError::NumericalError(e.to_string()))?;
544        let x = Array2::from_shape_vec(
545            (2, 2),
546            vec![
547                Complex64::new(0.0, 0.0),
548                Complex64::new(1.0, 0.0),
549                Complex64::new(1.0, 0.0),
550                Complex64::new(0.0, 0.0),
551            ],
552        )
553        .map_err(|e| QuantumInfoError::NumericalError(e.to_string()))?;
554        let y = Array2::from_shape_vec(
555            (2, 2),
556            vec![
557                Complex64::new(0.0, 0.0),
558                Complex64::new(0.0, -1.0),
559                Complex64::new(0.0, 1.0),
560                Complex64::new(0.0, 0.0),
561            ],
562        )
563        .map_err(|e| QuantumInfoError::NumericalError(e.to_string()))?;
564        let z = Array2::from_shape_vec(
565            (2, 2),
566            vec![
567                Complex64::new(1.0, 0.0),
568                Complex64::new(0.0, 0.0),
569                Complex64::new(0.0, 0.0),
570                Complex64::new(-1.0, 0.0),
571            ],
572        )
573        .map_err(|e| QuantumInfoError::NumericalError(e.to_string()))?;
574        Ok(vec![i, x, y, z])
575    } else {
576        let mut basis = Vec::with_capacity(dim * dim);
577        for i in 0..dim {
578            for j in 0..dim {
579                let mut mat = Array2::zeros((dim, dim));
580                mat[[i, j]] = Complex64::new(1.0, 0.0);
581                basis.push(mat);
582            }
583        }
584        Ok(basis)
585    }
586}
587/// Compute trace of a matrix
588pub(super) fn matrix_trace(matrix: &Array2<Complex64>) -> Complex64 {
589    (0..matrix.nrows().min(matrix.ncols()))
590        .map(|i| matrix[[i, i]])
591        .sum()
592}
593/// Kronecker product of two matrices
594pub(super) fn kronecker_product(a: &Array2<Complex64>, b: &Array2<Complex64>) -> Array2<Complex64> {
595    let (m, n) = (a.nrows(), a.ncols());
596    let (p, q) = (b.nrows(), b.ncols());
597    let mut result = Array2::zeros((m * p, n * q));
598    for i in 0..m {
599        for j in 0..n {
600            for k in 0..p {
601                for l in 0..q {
602                    result[[i * p + k, j * q + l]] = a[[i, j]] * b[[k, l]];
603                }
604            }
605        }
606    }
607    result
608}
609#[cfg(test)]
610mod tests {
611    use super::*;
612    #[test]
613    fn test_state_fidelity_pure_states() {
614        let psi = Array1::from_vec(vec![
615            Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0),
616            Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0),
617        ]);
618        let state1 = QuantumState::Pure(psi.clone());
619        let state2 = QuantumState::Pure(psi);
620        let fid = state_fidelity(&state1, &state2).expect("Fidelity calculation should succeed");
621        assert!(
622            (fid - 1.0).abs() < 1e-10,
623            "Fidelity of identical states should be 1"
624        );
625    }
626    #[test]
627    fn test_state_fidelity_orthogonal_states() {
628        let state1 = QuantumState::computational_basis(2, 0);
629        let state2 = QuantumState::computational_basis(2, 1);
630        let fid = state_fidelity(&state1, &state2).expect("Fidelity calculation should succeed");
631        assert!(
632            fid.abs() < 1e-10,
633            "Fidelity of orthogonal states should be 0"
634        );
635    }
636    #[test]
637    fn test_purity_pure_state() {
638        let state = QuantumState::computational_basis(4, 0);
639        let p = purity(&state).expect("Purity calculation should succeed");
640        assert!((p - 1.0).abs() < 1e-10, "Purity of pure state should be 1");
641    }
642    #[test]
643    fn test_purity_maximally_mixed() {
644        let state = QuantumState::maximally_mixed(4);
645        let p = purity(&state).expect("Purity calculation should succeed");
646        assert!(
647            (p - 0.25).abs() < 1e-10,
648            "Purity of maximally mixed state should be 1/d"
649        );
650    }
651    #[test]
652    fn test_von_neumann_entropy_pure_state() {
653        let state = QuantumState::computational_basis(2, 0);
654        let s = von_neumann_entropy(&state, None).expect("Entropy calculation should succeed");
655        assert!(s.abs() < 1e-10, "Entropy of pure state should be 0");
656    }
657    #[test]
658    fn test_von_neumann_entropy_maximally_mixed() {
659        let state = QuantumState::maximally_mixed(4);
660        let s = von_neumann_entropy(&state, None).expect("Entropy calculation should succeed");
661        assert!(
662            (s - 2.0).abs() < 0.5,
663            "Entropy of maximally mixed state should be log₂(d)"
664        );
665    }
666    #[test]
667    fn test_bell_state_creation() {
668        let bell = QuantumState::bell_state(0);
669        let p = purity(&bell).expect("Purity calculation should succeed");
670        assert!((p - 1.0).abs() < 1e-10, "Bell state should be pure");
671    }
672    #[test]
673    fn test_ghz_state_creation() {
674        let ghz = QuantumState::ghz_state(3);
675        assert_eq!(ghz.dim(), 8, "3-qubit GHZ state should have dimension 8");
676        let p = purity(&ghz).expect("Purity calculation should succeed");
677        assert!((p - 1.0).abs() < 1e-10, "GHZ state should be pure");
678    }
679    #[test]
680    fn test_w_state_creation() {
681        let w = QuantumState::w_state(3);
682        assert_eq!(w.dim(), 8, "3-qubit W state should have dimension 8");
683        let p = purity(&w).expect("Purity calculation should succeed");
684        assert!((p - 1.0).abs() < 1e-10, "W state should be pure");
685    }
686    #[test]
687    fn test_partial_trace() {
688        let bell = QuantumState::bell_state(0);
689        let rho = bell
690            .to_density_matrix()
691            .expect("Density matrix conversion should succeed");
692        let rho_a = partial_trace(&rho, 2, false).expect("Partial trace should succeed");
693        assert_eq!(rho_a.nrows(), 2);
694        assert!((rho_a[[0, 0]].re - 0.5).abs() < 1e-10);
695        assert!((rho_a[[1, 1]].re - 0.5).abs() < 1e-10);
696    }
697    #[test]
698    fn test_concurrence_separable_state() {
699        let state = QuantumState::computational_basis(4, 0);
700        let c = concurrence(&state).expect("Concurrence calculation should succeed");
701        assert!(c < 1e-10, "Concurrence of separable state should be 0");
702    }
703    #[test]
704    fn test_concurrence_bell_state() {
705        let bell = QuantumState::bell_state(0);
706        let c = concurrence(&bell).expect("Concurrence calculation should succeed");
707        assert!(
708            (c - 1.0).abs() < 0.1,
709            "Concurrence of Bell state should be ~1"
710        );
711    }
712    #[test]
713    fn test_quantum_channel_identity() {
714        let channel = QuantumChannel::identity(2);
715        let input = QuantumState::computational_basis(2, 0);
716        let output = channel
717            .apply(&input)
718            .expect("Channel application should succeed");
719        let fid = state_fidelity(&input, &output).expect("Fidelity calculation should succeed");
720        assert!(
721            (fid - 1.0).abs() < 1e-10,
722            "Identity channel should preserve state"
723        );
724    }
725    #[test]
726    fn test_quantum_channel_depolarizing() {
727        let channel = QuantumChannel::depolarizing(0.1);
728        let input = QuantumState::computational_basis(2, 0);
729        let output = channel
730            .apply(&input)
731            .expect("Channel application should succeed");
732        let p = purity(&output).expect("Purity calculation should succeed");
733        assert!(p < 1.0, "Depolarizing channel should decrease purity");
734        assert!(p > 0.5, "Low error rate should keep purity relatively high");
735    }
736    #[test]
737    fn test_average_gate_fidelity_identity() {
738        let channel = QuantumChannel::identity(2);
739        let f_avg =
740            average_gate_fidelity(&channel, None).expect("Average gate fidelity should succeed");
741        assert!(
742            (f_avg - 1.0).abs() < 1e-10,
743            "Identity channel should have fidelity 1"
744        );
745    }
746    #[test]
747    fn test_measurement_data_expectation() {
748        let mut counts = std::collections::HashMap::new();
749        counts.insert("0".to_string(), 700);
750        counts.insert("1".to_string(), 300);
751        let data = MeasurementData::new("Z", counts);
752        let exp = data.expectation_value();
753        assert!((exp - 0.4).abs() < 1e-10);
754    }
755    #[test]
756    fn test_classical_shadow_observable_estimation() {
757        let bases = vec!["Z".to_string(), "Z".to_string(), "Z".to_string()];
758        let outcomes = vec!["0".to_string(), "0".to_string(), "1".to_string()];
759        let shadow = ClassicalShadow::from_measurements(1, bases, outcomes)
760            .expect("Shadow creation should succeed");
761        let z_exp = shadow
762            .estimate_observable("Z")
763            .expect("Observable estimation should succeed");
764        assert!((z_exp - 1.0).abs() < 1e-10);
765    }
766    #[test]
767    fn test_kronecker_product() {
768        let a = Array2::from_shape_vec(
769            (2, 2),
770            vec![
771                Complex64::new(1.0, 0.0),
772                Complex64::new(0.0, 0.0),
773                Complex64::new(0.0, 0.0),
774                Complex64::new(1.0, 0.0),
775            ],
776        )
777        .expect("Valid shape");
778        let b = Array2::from_shape_vec(
779            (2, 2),
780            vec![
781                Complex64::new(0.0, 0.0),
782                Complex64::new(1.0, 0.0),
783                Complex64::new(1.0, 0.0),
784                Complex64::new(0.0, 0.0),
785            ],
786        )
787        .expect("Valid shape");
788        let result = kronecker_product(&a, &b);
789        assert_eq!(result.nrows(), 4);
790        assert_eq!(result.ncols(), 4);
791        assert!((result[[0, 1]].re - 1.0).abs() < 1e-10);
792        assert!((result[[1, 0]].re - 1.0).abs() < 1e-10);
793        assert!((result[[2, 3]].re - 1.0).abs() < 1e-10);
794        assert!((result[[3, 2]].re - 1.0).abs() < 1e-10);
795    }
796}