1use crate::{CircuitResult, DeviceError, DeviceResult};
7use quantrs2_circuit::prelude::*;
8use quantrs2_core::gate::{multi::*, single::*, GateOp};
9use scirs2_core::ndarray::{Array1, Array2};
10use std::collections::HashMap;
11use std::sync::Arc;
12
13#[derive(Debug, Clone, PartialEq)]
15pub enum Parameter {
16 Fixed(f64),
18 Named(String),
20 Expression(Box<ParameterExpression>),
22}
23
24#[derive(Debug, Clone, PartialEq)]
26pub enum ParameterExpression {
27 Param(String),
29 Const(f64),
31 Add(Parameter, Parameter),
33 Mul(Parameter, Parameter),
35 Div(Parameter, Parameter),
37 Sin(Parameter),
39 Cos(Parameter),
40 Pow(Parameter, f64),
42}
43
44#[derive(Debug, Clone)]
46pub struct ParametricGate {
47 pub gate_type: String,
49 pub qubits: Vec<usize>,
51 pub parameters: Vec<Parameter>,
53}
54
55#[derive(Debug, Clone)]
57pub struct ParametricCircuit {
58 pub num_qubits: usize,
60 pub gates: Vec<ParametricGate>,
62 pub parameter_names: Vec<String>,
64 pub metadata: HashMap<String, String>,
66}
67
68impl ParametricCircuit {
69 pub fn new(num_qubits: usize) -> Self {
71 Self {
72 num_qubits,
73 gates: Vec::new(),
74 parameter_names: Vec::new(),
75 metadata: HashMap::new(),
76 }
77 }
78
79 pub fn add_gate(&mut self, gate: ParametricGate) -> &mut Self {
81 for param in &gate.parameters {
83 self.extract_parameter_names(param);
84 }
85
86 self.gates.push(gate);
87 self
88 }
89
90 fn extract_parameter_names(&mut self, param: &Parameter) {
92 match param {
93 Parameter::Named(name) => {
94 if !self.parameter_names.contains(name) {
95 self.parameter_names.push(name.clone());
96 }
97 }
98 Parameter::Expression(expr) => {
99 self.extract_expr_parameter_names(expr);
100 }
101 Parameter::Fixed(_) => {}
102 }
103 }
104
105 fn extract_expr_parameter_names(&mut self, expr: &ParameterExpression) {
107 match expr {
108 ParameterExpression::Param(name) => {
109 if !self.parameter_names.contains(name) {
110 self.parameter_names.push(name.clone());
111 }
112 }
113 ParameterExpression::Add(p1, p2)
114 | ParameterExpression::Mul(p1, p2)
115 | ParameterExpression::Div(p1, p2) => {
116 self.extract_parameter_names(p1);
117 self.extract_parameter_names(p2);
118 }
119 ParameterExpression::Sin(p)
120 | ParameterExpression::Cos(p)
121 | ParameterExpression::Pow(p, _) => {
122 self.extract_parameter_names(p);
123 }
124 ParameterExpression::Const(_) => {}
125 }
126 }
127
128 pub fn num_parameters(&self) -> usize {
130 self.parameter_names.len()
131 }
132
133 pub fn bind_parameters<const N: usize>(
135 &self,
136 params: &HashMap<String, f64>,
137 ) -> DeviceResult<Circuit<N>> {
138 if N != self.num_qubits {
139 return Err(DeviceError::APIError(
140 "Circuit qubit count mismatch".to_string(),
141 ));
142 }
143
144 let mut circuit = Circuit::<N>::new();
145
146 for gate in &self.gates {
147 use quantrs2_core::qubit::QubitId;
148
149 match gate.gate_type.as_str() {
150 "H" => {
151 circuit
152 .add_gate(Hadamard {
153 target: QubitId(gate.qubits[0] as u32),
154 })
155 .map_err(|e| DeviceError::APIError(e.to_string()))?;
156 }
157 "X" => {
158 circuit
159 .add_gate(PauliX {
160 target: QubitId(gate.qubits[0] as u32),
161 })
162 .map_err(|e| DeviceError::APIError(e.to_string()))?;
163 }
164 "Y" => {
165 circuit
166 .add_gate(PauliY {
167 target: QubitId(gate.qubits[0] as u32),
168 })
169 .map_err(|e| DeviceError::APIError(e.to_string()))?;
170 }
171 "Z" => {
172 circuit
173 .add_gate(PauliZ {
174 target: QubitId(gate.qubits[0] as u32),
175 })
176 .map_err(|e| DeviceError::APIError(e.to_string()))?;
177 }
178 "RX" => {
179 let angle = self.evaluate_parameter(&gate.parameters[0], params)?;
180 circuit
181 .add_gate(RotationX {
182 target: QubitId(gate.qubits[0] as u32),
183 theta: angle,
184 })
185 .map_err(|e| DeviceError::APIError(e.to_string()))?;
186 }
187 "RY" => {
188 let angle = self.evaluate_parameter(&gate.parameters[0], params)?;
189 circuit
190 .add_gate(RotationY {
191 target: QubitId(gate.qubits[0] as u32),
192 theta: angle,
193 })
194 .map_err(|e| DeviceError::APIError(e.to_string()))?;
195 }
196 "RZ" => {
197 let angle = self.evaluate_parameter(&gate.parameters[0], params)?;
198 circuit
199 .add_gate(RotationZ {
200 target: QubitId(gate.qubits[0] as u32),
201 theta: angle,
202 })
203 .map_err(|e| DeviceError::APIError(e.to_string()))?;
204 }
205 "CNOT" => {
206 circuit
207 .add_gate(CNOT {
208 control: QubitId(gate.qubits[0] as u32),
209 target: QubitId(gate.qubits[1] as u32),
210 })
211 .map_err(|e| DeviceError::APIError(e.to_string()))?;
212 }
213 "CRX" => {
214 let angle = self.evaluate_parameter(&gate.parameters[0], params)?;
215 circuit
216 .add_gate(CRX {
217 control: QubitId(gate.qubits[0] as u32),
218 target: QubitId(gate.qubits[1] as u32),
219 theta: angle,
220 })
221 .map_err(|e| DeviceError::APIError(e.to_string()))?;
222 }
223 "CRY" => {
224 let angle = self.evaluate_parameter(&gate.parameters[0], params)?;
225 circuit
226 .add_gate(CRY {
227 control: QubitId(gate.qubits[0] as u32),
228 target: QubitId(gate.qubits[1] as u32),
229 theta: angle,
230 })
231 .map_err(|e| DeviceError::APIError(e.to_string()))?;
232 }
233 "CRZ" => {
234 let angle = self.evaluate_parameter(&gate.parameters[0], params)?;
235 circuit
236 .add_gate(CRZ {
237 control: QubitId(gate.qubits[0] as u32),
238 target: QubitId(gate.qubits[1] as u32),
239 theta: angle,
240 })
241 .map_err(|e| DeviceError::APIError(e.to_string()))?;
242 }
243 _ => {
244 return Err(DeviceError::UnsupportedOperation(format!(
245 "Gate type {} not supported",
246 gate.gate_type
247 )));
248 }
249 }
250 }
251
252 Ok(circuit)
253 }
254
255 pub fn bind_parameters_array<const N: usize>(
257 &self,
258 params: &[f64],
259 ) -> DeviceResult<Circuit<N>> {
260 if params.len() != self.parameter_names.len() {
261 return Err(DeviceError::APIError(
262 "Parameter count mismatch".to_string(),
263 ));
264 }
265
266 let param_map: HashMap<String, f64> = self
267 .parameter_names
268 .iter()
269 .zip(params.iter())
270 .map(|(name, &value)| (name.clone(), value))
271 .collect();
272
273 self.bind_parameters(¶m_map)
274 }
275
276 fn evaluate_parameter(
278 &self,
279 param: &Parameter,
280 values: &HashMap<String, f64>,
281 ) -> DeviceResult<f64> {
282 match param {
283 Parameter::Fixed(val) => Ok(*val),
284 Parameter::Named(name) => values
285 .get(name)
286 .copied()
287 .ok_or_else(|| DeviceError::APIError(format!("Missing parameter: {name}"))),
288 Parameter::Expression(expr) => self.evaluate_expression(expr, values),
289 }
290 }
291
292 fn evaluate_expression(
294 &self,
295 expr: &ParameterExpression,
296 values: &HashMap<String, f64>,
297 ) -> DeviceResult<f64> {
298 match expr {
299 ParameterExpression::Param(name) => values
300 .get(name)
301 .copied()
302 .ok_or_else(|| DeviceError::APIError(format!("Missing parameter: {name}"))),
303 ParameterExpression::Const(val) => Ok(*val),
304 ParameterExpression::Add(p1, p2) => {
305 let v1 = self.evaluate_parameter(p1, values)?;
306 let v2 = self.evaluate_parameter(p2, values)?;
307 Ok(v1 + v2)
308 }
309 ParameterExpression::Mul(p1, p2) => {
310 let v1 = self.evaluate_parameter(p1, values)?;
311 let v2 = self.evaluate_parameter(p2, values)?;
312 Ok(v1 * v2)
313 }
314 ParameterExpression::Div(p1, p2) => {
315 let v1 = self.evaluate_parameter(p1, values)?;
316 let v2 = self.evaluate_parameter(p2, values)?;
317 if v2.abs() < f64::EPSILON {
318 Err(DeviceError::APIError("Division by zero".to_string()))
319 } else {
320 Ok(v1 / v2)
321 }
322 }
323 ParameterExpression::Sin(p) => {
324 let v = self.evaluate_parameter(p, values)?;
325 Ok(v.sin())
326 }
327 ParameterExpression::Cos(p) => {
328 let v = self.evaluate_parameter(p, values)?;
329 Ok(v.cos())
330 }
331 ParameterExpression::Pow(p, exp) => {
332 let v = self.evaluate_parameter(p, values)?;
333 Ok(v.powf(*exp))
334 }
335 }
336 }
337
338 fn instantiate_gate(
340 &self,
341 gate: &ParametricGate,
342 values: &HashMap<String, f64>,
343 ) -> DeviceResult<Box<dyn GateOp>> {
344 use quantrs2_core::qubit::QubitId;
345
346 match gate.gate_type.as_str() {
347 "H" => Ok(Box::new(Hadamard {
348 target: QubitId(gate.qubits[0] as u32),
349 })),
350 "X" => Ok(Box::new(PauliX {
351 target: QubitId(gate.qubits[0] as u32),
352 })),
353 "Y" => Ok(Box::new(PauliY {
354 target: QubitId(gate.qubits[0] as u32),
355 })),
356 "Z" => Ok(Box::new(PauliZ {
357 target: QubitId(gate.qubits[0] as u32),
358 })),
359 "RX" => {
360 let angle = self.evaluate_parameter(&gate.parameters[0], values)?;
361 Ok(Box::new(RotationX {
362 target: QubitId(gate.qubits[0] as u32),
363 theta: angle,
364 }))
365 }
366 "RY" => {
367 let angle = self.evaluate_parameter(&gate.parameters[0], values)?;
368 Ok(Box::new(RotationY {
369 target: QubitId(gate.qubits[0] as u32),
370 theta: angle,
371 }))
372 }
373 "RZ" => {
374 let angle = self.evaluate_parameter(&gate.parameters[0], values)?;
375 Ok(Box::new(RotationZ {
376 target: QubitId(gate.qubits[0] as u32),
377 theta: angle,
378 }))
379 }
380 "CNOT" => Ok(Box::new(CNOT {
381 control: QubitId(gate.qubits[0] as u32),
382 target: QubitId(gate.qubits[1] as u32),
383 })),
384 "CRX" => {
385 let angle = self.evaluate_parameter(&gate.parameters[0], values)?;
386 Ok(Box::new(CRX {
387 control: QubitId(gate.qubits[0] as u32),
388 target: QubitId(gate.qubits[1] as u32),
389 theta: angle,
390 }))
391 }
392 "CRY" => {
393 let angle = self.evaluate_parameter(&gate.parameters[0], values)?;
394 Ok(Box::new(CRY {
395 control: QubitId(gate.qubits[0] as u32),
396 target: QubitId(gate.qubits[1] as u32),
397 theta: angle,
398 }))
399 }
400 "CRZ" => {
401 let angle = self.evaluate_parameter(&gate.parameters[0], values)?;
402 Ok(Box::new(CRZ {
403 control: QubitId(gate.qubits[0] as u32),
404 target: QubitId(gate.qubits[1] as u32),
405 theta: angle,
406 }))
407 }
408 _ => Err(DeviceError::APIError(format!(
409 "Unsupported gate type: {}",
410 gate.gate_type
411 ))),
412 }
413 }
414}
415
416pub struct ParametricCircuitBuilder {
418 circuit: ParametricCircuit,
419}
420
421impl ParametricCircuitBuilder {
422 pub fn new(num_qubits: usize) -> Self {
424 Self {
425 circuit: ParametricCircuit::new(num_qubits),
426 }
427 }
428
429 #[must_use]
431 pub fn h(mut self, qubit: usize) -> Self {
432 self.circuit.add_gate(ParametricGate {
433 gate_type: "H".to_string(),
434 qubits: vec![qubit],
435 parameters: vec![],
436 });
437 self
438 }
439
440 #[must_use]
442 pub fn rx(mut self, qubit: usize, param: Parameter) -> Self {
443 self.circuit.add_gate(ParametricGate {
444 gate_type: "RX".to_string(),
445 qubits: vec![qubit],
446 parameters: vec![param],
447 });
448 self
449 }
450
451 #[must_use]
453 pub fn ry(mut self, qubit: usize, param: Parameter) -> Self {
454 self.circuit.add_gate(ParametricGate {
455 gate_type: "RY".to_string(),
456 qubits: vec![qubit],
457 parameters: vec![param],
458 });
459 self
460 }
461
462 #[must_use]
464 pub fn rz(mut self, qubit: usize, param: Parameter) -> Self {
465 self.circuit.add_gate(ParametricGate {
466 gate_type: "RZ".to_string(),
467 qubits: vec![qubit],
468 parameters: vec![param],
469 });
470 self
471 }
472
473 #[must_use]
475 pub fn cnot(mut self, control: usize, target: usize) -> Self {
476 self.circuit.add_gate(ParametricGate {
477 gate_type: "CNOT".to_string(),
478 qubits: vec![control, target],
479 parameters: vec![],
480 });
481 self
482 }
483
484 #[must_use]
486 pub fn crx(mut self, control: usize, target: usize, param: Parameter) -> Self {
487 self.circuit.add_gate(ParametricGate {
488 gate_type: "CRX".to_string(),
489 qubits: vec![control, target],
490 parameters: vec![param],
491 });
492 self
493 }
494
495 pub fn build(self) -> ParametricCircuit {
497 self.circuit
498 }
499}
500
501pub struct ParametricExecutor<E> {
503 executor: Arc<E>,
505 cache: HashMap<String, Box<dyn GateOp>>,
507}
508
509impl<E> ParametricExecutor<E> {
510 pub fn new(executor: E) -> Self {
512 Self {
513 executor: Arc::new(executor),
514 cache: HashMap::new(),
515 }
516 }
517}
518
519#[derive(Debug, Clone)]
521pub struct BatchExecutionRequest {
522 pub circuit: ParametricCircuit,
524 pub parameter_sets: Vec<Vec<f64>>,
526 pub shots: usize,
528 pub observable: Option<crate::zero_noise_extrapolation::Observable>,
530}
531
532#[derive(Debug, Clone)]
534pub struct BatchExecutionResult {
535 pub results: Vec<CircuitResult>,
537 pub expectation_values: Option<Vec<f64>>,
539 pub execution_time: u128,
541}
542
543pub struct ParametricTemplates;
545
546impl ParametricTemplates {
547 pub fn hardware_efficient_ansatz(num_qubits: usize, num_layers: usize) -> ParametricCircuit {
549 let mut builder = ParametricCircuitBuilder::new(num_qubits);
550 let mut param_idx = 0;
551
552 for layer in 0..num_layers {
553 for q in 0..num_qubits {
555 builder = builder
556 .ry(q, Parameter::Named(format!("theta_{layer}_{q}_y")))
557 .rz(q, Parameter::Named(format!("theta_{layer}_{q}_z")));
558 param_idx += 2;
559 }
560
561 for q in 0..num_qubits - 1 {
563 builder = builder.cnot(q, q + 1);
564 }
565 }
566
567 for q in 0..num_qubits {
569 builder = builder.ry(q, Parameter::Named(format!("theta_final_{q}")));
570 }
571
572 builder.build()
573 }
574
575 pub fn qaoa_ansatz(
577 num_qubits: usize,
578 num_layers: usize,
579 problem_edges: Vec<(usize, usize)>,
580 ) -> ParametricCircuit {
581 let mut builder = ParametricCircuitBuilder::new(num_qubits);
582
583 for q in 0..num_qubits {
585 builder = builder.h(q);
586 }
587
588 for p in 0..num_layers {
589 let gamma = Parameter::Named(format!("gamma_{p}"));
591 for (u, v) in &problem_edges {
592 builder = builder.cnot(*u, *v).rz(*v, gamma.clone()).cnot(*u, *v);
593 }
594
595 let beta = Parameter::Named(format!("beta_{p}"));
597 for q in 0..num_qubits {
598 builder = builder.rx(q, beta.clone());
599 }
600 }
601
602 builder.build()
603 }
604
605 pub fn strongly_entangling_layers(num_qubits: usize, num_layers: usize) -> ParametricCircuit {
607 let mut builder = ParametricCircuitBuilder::new(num_qubits);
608
609 for layer in 0..num_layers {
610 for q in 0..num_qubits {
612 builder = builder
613 .rx(q, Parameter::Named(format!("r_{layer}_{q}_x")))
614 .ry(q, Parameter::Named(format!("r_{layer}_{q}_y")))
615 .rz(q, Parameter::Named(format!("r_{layer}_{q}_z")));
616 }
617
618 for q in 0..num_qubits {
620 let target = (q + 1) % num_qubits;
621 builder = builder.cnot(q, target);
622 }
623 }
624
625 builder.build()
626 }
627
628 pub fn excitation_preserving(num_qubits: usize, num_electrons: usize) -> ParametricCircuit {
630 let mut builder = ParametricCircuitBuilder::new(num_qubits);
631
632 for i in 0..num_electrons {
637 for a in num_electrons..num_qubits {
638 let theta = Parameter::Named(format!("t1_{i}_{a}"));
639 builder = builder.cnot(i, a).ry(a, theta).cnot(i, a);
641 }
642 }
643
644 for i in 0..num_electrons - 1 {
646 for j in i + 1..num_electrons {
647 for a in num_electrons..num_qubits - 1 {
648 for b in a + 1..num_qubits {
649 let theta = Parameter::Named(format!("t2_{i}_{j}_{a}"));
650 builder = builder
652 .cnot(i, a)
653 .cnot(j, b)
654 .rz(b, theta)
655 .cnot(j, b)
656 .cnot(i, a);
657 }
658 }
659 }
660 }
661
662 builder.build()
663 }
664}
665
666pub struct ParameterOptimizer;
668
669impl ParameterOptimizer {
670 pub fn parameter_shift_gradient(
672 circuit: &ParametricCircuit,
673 params: &[f64],
674 observable: &crate::zero_noise_extrapolation::Observable,
675 shift: f64,
676 ) -> Vec<f64> {
677 let mut gradients = vec![0.0; params.len()];
678
679 for (i, _) in params.iter().enumerate() {
681 let mut params_plus = params.to_vec();
683 params_plus[i] += shift;
684
685 let mut params_minus = params.to_vec();
687 params_minus[i] -= shift;
688
689 gradients[i] = 0.0; }
693
694 gradients
695 }
696
697 pub fn natural_gradient(
699 circuit: &ParametricCircuit,
700 params: &[f64],
701 gradients: &[f64],
702 regularization: f64,
703 ) -> Vec<f64> {
704 let n = params.len();
705
706 let mut fisher = Array2::<f64>::zeros((n, n));
708 for i in 0..n {
709 fisher[[i, i]] = 1.0 + regularization; }
711
712 gradients.to_vec()
715 }
716}
717
718#[cfg(test)]
719mod tests {
720 use super::*;
721
722 #[test]
723 fn test_parametric_circuit_builder() {
724 let circuit = ParametricCircuitBuilder::new(2)
725 .h(0)
726 .ry(0, Parameter::Named("theta".to_string()))
727 .cnot(0, 1)
728 .rz(1, Parameter::Fixed(1.57))
729 .build();
730
731 assert_eq!(circuit.num_qubits, 2);
732 assert_eq!(circuit.gates.len(), 4);
733 assert_eq!(circuit.parameter_names, vec!["theta"]);
734 }
735
736 #[test]
737 fn test_parameter_binding() {
738 let circuit = ParametricCircuitBuilder::new(2)
739 .ry(0, Parameter::Named("a".to_string()))
740 .rz(0, Parameter::Named("b".to_string()))
741 .build();
742
743 let mut params = HashMap::new();
744 params.insert("a".to_string(), 1.0);
745 params.insert("b".to_string(), 2.0);
746
747 let concrete = circuit
748 .bind_parameters::<2>(¶ms)
749 .expect("Parameter binding should succeed with valid params");
750 assert_eq!(concrete.num_gates(), 2);
751 }
752
753 #[test]
754 fn test_parameter_expressions() {
755 use std::f64::consts::PI;
756
757 let expr = Parameter::Expression(Box::new(ParameterExpression::Mul(
758 Parameter::Named("theta".to_string()),
759 Parameter::Fixed(2.0),
760 )));
761
762 let circuit = ParametricCircuitBuilder::new(1).rx(0, expr).build();
763
764 let mut params = HashMap::new();
765 params.insert("theta".to_string(), PI / 4.0);
766
767 let concrete = circuit
768 .bind_parameters::<1>(¶ms)
769 .expect("Parameter expression binding should succeed");
770 assert_eq!(concrete.num_gates(), 1);
771 }
772
773 #[test]
774 fn test_hardware_efficient_ansatz() {
775 let ansatz = ParametricTemplates::hardware_efficient_ansatz(4, 2);
776
777 assert_eq!(ansatz.num_qubits, 4);
778 assert_eq!(ansatz.num_parameters(), 20); }
780
781 #[test]
782 fn test_qaoa_ansatz() {
783 let edges = vec![(0, 1), (1, 2), (2, 3)];
784 let ansatz = ParametricTemplates::qaoa_ansatz(4, 3, edges);
785
786 assert_eq!(ansatz.num_qubits, 4);
787 assert_eq!(ansatz.num_parameters(), 6); }
789}