scirs2_fft/quantum/phase_estimation.rs
1//! Quantum Phase Estimation (QPE) circuit simulation.
2//!
3//! QPE estimates the eigenvalue e^{2πiφ} of a unitary operator U given an
4//! eigenstate |ψ⟩, using an ancilla register of n_ancilla qubits as a phase
5//! readout. The algorithm:
6//!
7//! 1. Initialise ancilla register in |0…0⟩, eigenstate register in |ψ⟩.
8//! 2. Apply Hadamard to each ancilla qubit.
9//! 3. For ancilla qubit j (0 = most significant): apply controlled-U^{2^j}
10//! acting on the eigenstate register.
11//! 4. Apply inverse QFT to the ancilla register.
12//! 5. Measure ancilla register — most probable outcome encodes φ.
13//!
14//! This module simulates the 1-qubit eigenvalue problem:
15//! U|1⟩ = e^{2πiφ}|1⟩, U|0⟩ = |0⟩
16//! which is sufficient to test and benchmark the full QPE protocol.
17
18use std::f64::consts::PI;
19
20use scirs2_core::numeric::Complex64;
21
22use crate::error::{FFTError, FFTResult};
23use crate::quantum::qft::{iqft, QftConfig, Statevector};
24
25/// Result of a Quantum Phase Estimation simulation.
26#[derive(Debug, Clone)]
27pub struct PhaseEstimationResult {
28 /// Estimated phase φ ∈ [0, 1) such that the eigenvalue is e^{2πiφ}.
29 pub phase_estimate: f64,
30 /// Probability of the dominant ancilla measurement outcome.
31 pub confidence: f64,
32 /// Full probability distribution over all 2^n_ancilla ancilla states,
33 /// ordered from |0…0⟩ to |1…1⟩.
34 pub ancilla_probs: Vec<f64>,
35}
36
37/// Configuration for Quantum Phase Estimation.
38#[derive(Debug, Clone)]
39pub struct PhaseEstimationConfig {
40 /// Number of ancilla (readout) qubits. Precision ≈ 1 / 2^n_ancilla.
41 /// Default: 8 (i.e. ≈ 1/256 precision).
42 pub n_ancilla: usize,
43 /// Number of independent trials used for iterative QPE (currently unused;
44 /// included for API forward compatibility). Default: 1.
45 pub n_trials: usize,
46}
47
48impl Default for PhaseEstimationConfig {
49 fn default() -> Self {
50 Self {
51 n_ancilla: 8,
52 n_trials: 1,
53 }
54 }
55}
56
57// ── Internal helpers ─────────────────────────────────────────────────────────
58
59/// Apply a single-qubit Hadamard to qubit `qubit` in the full statevector
60/// of size 2^n_qubits. Re-exported from qft module logic but simplified here
61/// so phase_estimation.rs has no circular dependency.
62fn apply_hadamard_ancilla(state: &mut Statevector, qubit: usize, n_qubits: usize) {
63 let n = state.len();
64 let step = 1_usize << (n_qubits - 1 - qubit);
65 let inv_sqrt2 = 1.0_f64 / 2.0_f64.sqrt();
66
67 let mut i = 0;
68 while i < n {
69 for j in 0..step {
70 let idx0 = i + j;
71 let idx1 = i + j + step;
72 let a0 = state[idx0];
73 let a1 = state[idx1];
74 state[idx0] = Complex64::new(inv_sqrt2 * (a0.re + a1.re), inv_sqrt2 * (a0.im + a1.im));
75 state[idx1] = Complex64::new(inv_sqrt2 * (a0.re - a1.re), inv_sqrt2 * (a0.im - a1.im));
76 }
77 i += 2 * step;
78 }
79}
80
81/// Simulate controlled-U^{2^power} for the 1-qubit diagonal unitary
82/// U|1⟩ = e^{2πiφ}|1⟩, U|0⟩ = |0⟩.
83///
84/// The full statevector has n_qubits total qubits: `ancilla` qubits (indices
85/// 0..n_ancilla−1) followed by the 1-qubit eigenstate register (index
86/// n_ancilla). When both the ancilla qubit `anc_bit` is |1⟩ and the
87/// eigenstate qubit (= target) is |1⟩, we multiply the amplitude by
88/// exp(2πi φ 2^power).
89fn controlled_phase_power(
90 state: &mut Statevector,
91 ancilla_qubit: usize,
92 target_qubit: usize,
93 phase: f64,
94 power: u64,
95 n_qubits: usize,
96) {
97 let n = state.len();
98 let total_phase = 2.0 * PI * phase * (power as f64);
99 let rot = Complex64::new(total_phase.cos(), total_phase.sin());
100
101 let anc_bit = 1_usize << (n_qubits - 1 - ancilla_qubit);
102 let tgt_bit = 1_usize << (n_qubits - 1 - target_qubit);
103 let both_mask = anc_bit | tgt_bit;
104
105 for idx in 0..n {
106 if (idx & both_mask) == both_mask {
107 state[idx] *= rot;
108 }
109 }
110}
111
112// ── Public API ───────────────────────────────────────────────────────────────
113
114/// Simulate Quantum Phase Estimation for the 1-qubit diagonal phase unitary
115/// U|1⟩ = e^{2πiφ}|1⟩.
116///
117/// The function constructs a combined ancilla+eigenstate register
118/// (n_ancilla + 1 qubits total), performs the full QPE circuit, and returns
119/// the estimated phase together with the ancilla measurement distribution.
120///
121/// # Arguments
122/// * `phase` – True phase φ ∈ [0, 1).
123/// * `config` – QPE configuration.
124///
125/// # Returns
126/// A [`PhaseEstimationResult`] containing the estimate, confidence, and full
127/// ancilla probability distribution.
128///
129/// # Errors
130/// Returns `FFTError` if n_ancilla is 0 or the QFT step fails.
131///
132/// # Examples
133/// ```
134/// use scirs2_fft::quantum::{PhaseEstimationConfig, quantum_phase_estimation};
135///
136/// // φ = 0.5 is representable exactly with 1 bit, so 4 ancilla qubits suffice.
137/// let cfg = PhaseEstimationConfig { n_ancilla: 4, ..Default::default() };
138/// let result = quantum_phase_estimation(0.5, &cfg).unwrap();
139/// assert!((result.phase_estimate - 0.5).abs() < 1.0 / 16.0);
140/// ```
141pub fn quantum_phase_estimation(
142 phase: f64,
143 config: &PhaseEstimationConfig,
144) -> FFTResult<PhaseEstimationResult> {
145 let na = config.n_ancilla;
146 if na == 0 {
147 return Err(FFTError::ValueError(
148 "n_ancilla must be at least 1".to_string(),
149 ));
150 }
151
152 // Total qubits: na ancilla + 1 eigenstate qubit
153 let n_total = na + 1;
154 let dim = 1_usize << n_total;
155
156 // Initialise: ancilla = |0…0⟩, eigenstate = |1⟩ → |0…0 1⟩
157 // Index in little-endian-like layout: ancilla is high-order, eigenstate is qubit n_total−1
158 let eigenstate_bit = 1_usize; // qubit index n_total−1 maps to bit 0 of index
159 let mut state = vec![Complex64::new(0.0, 0.0); dim];
160 state[eigenstate_bit] = Complex64::new(1.0, 0.0);
161
162 // Step 1: Hadamard on each ancilla qubit
163 for anc in 0..na {
164 apply_hadamard_ancilla(&mut state, anc, n_total);
165 }
166
167 // Step 2: Controlled-U^{2^k} for each ancilla qubit.
168 // Following the standard QPE circuit: ancilla qubit j (0 = most significant)
169 // controls U^{2^(na-1-j)}, so that after the IQFT the ancilla register
170 // directly encodes the binary expansion of φ with the MSB at qubit 0.
171 for anc in 0..na {
172 let k = na - 1 - anc; // so qubit 0 controls U^{2^(na-1)}, qubit na-1 controls U^1
173 let power = 1_u64 << k;
174 controlled_phase_power(&mut state, anc, na, phase, power, n_total);
175 }
176
177 // Step 3: Inverse QFT on the ancilla register only.
178 // We extract the ancilla register marginal state, apply IQFT, then rebuild
179 // the full statevector (since eigenstate is |1⟩ throughout, we can factorise).
180 //
181 // Because the eigenstate register is |1⟩ (never changed, controlled ops only
182 // add phase when it is |1⟩), the full state is:
183 // |ancilla_state⟩ ⊗ |1⟩
184 // We can extract the ancilla amplitudes by looking at indices with bit0 = 1.
185 let ancilla_dim = 1_usize << na;
186 let mut ancilla_state: Statevector = (0..ancilla_dim)
187 .map(|anc_idx| {
188 // Full index = (anc_idx << 1) | 1 (eigenstate bit = 1)
189 let full_idx = (anc_idx << 1) | 1;
190 if full_idx < dim {
191 state[full_idx]
192 } else {
193 Complex64::new(0.0, 0.0)
194 }
195 })
196 .collect();
197
198 // Apply IQFT to ancilla state
199 let iqft_cfg = QftConfig {
200 n_qubits: na,
201 apply_swap: true,
202 approximate: false,
203 approx_threshold: 1e-10,
204 };
205 ancilla_state = iqft(&ancilla_state, &iqft_cfg)?;
206
207 // Measurement probabilities for ancilla
208 let ancilla_probs: Vec<f64> = ancilla_state
209 .iter()
210 .map(|a| a.re * a.re + a.im * a.im)
211 .collect();
212
213 // Most likely outcome
214 let (best_idx, &best_prob) = ancilla_probs
215 .iter()
216 .enumerate()
217 .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
218 .ok_or_else(|| {
219 FFTError::ComputationError("Empty ancilla probability vector".to_string())
220 })?;
221
222 let phase_estimate = best_idx as f64 / ancilla_dim as f64;
223
224 Ok(PhaseEstimationResult {
225 phase_estimate,
226 confidence: best_prob,
227 ancilla_probs,
228 })
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234 use approx::assert_relative_eq;
235
236 #[test]
237 fn test_phase_estimation_half() {
238 // φ = 0.5 is exactly representable with 1 ancilla bit, so with
239 // 4 ancilla qubits we expect probability ≈1 for the |1000⟩ state (index 8).
240 let cfg = PhaseEstimationConfig {
241 n_ancilla: 4,
242 ..Default::default()
243 };
244 let result = quantum_phase_estimation(0.5, &cfg).expect("QPE failed");
245 assert!(
246 (result.phase_estimate - 0.5).abs() < 1.0 / 16.0,
247 "Expected estimate ≈ 0.5 but got {}",
248 result.phase_estimate
249 );
250 assert!(result.confidence > 0.9, "Expected high confidence");
251 }
252
253 #[test]
254 fn test_phase_estimation_quarter() {
255 // φ = 0.25 is representable exactly with 2 ancilla bits.
256 let cfg = PhaseEstimationConfig {
257 n_ancilla: 6,
258 ..Default::default()
259 };
260 let result = quantum_phase_estimation(0.25, &cfg).expect("QPE failed");
261 let tolerance = 1.0 / (1_u64 << cfg.n_ancilla) as f64;
262 assert!(
263 (result.phase_estimate - 0.25).abs() <= tolerance,
264 "Expected estimate within {tolerance} of 0.25 but got {}",
265 result.phase_estimate
266 );
267 }
268
269 #[test]
270 fn test_phase_estimation_zero() {
271 // φ = 0.0 → U is identity on |1⟩; all ancilla phases are 0.
272 // The most likely outcome is |0…0⟩.
273 let cfg = PhaseEstimationConfig {
274 n_ancilla: 4,
275 ..Default::default()
276 };
277 let result = quantum_phase_estimation(0.0, &cfg).expect("QPE failed");
278 assert_relative_eq!(result.phase_estimate, 0.0, epsilon = 1e-10);
279 }
280
281 #[test]
282 fn test_phase_estimation_probabilities_sum_to_one() {
283 let cfg = PhaseEstimationConfig {
284 n_ancilla: 5,
285 ..Default::default()
286 };
287 let result = quantum_phase_estimation(0.375, &cfg).expect("QPE failed");
288 let total: f64 = result.ancilla_probs.iter().sum();
289 assert!(
290 (total - 1.0).abs() < 1e-10,
291 "Ancilla probabilities should sum to 1; got {total}"
292 );
293 }
294
295 #[test]
296 fn test_phase_estimation_error_zero_ancilla() {
297 let cfg = PhaseEstimationConfig {
298 n_ancilla: 0,
299 n_trials: 1,
300 };
301 let result = quantum_phase_estimation(0.5, &cfg);
302 assert!(result.is_err());
303 }
304
305 #[test]
306 fn test_phase_estimation_increasing_precision() {
307 // With more ancilla qubits the estimate should be more accurate.
308 let phase = 0.3;
309 let cfg4 = PhaseEstimationConfig {
310 n_ancilla: 4,
311 n_trials: 1,
312 };
313 let cfg8 = PhaseEstimationConfig {
314 n_ancilla: 8,
315 n_trials: 1,
316 };
317
318 let r4 = quantum_phase_estimation(phase, &cfg4).expect("QPE4 failed");
319 let r8 = quantum_phase_estimation(phase, &cfg8).expect("QPE8 failed");
320
321 let err4 = (r4.phase_estimate - phase).abs();
322 let err8 = (r8.phase_estimate - phase).abs();
323
324 // 8 ancilla bits can represent multiples of 1/256 ≈ 0.0039
325 // 4 ancilla bits can represent multiples of 1/16 = 0.0625
326 // err8 should be ≤ 1/256, err4 ≤ 1/16
327 assert!(
328 err8 <= 1.0 / 256.0 + 1e-10,
329 "8-ancilla error {err8} exceeds 1/256"
330 );
331 assert!(
332 err4 <= 1.0 / 16.0 + 1e-10,
333 "4-ancilla error {err4} exceeds 1/16"
334 );
335 }
336}