use crate::error::{QuantumError, QuantumResult};
use num_complex::Complex;
pub type Mat2 = [[Complex<f64>; 2]; 2];
#[inline]
fn cx(re: f64, im: f64) -> Complex<f64> {
Complex::new(re, im)
}
#[inline]
fn mat2_mul(a: &Mat2, b: &Mat2) -> Mat2 {
[
[
a[0][0] * b[0][0] + a[0][1] * b[1][0],
a[0][0] * b[0][1] + a[0][1] * b[1][1],
],
[
a[1][0] * b[0][0] + a[1][1] * b[1][0],
a[1][0] * b[0][1] + a[1][1] * b[1][1],
],
]
}
pub fn signal_operator(x: f64) -> QuantumResult<Mat2> {
if !x.is_finite() || x.abs() > 1.0 {
return Err(QuantumError::InvalidParameter {
name: format!("QSP signal x={x} must lie in [-1, 1]"),
});
}
let s = (1.0 - x * x).max(0.0).sqrt();
Ok([[cx(x, 0.0), cx(0.0, s)], [cx(0.0, s), cx(x, 0.0)]])
}
#[inline]
fn z_rotation(phi: f64) -> Mat2 {
[
[cx(phi.cos(), phi.sin()), cx(0.0, 0.0)],
[cx(0.0, 0.0), cx(phi.cos(), -phi.sin())],
]
}
pub fn qsp_unitary(phi: &[f64], x: f64) -> QuantumResult<Mat2> {
if phi.is_empty() {
return Err(QuantumError::EmptyInput);
}
let w = signal_operator(x)?;
let mut u = z_rotation(phi[0]);
for &phi_k in &phi[1..] {
let block = mat2_mul(&w, &z_rotation(phi_k));
u = mat2_mul(&u, &block);
}
Ok(u)
}
pub fn qsp_top_left(phi: &[f64], x: f64) -> QuantumResult<Complex<f64>> {
let u = qsp_unitary(phi, x)?;
Ok(u[0][0])
}
pub fn chebyshev_qsp_angles(d: usize) -> QuantumResult<Vec<f64>> {
Ok(vec![0.0_f64; d + 1])
}
#[must_use]
pub fn chebyshev_t(d: usize, x: f64) -> f64 {
if d == 0 {
return 1.0;
}
if d == 1 {
return x;
}
let mut t_prev = 1.0_f64; let mut t_cur = x; for _ in 2..=d {
let t_next = 2.0 * x * t_cur - t_prev;
t_prev = t_cur;
t_cur = t_next;
}
t_cur
}
#[cfg(test)]
mod tests {
use super::*;
use std::f64::consts::PI;
fn mat2_dagger(a: &Mat2) -> Mat2 {
[
[a[0][0].conj(), a[1][0].conj()],
[a[0][1].conj(), a[1][1].conj()],
]
}
fn assert_unitary(u: &Mat2, tol: f64) {
let prod = mat2_mul(u, &mat2_dagger(u));
let id = [[cx(1.0, 0.0), cx(0.0, 0.0)], [cx(0.0, 0.0), cx(1.0, 0.0)]];
for r in 0..2 {
for col in 0..2 {
let diff = prod[r][col] - id[r][col];
assert!(
diff.norm() < tol,
"U U† not identity at [{r}][{col}]: {:?}",
prod[r][col]
);
}
}
}
#[test]
fn qsp_degree_one_zero_angles_is_x() {
for &x in &[-0.9_f64, -0.3, 0.0, 0.25, 0.5, 0.87, 1.0] {
let p = qsp_top_left(&[0.0, 0.0], x)
.expect("x is within [-1, 1] and angles slice is non-empty");
assert!((p.re - x).abs() < 1e-12, "x={x}, P.re={}", p.re);
assert!(p.im.abs() < 1e-12, "x={x}, P.im={}", p.im);
}
}
#[test]
fn qsp_chebyshev_angles_reproduce_t_d() {
for &d in &[2_usize, 3] {
let angles =
chebyshev_qsp_angles(d).expect("chebyshev_qsp_angles is infallible for any degree");
assert_eq!(angles.len(), d + 1);
for &x in &[-1.0_f64, -0.73, -0.4, -0.1, 0.0, 0.2, 0.55, 0.81, 1.0] {
let p = qsp_top_left(&angles, x).expect(
"chebyshev angles are non-empty and all test x values are within [-1, 1]",
);
let want = chebyshev_t(d, x);
assert!(
(p.re - want).abs() < 1e-10,
"d={d}, x={x}: P.re={} vs T_d={want}",
p.re
);
assert!(p.im.abs() < 1e-10, "d={d}, x={x}: P.im={}", p.im);
}
}
}
#[test]
fn qsp_chebyshev_high_degree_matches() {
let d = 5;
let angles =
chebyshev_qsp_angles(d).expect("chebyshev_qsp_angles is infallible for any degree");
for &x in &[-0.95_f64, -0.5, 0.0, 0.33, 0.66, 0.99] {
let p = qsp_top_left(&angles, x)
.expect("angles are non-empty and all test x values are within [-1, 1]");
assert!((p.re - chebyshev_t(d, x)).abs() < 1e-9, "x={x}");
}
}
#[test]
fn qsp_unitary_is_unitary() {
let angle_sets: &[&[f64]] = &[
&[0.0],
&[0.3, -0.7],
&[0.1, 0.2, 0.3, 0.4],
&[PI / 4.0, -PI / 3.0, PI / 6.0, 0.9, -1.2],
];
for angles in angle_sets {
for &x in &[-1.0_f64, -0.6, -0.2, 0.0, 0.45, 0.77, 1.0] {
let u = qsp_unitary(angles, x).expect(
"all test angle sets are non-empty and all test x values are within [-1, 1]",
);
assert_unitary(&u, 1e-10);
}
}
}
#[test]
fn qsp_parity_matches_degree() {
for &d in &[2_usize, 3, 4, 5] {
let angles =
chebyshev_qsp_angles(d).expect("chebyshev_qsp_angles is infallible for any degree");
let sign = if d % 2 == 0 { 1.0 } else { -1.0 };
for &x in &[0.13_f64, 0.37, 0.61, 0.88] {
let p_pos = qsp_top_left(&angles, x)
.expect("chebyshev angles are non-empty and x is in (0, 1]");
let p_neg = qsp_top_left(&angles, -x)
.expect("chebyshev angles are non-empty and -x is in [-1, 0) since x > 0");
assert!(
(p_neg.re - sign * p_pos.re).abs() < 1e-10,
"d={d}, x={x}: P(-x).re={} vs {}·P(x).re={}",
p_neg.re,
sign,
sign * p_pos.re
);
assert!(
(p_neg.im - sign * p_pos.im).abs() < 1e-10,
"d={d}, x={x}: imag parity mismatch"
);
}
}
}
#[test]
fn qsp_top_left_bounded_by_one() {
let angle_sets: &[&[f64]] = &[
&[0.0, 0.0, 0.0], &[0.0, 0.0, 0.0, 0.0], &[0.5, -1.1, 0.7, 0.2], &[PI / 3.0, 0.0, -PI / 5.0],
];
for angles in angle_sets {
for i in 0..=200 {
let x = -1.0 + 2.0 * (i as f64) / 200.0;
let p = qsp_top_left(angles, x)
.expect("angle sets are all non-empty and x is on a grid within [-1, 1]");
assert!(
p.norm() <= 1.0 + 1e-10,
"|P({x})|={} exceeds 1 for angles={angles:?}",
p.norm()
);
}
}
}
#[test]
fn qsp_empty_angles_errors() {
assert!(qsp_unitary(&[], 0.5).is_err());
assert!(qsp_top_left(&[], 0.5).is_err());
}
#[test]
fn qsp_signal_out_of_range_errors() {
assert!(signal_operator(1.5).is_err());
assert!(signal_operator(-1.0001).is_err());
assert!(signal_operator(f64::NAN).is_err());
assert!(qsp_unitary(&[0.0, 0.0], 2.0).is_err());
}
#[test]
fn signal_operator_form_and_unitarity() {
let x = 0.6_f64;
let w = signal_operator(x).expect("x=0.6 is within the valid range [-1, 1]");
let s = (1.0_f64 - x * x).sqrt();
assert!((w[0][0].re - x).abs() < 1e-12);
assert!((w[0][1].im - s).abs() < 1e-12);
assert!((w[1][0].im - s).abs() < 1e-12);
assert!((w[1][1].re - x).abs() < 1e-12);
assert_unitary(&w, 1e-12);
}
}