quantrs2_sim/
utils.rs

1use scirs2_core::ndarray::{Array1, Array2};
2use scirs2_core::parallel_ops::{
3    IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator,
4};
5use scirs2_core::Complex64;
6
7use quantrs2_core::qubit::QubitId;
8
9/// Calculate the kronecker product of two matrices
10#[must_use]
11pub fn kron(a: &Array2<Complex64>, b: &Array2<Complex64>) -> Array2<Complex64> {
12    let a_shape = a.shape();
13    let b_shape = b.shape();
14
15    let rows = a_shape[0] * b_shape[0];
16    let cols = a_shape[1] * b_shape[1];
17
18    let mut result = Array2::zeros((rows, cols));
19
20    for i in 0..a_shape[0] {
21        for j in 0..a_shape[1] {
22            let a_val = a[[i, j]];
23            let i_offset = i * b_shape[0];
24            let j_offset = j * b_shape[1];
25
26            for k in 0..b_shape[0] {
27                for l in 0..b_shape[1] {
28                    result[[i_offset + k, j_offset + l]] = a_val * b[[k, l]];
29                }
30            }
31        }
32    }
33
34    result
35}
36
37/// Calculate the tensor product of two state vectors
38#[must_use]
39pub fn tensor_product(a: &Array1<Complex64>, b: &Array1<Complex64>) -> Array1<Complex64> {
40    let a_len = a.len();
41    let b_len = b.len();
42    let result_len = a_len * b_len;
43
44    let mut result = Array1::zeros(result_len);
45
46    for i in 0..a_len {
47        let a_val = a[i];
48        let offset = i * b_len;
49
50        for j in 0..b_len {
51            result[offset + j] = a_val * b[j];
52        }
53    }
54
55    result
56}
57
58/// Calculate the bit representation of an integer
59#[must_use]
60pub fn int_to_bits(n: usize, num_bits: usize) -> Vec<u8> {
61    let mut bits = vec![0; num_bits];
62    for i in 0..num_bits {
63        bits[num_bits - 1 - i] = ((n >> i) & 1) as u8;
64    }
65    bits
66}
67
68/// Calculate the integer representation of a bit string
69#[must_use]
70pub fn bits_to_int(bits: &[u8]) -> usize {
71    bits.iter().fold(0, |acc, &bit| (acc << 1) | bit as usize)
72}
73
74/// Compute the index with a bit flipped at the specified position
75#[must_use]
76pub const fn flip_bit(index: usize, pos: usize) -> usize {
77    index ^ (1 << pos)
78}
79
80/// Compute the index with a controlled bit flip
81///
82/// If the control bit at `ctrl_pos` is 1, then the target bit at `target_pos` is flipped.
83#[must_use]
84pub const fn controlled_flip(index: usize, ctrl_pos: usize, target_pos: usize) -> usize {
85    if (index >> ctrl_pos) & 1 == 1 {
86        flip_bit(index, target_pos)
87    } else {
88        index
89    }
90}
91
92/// Compute the global index for a multi-qubit system
93///
94/// Given a list of qubit indices, compute the global index into the state vector.
95#[must_use]
96pub fn compute_index(qubit_indices: &[QubitId], state_bits: &[u8]) -> usize {
97    assert!(
98        (qubit_indices.len() == state_bits.len()),
99        "Mismatch between qubit indices and state bits"
100    );
101
102    let mut index = 0;
103    for (&qubit, &bit) in qubit_indices.iter().zip(state_bits.iter()) {
104        let q = qubit.id() as usize;
105        if bit != 0 {
106            index |= 1 << q;
107        }
108    }
109
110    index
111}
112
113/// Parallel map over state vector elements with index
114///
115/// Apply a function to each element of the state vector in parallel.
116/// The function is passed the index and current value of each element.
117pub fn par_indexed_map<F>(state: &mut [Complex64], f: F)
118where
119    F: Fn(usize, Complex64) -> Complex64 + Sync,
120{
121    state.par_iter_mut().enumerate().for_each(|(i, v)| {
122        *v = f(i, *v);
123    });
124}
125
126/// Convert a matrix representation of a gate (row-major) to a 2D ndarray
127#[must_use]
128pub fn gate_vec_to_array2(matrix: &[Complex64], dim: usize) -> Array2<Complex64> {
129    let mut result = Array2::zeros((dim, dim));
130
131    for i in 0..dim {
132        for j in 0..dim {
133            result[[i, j]] = matrix[i * dim + j];
134        }
135    }
136
137    result
138}