use nalgebra::DMatrix;
use ndarray::{Array1, Array2, Axis};
use num_complex::Complex64;
use rayon::prelude::*;
pub fn kronecker_product_matrix(
m1: &Array2<Complex64>,
m2: &Array2<Complex64>,
) -> Array2<Complex64> {
let (m, n) = m1.dim();
let (p, q) = m2.dim();
let m1_expanded = m1.view().insert_axis(Axis(1)).insert_axis(Axis(3));
let m2_expanded = m2.view().insert_axis(Axis(0)).insert_axis(Axis(2));
let tensor_product = &m1_expanded * &m2_expanded;
tensor_product
.into_shape_with_order((m * p, n * q))
.expect("Error in Kronecker product")
}
pub fn trace(matrix: &Array2<Complex64>) -> Complex64 {
matrix.diag().sum()
}
pub fn expand_operator(
matrix: &Array2<Complex64>,
num_total_qubits: usize,
targets: &[usize],
controls: &[usize],
) -> Array2<Complex64> {
let dim = 1 << num_total_qubits;
let mut full_matrix = Array2::<Complex64>::zeros((dim, dim));
let mapped_controls: Vec<usize> = controls.iter().map(|&c| num_total_qubits - 1 - c).collect();
let mut mapped_targets: Vec<usize> =
targets.iter().map(|&t| num_total_qubits - 1 - t).collect();
mapped_targets.reverse();
let mut control_mask = 0usize;
for &c in &mapped_controls {
control_mask |= 1 << c;
}
let mut target_mask = 0usize;
for &t in &mapped_targets {
target_mask |= 1 << t;
}
let passive_mask = !target_mask;
for col_idx in 0..dim {
if (col_idx & control_mask) != control_mask {
full_matrix[[col_idx, col_idx]] = Complex64::new(1.0, 0.0);
continue;
}
let small_col = extract_bits(col_idx, &mapped_targets);
for small_row in 0..matrix.nrows() {
let val = matrix[[small_row, small_col]];
if val.norm_sqr() < f64::EPSILON {
continue;
}
let new_target_bits = deposit_bits(small_row, &mapped_targets);
let row_idx = (col_idx & passive_mask) | new_target_bits;
full_matrix[[row_idx, col_idx]] = val;
}
}
full_matrix
}
fn extract_bits(value: usize, indices: &[usize]) -> usize {
let mut result = 0;
for (i, &pos) in indices.iter().enumerate() {
if (value >> pos) & 1 == 1 {
result |= 1 << i;
}
}
result
}
fn deposit_bits(compact_value: usize, indices: &[usize]) -> usize {
let mut result = 0;
for (i, &pos) in indices.iter().enumerate() {
if (compact_value >> i) & 1 == 1 {
result |= 1 << pos;
}
}
result
}
pub fn find_duplicate(indices: &[usize]) -> Option<usize> {
let mut seen = std::collections::HashSet::new();
indices.iter().find(|&&idx| !seen.insert(idx)).copied()
}
pub fn check_kraus_completeness(ops: &[Array2<Complex64>], dim: usize) -> bool {
let eye = Array2::<Complex64>::eye(dim);
let sum = ops
.iter()
.fold(Array2::<Complex64>::zeros((dim, dim)), |acc, op| {
let dag = op.t().mapv(|c| c.conj());
acc + dag.dot(op)
});
sum.iter()
.zip(eye.iter())
.all(|(a, b)| (a - b).norm() < 1e-12)
}
pub fn check_povm_completeness(ops: &[Array2<Complex64>], dim: usize) -> bool {
let mut sum = Array2::<Complex64>::zeros((dim, dim));
for op in ops {
sum += op;
}
let identity = Array2::<Complex64>::eye(dim);
(sum - identity).iter().all(|x| x.norm() < 1e-9)
}
pub fn outer_product(a: &Array1<Complex64>, b: &Array1<Complex64>) -> Array2<Complex64> {
let n = a.len();
let m = b.len();
let mut res = Array2::zeros((n, m));
for i in 0..n {
for j in 0..m {
res[[i, j]] = a[i] * b[j].conj();
}
}
res
}
pub fn sqrt_positive_matrix(mat: &Array2<Complex64>) -> Array2<Complex64> {
let (rows, cols) = mat.dim();
if rows == 2 && cols == 2 {
return sqrt_2x2_analytical(mat);
}
sqrt_nxn_nalgebra(mat)
}
fn sqrt_2x2_analytical(mat: &Array2<Complex64>) -> Array2<Complex64> {
let tr = mat[[0, 0]] + mat[[1, 1]];
let det = mat[[0, 0]] * mat[[1, 1]] - mat[[0, 1]] * mat[[1, 0]];
let clean_det = if det.norm() < 1e-12 {
Complex64::new(0.0, 0.0)
} else {
det
};
let sqrt_det = clean_det.sqrt();
let s_sq = tr + Complex64::new(2.0, 0.0) * sqrt_det;
let s = s_sq.sqrt();
if s.norm() < 1e-12 {
return Array2::zeros((2, 2));
}
let factor = Complex64::new(1.0, 0.0) / s;
let identity = Array2::<Complex64>::eye(2);
let numerator = mat + &identity.mapv(|x| x * sqrt_det);
numerator.mapv(|x| x * factor)
}
fn sqrt_nxn_nalgebra(mat: &Array2<Complex64>) -> Array2<Complex64> {
let (rows, cols) = mat.dim();
let na_mat = DMatrix::from_fn(rows, cols, |r, c| mat[[r, c]]);
let eigen = na_mat.symmetric_eigen();
let mut sqrt_eigenvals = DMatrix::zeros(rows, rows);
for i in 0..rows {
let val = eigen.eigenvalues[i];
let clean_val = if val < 0.0 { 0.0 } else { val };
sqrt_eigenvals[(i, i)] = Complex64::new(clean_val.sqrt(), 0.0);
}
let v = &eigen.eigenvectors;
let v_adjoint = v.adjoint();
let result_na = v * sqrt_eigenvals * v_adjoint;
let mut result_nd = Array2::<Complex64>::zeros((rows, cols));
for r in 0..rows {
for c in 0..cols {
result_nd[[r, c]] = result_na[(r, c)];
}
}
result_nd
}
pub fn is_hermitian(mat: &Array2<Complex64>, tol: f64) -> bool {
mat.iter()
.zip(mat.t().iter())
.all(|(a, b)| (a - b.conj()).norm() < tol)
}
pub fn apply_local_left(
num_total_qubits: usize,
rho: &Array2<Complex64>,
local_matrix: &Array2<Complex64>,
targets: &[usize],
controls: &[usize],
) -> Array2<Complex64> {
let dim = 1 << num_total_qubits;
let mut new_rho = Array2::<Complex64>::zeros((dim, dim));
let mapped_controls: Vec<usize> = controls.iter().map(|&c| num_total_qubits - 1 - c).collect();
let mut mapped_targets: Vec<usize> =
targets.iter().map(|&t| num_total_qubits - 1 - t).collect();
mapped_targets.reverse();
let mut control_mask = 0usize;
for &c in &mapped_controls {
control_mask |= 1 << c;
}
let mut target_mask = 0usize;
for &t in &mapped_targets {
target_mask |= 1 << t;
}
let passive_mask = !target_mask;
let k_dim = 1 << targets.len();
new_rho
.axis_iter_mut(Axis(1))
.into_par_iter()
.enumerate()
.for_each(|(col, mut col_view)| {
for row in 0..dim {
if (row & control_mask) != control_mask {
col_view[row] = rho[[row, col]];
continue;
}
let small_row = extract_bits(row, &mapped_targets);
let mut sum = Complex64::new(0.0, 0.0);
for small_col in 0..k_dim {
let val = local_matrix[[small_row, small_col]];
if val.norm_sqr() < f64::EPSILON {
continue;
}
let m = (row & passive_mask) | deposit_bits(small_col, &mapped_targets);
sum += val * rho[[m, col]];
}
col_view[row] = sum;
}
});
new_rho
}
pub fn apply_local_right(
num_total_qubits: usize,
rho: &Array2<Complex64>,
local_matrix: &Array2<Complex64>,
targets: &[usize],
controls: &[usize],
) -> Array2<Complex64> {
let dim = 1 << num_total_qubits;
let mut new_rho = Array2::<Complex64>::zeros((dim, dim));
let mapped_controls: Vec<usize> = controls.iter().map(|&c| num_total_qubits - 1 - c).collect();
let mut mapped_targets: Vec<usize> =
targets.iter().map(|&t| num_total_qubits - 1 - t).collect();
mapped_targets.reverse();
let mut control_mask = 0usize;
for &c in &mapped_controls {
control_mask |= 1 << c;
}
let mut target_mask = 0usize;
for &t in &mapped_targets {
target_mask |= 1 << t;
}
let passive_mask = !target_mask;
let k_dim = 1 << targets.len();
new_rho
.axis_iter_mut(Axis(0))
.into_par_iter()
.enumerate()
.for_each(|(row, mut row_view)| {
for col in 0..dim {
if (col & control_mask) != control_mask {
row_view[col] = rho[[row, col]];
continue;
}
let small_col_idx = extract_bits(col, &mapped_targets);
let mut sum = Complex64::new(0.0, 0.0);
for small_row_idx in 0..k_dim {
let val = local_matrix[[small_row_idx, small_col_idx]];
if val.norm_sqr() < f64::EPSILON {
continue;
}
let m = (col & passive_mask) | deposit_bits(small_row_idx, &mapped_targets);
sum += rho[[row, m]] * val;
}
row_view[col] = sum;
}
});
new_rho
}
pub fn apply_local_vector(
num_total_qubits: usize,
psi: &Array1<Complex64>,
local_matrix: &Array2<Complex64>,
targets: &[usize],
controls: &[usize],
) -> Array1<Complex64> {
let dim = 1 << num_total_qubits;
let mut new_psi = Array1::<Complex64>::zeros(dim);
let mapped_controls: Vec<usize> = controls.iter().map(|&c| num_total_qubits - 1 - c).collect();
let mut mapped_targets: Vec<usize> =
targets.iter().map(|&t| num_total_qubits - 1 - t).collect();
mapped_targets.reverse();
let mut control_mask = 0usize;
for &c in &mapped_controls {
control_mask |= 1 << c;
}
let mut target_mask = 0usize;
for &t in &mapped_targets {
target_mask |= 1 << t;
}
let passive_mask = !target_mask;
let k_dim = 1 << targets.len();
new_psi
.as_slice_mut()
.unwrap()
.par_iter_mut()
.enumerate()
.for_each(|(row, el)| {
if (row & control_mask) != control_mask {
*el = psi[row];
return;
}
let small_row = extract_bits(row, &mapped_targets);
let mut sum = Complex64::new(0.0, 0.0);
for small_col in 0..k_dim {
let val = local_matrix[[small_row, small_col]];
if val.norm_sqr() < f64::EPSILON {
continue;
}
let m = (row & passive_mask) | deposit_bits(small_col, &mapped_targets);
sum += val * psi[m];
}
*el = sum;
});
new_psi
}
#[cfg(test)]
mod tests {
use super::*;
use ndarray::{Array2, array};
#[test]
fn test_extract_bits() {
assert_eq!(extract_bits(0b1101, &[0, 2]), 0b11);
assert_eq!(extract_bits(0b1010, &[1, 3]), 0b11);
assert_eq!(extract_bits(0b1010, &[0, 2]), 0b00);
}
#[test]
fn test_deposit_bits() {
assert_eq!(deposit_bits(0b11, &[0, 2]), 0b101);
assert_eq!(deposit_bits(0b01, &[1, 3]), 0b0010);
}
#[test]
fn test_extract_deposit_roundtrip() {
let indices = vec![1, 3];
let original = 0b1010usize; let extracted = extract_bits(original, &indices);
let deposited = deposit_bits(extracted, &indices);
assert_eq!(deposited, original & ((1 << 2) | (1 << 4) - 1)); assert_eq!(extracted, 0b11);
}
#[test]
fn test_kronecker_product_identity() {
let i2: Array2<Complex64> = Array2::eye(2);
let result = kronecker_product_matrix(&i2, &i2);
let i4: Array2<Complex64> = Array2::eye(4);
for (a, b) in result.iter().zip(i4.iter()) {
assert!((*a - *b).norm() < 1e-12);
}
}
#[test]
fn test_kronecker_product_dimensions() {
let a = Array2::<Complex64>::eye(2);
let b = Array2::<Complex64>::eye(4);
let result = kronecker_product_matrix(&a, &b);
assert_eq!(result.dim(), (8, 8));
}
#[test]
fn test_trace_identity() {
let eye: Array2<Complex64> = Array2::eye(4);
let tr = trace(&eye);
assert!((tr - Complex64::new(4.0, 0.0)).norm() < 1e-12);
}
#[test]
fn test_trace_zero() {
let zero = Array2::<Complex64>::zeros((3, 3));
let tr = trace(&zero);
assert!(tr.norm() < 1e-12);
}
#[test]
fn test_outer_product_projector() {
let v0: Array1<Complex64> = array![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)];
let proj = outer_product(&v0, &v0);
assert!((proj[[0, 0]] - Complex64::new(1.0, 0.0)).norm() < 1e-12);
assert!(proj[[0, 1]].norm() < 1e-12);
assert!(proj[[1, 0]].norm() < 1e-12);
assert!(proj[[1, 1]].norm() < 1e-12);
}
#[test]
fn test_find_duplicate_none() {
assert_eq!(find_duplicate(&[0, 1, 2, 3]), None);
}
#[test]
fn test_find_duplicate_found() {
assert_eq!(find_duplicate(&[0, 1, 2, 1]), Some(1));
}
#[test]
fn test_find_duplicate_empty() {
let empty: &[usize] = &[];
assert_eq!(find_duplicate(empty), None);
}
#[test]
fn test_kraus_completeness_identity() {
let eye: Array2<Complex64> = Array2::eye(2);
assert!(check_kraus_completeness(&[eye], 2));
}
#[test]
fn test_kraus_completeness_fails() {
let zero = Array2::<Complex64>::zeros((2, 2));
assert!(!check_kraus_completeness(&[zero], 2));
}
#[test]
fn test_povm_completeness_z_basis() {
let p0 = Array2::from_diag(&array![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)]);
let p1 = Array2::from_diag(&array![Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)]);
assert!(check_povm_completeness(&[p0, p1], 2));
}
#[test]
fn test_is_hermitian_identity() {
let eye: Array2<Complex64> = Array2::eye(2);
assert!(is_hermitian(&eye, 1e-12));
}
#[test]
fn test_is_hermitian_fails() {
let mat = array![
[Complex64::new(1.0, 0.0), Complex64::new(0.0, 1.0)],
[Complex64::new(0.0, 1.0), Complex64::new(1.0, 0.0)]
];
assert!(!is_hermitian(&mat, 1e-12));
}
#[test]
fn test_expand_identity_is_identity() {
let i2: Array2<Complex64> = Array2::eye(2);
let expanded = expand_operator(&i2, 2, &[0], &[]);
let i4: Array2<Complex64> = Array2::eye(4);
for (a, b) in expanded.iter().zip(i4.iter()) {
assert!((*a - *b).norm() < 1e-12);
}
}
#[test]
fn test_sqrt_positive_matrix_2x2_zero() {
let zero = Array2::<Complex64>::zeros((2, 2));
let result = sqrt_positive_matrix(&zero);
for &val in result.iter() {
assert!(val.norm() < 1e-12);
}
}
#[test]
fn test_sqrt_positive_matrix_4x4() {
let eye = Array2::<Complex64>::eye(4);
let result = sqrt_positive_matrix(&eye);
for (a, b) in result.iter().zip(eye.iter()) {
assert!((*a - *b).norm() < 1e-12);
}
}
#[test]
fn test_apply_local_left_identity() {
let i2 = Array2::<Complex64>::eye(2);
let rho = Array2::<Complex64>::eye(4);
let new_rho = apply_local_left(2, &rho, &i2, &[0], &[]);
for (a, b) in new_rho.iter().zip(rho.iter()) {
assert!((*a - *b).norm() < 1e-12);
}
}
#[test]
fn test_apply_local_right_identity() {
let i2 = Array2::<Complex64>::eye(2);
let rho = Array2::<Complex64>::eye(4);
let new_rho = apply_local_right(2, &rho, &i2, &[0], &[]);
for (a, b) in new_rho.iter().zip(rho.iter()) {
assert!((*a - *b).norm() < 1e-12);
}
}
#[test]
fn test_apply_local_vector_identity() {
let i2 = Array2::<Complex64>::eye(2);
let psi = Array1::<Complex64>::zeros(4);
let new_psi = apply_local_vector(2, &psi, &i2, &[0], &[]);
for (a, b) in new_psi.iter().zip(psi.iter()) {
assert!((*a - *b).norm() < 1e-12);
}
}
#[test]
fn test_expand_cnot_matrix_structure() {
let cnot = super::super::gates::Gate::cnot();
let m = &cnot.matrix;
assert!((m[[0, 0]] - Complex64::new(1.0, 0.0)).norm() < 1e-12);
assert!((m[[1, 1]] - Complex64::new(1.0, 0.0)).norm() < 1e-12);
assert!((m[[3, 2]] - Complex64::new(1.0, 0.0)).norm() < 1e-12);
assert!(m[[2, 2]].norm() < 1e-12);
assert!((m[[2, 3]] - Complex64::new(1.0, 0.0)).norm() < 1e-12);
assert!(m[[3, 3]].norm() < 1e-12);
}
#[test]
fn test_expand_operator_qubit_ordering_3q() {
let x = super::super::gates::Gate::x();
let expanded = expand_operator(&x.matrix, 3, &[1], &[]);
assert!((expanded[[2, 0]] - Complex64::new(1.0, 0.0)).norm() < 1e-12);
assert!(expanded[[0, 0]].norm() < 1e-12);
assert!((expanded[[0, 2]] - Complex64::new(1.0, 0.0)).norm() < 1e-12);
assert!((expanded[[7, 5]] - Complex64::new(1.0, 0.0)).norm() < 1e-12);
assert!(expanded[[5, 5]].norm() < 1e-12);
}
#[test]
fn test_apply_local_vector_cnot_ordering() {
let cnot = super::super::gates::Gate::cnot();
let mut psi = Array1::<Complex64>::zeros(4);
psi[2] = Complex64::new(1.0, 0.0);
let result = apply_local_vector(2, &psi, &cnot.matrix, &[0, 1], &[]);
assert!(result[2].norm() < 1e-12, "|10⟩ should be empty");
assert!(
(result[3] - Complex64::new(1.0, 0.0)).norm() < 1e-12,
"Expected |11⟩"
);
}
#[test]
fn test_apply_local_vector_cnot_no_fire() {
let cnot = super::super::gates::Gate::cnot();
let mut psi = Array1::<Complex64>::zeros(4);
psi[1] = Complex64::new(1.0, 0.0);
let result = apply_local_vector(2, &psi, &cnot.matrix, &[0, 1], &[]);
assert!(
(result[1] - Complex64::new(1.0, 0.0)).norm() < 1e-12,
"|01⟩ must stay"
);
}
#[test]
fn test_apply_local_vector_cnot_reversed_targets() {
let cnot = super::super::gates::Gate::cnot();
let mut psi = Array1::<Complex64>::zeros(4);
psi[1] = Complex64::new(1.0, 0.0);
let result = apply_local_vector(2, &psi, &cnot.matrix, &[1, 0], &[]);
assert!(
(result[3] - Complex64::new(1.0, 0.0)).norm() < 1e-12,
"Expected |11⟩"
);
assert!(result[1].norm() < 1e-12, "|01⟩ should be empty");
}
#[test]
fn test_apply_local_vector_swap_ordering() {
let swap = super::super::gates::Gate::swap();
let mut psi = Array1::<Complex64>::zeros(4);
psi[2] = Complex64::new(1.0, 0.0);
let result = apply_local_vector(2, &psi, &swap.matrix, &[0, 1], &[]);
assert!(
(result[1] - Complex64::new(1.0, 0.0)).norm() < 1e-12,
"Expected |01⟩"
);
assert!(result[2].norm() < 1e-12, "|10⟩ should be empty");
}
#[test]
fn test_apply_local_vector_bell_state() {
let h = super::super::gates::Gate::h();
let mut psi = Array1::<Complex64>::zeros(4);
psi[0] = Complex64::new(1.0, 0.0);
let psi = apply_local_vector(2, &psi, &h.matrix, &[0], &[]);
let cnot = super::super::gates::Gate::cnot();
let result = apply_local_vector(2, &psi, &cnot.matrix, &[0, 1], &[]);
let s = 1.0 / 2.0_f64.sqrt();
assert!(
(result[0] - Complex64::new(s, 0.0)).norm() < 1e-12,
"Expected |00⟩ amp"
);
assert!(result[1].norm() < 1e-12, "|01⟩ must be zero");
assert!(result[2].norm() < 1e-12, "|10⟩ must be zero");
assert!(
(result[3] - Complex64::new(s, 0.0)).norm() < 1e-12,
"Expected |11⟩ amp"
);
}
#[test]
fn test_apply_local_left_cnot_ordering() {
let cnot = super::super::gates::Gate::cnot();
let mut rho = Array2::<Complex64>::zeros((4, 4));
rho[[2, 2]] = Complex64::new(1.0, 0.0);
let result = apply_local_left(2, &rho, &cnot.matrix, &[0, 1], &[]);
assert!((result[[3, 2]] - Complex64::new(1.0, 0.0)).norm() < 1e-12);
assert!(result[[2, 2]].norm() < 1e-12);
}
#[test]
fn test_apply_local_right_cnot_ordering() {
let cnot = super::super::gates::Gate::cnot();
let mut rho = Array2::<Complex64>::zeros((4, 4));
rho[[2, 2]] = Complex64::new(1.0, 0.0);
let result = apply_local_right(2, &rho, &cnot.matrix, &[0, 1], &[]);
assert!((result[[2, 3]] - Complex64::new(1.0, 0.0)).norm() < 1e-12);
assert!(result[[2, 2]].norm() < 1e-12);
}
#[test]
fn test_apply_local_left_right_full_unitary() {
let cnot = super::super::gates::Gate::cnot();
let mut rho = Array2::<Complex64>::zeros((4, 4));
rho[[2, 2]] = Complex64::new(1.0, 0.0);
let tmp = apply_local_left(2, &rho, &cnot.matrix, &[0, 1], &[]);
let result = apply_local_right(2, &tmp, &cnot.matrix, &[0, 1], &[]);
assert!((result[[3, 3]] - Complex64::new(1.0, 0.0)).norm() < 1e-12);
assert!(result[[2, 2]].norm() < 1e-12);
assert!(result[[2, 3]].norm() < 1e-12);
assert!(result[[3, 2]].norm() < 1e-12);
}
#[test]
fn test_apply_local_vector_cnot_in_3q_system() {
let cnot = super::super::gates::Gate::cnot();
let mut psi = Array1::<Complex64>::zeros(8);
psi[4] = Complex64::new(1.0, 0.0);
let result = apply_local_vector(3, &psi, &cnot.matrix, &[0, 2], &[]);
assert!(
(result[5] - Complex64::new(1.0, 0.0)).norm() < 1e-12,
"Expected |101⟩"
);
assert!(result[4].norm() < 1e-12, "|100⟩ should be empty");
}
#[test]
fn test_apply_local_vector_cnot_3q_no_fire() {
let cnot = super::super::gates::Gate::cnot();
let mut psi = Array1::<Complex64>::zeros(8);
psi[2] = Complex64::new(1.0, 0.0);
let result = apply_local_vector(3, &psi, &cnot.matrix, &[0, 2], &[]);
assert!(
(result[2] - Complex64::new(1.0, 0.0)).norm() < 1e-12,
"|010⟩ must stay"
);
}
#[test]
fn test_sqrt_2x2_analytical_zero_trace() {
let mat = ndarray::array![
[Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)],
[Complex64::new(0.0, 0.0), Complex64::new(0.0, 0.0)]
];
let sqrt = sqrt_positive_matrix(&mat);
for &val in sqrt.iter() {
assert!(val.norm() < 1e-12);
}
}
#[test]
fn test_sqrt_2x2_analytical_nonzero_det() {
let mat = ndarray::array![
[Complex64::new(4.0, 0.0), Complex64::new(0.0, 0.0)],
[Complex64::new(0.0, 0.0), Complex64::new(9.0, 0.0)]
];
let sqrt = sqrt_positive_matrix(&mat);
assert!((sqrt[[0, 0]] - Complex64::new(2.0, 0.0)).norm() < 1e-12);
assert!((sqrt[[1, 1]] - Complex64::new(3.0, 0.0)).norm() < 1e-12);
}
}