#![allow(clippy::doc_markdown)]
use std::f64::consts::TAU;
use crate::algorithms::qft;
use crate::circuit::Circuit;
use crate::gate::Gate1;
use crate::state::State;
#[must_use]
pub fn phase_estimation(counting_qubits: usize, phi: f64) -> Circuit {
let m = counting_qubits;
let total = m + 1; let target = m;
let mut c = Circuit::new(total);
c.x(target);
for j in 0..m {
c.h(j);
}
for j in 0..m {
let angle = TAU * phi * f64::from(1u32 << j);
c.controlled(&[j], Gate1::phase(angle), target);
}
let iqft_circ = qft::iqft(m);
c.compose(&iqft_circ);
c
}
#[must_use]
pub fn most_probable_phase(state: &State, counting_qubits: usize) -> f64 {
let m = counting_qubits;
let counting_states = 1usize << m;
let mut marginal = vec![0.0f64; counting_states];
for (basis, &) in state.amplitudes().iter().enumerate() {
let counting_index = basis & (counting_states - 1); marginal[counting_index] += amp.norm_sqr();
}
let best = marginal
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.map_or(0, |(i, _)| i);
best as f64 / counting_states as f64
}
#[cfg(test)]
mod tests {
use super::*;
use crate::StateVectorBackend;
fn estimate(m: usize, phi: f64) -> f64 {
let c = phase_estimation(m, phi);
let exec = StateVectorBackend::run(&c).unwrap();
most_probable_phase(exec.state(), m)
}
#[test]
fn dyadic_phases_recovered_exactly() {
assert!((estimate(1, 0.5) - 0.5).abs() < 1e-10);
assert!((estimate(2, 0.25) - 0.25).abs() < 1e-10);
assert!((estimate(2, 0.75) - 0.75).abs() < 1e-10);
assert!((estimate(3, 0.125) - 0.125).abs() < 1e-10);
assert!((estimate(3, 0.375) - 0.375).abs() < 1e-10);
}
#[test]
fn non_dyadic_phase_approximated() {
let est = estimate(4, 0.3);
assert!((est - 0.3).abs() < 0.1, "expected ~0.3, got {est}");
}
}