Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
This commit is contained in:
663
crates/ruqu-core/tests/test_gates.rs
Normal file
663
crates/ruqu-core/tests/test_gates.rs
Normal file
@@ -0,0 +1,663 @@
|
||||
//! Tests for ruqu_core::gate — gate matrix correctness and unitarity.
|
||||
|
||||
use ruqu_core::gate::Gate;
|
||||
use ruqu_core::types::Complex;
|
||||
|
||||
const EPSILON: f64 = 1e-10;
|
||||
|
||||
fn approx_eq(a: f64, b: f64) -> bool {
|
||||
(a - b).abs() < EPSILON
|
||||
}
|
||||
|
||||
fn complex_approx_eq(a: &Complex, b: &Complex) -> bool {
|
||||
approx_eq(a.re, b.re) && approx_eq(a.im, b.im)
|
||||
}
|
||||
|
||||
/// Convenience: build a Complex value.
|
||||
fn c(re: f64, im: f64) -> Complex {
|
||||
Complex { re, im }
|
||||
}
|
||||
|
||||
/// Check that a 2x2 matrix satisfies U^dag * U = I (unitarity).
|
||||
fn assert_unitary_2x2(m: &[[Complex; 2]; 2]) {
|
||||
// Flatten to make indexing easier: flat[i*2+j] = m[i][j]
|
||||
let flat = [m[0][0], m[0][1], m[1][0], m[1][1]];
|
||||
// U^dag * U should equal identity
|
||||
let udu = [
|
||||
// (0,0)
|
||||
flat[0].conj() * flat[0] + flat[2].conj() * flat[2],
|
||||
// (0,1)
|
||||
flat[0].conj() * flat[1] + flat[2].conj() * flat[3],
|
||||
// (1,0)
|
||||
flat[1].conj() * flat[0] + flat[3].conj() * flat[2],
|
||||
// (1,1)
|
||||
flat[1].conj() * flat[1] + flat[3].conj() * flat[3],
|
||||
];
|
||||
assert!(
|
||||
complex_approx_eq(&udu[0], &c(1.0, 0.0)),
|
||||
"U^dag U [0,0] = {:?}, expected 1",
|
||||
udu[0]
|
||||
);
|
||||
assert!(
|
||||
complex_approx_eq(&udu[1], &c(0.0, 0.0)),
|
||||
"U^dag U [0,1] = {:?}, expected 0",
|
||||
udu[1]
|
||||
);
|
||||
assert!(
|
||||
complex_approx_eq(&udu[2], &c(0.0, 0.0)),
|
||||
"U^dag U [1,0] = {:?}, expected 0",
|
||||
udu[2]
|
||||
);
|
||||
assert!(
|
||||
complex_approx_eq(&udu[3], &c(1.0, 0.0)),
|
||||
"U^dag U [1,1] = {:?}, expected 1",
|
||||
udu[3]
|
||||
);
|
||||
}
|
||||
|
||||
/// Check unitarity for a 4x4 matrix.
|
||||
fn assert_unitary_4x4(m: &[[Complex; 4]; 4]) {
|
||||
for i in 0..4 {
|
||||
for j in 0..4 {
|
||||
let mut sum = c(0.0, 0.0);
|
||||
for k in 0..4 {
|
||||
// (U^dag)_{ik} = conj(U_{ki})
|
||||
let u_dag_ik = m[k][i].conj();
|
||||
let u_kj = m[k][j];
|
||||
sum = sum + u_dag_ik * u_kj;
|
||||
}
|
||||
let expected = if i == j { c(1.0, 0.0) } else { c(0.0, 0.0) };
|
||||
assert!(
|
||||
complex_approx_eq(&sum, &expected),
|
||||
"U^dag U [{},{}] = ({}, {}), expected ({}, {})",
|
||||
i,
|
||||
j,
|
||||
sum.re,
|
||||
sum.im,
|
||||
expected.re,
|
||||
expected.im
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Hadamard gate: H = 1/sqrt(2) * [[1, 1], [1, -1]]
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_hadamard_matrix() {
|
||||
let matrix = Gate::H(0).matrix_1q().expect("H should have a 2x2 matrix");
|
||||
let s = std::f64::consts::FRAC_1_SQRT_2;
|
||||
|
||||
assert!(complex_approx_eq(&matrix[0][0], &c(s, 0.0))); // [0,0]
|
||||
assert!(complex_approx_eq(&matrix[0][1], &c(s, 0.0))); // [0,1]
|
||||
assert!(complex_approx_eq(&matrix[1][0], &c(s, 0.0))); // [1,0]
|
||||
assert!(complex_approx_eq(&matrix[1][1], &c(-s, 0.0))); // [1,1]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hadamard_is_self_inverse() {
|
||||
// H * H = I
|
||||
let m = Gate::H(0).matrix_1q().unwrap();
|
||||
// Multiply m * m
|
||||
let prod = [
|
||||
m[0][0] * m[0][0] + m[0][1] * m[1][0],
|
||||
m[0][0] * m[0][1] + m[0][1] * m[1][1],
|
||||
m[1][0] * m[0][0] + m[1][1] * m[1][0],
|
||||
m[1][0] * m[0][1] + m[1][1] * m[1][1],
|
||||
];
|
||||
assert!(complex_approx_eq(&prod[0], &c(1.0, 0.0)));
|
||||
assert!(complex_approx_eq(&prod[1], &c(0.0, 0.0)));
|
||||
assert!(complex_approx_eq(&prod[2], &c(0.0, 0.0)));
|
||||
assert!(complex_approx_eq(&prod[3], &c(1.0, 0.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hadamard_unitarity() {
|
||||
let m = Gate::H(0).matrix_1q().unwrap();
|
||||
assert_unitary_2x2(&m);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Pauli-X gate: [[0, 1], [1, 0]]
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_pauli_x_matrix() {
|
||||
let m = Gate::X(0).matrix_1q().expect("X should have a 2x2 matrix");
|
||||
assert!(complex_approx_eq(&m[0][0], &c(0.0, 0.0)));
|
||||
assert!(complex_approx_eq(&m[0][1], &c(1.0, 0.0)));
|
||||
assert!(complex_approx_eq(&m[1][0], &c(1.0, 0.0)));
|
||||
assert!(complex_approx_eq(&m[1][1], &c(0.0, 0.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pauli_x_is_self_inverse() {
|
||||
let m = Gate::X(0).matrix_1q().unwrap();
|
||||
let prod = [
|
||||
m[0][0] * m[0][0] + m[0][1] * m[1][0],
|
||||
m[0][0] * m[0][1] + m[0][1] * m[1][1],
|
||||
m[1][0] * m[0][0] + m[1][1] * m[1][0],
|
||||
m[1][0] * m[0][1] + m[1][1] * m[1][1],
|
||||
];
|
||||
assert!(complex_approx_eq(&prod[0], &c(1.0, 0.0)));
|
||||
assert!(complex_approx_eq(&prod[3], &c(1.0, 0.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pauli_x_unitarity() {
|
||||
let m = Gate::X(0).matrix_1q().unwrap();
|
||||
assert_unitary_2x2(&m);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Pauli-Y gate: [[0, -i], [i, 0]]
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_pauli_y_matrix() {
|
||||
let m = Gate::Y(0).matrix_1q().expect("Y should have a 2x2 matrix");
|
||||
assert!(complex_approx_eq(&m[0][0], &c(0.0, 0.0)));
|
||||
assert!(complex_approx_eq(&m[0][1], &c(0.0, -1.0)));
|
||||
assert!(complex_approx_eq(&m[1][0], &c(0.0, 1.0)));
|
||||
assert!(complex_approx_eq(&m[1][1], &c(0.0, 0.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pauli_y_is_self_inverse() {
|
||||
let m = Gate::Y(0).matrix_1q().unwrap();
|
||||
let prod = [
|
||||
m[0][0] * m[0][0] + m[0][1] * m[1][0],
|
||||
m[0][0] * m[0][1] + m[0][1] * m[1][1],
|
||||
m[1][0] * m[0][0] + m[1][1] * m[1][0],
|
||||
m[1][0] * m[0][1] + m[1][1] * m[1][1],
|
||||
];
|
||||
assert!(complex_approx_eq(&prod[0], &c(1.0, 0.0)));
|
||||
assert!(complex_approx_eq(&prod[3], &c(1.0, 0.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pauli_y_unitarity() {
|
||||
let m = Gate::Y(0).matrix_1q().unwrap();
|
||||
assert_unitary_2x2(&m);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Pauli-Z gate: [[1, 0], [0, -1]]
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_pauli_z_matrix() {
|
||||
let m = Gate::Z(0).matrix_1q().expect("Z should have a 2x2 matrix");
|
||||
assert!(complex_approx_eq(&m[0][0], &c(1.0, 0.0)));
|
||||
assert!(complex_approx_eq(&m[0][1], &c(0.0, 0.0)));
|
||||
assert!(complex_approx_eq(&m[1][0], &c(0.0, 0.0)));
|
||||
assert!(complex_approx_eq(&m[1][1], &c(-1.0, 0.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pauli_z_is_self_inverse() {
|
||||
let m = Gate::Z(0).matrix_1q().unwrap();
|
||||
let prod = [
|
||||
m[0][0] * m[0][0] + m[0][1] * m[1][0],
|
||||
m[0][0] * m[0][1] + m[0][1] * m[1][1],
|
||||
m[1][0] * m[0][0] + m[1][1] * m[1][0],
|
||||
m[1][0] * m[0][1] + m[1][1] * m[1][1],
|
||||
];
|
||||
assert!(complex_approx_eq(&prod[0], &c(1.0, 0.0)));
|
||||
assert!(complex_approx_eq(&prod[3], &c(1.0, 0.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pauli_z_unitarity() {
|
||||
let m = Gate::Z(0).matrix_1q().unwrap();
|
||||
assert_unitary_2x2(&m);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Pauli algebra: X*Y = iZ, Y*Z = iX, Z*X = iY
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_pauli_xy_equals_iz() {
|
||||
let x = Gate::X(0).matrix_1q().unwrap();
|
||||
let y = Gate::Y(0).matrix_1q().unwrap();
|
||||
let z = Gate::Z(0).matrix_1q().unwrap();
|
||||
|
||||
// X * Y (2x2 matrix multiply)
|
||||
let xy = [
|
||||
[
|
||||
x[0][0] * y[0][0] + x[0][1] * y[1][0],
|
||||
x[0][0] * y[0][1] + x[0][1] * y[1][1],
|
||||
],
|
||||
[
|
||||
x[1][0] * y[0][0] + x[1][1] * y[1][0],
|
||||
x[1][0] * y[0][1] + x[1][1] * y[1][1],
|
||||
],
|
||||
];
|
||||
// i * Z
|
||||
let iz = [
|
||||
[c(0.0, 1.0) * z[0][0], c(0.0, 1.0) * z[0][1]],
|
||||
[c(0.0, 1.0) * z[1][0], c(0.0, 1.0) * z[1][1]],
|
||||
];
|
||||
for i in 0..2 {
|
||||
for j in 0..2 {
|
||||
assert!(
|
||||
complex_approx_eq(&xy[i][j], &iz[i][j]),
|
||||
"XY[{},{}] = ({}, {}), iZ[{},{}] = ({}, {})",
|
||||
i,
|
||||
j,
|
||||
xy[i][j].re,
|
||||
xy[i][j].im,
|
||||
i,
|
||||
j,
|
||||
iz[i][j].re,
|
||||
iz[i][j].im
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// S gate: [[1, 0], [0, i]]
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_s_gate_matrix() {
|
||||
let m = Gate::S(0).matrix_1q().expect("S should have a 2x2 matrix");
|
||||
assert!(complex_approx_eq(&m[0][0], &c(1.0, 0.0)));
|
||||
assert!(complex_approx_eq(&m[0][1], &c(0.0, 0.0)));
|
||||
assert!(complex_approx_eq(&m[1][0], &c(0.0, 0.0)));
|
||||
assert!(complex_approx_eq(&m[1][1], &c(0.0, 1.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_s_gate_unitarity() {
|
||||
let m = Gate::S(0).matrix_1q().unwrap();
|
||||
assert_unitary_2x2(&m);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_s_squared_is_z() {
|
||||
// S^2 = Z
|
||||
let s = Gate::S(0).matrix_1q().unwrap();
|
||||
let z = Gate::Z(0).matrix_1q().unwrap();
|
||||
let s2 = [
|
||||
[
|
||||
s[0][0] * s[0][0] + s[0][1] * s[1][0],
|
||||
s[0][0] * s[0][1] + s[0][1] * s[1][1],
|
||||
],
|
||||
[
|
||||
s[1][0] * s[0][0] + s[1][1] * s[1][0],
|
||||
s[1][0] * s[0][1] + s[1][1] * s[1][1],
|
||||
],
|
||||
];
|
||||
for i in 0..2 {
|
||||
for j in 0..2 {
|
||||
assert!(
|
||||
complex_approx_eq(&s2[i][j], &z[i][j]),
|
||||
"S^2[{},{}] != Z[{},{}]",
|
||||
i,
|
||||
j,
|
||||
i,
|
||||
j
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// T gate: [[1, 0], [0, e^{i*pi/4}]]
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_t_gate_matrix() {
|
||||
let m = Gate::T(0).matrix_1q().expect("T should have a 2x2 matrix");
|
||||
let phase = std::f64::consts::FRAC_PI_4;
|
||||
assert!(complex_approx_eq(&m[0][0], &c(1.0, 0.0)));
|
||||
assert!(complex_approx_eq(&m[0][1], &c(0.0, 0.0)));
|
||||
assert!(complex_approx_eq(&m[1][0], &c(0.0, 0.0)));
|
||||
assert!(complex_approx_eq(&m[1][1], &c(phase.cos(), phase.sin())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_t_gate_unitarity() {
|
||||
let m = Gate::T(0).matrix_1q().unwrap();
|
||||
assert_unitary_2x2(&m);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_t_squared_is_s() {
|
||||
// T^2 = S
|
||||
let t = Gate::T(0).matrix_1q().unwrap();
|
||||
let s = Gate::S(0).matrix_1q().unwrap();
|
||||
let t2 = [
|
||||
[
|
||||
t[0][0] * t[0][0] + t[0][1] * t[1][0],
|
||||
t[0][0] * t[0][1] + t[0][1] * t[1][1],
|
||||
],
|
||||
[
|
||||
t[1][0] * t[0][0] + t[1][1] * t[1][0],
|
||||
t[1][0] * t[0][1] + t[1][1] * t[1][1],
|
||||
],
|
||||
];
|
||||
for i in 0..2 {
|
||||
for j in 0..2 {
|
||||
assert!(
|
||||
complex_approx_eq(&t2[i][j], &s[i][j]),
|
||||
"T^2[{},{}] != S[{},{}]",
|
||||
i,
|
||||
j,
|
||||
i,
|
||||
j
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Rotation gates: Rx, Ry, Rz
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_rx_zero_is_identity() {
|
||||
let m = Gate::Rx(0, 0.0).matrix_1q().unwrap();
|
||||
assert!(complex_approx_eq(&m[0][0], &c(1.0, 0.0)));
|
||||
assert!(complex_approx_eq(&m[0][1], &c(0.0, 0.0)));
|
||||
assert!(complex_approx_eq(&m[1][0], &c(0.0, 0.0)));
|
||||
assert!(complex_approx_eq(&m[1][1], &c(1.0, 0.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rx_pi_matrix() {
|
||||
// Rx(pi) = [[cos(pi/2), -i*sin(pi/2)], [-i*sin(pi/2), cos(pi/2)]]
|
||||
// = [[0, -i], [-i, 0]]
|
||||
let m = Gate::Rx(0, std::f64::consts::PI).matrix_1q().unwrap();
|
||||
assert!(complex_approx_eq(&m[0][0], &c(0.0, 0.0)));
|
||||
assert!(complex_approx_eq(&m[0][1], &c(0.0, -1.0)));
|
||||
assert!(complex_approx_eq(&m[1][0], &c(0.0, -1.0)));
|
||||
assert!(complex_approx_eq(&m[1][1], &c(0.0, 0.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rx_unitarity() {
|
||||
let m = Gate::Rx(0, 1.234).matrix_1q().unwrap();
|
||||
assert_unitary_2x2(&m);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ry_zero_is_identity() {
|
||||
let m = Gate::Ry(0, 0.0).matrix_1q().unwrap();
|
||||
assert!(complex_approx_eq(&m[0][0], &c(1.0, 0.0)));
|
||||
assert!(complex_approx_eq(&m[1][1], &c(1.0, 0.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ry_pi_matrix() {
|
||||
// Ry(pi) = [[cos(pi/2), -sin(pi/2)], [sin(pi/2), cos(pi/2)]]
|
||||
// = [[0, -1], [1, 0]]
|
||||
let m = Gate::Ry(0, std::f64::consts::PI).matrix_1q().unwrap();
|
||||
assert!(complex_approx_eq(&m[0][0], &c(0.0, 0.0)));
|
||||
assert!(complex_approx_eq(&m[0][1], &c(-1.0, 0.0)));
|
||||
assert!(complex_approx_eq(&m[1][0], &c(1.0, 0.0)));
|
||||
assert!(complex_approx_eq(&m[1][1], &c(0.0, 0.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ry_unitarity() {
|
||||
let m = Gate::Ry(0, 2.718).matrix_1q().unwrap();
|
||||
assert_unitary_2x2(&m);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rz_zero_is_identity() {
|
||||
let m = Gate::Rz(0, 0.0).matrix_1q().unwrap();
|
||||
assert!(complex_approx_eq(&m[0][0], &c(1.0, 0.0)));
|
||||
assert!(complex_approx_eq(&m[1][1], &c(1.0, 0.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rz_pi_matrix() {
|
||||
// Rz(pi) = [[e^{-i*pi/2}, 0], [0, e^{i*pi/2}]] = [[-i, 0], [0, i]]
|
||||
let m = Gate::Rz(0, std::f64::consts::PI).matrix_1q().unwrap();
|
||||
assert!(complex_approx_eq(&m[0][0], &c(0.0, -1.0)));
|
||||
assert!(complex_approx_eq(&m[0][1], &c(0.0, 0.0)));
|
||||
assert!(complex_approx_eq(&m[1][0], &c(0.0, 0.0)));
|
||||
assert!(complex_approx_eq(&m[1][1], &c(0.0, 1.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rz_unitarity() {
|
||||
let m = Gate::Rz(0, 0.789).matrix_1q().unwrap();
|
||||
assert_unitary_2x2(&m);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rotation_gates_various_angles_unitary() {
|
||||
let angles = [
|
||||
0.0,
|
||||
0.1,
|
||||
0.5,
|
||||
1.0,
|
||||
std::f64::consts::PI,
|
||||
2.0 * std::f64::consts::PI,
|
||||
-0.7,
|
||||
];
|
||||
for &theta in &angles {
|
||||
let rx = Gate::Rx(0, theta).matrix_1q().unwrap();
|
||||
assert_unitary_2x2(&rx);
|
||||
|
||||
let ry = Gate::Ry(0, theta).matrix_1q().unwrap();
|
||||
assert_unitary_2x2(&ry);
|
||||
|
||||
let rz = Gate::Rz(0, theta).matrix_1q().unwrap();
|
||||
assert_unitary_2x2(&rz);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CNOT gate: 4x4 matrix
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_cnot_matrix() {
|
||||
// CNOT = [[1,0,0,0],[0,1,0,0],[0,0,0,1],[0,0,1,0]]
|
||||
let m = Gate::CNOT(0, 1)
|
||||
.matrix_2q()
|
||||
.expect("CNOT should have a 4x4 matrix");
|
||||
|
||||
let expected: [[Complex; 4]; 4] = [
|
||||
[c(1.0, 0.0), c(0.0, 0.0), c(0.0, 0.0), c(0.0, 0.0)], // row 0
|
||||
[c(0.0, 0.0), c(1.0, 0.0), c(0.0, 0.0), c(0.0, 0.0)], // row 1
|
||||
[c(0.0, 0.0), c(0.0, 0.0), c(0.0, 0.0), c(1.0, 0.0)], // row 2
|
||||
[c(0.0, 0.0), c(0.0, 0.0), c(1.0, 0.0), c(0.0, 0.0)], // row 3
|
||||
];
|
||||
|
||||
for i in 0..4 {
|
||||
for j in 0..4 {
|
||||
assert!(
|
||||
complex_approx_eq(&m[i][j], &expected[i][j]),
|
||||
"CNOT matrix[{}][{}]: got ({}, {}), expected ({}, {})",
|
||||
i,
|
||||
j,
|
||||
m[i][j].re,
|
||||
m[i][j].im,
|
||||
expected[i][j].re,
|
||||
expected[i][j].im
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cnot_unitarity() {
|
||||
let m = Gate::CNOT(0, 1).matrix_2q().unwrap();
|
||||
assert_unitary_4x4(&m);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cnot_is_self_inverse() {
|
||||
// CNOT * CNOT = I
|
||||
let m = Gate::CNOT(0, 1).matrix_2q().unwrap();
|
||||
for i in 0..4 {
|
||||
for j in 0..4 {
|
||||
let mut sum = c(0.0, 0.0);
|
||||
for k in 0..4 {
|
||||
sum = sum + m[i][k] * m[k][j];
|
||||
}
|
||||
let expected = if i == j { c(1.0, 0.0) } else { c(0.0, 0.0) };
|
||||
assert!(
|
||||
complex_approx_eq(&sum, &expected),
|
||||
"CNOT^2 [{},{}] = ({}, {}), expected ({}, {})",
|
||||
i,
|
||||
j,
|
||||
sum.re,
|
||||
sum.im,
|
||||
expected.re,
|
||||
expected.im
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CZ gate: 4x4 matrix diag(1, 1, 1, -1)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_cz_matrix() {
|
||||
let m = Gate::CZ(0, 1)
|
||||
.matrix_2q()
|
||||
.expect("CZ should have a 4x4 matrix");
|
||||
|
||||
// CZ = diag(1, 1, 1, -1)
|
||||
for i in 0..4 {
|
||||
for j in 0..4 {
|
||||
let expected = if i == j {
|
||||
if i == 3 {
|
||||
c(-1.0, 0.0)
|
||||
} else {
|
||||
c(1.0, 0.0)
|
||||
}
|
||||
} else {
|
||||
c(0.0, 0.0)
|
||||
};
|
||||
assert!(
|
||||
complex_approx_eq(&m[i][j], &expected),
|
||||
"CZ[{},{}] mismatch",
|
||||
i,
|
||||
j
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cz_unitarity() {
|
||||
let m = Gate::CZ(0, 1).matrix_2q().unwrap();
|
||||
assert_unitary_4x4(&m);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cz_is_symmetric() {
|
||||
// CZ(0,1) should equal CZ(1,0) — the gate is symmetric in control/target
|
||||
let m01 = Gate::CZ(0, 1).matrix_2q().unwrap();
|
||||
let m10 = Gate::CZ(1, 0).matrix_2q().unwrap();
|
||||
for i in 0..4 {
|
||||
for j in 0..4 {
|
||||
assert!(
|
||||
complex_approx_eq(&m01[i][j], &m10[i][j]),
|
||||
"CZ symmetry mismatch at [{},{}]",
|
||||
i,
|
||||
j
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SWAP gate: 4x4 matrix
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_swap_matrix() {
|
||||
// SWAP = [[1,0,0,0],[0,0,1,0],[0,1,0,0],[0,0,0,1]]
|
||||
let m = Gate::SWAP(0, 1)
|
||||
.matrix_2q()
|
||||
.expect("SWAP should have a 4x4 matrix");
|
||||
|
||||
let expected: [[Complex; 4]; 4] = [
|
||||
[c(1.0, 0.0), c(0.0, 0.0), c(0.0, 0.0), c(0.0, 0.0)], // row 0
|
||||
[c(0.0, 0.0), c(0.0, 0.0), c(1.0, 0.0), c(0.0, 0.0)], // row 1
|
||||
[c(0.0, 0.0), c(1.0, 0.0), c(0.0, 0.0), c(0.0, 0.0)], // row 2
|
||||
[c(0.0, 0.0), c(0.0, 0.0), c(0.0, 0.0), c(1.0, 0.0)], // row 3
|
||||
];
|
||||
|
||||
for i in 0..4 {
|
||||
for j in 0..4 {
|
||||
assert!(
|
||||
complex_approx_eq(&m[i][j], &expected[i][j]),
|
||||
"SWAP matrix[{}][{}] mismatch",
|
||||
i,
|
||||
j
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_swap_unitarity() {
|
||||
let m = Gate::SWAP(0, 1).matrix_2q().unwrap();
|
||||
assert_unitary_4x4(&m);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_swap_is_self_inverse() {
|
||||
// SWAP * SWAP = I
|
||||
let m = Gate::SWAP(0, 1).matrix_2q().unwrap();
|
||||
for i in 0..4 {
|
||||
for j in 0..4 {
|
||||
let mut sum = c(0.0, 0.0);
|
||||
for k in 0..4 {
|
||||
sum = sum + m[i][k] * m[k][j];
|
||||
}
|
||||
let expected = if i == j { c(1.0, 0.0) } else { c(0.0, 0.0) };
|
||||
assert!(
|
||||
complex_approx_eq(&sum, &expected),
|
||||
"SWAP^2 [{},{}] mismatch",
|
||||
i,
|
||||
j
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Gate qubit index extraction
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_gate_qubit_indices() {
|
||||
// Single-qubit gates should report their target qubit via qubits().
|
||||
assert_eq!(Gate::H(3).qubits(), vec![3]);
|
||||
assert_eq!(Gate::X(0).qubits(), vec![0]);
|
||||
assert_eq!(Gate::Y(7).qubits(), vec![7]);
|
||||
assert_eq!(Gate::Z(15).qubits(), vec![15]);
|
||||
assert_eq!(Gate::Rx(2, 0.5).qubits(), vec![2]);
|
||||
assert_eq!(Gate::Ry(4, 1.0).qubits(), vec![4]);
|
||||
assert_eq!(Gate::Rz(6, 0.3).qubits(), vec![6]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_two_qubit_gate_indices() {
|
||||
// Two-qubit gates should report both qubits.
|
||||
let qubits = Gate::CNOT(0, 1).qubits();
|
||||
assert_eq!(qubits.len(), 2);
|
||||
assert_eq!(qubits[0], 0);
|
||||
assert_eq!(qubits[1], 1);
|
||||
|
||||
let qubits2 = Gate::CNOT(5, 3).qubits();
|
||||
assert_eq!(qubits2[0], 5);
|
||||
assert_eq!(qubits2[1], 3);
|
||||
}
|
||||
446
crates/ruqu-core/tests/test_simulator.rs
Normal file
446
crates/ruqu-core/tests/test_simulator.rs
Normal file
@@ -0,0 +1,446 @@
|
||||
//! Tests for ruqu_core::simulator — high-level simulator, circuits, shots, reproducibility.
|
||||
|
||||
use ruqu_core::prelude::*;
|
||||
|
||||
const EPSILON: f64 = 1e-10;
|
||||
|
||||
fn approx_eq(a: f64, b: f64) -> bool {
|
||||
(a - b).abs() < EPSILON
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Basic circuit construction
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_circuit_new() {
|
||||
let circuit = QuantumCircuit::new(3);
|
||||
assert_eq!(circuit.num_qubits(), 3);
|
||||
assert_eq!(circuit.gate_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_circuit_add_gates() {
|
||||
let mut circuit = QuantumCircuit::new(2);
|
||||
circuit.h(0).cnot(0, 1);
|
||||
assert_eq!(circuit.num_qubits(), 2);
|
||||
assert_eq!(circuit.gate_count(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_circuit_chaining() {
|
||||
let mut circuit = QuantumCircuit::new(3);
|
||||
circuit.h(0).h(1).h(2).cnot(0, 1).cnot(1, 2);
|
||||
assert_eq!(circuit.gate_count(), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_circuit_gates_ref() {
|
||||
let mut circuit = QuantumCircuit::new(1);
|
||||
circuit.h(0).x(0);
|
||||
let gates = circuit.gates();
|
||||
assert_eq!(gates.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_circuit_all_single_qubit_gates() {
|
||||
let mut circuit = QuantumCircuit::new(1);
|
||||
circuit
|
||||
.h(0)
|
||||
.x(0)
|
||||
.y(0)
|
||||
.z(0)
|
||||
.s(0)
|
||||
.t(0)
|
||||
.rx(0, 0.5)
|
||||
.ry(0, 0.5)
|
||||
.rz(0, 0.5);
|
||||
assert_eq!(circuit.gate_count(), 9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_circuit_two_qubit_gates() {
|
||||
let mut circuit = QuantumCircuit::new(2);
|
||||
circuit.cnot(0, 1).cz(0, 1).swap(0, 1).rzz(0, 1, 0.5);
|
||||
assert_eq!(circuit.gate_count(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_circuit_measure() {
|
||||
let mut circuit = QuantumCircuit::new(2);
|
||||
circuit.h(0).cnot(0, 1).measure(0).measure(1);
|
||||
assert_eq!(circuit.gate_count(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_circuit_measure_all() {
|
||||
let mut circuit = QuantumCircuit::new(3);
|
||||
circuit.h(0).h(1).h(2).measure_all();
|
||||
// measure_all adds a measure for each qubit
|
||||
assert!(circuit.gate_count() >= 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_circuit_reset() {
|
||||
let mut circuit = QuantumCircuit::new(1);
|
||||
circuit.x(0).reset(0);
|
||||
assert_eq!(circuit.gate_count(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_circuit_barrier() {
|
||||
let mut circuit = QuantumCircuit::new(2);
|
||||
circuit.h(0).barrier().cnot(0, 1);
|
||||
assert_eq!(circuit.gate_count(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_circuit_add_gate_directly() {
|
||||
let mut circuit = QuantumCircuit::new(2);
|
||||
circuit.add_gate(Gate::H(0));
|
||||
circuit.add_gate(Gate::CNOT(0, 1));
|
||||
assert_eq!(circuit.gate_count(), 2);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Circuit depth
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_circuit_depth_single_gate() {
|
||||
let mut circuit = QuantumCircuit::new(1);
|
||||
circuit.h(0);
|
||||
assert_eq!(circuit.depth(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_circuit_depth_parallel_gates() {
|
||||
let mut circuit = QuantumCircuit::new(3);
|
||||
circuit.h(0).h(1).h(2);
|
||||
// All H gates act on different qubits, so depth = 1
|
||||
assert!(circuit.depth() >= 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_circuit_depth_sequential() {
|
||||
let mut circuit = QuantumCircuit::new(1);
|
||||
circuit.h(0).x(0).y(0);
|
||||
// All on same qubit, depth = 3
|
||||
assert!(circuit.depth() >= 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_circuit_depth_mixed() {
|
||||
let mut circuit = QuantumCircuit::new(3);
|
||||
circuit.h(0).h(1).h(2).cnot(0, 1).cnot(1, 2);
|
||||
// H gates parallel (depth 1), then CNOT(0,1) (depth 2), then CNOT(1,2) (depth 3)
|
||||
assert!(circuit.depth() >= 2);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Simulator::run
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_simulator_basic() {
|
||||
let mut circuit = QuantumCircuit::new(2);
|
||||
circuit.h(0).cnot(0, 1);
|
||||
let result = Simulator::run(&circuit).unwrap();
|
||||
assert_eq!(result.metrics.num_qubits, 2);
|
||||
assert!(result.metrics.gate_count >= 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simulator_bell_state_probabilities() {
|
||||
let mut circuit = QuantumCircuit::new(2);
|
||||
circuit.h(0).cnot(0, 1);
|
||||
let result = Simulator::run(&circuit).unwrap();
|
||||
let probs = result.state.probabilities();
|
||||
assert!(approx_eq(probs[0], 0.5)); // |00>
|
||||
assert!(approx_eq(probs[1], 0.0)); // |01>
|
||||
assert!(approx_eq(probs[2], 0.0)); // |10>
|
||||
assert!(approx_eq(probs[3], 0.5)); // |11>
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simulator_identity_circuit() {
|
||||
// No gates at all: state should remain |0>
|
||||
let circuit = QuantumCircuit::new(1);
|
||||
let result = Simulator::run(&circuit).unwrap();
|
||||
let probs = result.state.probabilities();
|
||||
assert!(approx_eq(probs[0], 1.0));
|
||||
assert!(approx_eq(probs[1], 0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simulator_x_gate() {
|
||||
let mut circuit = QuantumCircuit::new(1);
|
||||
circuit.x(0);
|
||||
let result = Simulator::run(&circuit).unwrap();
|
||||
let probs = result.state.probabilities();
|
||||
assert!(approx_eq(probs[0], 0.0));
|
||||
assert!(approx_eq(probs[1], 1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simulator_ghz() {
|
||||
let mut circuit = QuantumCircuit::new(3);
|
||||
circuit.h(0).cnot(0, 1).cnot(1, 2);
|
||||
let result = Simulator::run(&circuit).unwrap();
|
||||
let probs = result.state.probabilities();
|
||||
assert!(approx_eq(probs[0], 0.5));
|
||||
assert!(approx_eq(probs[7], 0.5));
|
||||
for i in 1..7 {
|
||||
assert!(approx_eq(probs[i], 0.0));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simulator_with_measurement() {
|
||||
let mut circuit = QuantumCircuit::new(1);
|
||||
circuit.x(0).measure(0);
|
||||
let result = Simulator::run(&circuit).unwrap();
|
||||
assert!(!result.measurements.is_empty());
|
||||
// X|0> = |1>, so measurement should be 1
|
||||
assert!(result.measurements[0].result);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Simulator::run_with_config (seeded)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_seeded_reproducibility() {
|
||||
let mut circuit = QuantumCircuit::new(2);
|
||||
circuit.h(0).measure(0);
|
||||
|
||||
let config = SimConfig {
|
||||
seed: Some(42),
|
||||
noise: None,
|
||||
shots: None,
|
||||
};
|
||||
|
||||
let r1 = Simulator::run_with_config(&circuit, &config).unwrap();
|
||||
let r2 = Simulator::run_with_config(&circuit, &config).unwrap();
|
||||
|
||||
assert_eq!(r1.measurements.len(), r2.measurements.len());
|
||||
if !r1.measurements.is_empty() && !r2.measurements.is_empty() {
|
||||
assert_eq!(r1.measurements[0].result, r2.measurements[0].result);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_different_seeds_may_differ() {
|
||||
// With different seeds and a probabilistic circuit, results may differ
|
||||
// (not guaranteed per single run, but validates that config is used)
|
||||
let mut circuit = QuantumCircuit::new(1);
|
||||
circuit.h(0).measure(0);
|
||||
|
||||
let c1 = SimConfig {
|
||||
seed: Some(42),
|
||||
noise: None,
|
||||
shots: None,
|
||||
};
|
||||
let c2 = SimConfig {
|
||||
seed: Some(99),
|
||||
noise: None,
|
||||
shots: None,
|
||||
};
|
||||
|
||||
let _r1 = Simulator::run_with_config(&circuit, &c1).unwrap();
|
||||
let _r2 = Simulator::run_with_config(&circuit, &c2).unwrap();
|
||||
// We just verify both complete without error.
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_no_seed() {
|
||||
let mut circuit = QuantumCircuit::new(1);
|
||||
circuit.h(0).measure(0);
|
||||
|
||||
let config = SimConfig {
|
||||
seed: None,
|
||||
noise: None,
|
||||
shots: None,
|
||||
};
|
||||
|
||||
let result = Simulator::run_with_config(&circuit, &config).unwrap();
|
||||
assert!(!result.measurements.is_empty());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Simulator::run_shots
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_run_shots_basic() {
|
||||
let mut circuit = QuantumCircuit::new(1);
|
||||
circuit.x(0).measure(0);
|
||||
|
||||
let result = Simulator::run_shots(&circuit, 100, Some(42)).unwrap();
|
||||
// X|0> = |1>, every shot should measure 1
|
||||
let total: usize = result.counts.values().sum();
|
||||
assert_eq!(total, 100);
|
||||
// All shots should give outcome |1> -> vec![true]
|
||||
let count_one = result.counts.get(&vec![true]).copied().unwrap_or(0);
|
||||
assert_eq!(count_one, 100, "All shots should measure |1>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_shots_superposition() {
|
||||
let mut circuit = QuantumCircuit::new(1);
|
||||
circuit.h(0).measure(0);
|
||||
|
||||
let result = Simulator::run_shots(&circuit, 1000, Some(42)).unwrap();
|
||||
let total: usize = result.counts.values().sum();
|
||||
assert_eq!(total, 1000);
|
||||
|
||||
let count_zero = result.counts.get(&vec![false]).copied().unwrap_or(0);
|
||||
let count_one = result.counts.get(&vec![true]).copied().unwrap_or(0);
|
||||
assert_eq!(count_zero + count_one, 1000);
|
||||
|
||||
// Expect roughly 50/50 with tolerance
|
||||
let ratio = count_zero as f64 / 1000.0;
|
||||
assert!(
|
||||
ratio > 0.4 && ratio < 0.6,
|
||||
"Expected ~50% zeros, got {:.1}%",
|
||||
ratio * 100.0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_shots_bell_state() {
|
||||
let mut circuit = QuantumCircuit::new(2);
|
||||
circuit.h(0).cnot(0, 1).measure(0).measure(1);
|
||||
|
||||
let result = Simulator::run_shots(&circuit, 500, Some(42)).unwrap();
|
||||
let total: usize = result.counts.values().sum();
|
||||
assert_eq!(total, 500);
|
||||
|
||||
// Bell state: only |00> and |11> outcomes
|
||||
let count_00 = result.counts.get(&vec![false, false]).copied().unwrap_or(0);
|
||||
let count_11 = result.counts.get(&vec![true, true]).copied().unwrap_or(0);
|
||||
let count_01 = result.counts.get(&vec![false, true]).copied().unwrap_or(0);
|
||||
let count_10 = result.counts.get(&vec![true, false]).copied().unwrap_or(0);
|
||||
|
||||
assert_eq!(
|
||||
count_01 + count_10,
|
||||
0,
|
||||
"Bell state should never produce |01> or |10>"
|
||||
);
|
||||
assert_eq!(count_00 + count_11, 500);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_shots_seeded_reproducibility() {
|
||||
let mut circuit = QuantumCircuit::new(1);
|
||||
circuit.h(0).measure(0);
|
||||
|
||||
let r1 = Simulator::run_shots(&circuit, 100, Some(42)).unwrap();
|
||||
let r2 = Simulator::run_shots(&circuit, 100, Some(42)).unwrap();
|
||||
|
||||
assert_eq!(r1.counts, r2.counts);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_shots_single_shot() {
|
||||
let mut circuit = QuantumCircuit::new(1);
|
||||
circuit.h(0).measure(0);
|
||||
|
||||
let result = Simulator::run_shots(&circuit, 1, Some(42)).unwrap();
|
||||
let total: usize = result.counts.values().sum();
|
||||
assert_eq!(total, 1);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Memory estimation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_memory_estimate_1_qubit() {
|
||||
// 2^1 = 2 complex numbers * 16 bytes (re: f64 + im: f64) = 32
|
||||
assert_eq!(QuantumState::estimate_memory(1), 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_memory_estimate_10_qubits() {
|
||||
// 2^10 = 1024 * 16 = 16384
|
||||
assert_eq!(QuantumState::estimate_memory(10), 16384);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_memory_estimate_scales_exponentially() {
|
||||
let m5 = QuantumState::estimate_memory(5);
|
||||
let m6 = QuantumState::estimate_memory(6);
|
||||
assert_eq!(m6, m5 * 2);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metrics from simulation result
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_simulation_metrics_qubit_count() {
|
||||
let mut circuit = QuantumCircuit::new(5);
|
||||
circuit.h(0);
|
||||
let result = Simulator::run(&circuit).unwrap();
|
||||
assert_eq!(result.metrics.num_qubits, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simulation_metrics_gate_count() {
|
||||
let mut circuit = QuantumCircuit::new(3);
|
||||
circuit.h(0).cnot(0, 1).cnot(1, 2).x(2);
|
||||
let result = Simulator::run(&circuit).unwrap();
|
||||
assert!(result.metrics.gate_count >= 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simulation_result_state_vector_length() {
|
||||
let mut circuit = QuantumCircuit::new(3);
|
||||
circuit.h(0);
|
||||
let result = Simulator::run(&circuit).unwrap();
|
||||
// 2^3 = 8 probabilities
|
||||
let probs = result.state.probabilities();
|
||||
assert_eq!(probs.len(), 8);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Complex circuits
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_qft_like_circuit() {
|
||||
// Simplified QFT-like pattern: H on each, controlled rotations
|
||||
let mut circuit = QuantumCircuit::new(3);
|
||||
circuit
|
||||
.h(0)
|
||||
.rz(0, std::f64::consts::FRAC_PI_4)
|
||||
.rz(0, std::f64::consts::FRAC_PI_2)
|
||||
.h(1)
|
||||
.rz(1, std::f64::consts::FRAC_PI_4)
|
||||
.h(2)
|
||||
.swap(0, 2);
|
||||
|
||||
let result = Simulator::run(&circuit).unwrap();
|
||||
let total: f64 = result.state.probabilities().iter().sum();
|
||||
assert!(approx_eq(total, 1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_many_gate_circuit() {
|
||||
// Stress test: many gates, verify normalization
|
||||
let n = 5;
|
||||
let mut circuit = QuantumCircuit::new(n);
|
||||
for i in 0..n {
|
||||
circuit.h(i);
|
||||
}
|
||||
for i in 0..(n - 1) {
|
||||
circuit.cnot(i, i + 1);
|
||||
}
|
||||
for i in 0..n {
|
||||
circuit.rz(i, 0.3 * (i as f64));
|
||||
}
|
||||
let result = Simulator::run(&circuit).unwrap();
|
||||
let total: f64 = result.state.probabilities().iter().sum();
|
||||
assert!(approx_eq(total, 1.0));
|
||||
}
|
||||
1029
crates/ruqu-core/tests/test_state.rs
Normal file
1029
crates/ruqu-core/tests/test_state.rs
Normal file
File diff suppressed because it is too large
Load Diff
424
crates/ruqu-core/tests/test_types.rs
Normal file
424
crates/ruqu-core/tests/test_types.rs
Normal file
@@ -0,0 +1,424 @@
|
||||
//! Tests for ruqu_core::types — Complex arithmetic, PauliString, Hamiltonian.
|
||||
|
||||
use ruqu_core::types::*;
|
||||
|
||||
const EPSILON: f64 = 1e-10;
|
||||
|
||||
fn approx_eq(a: f64, b: f64) -> bool {
|
||||
(a - b).abs() < EPSILON
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Complex – basic construction
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_complex_zero() {
|
||||
let z = Complex { re: 0.0, im: 0.0 };
|
||||
assert!(approx_eq(z.re, 0.0));
|
||||
assert!(approx_eq(z.im, 0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_real_only() {
|
||||
let z = Complex { re: 3.0, im: 0.0 };
|
||||
assert!(approx_eq(z.re, 3.0));
|
||||
assert!(approx_eq(z.im, 0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_imaginary_only() {
|
||||
let z = Complex { re: 0.0, im: 4.0 };
|
||||
assert!(approx_eq(z.re, 0.0));
|
||||
assert!(approx_eq(z.im, 4.0));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Complex – arithmetic operations
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_complex_addition() {
|
||||
let a = Complex { re: 1.0, im: 2.0 };
|
||||
let b = Complex { re: 3.0, im: -1.0 };
|
||||
let c = a + b;
|
||||
assert!(approx_eq(c.re, 4.0));
|
||||
assert!(approx_eq(c.im, 1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_subtraction() {
|
||||
let a = Complex { re: 5.0, im: 3.0 };
|
||||
let b = Complex { re: 2.0, im: 7.0 };
|
||||
let c = a - b;
|
||||
assert!(approx_eq(c.re, 3.0));
|
||||
assert!(approx_eq(c.im, -4.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_multiplication() {
|
||||
// (1+2i)*(3+4i) = 3+4i+6i+8i^2 = (3-8)+(4+6)i = -5+10i
|
||||
let a = Complex { re: 1.0, im: 2.0 };
|
||||
let b = Complex { re: 3.0, im: 4.0 };
|
||||
let c = a * b;
|
||||
assert!(approx_eq(c.re, -5.0));
|
||||
assert!(approx_eq(c.im, 10.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_multiplication_real() {
|
||||
// (2+3i) * (4+0i) = 8+12i
|
||||
let a = Complex { re: 2.0, im: 3.0 };
|
||||
let b = Complex { re: 4.0, im: 0.0 };
|
||||
let c = a * b;
|
||||
assert!(approx_eq(c.re, 8.0));
|
||||
assert!(approx_eq(c.im, 12.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_multiplication_imaginary() {
|
||||
// (0+1i) * (0+1i) = -1+0i
|
||||
let a = Complex { re: 0.0, im: 1.0 };
|
||||
let c = a * a;
|
||||
assert!(approx_eq(c.re, -1.0));
|
||||
assert!(approx_eq(c.im, 0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_negation() {
|
||||
let a = Complex { re: 3.0, im: -4.0 };
|
||||
let b = -a;
|
||||
assert!(approx_eq(b.re, -3.0));
|
||||
assert!(approx_eq(b.im, 4.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_conjugate() {
|
||||
let a = Complex { re: 3.0, im: 4.0 };
|
||||
let c = a.conj();
|
||||
assert!(approx_eq(c.re, 3.0));
|
||||
assert!(approx_eq(c.im, -4.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_conjugate_real() {
|
||||
let a = Complex { re: 5.0, im: 0.0 };
|
||||
let c = a.conj();
|
||||
assert!(approx_eq(c.re, 5.0));
|
||||
assert!(approx_eq(c.im, 0.0));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Complex – norm / magnitude
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_complex_norm_sq() {
|
||||
// |3+4i|^2 = 9+16 = 25
|
||||
let a = Complex { re: 3.0, im: 4.0 };
|
||||
assert!(approx_eq(a.norm_sq(), 25.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_norm() {
|
||||
// |3+4i| = 5
|
||||
let a = Complex { re: 3.0, im: 4.0 };
|
||||
assert!(approx_eq(a.norm(), 5.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_norm_zero() {
|
||||
let z = Complex { re: 0.0, im: 0.0 };
|
||||
assert!(approx_eq(z.norm(), 0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_unit_norm() {
|
||||
// e^{i*pi/4} has norm 1
|
||||
let angle = std::f64::consts::FRAC_PI_4;
|
||||
let z = Complex {
|
||||
re: angle.cos(),
|
||||
im: angle.sin(),
|
||||
};
|
||||
assert!(approx_eq(z.norm(), 1.0));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Complex – from_polar
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_complex_from_polar_zero_angle() {
|
||||
let z = Complex::from_polar(2.0, 0.0);
|
||||
assert!(approx_eq(z.re, 2.0));
|
||||
assert!(approx_eq(z.im, 0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_from_polar_pi_half() {
|
||||
let z = Complex::from_polar(1.0, std::f64::consts::FRAC_PI_2);
|
||||
assert!(approx_eq(z.re, 0.0));
|
||||
assert!(approx_eq(z.im, 1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_from_polar_pi() {
|
||||
let z = Complex::from_polar(1.0, std::f64::consts::PI);
|
||||
assert!(approx_eq(z.re, -1.0));
|
||||
assert!(approx_eq(z.im, 0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_from_polar_three_pi_half() {
|
||||
let z = Complex::from_polar(1.0, 3.0 * std::f64::consts::FRAC_PI_2);
|
||||
assert!(approx_eq(z.re, 0.0));
|
||||
assert!(approx_eq(z.im, -1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_from_polar_roundtrip() {
|
||||
let r = 3.5;
|
||||
let theta = 1.23;
|
||||
let z = Complex::from_polar(r, theta);
|
||||
assert!(approx_eq(z.norm(), r));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Complex – algebraic identities
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_complex_mul_conjugate_is_norm_sq() {
|
||||
// z * conj(z) = |z|^2 (real)
|
||||
let z = Complex { re: 2.0, im: -7.0 };
|
||||
let product = z * z.conj();
|
||||
assert!(approx_eq(product.re, z.norm_sq()));
|
||||
assert!(approx_eq(product.im, 0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_addition_commutativity() {
|
||||
let a = Complex { re: 1.5, im: -2.3 };
|
||||
let b = Complex { re: -0.7, im: 4.1 };
|
||||
let ab = a + b;
|
||||
let ba = b + a;
|
||||
assert!(approx_eq(ab.re, ba.re));
|
||||
assert!(approx_eq(ab.im, ba.im));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_multiplication_commutativity() {
|
||||
let a = Complex { re: 1.5, im: -2.3 };
|
||||
let b = Complex { re: -0.7, im: 4.1 };
|
||||
let ab = a * b;
|
||||
let ba = b * a;
|
||||
assert!(approx_eq(ab.re, ba.re));
|
||||
assert!(approx_eq(ab.im, ba.im));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_distributivity() {
|
||||
// a*(b+c) = a*b + a*c
|
||||
let a = Complex { re: 1.0, im: 2.0 };
|
||||
let b = Complex { re: 3.0, im: -1.0 };
|
||||
let c = Complex { re: -2.0, im: 0.5 };
|
||||
let lhs = a * (b + c);
|
||||
let rhs = a * b + a * c;
|
||||
assert!(approx_eq(lhs.re, rhs.re));
|
||||
assert!(approx_eq(lhs.im, rhs.im));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// PauliOp
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_pauli_op_variants_exist() {
|
||||
// Ensure all four Pauli operators can be constructed.
|
||||
let _i = PauliOp::I;
|
||||
let _x = PauliOp::X;
|
||||
let _y = PauliOp::Y;
|
||||
let _z = PauliOp::Z;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// PauliString
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_pauli_string_single_z() {
|
||||
let ps = PauliString {
|
||||
ops: vec![(0, PauliOp::Z)],
|
||||
};
|
||||
assert_eq!(ps.ops.len(), 1);
|
||||
assert_eq!(ps.ops[0].0, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pauli_string_zz() {
|
||||
let ps = PauliString {
|
||||
ops: vec![(0, PauliOp::Z), (1, PauliOp::Z)],
|
||||
};
|
||||
assert_eq!(ps.ops.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pauli_string_xx() {
|
||||
let ps = PauliString {
|
||||
ops: vec![(0, PauliOp::X), (1, PauliOp::X)],
|
||||
};
|
||||
assert_eq!(ps.ops.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pauli_string_mixed() {
|
||||
let ps = PauliString {
|
||||
ops: vec![
|
||||
(0, PauliOp::X),
|
||||
(1, PauliOp::Y),
|
||||
(2, PauliOp::Z),
|
||||
(3, PauliOp::I),
|
||||
],
|
||||
};
|
||||
assert_eq!(ps.ops.len(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pauli_string_empty() {
|
||||
// An empty Pauli string acts as the identity on all qubits.
|
||||
let ps = PauliString { ops: vec![] };
|
||||
assert_eq!(ps.ops.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pauli_string_high_qubit_index() {
|
||||
let ps = PauliString {
|
||||
ops: vec![(15, PauliOp::Z)],
|
||||
};
|
||||
assert_eq!(ps.ops[0].0, 15);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Hamiltonian
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_hamiltonian_single_term() {
|
||||
let h = Hamiltonian {
|
||||
terms: vec![(
|
||||
1.0,
|
||||
PauliString {
|
||||
ops: vec![(0, PauliOp::Z)],
|
||||
},
|
||||
)],
|
||||
num_qubits: 1,
|
||||
};
|
||||
assert_eq!(h.terms.len(), 1);
|
||||
assert_eq!(h.num_qubits, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hamiltonian_multiple_terms() {
|
||||
// H = 0.5*Z0 + 0.3*Z1 + 0.2*Z0Z1
|
||||
let h = Hamiltonian {
|
||||
terms: vec![
|
||||
(
|
||||
0.5,
|
||||
PauliString {
|
||||
ops: vec![(0, PauliOp::Z)],
|
||||
},
|
||||
),
|
||||
(
|
||||
0.3,
|
||||
PauliString {
|
||||
ops: vec![(1, PauliOp::Z)],
|
||||
},
|
||||
),
|
||||
(
|
||||
0.2,
|
||||
PauliString {
|
||||
ops: vec![(0, PauliOp::Z), (1, PauliOp::Z)],
|
||||
},
|
||||
),
|
||||
],
|
||||
num_qubits: 2,
|
||||
};
|
||||
assert_eq!(h.terms.len(), 3);
|
||||
assert_eq!(h.num_qubits, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hamiltonian_with_negative_coefficients() {
|
||||
let h = Hamiltonian {
|
||||
terms: vec![
|
||||
(
|
||||
-0.5,
|
||||
PauliString {
|
||||
ops: vec![(0, PauliOp::Z)],
|
||||
},
|
||||
),
|
||||
(
|
||||
1.2,
|
||||
PauliString {
|
||||
ops: vec![(0, PauliOp::X), (1, PauliOp::X)],
|
||||
},
|
||||
),
|
||||
],
|
||||
num_qubits: 2,
|
||||
};
|
||||
assert!(approx_eq(h.terms[0].0, -0.5));
|
||||
assert!(approx_eq(h.terms[1].0, 1.2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hamiltonian_empty() {
|
||||
let h = Hamiltonian {
|
||||
terms: vec![],
|
||||
num_qubits: 0,
|
||||
};
|
||||
assert_eq!(h.terms.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hamiltonian_ising_model() {
|
||||
// Simple 3-qubit Ising: H = -J * sum(Z_i Z_{i+1}) - h * sum(X_i)
|
||||
let j = 1.0_f64;
|
||||
let field = 0.5_f64;
|
||||
let h = Hamiltonian {
|
||||
terms: vec![
|
||||
(
|
||||
-j,
|
||||
PauliString {
|
||||
ops: vec![(0, PauliOp::Z), (1, PauliOp::Z)],
|
||||
},
|
||||
),
|
||||
(
|
||||
-j,
|
||||
PauliString {
|
||||
ops: vec![(1, PauliOp::Z), (2, PauliOp::Z)],
|
||||
},
|
||||
),
|
||||
(
|
||||
-field,
|
||||
PauliString {
|
||||
ops: vec![(0, PauliOp::X)],
|
||||
},
|
||||
),
|
||||
(
|
||||
-field,
|
||||
PauliString {
|
||||
ops: vec![(1, PauliOp::X)],
|
||||
},
|
||||
),
|
||||
(
|
||||
-field,
|
||||
PauliString {
|
||||
ops: vec![(2, PauliOp::X)],
|
||||
},
|
||||
),
|
||||
],
|
||||
num_qubits: 3,
|
||||
};
|
||||
assert_eq!(h.terms.len(), 5);
|
||||
assert_eq!(h.num_qubits, 3);
|
||||
}
|
||||
Reference in New Issue
Block a user