quantrs2_device/
parametric.rs

1//! Parametric circuit execution for variational quantum algorithms.
2//!
3//! This module provides support for parameterized quantum circuits,
4//! enabling efficient execution of variational algorithms like VQE and QAOA.
5
6use 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/// Parameter type for circuits
14#[derive(Debug, Clone, PartialEq)]
15pub enum Parameter {
16    /// Fixed value
17    Fixed(f64),
18    /// Named parameter
19    Named(String),
20    /// Expression of parameters
21    Expression(Box<ParameterExpression>),
22}
23
24/// Parameter expression for complex relationships
25#[derive(Debug, Clone, PartialEq)]
26pub enum ParameterExpression {
27    /// Single parameter
28    Param(String),
29    /// Constant value
30    Const(f64),
31    /// Addition
32    Add(Parameter, Parameter),
33    /// Multiplication
34    Mul(Parameter, Parameter),
35    /// Division
36    Div(Parameter, Parameter),
37    /// Trigonometric functions
38    Sin(Parameter),
39    Cos(Parameter),
40    /// Power
41    Pow(Parameter, f64),
42}
43
44/// Parametric gate representation
45#[derive(Debug, Clone)]
46pub struct ParametricGate {
47    /// Gate type
48    pub gate_type: String,
49    /// Target qubits
50    pub qubits: Vec<usize>,
51    /// Parameters (if any)
52    pub parameters: Vec<Parameter>,
53}
54
55/// Parametric quantum circuit
56#[derive(Debug, Clone)]
57pub struct ParametricCircuit {
58    /// Number of qubits
59    pub num_qubits: usize,
60    /// Gates in the circuit
61    pub gates: Vec<ParametricGate>,
62    /// Parameter names
63    pub parameter_names: Vec<String>,
64    /// Metadata
65    pub metadata: HashMap<String, String>,
66}
67
68impl ParametricCircuit {
69    /// Create a new parametric circuit
70    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    /// Add a parametric gate
80    pub fn add_gate(&mut self, gate: ParametricGate) -> &mut Self {
81        // Extract parameter names
82        for param in &gate.parameters {
83            self.extract_parameter_names(param);
84        }
85
86        self.gates.push(gate);
87        self
88    }
89
90    /// Extract parameter names from a parameter
91    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    /// Extract parameter names from expression
106    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    /// Get number of parameters
129    pub fn num_parameters(&self) -> usize {
130        self.parameter_names.len()
131    }
132
133    /// Bind parameters to create a concrete circuit
134    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    /// Bind parameters using array
256    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(&param_map)
274    }
275
276    /// Evaluate parameter value
277    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    /// Evaluate parameter expression
293    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    /// Instantiate a gate with concrete values
339    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
416/// Builder for parametric circuits
417pub struct ParametricCircuitBuilder {
418    circuit: ParametricCircuit,
419}
420
421impl ParametricCircuitBuilder {
422    /// Create a new builder
423    pub fn new(num_qubits: usize) -> Self {
424        Self {
425            circuit: ParametricCircuit::new(num_qubits),
426        }
427    }
428
429    /// Add a Hadamard gate
430    #[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    /// Add a parametric RX gate
441    #[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    /// Add a parametric RY gate
452    #[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    /// Add a parametric RZ gate
463    #[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    /// Add a CNOT gate
474    #[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    /// Add a parametric controlled rotation
485    #[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    /// Build the circuit
496    pub fn build(self) -> ParametricCircuit {
497        self.circuit
498    }
499}
500
501/// Batch executor for parametric circuits
502pub struct ParametricExecutor<E> {
503    /// Underlying executor
504    executor: Arc<E>,
505    /// Cache for compiled circuits
506    cache: HashMap<String, Box<dyn GateOp>>,
507}
508
509impl<E> ParametricExecutor<E> {
510    /// Create a new parametric executor
511    pub fn new(executor: E) -> Self {
512        Self {
513            executor: Arc::new(executor),
514            cache: HashMap::new(),
515        }
516    }
517}
518
519/// Batch execution request
520#[derive(Debug, Clone)]
521pub struct BatchExecutionRequest {
522    /// Parametric circuit
523    pub circuit: ParametricCircuit,
524    /// Parameter sets to execute
525    pub parameter_sets: Vec<Vec<f64>>,
526    /// Number of shots per parameter set
527    pub shots: usize,
528    /// Observable to measure (optional)
529    pub observable: Option<crate::zero_noise_extrapolation::Observable>,
530}
531
532/// Batch execution result
533#[derive(Debug, Clone)]
534pub struct BatchExecutionResult {
535    /// Results for each parameter set
536    pub results: Vec<CircuitResult>,
537    /// Expectation values (if observable provided)
538    pub expectation_values: Option<Vec<f64>>,
539    /// Execution time (milliseconds)
540    pub execution_time: u128,
541}
542
543/// Standard parametric circuit templates
544pub struct ParametricTemplates;
545
546impl ParametricTemplates {
547    /// Hardware-efficient ansatz
548    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            // Single-qubit rotations
554            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            // Entangling gates
562            for q in 0..num_qubits - 1 {
563                builder = builder.cnot(q, q + 1);
564            }
565        }
566
567        // Final layer of rotations
568        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    /// QAOA ansatz
576    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        // Initial Hadamard layer
584        for q in 0..num_qubits {
585            builder = builder.h(q);
586        }
587
588        for p in 0..num_layers {
589            // Problem Hamiltonian layer
590            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            // Mixer Hamiltonian layer
596            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    /// Strongly entangling layers
606    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            // Rotation layer
611            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            // Entangling layer with circular connectivity
619            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    /// Excitation preserving ansatz (for chemistry)
629    pub fn excitation_preserving(num_qubits: usize, num_electrons: usize) -> ParametricCircuit {
630        let mut builder = ParametricCircuitBuilder::new(num_qubits);
631
632        // Initialize with computational basis state for electrons
633        // (This would need X gates in practice)
634
635        // Single excitations
636        for i in 0..num_electrons {
637            for a in num_electrons..num_qubits {
638                let theta = Parameter::Named(format!("t1_{i}_{a}"));
639                // Simplified - real implementation would use controlled rotations
640                builder = builder.cnot(i, a).ry(a, theta).cnot(i, a);
641            }
642        }
643
644        // Double excitations (simplified)
645        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                        // Very simplified - real implementation is more complex
651                        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
666/// Parameter optimization utilities
667pub struct ParameterOptimizer;
668
669impl ParameterOptimizer {
670    /// Calculate parameter gradient using parameter shift rule
671    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 each parameter
680        for (i, _) in params.iter().enumerate() {
681            // Shift parameter positively
682            let mut params_plus = params.to_vec();
683            params_plus[i] += shift;
684
685            // Shift parameter negatively
686            let mut params_minus = params.to_vec();
687            params_minus[i] -= shift;
688
689            // Calculate gradient (would need actual execution)
690            // gradient[i] = (f(θ + s) - f(θ - s)) / (2 * sin(s))
691            gradients[i] = 0.0; // Placeholder
692        }
693
694        gradients
695    }
696
697    /// Natural gradient using quantum Fisher information
698    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        // Calculate quantum Fisher information matrix (simplified)
707        let mut fisher = Array2::<f64>::zeros((n, n));
708        for i in 0..n {
709            fisher[[i, i]] = 1.0 + regularization; // Placeholder
710        }
711
712        // Solve F * nat_grad = grad
713        // Simplified - would use proper linear algebra
714        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>(&params)
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>(&params)
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); // 2 layers * 4 qubits * 2 params + 4 final
779    }
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); // 3 layers * 2 params (gamma, beta)
788    }
789}