use std::f64::consts::PI;
use scirs2_core::numeric::Complex64;
use crate::error::{FFTError, FFTResult};
use crate::quantum::qft::{iqft, QftConfig, Statevector};
#[derive(Debug, Clone)]
pub struct PhaseEstimationResult {
pub phase_estimate: f64,
pub confidence: f64,
pub ancilla_probs: Vec<f64>,
}
#[derive(Debug, Clone)]
pub struct PhaseEstimationConfig {
pub n_ancilla: usize,
pub n_trials: usize,
}
impl Default for PhaseEstimationConfig {
fn default() -> Self {
Self {
n_ancilla: 8,
n_trials: 1,
}
}
}
fn apply_hadamard_ancilla(state: &mut Statevector, qubit: usize, n_qubits: usize) {
let n = state.len();
let step = 1_usize << (n_qubits - 1 - qubit);
let inv_sqrt2 = 1.0_f64 / 2.0_f64.sqrt();
let mut i = 0;
while i < n {
for j in 0..step {
let idx0 = i + j;
let idx1 = i + j + step;
let a0 = state[idx0];
let a1 = state[idx1];
state[idx0] = Complex64::new(inv_sqrt2 * (a0.re + a1.re), inv_sqrt2 * (a0.im + a1.im));
state[idx1] = Complex64::new(inv_sqrt2 * (a0.re - a1.re), inv_sqrt2 * (a0.im - a1.im));
}
i += 2 * step;
}
}
fn controlled_phase_power(
state: &mut Statevector,
ancilla_qubit: usize,
target_qubit: usize,
phase: f64,
power: u64,
n_qubits: usize,
) {
let n = state.len();
let total_phase = 2.0 * PI * phase * (power as f64);
let rot = Complex64::new(total_phase.cos(), total_phase.sin());
let anc_bit = 1_usize << (n_qubits - 1 - ancilla_qubit);
let tgt_bit = 1_usize << (n_qubits - 1 - target_qubit);
let both_mask = anc_bit | tgt_bit;
for idx in 0..n {
if (idx & both_mask) == both_mask {
state[idx] *= rot;
}
}
}
pub fn quantum_phase_estimation(
phase: f64,
config: &PhaseEstimationConfig,
) -> FFTResult<PhaseEstimationResult> {
let na = config.n_ancilla;
if na == 0 {
return Err(FFTError::ValueError(
"n_ancilla must be at least 1".to_string(),
));
}
let n_total = na + 1;
let dim = 1_usize << n_total;
let eigenstate_bit = 1_usize; let mut state = vec![Complex64::new(0.0, 0.0); dim];
state[eigenstate_bit] = Complex64::new(1.0, 0.0);
for anc in 0..na {
apply_hadamard_ancilla(&mut state, anc, n_total);
}
for anc in 0..na {
let k = na - 1 - anc; let power = 1_u64 << k;
controlled_phase_power(&mut state, anc, na, phase, power, n_total);
}
let ancilla_dim = 1_usize << na;
let mut ancilla_state: Statevector = (0..ancilla_dim)
.map(|anc_idx| {
let full_idx = (anc_idx << 1) | 1;
if full_idx < dim {
state[full_idx]
} else {
Complex64::new(0.0, 0.0)
}
})
.collect();
let iqft_cfg = QftConfig {
n_qubits: na,
apply_swap: true,
approximate: false,
approx_threshold: 1e-10,
};
ancilla_state = iqft(&ancilla_state, &iqft_cfg)?;
let ancilla_probs: Vec<f64> = ancilla_state
.iter()
.map(|a| a.re * a.re + a.im * a.im)
.collect();
let (best_idx, &best_prob) = ancilla_probs
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.ok_or_else(|| {
FFTError::ComputationError("Empty ancilla probability vector".to_string())
})?;
let phase_estimate = best_idx as f64 / ancilla_dim as f64;
Ok(PhaseEstimationResult {
phase_estimate,
confidence: best_prob,
ancilla_probs,
})
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_phase_estimation_half() {
let cfg = PhaseEstimationConfig {
n_ancilla: 4,
..Default::default()
};
let result = quantum_phase_estimation(0.5, &cfg).expect("QPE failed");
assert!(
(result.phase_estimate - 0.5).abs() < 1.0 / 16.0,
"Expected estimate ≈ 0.5 but got {}",
result.phase_estimate
);
assert!(result.confidence > 0.9, "Expected high confidence");
}
#[test]
fn test_phase_estimation_quarter() {
let cfg = PhaseEstimationConfig {
n_ancilla: 6,
..Default::default()
};
let result = quantum_phase_estimation(0.25, &cfg).expect("QPE failed");
let tolerance = 1.0 / (1_u64 << cfg.n_ancilla) as f64;
assert!(
(result.phase_estimate - 0.25).abs() <= tolerance,
"Expected estimate within {tolerance} of 0.25 but got {}",
result.phase_estimate
);
}
#[test]
fn test_phase_estimation_zero() {
let cfg = PhaseEstimationConfig {
n_ancilla: 4,
..Default::default()
};
let result = quantum_phase_estimation(0.0, &cfg).expect("QPE failed");
assert_relative_eq!(result.phase_estimate, 0.0, epsilon = 1e-10);
}
#[test]
fn test_phase_estimation_probabilities_sum_to_one() {
let cfg = PhaseEstimationConfig {
n_ancilla: 5,
..Default::default()
};
let result = quantum_phase_estimation(0.375, &cfg).expect("QPE failed");
let total: f64 = result.ancilla_probs.iter().sum();
assert!(
(total - 1.0).abs() < 1e-10,
"Ancilla probabilities should sum to 1; got {total}"
);
}
#[test]
fn test_phase_estimation_error_zero_ancilla() {
let cfg = PhaseEstimationConfig {
n_ancilla: 0,
n_trials: 1,
};
let result = quantum_phase_estimation(0.5, &cfg);
assert!(result.is_err());
}
#[test]
fn test_phase_estimation_increasing_precision() {
let phase = 0.3;
let cfg4 = PhaseEstimationConfig {
n_ancilla: 4,
n_trials: 1,
};
let cfg8 = PhaseEstimationConfig {
n_ancilla: 8,
n_trials: 1,
};
let r4 = quantum_phase_estimation(phase, &cfg4).expect("QPE4 failed");
let r8 = quantum_phase_estimation(phase, &cfg8).expect("QPE8 failed");
let err4 = (r4.phase_estimate - phase).abs();
let err8 = (r8.phase_estimate - phase).abs();
assert!(
err8 <= 1.0 / 256.0 + 1e-10,
"8-ancilla error {err8} exceeds 1/256"
);
assert!(
err4 <= 1.0 / 16.0 + 1e-10,
"4-ancilla error {err4} exceeds 1/16"
);
}
}