1use ndarray::{Array1, Array2};
8use num_complex::Complex64;
9use std::f64::consts::PI;
10
11#[derive(Debug, Clone)]
13pub struct HHLParams {
14 pub n_qubits: usize,
16 pub clock_qubits: usize,
18 pub evolution_time: f64,
20 pub condition_number: f64,
22 pub eigenvalue_scale: f64,
24}
25
26impl HHLParams {
27 pub fn new(n_qubits: usize) -> Self {
29 Self {
30 n_qubits,
31 clock_qubits: n_qubits + 2, evolution_time: PI,
33 condition_number: 10.0,
34 eigenvalue_scale: 1.0,
35 }
36 }
37}
38
39pub struct HHLAlgorithm {
41 params: HHLParams,
42 #[allow(dead_code)]
43 matrix: Array2<Complex64>,
44 vector_b: Array1<Complex64>,
45}
46
47impl HHLAlgorithm {
48 pub fn new(
50 matrix: Array2<Complex64>,
51 vector_b: Array1<Complex64>,
52 params: HHLParams,
53 ) -> Result<Self, String> {
54 let (n, m) = matrix.dim();
56 if n != m {
57 return Err("Matrix must be square".to_string());
58 }
59
60 if n != 1 << params.n_qubits {
61 return Err(format!(
62 "Matrix size {} doesn't match qubit count {} (expected {})",
63 n,
64 params.n_qubits,
65 1 << params.n_qubits
66 ));
67 }
68
69 if vector_b.len() != n {
70 return Err("Vector b must have same dimension as matrix".to_string());
71 }
72
73 if !is_hermitian(&matrix, 1e-10) {
75 return Err("Matrix must be Hermitian".to_string());
76 }
77
78 Ok(Self {
79 params,
80 matrix,
81 vector_b,
82 })
83 }
84
85 pub fn total_qubits(&self) -> usize {
87 self.params.n_qubits + self.params.clock_qubits + 1 }
89
90 pub fn prepare_input_state(&self, state: &mut Vec<Complex64>) {
92 let n = 1 << self.params.n_qubits;
93 let clock_size = 1 << self.params.clock_qubits;
94 let total_size = n * clock_size * 2; state.clear();
98 state.resize(total_size, Complex64::new(0.0, 0.0));
99
100 let norm: f64 = self
102 .vector_b
103 .iter()
104 .map(|c| c.norm_sqr())
105 .sum::<f64>()
106 .sqrt();
107
108 for i in 0..n {
110 let amplitude = self.vector_b[i] / norm;
111 let index = i * clock_size * 2;
113 state[index] = amplitude;
114 }
115 }
116
117 pub fn apply_phase_estimation(&self, state: &mut [Complex64]) {
119 let clock_size = 1 << self.params.clock_qubits;
125 let n = 1 << self.params.n_qubits;
126
127 for clock_idx in 0..clock_size {
129 for input_idx in 0..n {
130 for ancilla_idx in 0..2 {
131 let idx = ancilla_idx + 2 * (input_idx + n * clock_idx);
132 state[idx] *= Complex64::new(1.0 / (clock_size as f64).sqrt(), 0.0);
133 }
134 }
135 }
136
137 }
142
143 pub fn apply_eigenvalue_inversion(&self, state: &mut [Complex64]) {
145 let clock_size = 1 << self.params.clock_qubits;
146 let n = 1 << self.params.n_qubits;
147
148 for clock_idx in 1..clock_size {
151 let eigenvalue =
153 (clock_idx as f64) / (clock_size as f64) * self.params.eigenvalue_scale;
154
155 let c = 1.0 / self.params.condition_number;
157 let angle = if eigenvalue > c {
158 (c / eigenvalue).asin()
159 } else {
160 PI / 2.0 };
162
163 for input_idx in 0..n {
165 let idx0 = 2 * (input_idx + n * clock_idx); let idx1 = 1 + 2 * (input_idx + n * clock_idx); let cos_angle = angle.cos();
169 let sin_angle = angle.sin();
170
171 let amp0 = state[idx0];
172 let amp1 = state[idx1];
173
174 state[idx0] = amp0 * cos_angle - amp1 * sin_angle;
175 state[idx1] = amp0 * sin_angle + amp1 * cos_angle;
176 }
177 }
178 }
179
180 pub fn apply_inverse_phase_estimation(&self, state: &mut [Complex64]) {
182 self.apply_phase_estimation(state); }
186
187 pub fn postselect_ancilla(&self, state: &mut Vec<Complex64>) -> f64 {
189 let total_size = state.len();
190 let mut success_probability = 0.0;
191 let mut new_state = vec![Complex64::new(0.0, 0.0); total_size / 2];
192
193 for (i, amp) in new_state.iter_mut().enumerate() {
195 let idx1 = 2 * i + 1; *amp = state[idx1];
197 success_probability += state[idx1].norm_sqr();
198 }
199
200 if success_probability > 1e-10 {
202 let norm = success_probability.sqrt();
203 for amp in new_state.iter_mut() {
204 *amp /= norm;
205 }
206 }
207
208 state.clear();
210 state.extend_from_slice(&new_state);
211
212 success_probability
213 }
214
215 pub fn extract_solution(&self, state: &[Complex64]) -> Array1<Complex64> {
217 let n = 1 << self.params.n_qubits;
218 let clock_size = 1 << self.params.clock_qubits;
219 let mut solution = Array1::zeros(n);
220
221 for input_idx in 0..n {
223 let mut amplitude = Complex64::new(0.0, 0.0);
224 for clock_idx in 0..clock_size {
225 let idx = input_idx + n * clock_idx;
226 if idx < state.len() {
227 amplitude += state[idx];
228 }
229 }
230 solution[input_idx] = amplitude;
231 }
232
233 let norm: f64 = solution.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
235
236 if norm > 1e-10 {
237 for amp in solution.iter_mut() {
238 *amp /= norm;
239 }
240 }
241
242 solution
243 }
244
245 pub fn run(&self) -> Result<(Array1<Complex64>, f64), String> {
247 let total_size = 1 << self.total_qubits();
248 let mut state = vec![Complex64::new(0.0, 0.0); total_size];
249
250 self.prepare_input_state(&mut state);
252
253 self.apply_phase_estimation(&mut state);
255
256 self.apply_eigenvalue_inversion(&mut state);
258
259 self.apply_inverse_phase_estimation(&mut state);
261
262 let success_probability = self.postselect_ancilla(&mut state);
264
265 let solution = self.extract_solution(&state);
267
268 Ok((solution, success_probability))
269 }
270}
271
272fn is_hermitian(matrix: &Array2<Complex64>, tolerance: f64) -> bool {
274 let (n, m) = matrix.dim();
275 if n != m {
276 return false;
277 }
278
279 for i in 0..n {
280 for j in 0..n {
281 let diff = (matrix[[i, j]] - matrix[[j, i]].conj()).norm();
282 if diff > tolerance {
283 return false;
284 }
285 }
286 }
287
288 true
289}
290
291pub fn hhl_example() -> Result<(), String> {
293 let matrix = Array2::from_shape_vec(
295 (2, 2),
296 vec![
297 Complex64::new(3.0, 0.0),
298 Complex64::new(1.0, 0.0),
299 Complex64::new(1.0, 0.0),
300 Complex64::new(3.0, 0.0),
301 ],
302 )
303 .unwrap();
304
305 let vector_b = Array1::from_vec(vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)]);
307
308 let params = HHLParams::new(1); let hhl = HHLAlgorithm::new(matrix.clone(), vector_b.clone(), params)?;
311
312 let (solution, success_prob) = hhl.run()?;
314
315 println!("HHL Algorithm Results:");
316 println!("Matrix A:\n{:?}", matrix);
317 println!("Vector b: {:?}", vector_b);
318 println!("Quantum solution |x⟩: {:?}", solution);
319 println!("Success probability: {:.4}", success_prob);
320
321 let ax = matrix.dot(&solution);
323 println!("Verification A|x⟩: {:?}", ax);
324
325 Ok(())
326}
327
328#[cfg(test)]
329mod tests {
330 use super::*;
331
332 #[test]
333 fn test_hermitian_check() {
334 let h = Array2::from_shape_vec(
336 (2, 2),
337 vec![
338 Complex64::new(1.0, 0.0),
339 Complex64::new(0.0, 1.0),
340 Complex64::new(0.0, -1.0),
341 Complex64::new(2.0, 0.0),
342 ],
343 )
344 .unwrap();
345 assert!(is_hermitian(&h, 1e-10));
346
347 let nh = Array2::from_shape_vec(
349 (2, 2),
350 vec![
351 Complex64::new(1.0, 0.0),
352 Complex64::new(2.0, 0.0),
353 Complex64::new(3.0, 0.0),
354 Complex64::new(4.0, 0.0),
355 ],
356 )
357 .unwrap();
358 assert!(!is_hermitian(&nh, 1e-10));
359 }
360
361 #[test]
362 fn test_hhl_creation() {
363 let matrix = Array2::eye(2);
364 let vector_b = Array1::from_vec(vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)]);
365
366 let params = HHLParams::new(1);
367 let hhl = HHLAlgorithm::new(matrix, vector_b, params);
368 assert!(hhl.is_ok());
369 }
370}