use crate::circuit::{CliffordCircuit, CliffordGate};
use ndarray::{Array1, Array2};
use rand::{Rng, SeedableRng};
fn sample_quantum_mallows<R: Rng>(n: usize, rng: &mut R) -> (Array1<u8>, Array1<usize>) {
let mut h = Array1::zeros(n);
let mut s_perm = Array1::from_elem(n, 0);
let mut available_indices: Vec<usize> = (0..n).collect();
for i in 0..n {
let m = n - i;
if m == 0 {
continue;
}
let r: f64 = rng.r#gen();
let val_to_log = r * (4.0f64.powi(m as i32) - 1.0) + 1.0;
let ceil_log = val_to_log.log2().ceil();
let a = (2 * m + 1) as isize - ceil_log as isize;
let a = if a <= 0 { 1 } else { a as usize };
let k_1_based = if a <= m {
h[i] = 1;
a
} else {
h[i] = 0;
2 * m - a + 1
};
let k_0_based = k_1_based - 1;
if k_0_based < available_indices.len() {
s_perm[i] = available_indices.remove(k_0_based);
} else {
s_perm[i] = available_indices.pop().unwrap();
}
}
(h, s_perm)
}
struct CliffordParams {
h: Array1<u8>,
s: Array1<usize>,
pauli2_z: Array1<u8>,
pauli2_x: Array1<u8>,
gamma1: Array2<u8>,
delta1: Array2<u8>,
gamma2: Array2<u8>,
delta2: Array2<u8>,
}
fn generate_clifford_params<R: Rng>(n: usize, rng: &mut R) -> CliffordParams {
let (h, s) = sample_quantum_mallows(n, rng);
let mut gamma1 = Array2::zeros((n, n));
let mut delta1 = Array2::eye(n);
let mut gamma2 = Array2::zeros((n, n));
let mut delta2 = Array2::eye(n);
let pauli2_z = Array1::from_shape_fn(n, |_| rng.gen_range(0..=1));
let pauli2_x = Array1::from_shape_fn(n, |_| rng.gen_range(0..=1));
for i in 0..n {
gamma2[[i, i]] = rng.gen_range(0..=1);
if h[i] == 1 {
gamma1[[i, i]] = rng.gen_range(0..=1);
}
}
for i in 1..n {
for j in 0..i {
let b_gamma2 = rng.gen_range(0..=1);
gamma2[[i, j]] = b_gamma2;
gamma2[[j, i]] = b_gamma2;
delta2[[i, j]] = rng.gen_range(0..=1);
let (h_i, h_j) = (h[i] == 1, h[j] == 1);
let (s_i, s_j) = (s[i], s[j]);
if (s_i < s_j || h_j) && h_i || h_j && s_j < s_i {
let b = rng.gen_range(0..=1);
gamma1[[i, j]] = b;
gamma1[[j, i]] = b;
}
if (s_i < s_j || h_j) && (s_i > s_j || !h_i) && (h_j || !h_i) {
delta1[[i, j]] = rng.gen_range(0..=1);
}
}
}
CliffordParams {
h,
s,
pauli2_z,
pauli2_x,
gamma1,
delta1,
gamma2,
delta2,
}
}
fn apply_hadamard_free_layer(
qc: &mut CliffordCircuit,
n: usize,
gamma: &Array2<u8>,
delta: &Array2<u8>,
pauli_z: Option<&Array1<u8>>,
pauli_x: Option<&Array1<u8>>,
) {
for j in 0..n {
for i in (j + 1)..n {
if delta[[i, j]] == 1 {
qc.add_gate(CliffordGate::CX(j, i));
}
}
}
for i in 0..n {
if gamma[[i, i]] == 1 {
qc.add_gate(CliffordGate::S(i));
}
for j in 0..i {
if gamma[[i, j]] == 1 {
qc.add_gate(CliffordGate::CZ(i, j));
}
}
}
if let (Some(z), Some(x)) = (pauli_z, pauli_x) {
for i in 0..n {
if z[i] == 1 && x[i] == 1 {
qc.add_gate(CliffordGate::Y(i));
} else if z[i] == 1 {
qc.add_gate(CliffordGate::Z(i));
} else if x[i] == 1 {
qc.add_gate(CliffordGate::X(i));
}
}
}
}
fn apply_permutation_layer(qc: &mut CliffordCircuit, s_perm: &Array1<usize>) {
let n = s_perm.len();
let mut p: Vec<usize> = (0..n).collect();
for i in 0..n {
let target_pos = s_perm[i];
if p[i] != target_pos {
let j = p.iter().position(|&x| x == target_pos).unwrap();
qc.add_gate(CliffordGate::Swap(i, j));
p.swap(i, j);
}
}
}
pub(crate) fn random_clifford(n: usize, seed: Option<[u8; 32]>) -> CliffordCircuit {
if n == 0 {
return CliffordCircuit::new(0);
}
let mut rng = match seed {
Some(s) => rand::rngs::StdRng::from_seed(s),
None => rand::rngs::StdRng::from_entropy(),
};
let params = generate_clifford_params(n, &mut rng);
let mut qc = CliffordCircuit::new(n);
apply_hadamard_free_layer(
&mut qc,
n,
¶ms.gamma2,
¶ms.delta2,
Some(¶ms.pauli2_z),
Some(¶ms.pauli2_x),
);
apply_permutation_layer(&mut qc, ¶ms.s);
for i in 0..n {
if params.h[i] == 1 {
qc.add_gate(CliffordGate::H(i));
}
}
apply_hadamard_free_layer(&mut qc, n, ¶ms.gamma1, ¶ms.delta1, None, None);
qc
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_random_clifford_generation() {
let num_qubits = 3;
let circuit = random_clifford(num_qubits, Some([42; 32]));
assert_eq!(circuit.num_qubits, num_qubits);
assert!(circuit.gates.len() > 0);
}
#[test]
fn test_random_clifford_determinism() {
let num_qubits = 4;
let seed = [123; 32];
let circuit1 = random_clifford(num_qubits, Some(seed));
let circuit2 = random_clifford(num_qubits, Some(seed));
assert_eq!(circuit1.gates, circuit2.gates);
}
#[test]
fn test_random_clifford_validity() {
let num_qubits = 4;
let circuit = random_clifford(num_qubits, Some([56; 32]));
for gate in circuit.gates {
match gate {
CliffordGate::H(q)
| CliffordGate::X(q)
| CliffordGate::Y(q)
| CliffordGate::Z(q)
| CliffordGate::S(q)
| CliffordGate::Sdg(q)
| CliffordGate::SqrtX(q)
| CliffordGate::SqrtXdg(q) => {
assert!(q < num_qubits);
}
CliffordGate::CX(c, t) | CliffordGate::CZ(c, t) | CliffordGate::Swap(c, t) => {
assert!(c < num_qubits && t < num_qubits && c != t);
}
}
}
}
}