1use crate::error::{QuantRS2Error, QuantRS2Result};
8use crate::symbolic_hamiltonian::SymbolicHamiltonian;
9use scirs2_core::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 Self {
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 Self {
108 circuit_parameters: hamiltonian.variables(),
109 hamiltonian,
110 state_prep: None,
111 }
112 }
113
114 #[must_use]
116 pub fn with_state_prep<F>(mut self, state_prep: F) -> Self
117 where
118 F: Fn(&HashMap<String, f64>) -> QuantRS2Result<Vec<Complex64>> + 'static,
119 {
120 self.state_prep = Some(Box::new(state_prep));
121 self
122 }
123}
124
125impl SymbolicObjective for HamiltonianExpectation {
126 fn evaluate(&self, parameters: &HashMap<String, f64>) -> QuantRS2Result<f64> {
127 let terms = self.hamiltonian.evaluate(parameters)?;
129
130 let n_qubits = self.hamiltonian.n_qubits;
133 let mut state_vector = vec![Complex64::new(0.0, 0.0); 1 << n_qubits];
134
135 if let Some(ref state_prep) = self.state_prep {
136 state_vector = state_prep(parameters)?;
137 } else {
138 state_vector[0] = Complex64::new(1.0, 0.0);
140 }
141
142 let mut expectation = 0.0;
144 for (coeff, pauli_string) in terms {
145 let pauli_expectation = compute_pauli_expectation_real(&pauli_string, &state_vector)?;
146 expectation += coeff * pauli_expectation;
147 }
148
149 Ok(expectation)
150 }
151
152 fn gradients(&self, parameters: &HashMap<String, f64>) -> QuantRS2Result<HashMap<String, f64>> {
153 let mut gradients = HashMap::new();
154
155 #[cfg(feature = "symbolic")]
156 {
157 if self
159 .hamiltonian
160 .variables()
161 .iter()
162 .all(|v| parameters.contains_key(v))
163 {
164 let grad_hamiltonians = self.hamiltonian.gradients(&self.parameter_names())?;
165
166 for (param_name, grad_hamiltonian) in grad_hamiltonians {
167 let grad_obj = Self {
168 hamiltonian: grad_hamiltonian,
169 circuit_parameters: self.circuit_parameters.clone(),
170 state_prep: None, };
172
173 let grad_value = grad_obj.evaluate(parameters)?;
174 gradients.insert(param_name, grad_value);
175 }
176
177 return Ok(gradients);
178 }
179 }
180
181 let step = 1e-8;
183 let _base_value = self.evaluate(parameters)?;
184
185 for param_name in self.parameter_names() {
186 let mut params_plus = parameters.clone();
187 let mut params_minus = parameters.clone();
188
189 let current_value = parameters.get(¶m_name).unwrap_or(&0.0);
190 params_plus.insert(param_name.clone(), current_value + step);
191 params_minus.insert(param_name.clone(), current_value - step);
192
193 let value_plus = self.evaluate(¶ms_plus)?;
194 let value_minus = self.evaluate(¶ms_minus)?;
195
196 let gradient = (value_plus - value_minus) / (2.0 * step);
197 gradients.insert(param_name, gradient);
198 }
199
200 Ok(gradients)
201 }
202
203 fn parameter_names(&self) -> Vec<String> {
204 let mut names = self.hamiltonian.variables();
205 names.extend(self.circuit_parameters.iter().cloned());
206 names.sort();
207 names.dedup();
208 names
209 }
210}
211
212#[derive(Debug, Clone)]
214pub struct QAOACostFunction {
215 pub cost_hamiltonian: SymbolicHamiltonian,
217 pub mixer_hamiltonian: SymbolicHamiltonian,
219 pub p_layers: usize,
221}
222
223impl QAOACostFunction {
224 pub const fn new(
226 cost_hamiltonian: SymbolicHamiltonian,
227 mixer_hamiltonian: SymbolicHamiltonian,
228 p_layers: usize,
229 ) -> Self {
230 Self {
231 cost_hamiltonian,
232 mixer_hamiltonian,
233 p_layers,
234 }
235 }
236}
237
238impl SymbolicObjective for QAOACostFunction {
239 fn evaluate(&self, parameters: &HashMap<String, f64>) -> QuantRS2Result<f64> {
240 let mut total_cost = 0.0;
245
246 for layer in 0..self.p_layers {
247 let gamma_key = format!("gamma_{layer}");
248 let beta_key = format!("beta_{layer}");
249
250 let gamma = parameters.get(&gamma_key).unwrap_or(&0.0);
251 let beta = parameters.get(&beta_key).unwrap_or(&0.0);
252
253 total_cost += gamma * gamma + beta * beta;
255 }
256
257 Ok(total_cost)
258 }
259
260 fn gradients(&self, parameters: &HashMap<String, f64>) -> QuantRS2Result<HashMap<String, f64>> {
261 let mut gradients = HashMap::new();
262
263 for layer in 0..self.p_layers {
265 let gamma_key = format!("gamma_{layer}");
266 let beta_key = format!("beta_{layer}");
267
268 let gamma = parameters.get(&gamma_key).unwrap_or(&0.0);
269 let beta = parameters.get(&beta_key).unwrap_or(&0.0);
270
271 gradients.insert(gamma_key, 2.0 * gamma);
272 gradients.insert(beta_key, 2.0 * beta);
273 }
274
275 Ok(gradients)
276 }
277
278 fn parameter_names(&self) -> Vec<String> {
279 let mut names = Vec::new();
280 for layer in 0..self.p_layers {
281 names.push(format!("gamma_{layer}"));
282 names.push(format!("beta_{layer}"));
283 }
284 names
285 }
286
287 fn parameter_bounds(&self) -> HashMap<String, (Option<f64>, Option<f64>)> {
288 let mut bounds = HashMap::new();
289 for layer in 0..self.p_layers {
290 bounds.insert(
292 format!("gamma_{layer}"),
293 (Some(0.0), Some(2.0 * std::f64::consts::PI)),
294 );
295 bounds.insert(
296 format!("beta_{layer}"),
297 (Some(0.0), Some(std::f64::consts::PI)),
298 );
299 }
300 bounds
301 }
302}
303
304pub struct SymbolicOptimizer {
306 config: SymbolicOptimizationConfig,
307}
308
309impl SymbolicOptimizer {
310 pub const fn new(config: SymbolicOptimizationConfig) -> Self {
312 Self { config }
313 }
314
315 pub fn default() -> Self {
317 Self::new(SymbolicOptimizationConfig::default())
318 }
319
320 pub fn optimize<O: SymbolicObjective>(
322 &self,
323 objective: &O,
324 initial_parameters: HashMap<String, f64>,
325 ) -> QuantRS2Result<OptimizationResult> {
326 let mut parameters = initial_parameters;
327 let mut history = Vec::new();
328 let mut converged = false;
329
330 let mut prev_value = objective.evaluate(¶meters)?;
331 history.push((parameters.clone(), prev_value));
332
333 for iteration in 0..self.config.max_iterations {
334 let gradients = objective.gradients(¶meters)?;
336
337 let mut max_gradient: f64 = 0.0;
339 for param_name in objective.parameter_names() {
340 if let Some(gradient) = gradients.get(¶m_name) {
341 let current_value = parameters.get(¶m_name).unwrap_or(&0.0);
342 let new_value = current_value - self.config.learning_rate * gradient;
343
344 let bounded_value = if let Some((lower, upper)) =
346 objective.parameter_bounds().get(¶m_name)
347 {
348 let mut val = new_value;
349 if let Some(lower_bound) = lower {
350 val = val.max(*lower_bound);
351 }
352 if let Some(upper_bound) = upper {
353 val = val.min(*upper_bound);
354 }
355 val
356 } else {
357 new_value
358 };
359
360 parameters.insert(param_name.clone(), bounded_value);
361 max_gradient = max_gradient.max(gradient.abs());
362 }
363 }
364
365 let current_value = objective.evaluate(¶meters)?;
367 history.push((parameters.clone(), current_value));
368
369 let value_change = (current_value - prev_value).abs();
371 if value_change < self.config.tolerance && max_gradient < self.config.tolerance {
372 converged = true;
373 break;
374 }
375
376 prev_value = current_value;
377
378 if iteration % 100 == 0 {
380 println!(
381 "Iteration {iteration}: objective = {current_value:.6e}, max_grad = {max_gradient:.6e}"
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(0.0, 0.0)
495 } else {
496 Complex64::new(1.0, 0.0)
497 }
498 }
499 PauliOperator::Y => {
500 if bit_i == bit_j {
501 Complex64::new(0.0, 0.0)
502 } else if bit_j == 1 {
503 Complex64::new(0.0, 1.0)
504 } else {
505 Complex64::new(0.0, -1.0)
506 }
507 }
508 PauliOperator::Z => {
509 if bit_i == bit_j {
510 if bit_i == 0 {
511 Complex64::new(1.0, 0.0)
512 } else {
513 Complex64::new(-1.0, 0.0)
514 }
515 } else {
516 Complex64::new(0.0, 0.0)
517 }
518 }
519 };
520
521 matrix_element *= local_element;
522 if matrix_element.norm() < 1e-12 {
523 break;
524 }
525 }
526
527 let contribution = state_vector[i].conj() * matrix_element * state_vector[j];
528 expectation += contribution.re;
529 }
530 }
531
532 Ok(expectation)
533}
534
535#[cfg(test)]
536mod tests {
537 use super::*;
538 use crate::parametric::Parameter;
539 use crate::qubit::QubitId;
540 use crate::symbolic_hamiltonian::hamiltonians;
541
542 #[test]
543 fn test_hamiltonian_expectation() {
544 let hamiltonian = hamiltonians::transverse_field_ising(
545 2,
546 Parameter::constant(1.0),
547 Parameter::constant(0.5),
548 );
549
550 let objective = HamiltonianExpectation::new(hamiltonian);
551 let parameters = HashMap::new();
552
553 let result = objective.evaluate(¶meters);
554 assert!(result.is_ok());
555 }
556
557 #[test]
558 fn test_qaoa_cost_function() {
559 let cost_h = hamiltonians::maxcut(&[(QubitId::from(0), QubitId::from(1), 1.0)], 2);
560 let mixer_h = hamiltonians::transverse_field_ising(
561 2,
562 Parameter::constant(0.0),
563 Parameter::constant(1.0),
564 );
565
566 let objective = QAOACostFunction::new(cost_h, mixer_h, 1);
567
568 let mut parameters = HashMap::new();
569 parameters.insert("gamma_0".to_string(), 0.5);
570 parameters.insert("beta_0".to_string(), 0.3);
571
572 let result = objective.evaluate(¶meters);
573 assert!(result.is_ok());
574
575 let gradients = objective.gradients(¶meters);
576 assert!(gradients.is_ok());
577 }
578
579 #[test]
580 fn test_symbolic_optimizer() {
581 let cost_h = hamiltonians::maxcut(&[(QubitId::from(0), QubitId::from(1), 1.0)], 2);
582 let mixer_h = hamiltonians::transverse_field_ising(
583 2,
584 Parameter::constant(0.0),
585 Parameter::constant(1.0),
586 );
587
588 let objective = QAOACostFunction::new(cost_h, mixer_h, 1);
589
590 let mut initial_params = HashMap::new();
591 initial_params.insert("gamma_0".to_string(), 1.0);
592 initial_params.insert("beta_0".to_string(), 1.0);
593
594 let mut config = SymbolicOptimizationConfig::default();
595 config.max_iterations = 10; let optimizer = SymbolicOptimizer::new(config);
598 let result = optimizer.optimize(&objective, initial_params);
599
600 assert!(result.is_ok());
601 let opt_result = result.expect("Optimization should succeed");
602 assert_eq!(opt_result.iterations, 10);
603 }
604}