1use crate::error::{QuantRS2Error, QuantRS2Result};
8use crate::symbolic_hamiltonian::SymbolicHamiltonian;
9use num_complex::Complex64;
10use std::collections::HashMap;
11
12#[derive(Debug, Clone)]
14pub struct SymbolicOptimizationConfig {
15 pub max_iterations: usize,
17 pub tolerance: f64,
19 pub learning_rate: f64,
21 pub use_analytical_gradients: bool,
23 pub finite_difference_step: f64,
25}
26
27impl Default for SymbolicOptimizationConfig {
28 fn default() -> Self {
29 SymbolicOptimizationConfig {
30 max_iterations: 1000,
31 tolerance: 1e-6,
32 learning_rate: 0.01,
33 use_analytical_gradients: true,
34 finite_difference_step: 1e-8,
35 }
36 }
37}
38
39#[derive(Debug, Clone)]
41pub struct OptimizationResult {
42 pub optimal_parameters: HashMap<String, f64>,
44 pub final_value: f64,
46 pub iterations: usize,
48 pub converged: bool,
50 pub history: Vec<(HashMap<String, f64>, f64)>,
52}
53
54pub trait SymbolicObjective {
56 fn evaluate(&self, parameters: &HashMap<String, f64>) -> QuantRS2Result<f64>;
58
59 fn gradients(&self, parameters: &HashMap<String, f64>) -> QuantRS2Result<HashMap<String, f64>>;
61
62 fn parameter_names(&self) -> Vec<String>;
64
65 fn parameter_bounds(&self) -> HashMap<String, (Option<f64>, Option<f64>)> {
67 HashMap::new()
68 }
69}
70
71pub struct HamiltonianExpectation {
73 pub hamiltonian: SymbolicHamiltonian,
75 pub circuit_parameters: Vec<String>,
77 pub state_prep: Option<Box<dyn Fn(&HashMap<String, f64>) -> QuantRS2Result<Vec<Complex64>>>>,
79}
80
81impl std::fmt::Debug for HamiltonianExpectation {
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 f.debug_struct("HamiltonianExpectation")
84 .field("hamiltonian", &self.hamiltonian)
85 .field("circuit_parameters", &self.circuit_parameters)
86 .field(
87 "state_prep",
88 &self.state_prep.as_ref().map(|_| "<function>"),
89 )
90 .finish()
91 }
92}
93
94impl Clone for HamiltonianExpectation {
95 fn clone(&self) -> Self {
96 Self {
97 hamiltonian: self.hamiltonian.clone(),
98 circuit_parameters: self.circuit_parameters.clone(),
99 state_prep: None, }
101 }
102}
103
104impl HamiltonianExpectation {
105 pub fn new(hamiltonian: SymbolicHamiltonian) -> Self {
107 HamiltonianExpectation {
108 circuit_parameters: hamiltonian.variables(),
109 hamiltonian,
110 state_prep: None,
111 }
112 }
113
114 pub fn with_state_prep<F>(mut self, state_prep: F) -> Self
116 where
117 F: Fn(&HashMap<String, f64>) -> QuantRS2Result<Vec<Complex64>> + 'static,
118 {
119 self.state_prep = Some(Box::new(state_prep));
120 self
121 }
122}
123
124impl SymbolicObjective for HamiltonianExpectation {
125 fn evaluate(&self, parameters: &HashMap<String, f64>) -> QuantRS2Result<f64> {
126 let terms = self.hamiltonian.evaluate(parameters)?;
128
129 let n_qubits = self.hamiltonian.n_qubits;
132 let mut state_vector = vec![Complex64::new(0.0, 0.0); 1 << n_qubits];
133
134 if let Some(ref state_prep) = self.state_prep {
135 state_vector = state_prep(parameters)?;
136 } else {
137 state_vector[0] = Complex64::new(1.0, 0.0);
139 }
140
141 let mut expectation = 0.0;
143 for (coeff, pauli_string) in terms {
144 let pauli_expectation = compute_pauli_expectation_real(&pauli_string, &state_vector)?;
145 expectation += coeff * pauli_expectation;
146 }
147
148 Ok(expectation)
149 }
150
151 fn gradients(&self, parameters: &HashMap<String, f64>) -> QuantRS2Result<HashMap<String, f64>> {
152 let mut gradients = HashMap::new();
153
154 #[cfg(feature = "symbolic")]
155 {
156 if self
158 .hamiltonian
159 .variables()
160 .iter()
161 .all(|v| parameters.contains_key(v))
162 {
163 let grad_hamiltonians = self.hamiltonian.gradients(&self.parameter_names())?;
164
165 for (param_name, grad_hamiltonian) in grad_hamiltonians {
166 let grad_obj = HamiltonianExpectation {
167 hamiltonian: grad_hamiltonian,
168 circuit_parameters: self.circuit_parameters.clone(),
169 state_prep: None, };
171
172 let grad_value = grad_obj.evaluate(parameters)?;
173 gradients.insert(param_name, grad_value);
174 }
175
176 return Ok(gradients);
177 }
178 }
179
180 let step = 1e-8;
182 let _base_value = self.evaluate(parameters)?;
183
184 for param_name in self.parameter_names() {
185 let mut params_plus = parameters.clone();
186 let mut params_minus = parameters.clone();
187
188 let current_value = parameters.get(¶m_name).unwrap_or(&0.0);
189 params_plus.insert(param_name.clone(), current_value + step);
190 params_minus.insert(param_name.clone(), current_value - step);
191
192 let value_plus = self.evaluate(¶ms_plus)?;
193 let value_minus = self.evaluate(¶ms_minus)?;
194
195 let gradient = (value_plus - value_minus) / (2.0 * step);
196 gradients.insert(param_name, gradient);
197 }
198
199 Ok(gradients)
200 }
201
202 fn parameter_names(&self) -> Vec<String> {
203 let mut names = self.hamiltonian.variables();
204 names.extend(self.circuit_parameters.iter().cloned());
205 names.sort();
206 names.dedup();
207 names
208 }
209}
210
211#[derive(Debug, Clone)]
213pub struct QAOACostFunction {
214 pub cost_hamiltonian: SymbolicHamiltonian,
216 pub mixer_hamiltonian: SymbolicHamiltonian,
218 pub p_layers: usize,
220}
221
222impl QAOACostFunction {
223 pub fn new(
225 cost_hamiltonian: SymbolicHamiltonian,
226 mixer_hamiltonian: SymbolicHamiltonian,
227 p_layers: usize,
228 ) -> Self {
229 QAOACostFunction {
230 cost_hamiltonian,
231 mixer_hamiltonian,
232 p_layers,
233 }
234 }
235}
236
237impl SymbolicObjective for QAOACostFunction {
238 fn evaluate(&self, parameters: &HashMap<String, f64>) -> QuantRS2Result<f64> {
239 let mut total_cost = 0.0;
244
245 for layer in 0..self.p_layers {
246 let gamma_key = format!("gamma_{}", layer);
247 let beta_key = format!("beta_{}", layer);
248
249 let gamma = parameters.get(&gamma_key).unwrap_or(&0.0);
250 let beta = parameters.get(&beta_key).unwrap_or(&0.0);
251
252 total_cost += gamma * gamma + beta * beta;
254 }
255
256 Ok(total_cost)
257 }
258
259 fn gradients(&self, parameters: &HashMap<String, f64>) -> QuantRS2Result<HashMap<String, f64>> {
260 let mut gradients = HashMap::new();
261
262 for layer in 0..self.p_layers {
264 let gamma_key = format!("gamma_{}", layer);
265 let beta_key = format!("beta_{}", layer);
266
267 let gamma = parameters.get(&gamma_key).unwrap_or(&0.0);
268 let beta = parameters.get(&beta_key).unwrap_or(&0.0);
269
270 gradients.insert(gamma_key, 2.0 * gamma);
271 gradients.insert(beta_key, 2.0 * beta);
272 }
273
274 Ok(gradients)
275 }
276
277 fn parameter_names(&self) -> Vec<String> {
278 let mut names = Vec::new();
279 for layer in 0..self.p_layers {
280 names.push(format!("gamma_{}", layer));
281 names.push(format!("beta_{}", layer));
282 }
283 names
284 }
285
286 fn parameter_bounds(&self) -> HashMap<String, (Option<f64>, Option<f64>)> {
287 let mut bounds = HashMap::new();
288 for layer in 0..self.p_layers {
289 bounds.insert(
291 format!("gamma_{}", layer),
292 (Some(0.0), Some(2.0 * std::f64::consts::PI)),
293 );
294 bounds.insert(
295 format!("beta_{}", layer),
296 (Some(0.0), Some(std::f64::consts::PI)),
297 );
298 }
299 bounds
300 }
301}
302
303pub struct SymbolicOptimizer {
305 config: SymbolicOptimizationConfig,
306}
307
308impl SymbolicOptimizer {
309 pub fn new(config: SymbolicOptimizationConfig) -> Self {
311 SymbolicOptimizer { config }
312 }
313
314 pub fn default() -> Self {
316 SymbolicOptimizer::new(SymbolicOptimizationConfig::default())
317 }
318
319 pub fn optimize<O: SymbolicObjective>(
321 &self,
322 objective: &O,
323 initial_parameters: HashMap<String, f64>,
324 ) -> QuantRS2Result<OptimizationResult> {
325 let mut parameters = initial_parameters;
326 let mut history = Vec::new();
327 let mut converged = false;
328
329 let mut prev_value = objective.evaluate(¶meters)?;
330 history.push((parameters.clone(), prev_value));
331
332 for iteration in 0..self.config.max_iterations {
333 let gradients = objective.gradients(¶meters)?;
335
336 let mut max_gradient: f64 = 0.0;
338 for param_name in objective.parameter_names() {
339 if let Some(gradient) = gradients.get(¶m_name) {
340 let current_value = parameters.get(¶m_name).unwrap_or(&0.0);
341 let new_value = current_value - self.config.learning_rate * gradient;
342
343 let bounded_value = if let Some((lower, upper)) =
345 objective.parameter_bounds().get(¶m_name)
346 {
347 let mut val = new_value;
348 if let Some(lower_bound) = lower {
349 val = val.max(*lower_bound);
350 }
351 if let Some(upper_bound) = upper {
352 val = val.min(*upper_bound);
353 }
354 val
355 } else {
356 new_value
357 };
358
359 parameters.insert(param_name.clone(), bounded_value);
360 max_gradient = max_gradient.max(gradient.abs());
361 }
362 }
363
364 let current_value = objective.evaluate(¶meters)?;
366 history.push((parameters.clone(), current_value));
367
368 let value_change = (current_value - prev_value).abs();
370 if value_change < self.config.tolerance && max_gradient < self.config.tolerance {
371 converged = true;
372 break;
373 }
374
375 prev_value = current_value;
376
377 if iteration % 100 == 0 {
379 println!(
380 "Iteration {}: objective = {:.6e}, max_grad = {:.6e}",
381 iteration, current_value, max_gradient
382 );
383 }
384 }
385
386 Ok(OptimizationResult {
387 optimal_parameters: parameters,
388 final_value: prev_value,
389 iterations: history.len() - 1,
390 converged,
391 history,
392 })
393 }
394
395 pub fn optimize_bfgs<O: SymbolicObjective>(
397 &self,
398 objective: &O,
399 initial_parameters: HashMap<String, f64>,
400 ) -> QuantRS2Result<OptimizationResult> {
401 self.optimize(objective, initial_parameters)
404 }
405}
406
407pub mod circuit_optimization {
409 use super::*;
410 use crate::parametric::ParametricGate;
411
412 pub fn optimize_parametric_circuit<O: SymbolicObjective>(
414 _gates: &[Box<dyn ParametricGate>],
415 objective: &O,
416 initial_parameters: HashMap<String, f64>,
417 ) -> QuantRS2Result<OptimizationResult> {
418 let optimizer = SymbolicOptimizer::default();
419 optimizer.optimize(objective, initial_parameters)
420 }
421
422 pub fn extract_circuit_parameters(gates: &[Box<dyn ParametricGate>]) -> Vec<String> {
424 let mut parameters = Vec::new();
425 for gate in gates {
426 parameters.extend(gate.parameter_names());
427 }
428 parameters.sort();
429 parameters.dedup();
430 parameters
431 }
432
433 pub fn apply_optimized_parameters(
435 gates: &[Box<dyn ParametricGate>],
436 parameters: &HashMap<String, f64>,
437 ) -> QuantRS2Result<Vec<Box<dyn ParametricGate>>> {
438 let mut optimized_gates = Vec::new();
439
440 for gate in gates {
441 let param_assignments: Vec<(String, f64)> = gate
442 .parameter_names()
443 .into_iter()
444 .filter_map(|name| parameters.get(&name).map(|&value| (name, value)))
445 .collect();
446
447 let optimized_gate = gate.assign(¶m_assignments)?;
448 optimized_gates.push(optimized_gate);
449 }
450
451 Ok(optimized_gates)
452 }
453}
454
455fn compute_pauli_expectation_real(
458 pauli_string: &crate::symbolic_hamiltonian::PauliString,
459 state_vector: &[Complex64],
460) -> QuantRS2Result<f64> {
461 use crate::symbolic_hamiltonian::PauliOperator;
463
464 let n_qubits = pauli_string.n_qubits;
465 let n_states = 1 << n_qubits;
466
467 if state_vector.len() != n_states {
468 return Err(QuantRS2Error::InvalidInput(
469 "State vector size mismatch".to_string(),
470 ));
471 }
472
473 let mut expectation = 0.0;
474
475 for i in 0..n_states {
476 for j in 0..n_states {
477 let mut matrix_element = Complex64::new(1.0, 0.0);
478
479 for qubit in 0..n_qubits {
480 let op = pauli_string.get_operator(qubit.into());
481 let bit_i = (i >> qubit) & 1;
482 let bit_j = (j >> qubit) & 1;
483
484 let local_element = match op {
485 PauliOperator::I => {
486 if bit_i == bit_j {
487 Complex64::new(1.0, 0.0)
488 } else {
489 Complex64::new(0.0, 0.0)
490 }
491 }
492 PauliOperator::X => {
493 if bit_i != bit_j {
494 Complex64::new(1.0, 0.0)
495 } else {
496 Complex64::new(0.0, 0.0)
497 }
498 }
499 PauliOperator::Y => {
500 if bit_i != bit_j {
501 if bit_j == 1 {
502 Complex64::new(0.0, 1.0)
503 } else {
504 Complex64::new(0.0, -1.0)
505 }
506 } else {
507 Complex64::new(0.0, 0.0)
508 }
509 }
510 PauliOperator::Z => {
511 if bit_i == bit_j {
512 if bit_i == 0 {
513 Complex64::new(1.0, 0.0)
514 } else {
515 Complex64::new(-1.0, 0.0)
516 }
517 } else {
518 Complex64::new(0.0, 0.0)
519 }
520 }
521 };
522
523 matrix_element *= local_element;
524 if matrix_element.norm() < 1e-12 {
525 break;
526 }
527 }
528
529 let contribution = state_vector[i].conj() * matrix_element * state_vector[j];
530 expectation += contribution.re;
531 }
532 }
533
534 Ok(expectation)
535}
536
537#[cfg(test)]
538mod tests {
539 use super::*;
540 use crate::parametric::Parameter;
541 use crate::qubit::QubitId;
542 use crate::symbolic_hamiltonian::hamiltonians;
543
544 #[test]
545 fn test_hamiltonian_expectation() {
546 let hamiltonian = hamiltonians::transverse_field_ising(
547 2,
548 Parameter::constant(1.0),
549 Parameter::constant(0.5),
550 );
551
552 let objective = HamiltonianExpectation::new(hamiltonian);
553 let parameters = HashMap::new();
554
555 let result = objective.evaluate(¶meters);
556 assert!(result.is_ok());
557 }
558
559 #[test]
560 fn test_qaoa_cost_function() {
561 let cost_h = hamiltonians::maxcut(&[(QubitId::from(0), QubitId::from(1), 1.0)], 2);
562 let mixer_h = hamiltonians::transverse_field_ising(
563 2,
564 Parameter::constant(0.0),
565 Parameter::constant(1.0),
566 );
567
568 let objective = QAOACostFunction::new(cost_h, mixer_h, 1);
569
570 let mut parameters = HashMap::new();
571 parameters.insert("gamma_0".to_string(), 0.5);
572 parameters.insert("beta_0".to_string(), 0.3);
573
574 let result = objective.evaluate(¶meters);
575 assert!(result.is_ok());
576
577 let gradients = objective.gradients(¶meters);
578 assert!(gradients.is_ok());
579 }
580
581 #[test]
582 fn test_symbolic_optimizer() {
583 let cost_h = hamiltonians::maxcut(&[(QubitId::from(0), QubitId::from(1), 1.0)], 2);
584 let mixer_h = hamiltonians::transverse_field_ising(
585 2,
586 Parameter::constant(0.0),
587 Parameter::constant(1.0),
588 );
589
590 let objective = QAOACostFunction::new(cost_h, mixer_h, 1);
591
592 let mut initial_params = HashMap::new();
593 initial_params.insert("gamma_0".to_string(), 1.0);
594 initial_params.insert("beta_0".to_string(), 1.0);
595
596 let mut config = SymbolicOptimizationConfig::default();
597 config.max_iterations = 10; let optimizer = SymbolicOptimizer::new(config);
600 let result = optimizer.optimize(&objective, initial_params);
601
602 assert!(result.is_ok());
603 let opt_result = result.unwrap();
604 assert_eq!(opt_result.iterations, 10);
605 }
606}