Skip to main content

quantrs2_core/
parametric.rs

1//! Parametric gate support: symbolic and numeric parameter values.
2//!
3//! Defines [`Parameter`] and [`ParametricGate`] for circuits whose gate angles
4//! can be symbolic (for variational algorithms) or concrete numeric constants.
5//! Supports binding, differentiation, and parameter sweeps.
6
7use scirs2_core::Complex64;
8use std::collections::HashMap;
9use std::ops::{Add, Div, Mul, Sub};
10
11use crate::error::QuantRS2Result;
12use crate::gate::GateOp;
13use crate::qubit::QubitId;
14use crate::symbolic::SymbolicExpression;
15
16/// Parameter value representation for parametric gates
17#[derive(Debug, Clone)]
18pub enum Parameter {
19    /// Constant floating-point value
20    Constant(f64),
21
22    /// Complex constant value
23    ComplexConstant(Complex64),
24
25    /// Symbolic parameter with a name and optional value (legacy)
26    Symbol(SymbolicParameter),
27
28    /// Full symbolic expression using SymEngine
29    Symbolic(SymbolicExpression),
30}
31
32impl Parameter {
33    /// Create a new constant parameter
34    pub const fn constant(value: f64) -> Self {
35        Self::Constant(value)
36    }
37
38    /// Create a new complex constant parameter
39    pub const fn complex_constant(value: Complex64) -> Self {
40        Self::ComplexConstant(value)
41    }
42
43    /// Create a new symbolic parameter (legacy)
44    pub fn symbol(name: &str) -> Self {
45        Self::Symbol(SymbolicParameter::new(name))
46    }
47
48    /// Create a new symbolic parameter with a value (legacy)
49    pub fn symbol_with_value(name: &str, value: f64) -> Self {
50        Self::Symbol(SymbolicParameter::with_value(name, value))
51    }
52
53    /// Create a symbolic expression parameter
54    pub const fn symbolic(expr: SymbolicExpression) -> Self {
55        Self::Symbolic(expr)
56    }
57
58    /// Create a symbolic variable
59    pub fn variable(name: &str) -> Self {
60        Self::Symbolic(SymbolicExpression::variable(name))
61    }
62
63    /// Parse a parameter from a string expression
64    pub fn parse(expr: &str) -> QuantRS2Result<Self> {
65        // Try to parse as a number first
66        if let Ok(value) = expr.parse::<f64>() {
67            return Ok(Self::Constant(value));
68        }
69
70        // Otherwise parse as symbolic expression
71        let symbolic_expr = SymbolicExpression::parse(expr)?;
72        Ok(Self::Symbolic(symbolic_expr))
73    }
74
75    /// Get the value of the parameter, if available
76    pub fn value(&self) -> Option<f64> {
77        match self {
78            Self::Constant(val) => Some(*val),
79            Self::ComplexConstant(val) => {
80                if val.im.abs() < 1e-12 {
81                    Some(val.re)
82                } else {
83                    None // Cannot convert complex to real
84                }
85            }
86            Self::Symbol(sym) => sym.value,
87            Self::Symbolic(expr) => {
88                // Try to evaluate with empty variables
89                expr.evaluate(&HashMap::new()).ok()
90            }
91        }
92    }
93
94    /// Get the complex value of the parameter, if available
95    pub fn complex_value(&self) -> Option<Complex64> {
96        match self {
97            Self::Constant(val) => Some(Complex64::new(*val, 0.0)),
98            Self::ComplexConstant(val) => Some(*val),
99            Self::Symbol(sym) => sym.value.map(|v| Complex64::new(v, 0.0)),
100            Self::Symbolic(expr) => {
101                // Try to evaluate with empty variables
102                expr.evaluate_complex(&HashMap::new()).ok()
103            }
104        }
105    }
106
107    /// Check if the parameter has a concrete value
108    pub fn has_value(&self) -> bool {
109        match self {
110            Self::Constant(_) | Self::ComplexConstant(_) => true,
111            Self::Symbol(sym) => sym.value.is_some(),
112            Self::Symbolic(expr) => expr.is_constant(),
113        }
114    }
115
116    /// Evaluate the parameter with given variable values
117    pub fn evaluate(&self, variables: &HashMap<String, f64>) -> QuantRS2Result<f64> {
118        match self {
119            Self::Constant(val) => Ok(*val),
120            Self::ComplexConstant(val) => {
121                if val.im.abs() < 1e-12 {
122                    Ok(val.re)
123                } else {
124                    Err(crate::error::QuantRS2Error::InvalidInput(
125                        "Cannot evaluate complex parameter to real number".to_string(),
126                    ))
127                }
128            }
129            Self::Symbol(sym) => sym.value.map_or_else(
130                || {
131                    variables.get(&sym.name).copied().ok_or_else(|| {
132                        crate::error::QuantRS2Error::InvalidInput(format!(
133                            "Variable '{}' not found",
134                            sym.name
135                        ))
136                    })
137                },
138                Ok,
139            ),
140            Self::Symbolic(expr) => expr.evaluate(variables),
141        }
142    }
143
144    /// Evaluate the parameter with given variable values (complex)
145    pub fn evaluate_complex(
146        &self,
147        variables: &HashMap<String, Complex64>,
148    ) -> QuantRS2Result<Complex64> {
149        match self {
150            Self::Constant(val) => Ok(Complex64::new(*val, 0.0)),
151            Self::ComplexConstant(val) => Ok(*val),
152            Self::Symbol(sym) => sym.value.map_or_else(
153                || {
154                    variables.get(&sym.name).copied().ok_or_else(|| {
155                        crate::error::QuantRS2Error::InvalidInput(format!(
156                            "Variable '{}' not found",
157                            sym.name
158                        ))
159                    })
160                },
161                |value| Ok(Complex64::new(value, 0.0)),
162            ),
163            Self::Symbolic(expr) => expr.evaluate_complex(variables),
164        }
165    }
166
167    /// Get all variable names in the parameter
168    pub fn variables(&self) -> Vec<String> {
169        match self {
170            Self::Constant(_) | Self::ComplexConstant(_) => Vec::new(),
171            Self::Symbol(sym) => {
172                if sym.value.is_some() {
173                    Vec::new()
174                } else {
175                    vec![sym.name.clone()]
176                }
177            }
178            Self::Symbolic(expr) => expr.variables(),
179        }
180    }
181
182    /// Substitute variables with expressions
183    pub fn substitute(&self, substitutions: &HashMap<String, Self>) -> QuantRS2Result<Self> {
184        match self {
185            Self::Constant(_) | Self::ComplexConstant(_) => Ok(self.clone()),
186            Self::Symbol(sym) => Ok(substitutions
187                .get(&sym.name)
188                .map_or_else(|| self.clone(), |r| r.clone())),
189            Self::Symbolic(expr) => {
190                // Convert Parameter substitutions to SymbolicExpression substitutions
191                let symbolic_subs: HashMap<String, SymbolicExpression> = substitutions
192                    .iter()
193                    .map(|(k, v)| (k.clone(), v.to_symbolic_expression()))
194                    .collect();
195
196                let new_expr = expr.substitute(&symbolic_subs)?;
197                Ok(Self::Symbolic(new_expr))
198            }
199        }
200    }
201
202    /// Convert parameter to SymbolicExpression
203    pub fn to_symbolic_expression(&self) -> SymbolicExpression {
204        match self {
205            Self::Constant(val) => SymbolicExpression::constant(*val),
206            Self::ComplexConstant(val) => SymbolicExpression::complex_constant(*val),
207            Self::Symbol(sym) => sym.value.map_or_else(
208                || SymbolicExpression::variable(&sym.name),
209                SymbolicExpression::constant,
210            ),
211            Self::Symbolic(expr) => expr.clone(),
212        }
213    }
214
215    /// Differentiate the parameter with respect to a variable
216    #[cfg(feature = "symbolic")]
217    pub fn diff(&self, var: &str) -> QuantRS2Result<Self> {
218        use crate::symbolic::calculus;
219
220        let expr = self.to_symbolic_expression();
221        let diff_expr = calculus::diff(&expr, var)?;
222        Ok(Self::Symbolic(diff_expr))
223    }
224
225    /// Integrate the parameter with respect to a variable
226    #[cfg(feature = "symbolic")]
227    pub fn integrate(&self, var: &str) -> QuantRS2Result<Self> {
228        use crate::symbolic::calculus;
229
230        let expr = self.to_symbolic_expression();
231        let int_expr = calculus::integrate(&expr, var)?;
232        Ok(Self::Symbolic(int_expr))
233    }
234}
235
236impl From<f64> for Parameter {
237    fn from(value: f64) -> Self {
238        Self::Constant(value)
239    }
240}
241
242impl From<Complex64> for Parameter {
243    fn from(value: Complex64) -> Self {
244        if value.im == 0.0 {
245            Self::Constant(value.re)
246        } else {
247            Self::ComplexConstant(value)
248        }
249    }
250}
251
252impl From<SymbolicExpression> for Parameter {
253    fn from(expr: SymbolicExpression) -> Self {
254        Self::Symbolic(expr)
255    }
256}
257
258impl From<&str> for Parameter {
259    fn from(name: &str) -> Self {
260        Self::variable(name)
261    }
262}
263
264impl Add for Parameter {
265    type Output = Self;
266
267    fn add(self, rhs: Self) -> Self::Output {
268        match (self, rhs) {
269            (Self::Constant(a), Self::Constant(b)) => Self::Constant(a + b),
270            (Self::ComplexConstant(a), Self::ComplexConstant(b)) => Self::ComplexConstant(a + b),
271            (Self::Constant(a), Self::ComplexConstant(b)) => {
272                Self::ComplexConstant(Complex64::new(a, 0.0) + b)
273            }
274            (Self::ComplexConstant(a), Self::Constant(b)) => {
275                Self::ComplexConstant(a + Complex64::new(b, 0.0))
276            }
277            (a, b) => {
278                // Convert to symbolic expressions and add
279                let a_expr = a.to_symbolic_expression();
280                let b_expr = b.to_symbolic_expression();
281                Self::Symbolic(a_expr + b_expr)
282            }
283        }
284    }
285}
286
287impl Sub for Parameter {
288    type Output = Self;
289
290    fn sub(self, rhs: Self) -> Self::Output {
291        match (self, rhs) {
292            (Self::Constant(a), Self::Constant(b)) => Self::Constant(a - b),
293            (Self::ComplexConstant(a), Self::ComplexConstant(b)) => Self::ComplexConstant(a - b),
294            (Self::Constant(a), Self::ComplexConstant(b)) => {
295                Self::ComplexConstant(Complex64::new(a, 0.0) - b)
296            }
297            (Self::ComplexConstant(a), Self::Constant(b)) => {
298                Self::ComplexConstant(a - Complex64::new(b, 0.0))
299            }
300            (a, b) => {
301                // Convert to symbolic expressions and subtract
302                let a_expr = a.to_symbolic_expression();
303                let b_expr = b.to_symbolic_expression();
304                Self::Symbolic(a_expr - b_expr)
305            }
306        }
307    }
308}
309
310impl Mul for Parameter {
311    type Output = Self;
312
313    fn mul(self, rhs: Self) -> Self::Output {
314        match (self, rhs) {
315            (Self::Constant(a), Self::Constant(b)) => Self::Constant(a * b),
316            (Self::ComplexConstant(a), Self::ComplexConstant(b)) => Self::ComplexConstant(a * b),
317            (Self::Constant(a), Self::ComplexConstant(b)) => {
318                Self::ComplexConstant(Complex64::new(a, 0.0) * b)
319            }
320            (Self::ComplexConstant(a), Self::Constant(b)) => {
321                Self::ComplexConstant(a * Complex64::new(b, 0.0))
322            }
323            (a, b) => {
324                // Convert to symbolic expressions and multiply
325                let a_expr = a.to_symbolic_expression();
326                let b_expr = b.to_symbolic_expression();
327                Self::Symbolic(a_expr * b_expr)
328            }
329        }
330    }
331}
332
333impl Div for Parameter {
334    type Output = Self;
335
336    fn div(self, rhs: Self) -> Self::Output {
337        match (self, rhs) {
338            (Self::Constant(a), Self::Constant(b)) => Self::Constant(a / b),
339            (Self::ComplexConstant(a), Self::ComplexConstant(b)) => Self::ComplexConstant(a / b),
340            (Self::Constant(a), Self::ComplexConstant(b)) => {
341                Self::ComplexConstant(Complex64::new(a, 0.0) / b)
342            }
343            (Self::ComplexConstant(a), Self::Constant(b)) => {
344                Self::ComplexConstant(a / Complex64::new(b, 0.0))
345            }
346            (a, b) => {
347                // Convert to symbolic expressions and divide
348                let a_expr = a.to_symbolic_expression();
349                let b_expr = b.to_symbolic_expression();
350                Self::Symbolic(a_expr / b_expr)
351            }
352        }
353    }
354}
355
356/// Symbolic parameter that can be used in parametric gates
357#[derive(Debug, Clone)]
358pub struct SymbolicParameter {
359    /// Name of the parameter
360    pub name: String,
361
362    /// Optional value of the parameter
363    pub value: Option<f64>,
364}
365
366impl SymbolicParameter {
367    /// Create a new symbolic parameter without a value
368    pub fn new(name: &str) -> Self {
369        Self {
370            name: name.to_string(),
371            value: None,
372        }
373    }
374
375    /// Create a new symbolic parameter with a value
376    pub fn with_value(name: &str, value: f64) -> Self {
377        Self {
378            name: name.to_string(),
379            value: Some(value),
380        }
381    }
382
383    /// Set the value of the parameter
384    pub const fn set_value(&mut self, value: f64) {
385        self.value = Some(value);
386    }
387
388    /// Clear the value of the parameter
389    pub const fn clear_value(&mut self) {
390        self.value = None;
391    }
392}
393
394// Note: Cannot implement Copy because String doesn't implement Copy
395// Use Clone instead (already implemented above)
396
397/// Trait for parametric gates that extends GateOp with parameter-related functionality
398pub trait ParametricGate: GateOp {
399    /// Returns the parameters of the gate
400    fn parameters(&self) -> Vec<Parameter>;
401
402    /// Returns the names of the parameters
403    fn parameter_names(&self) -> Vec<String>;
404
405    /// Returns a new gate with updated parameters
406    fn with_parameters(&self, params: &[Parameter]) -> QuantRS2Result<Box<dyn ParametricGate>>;
407
408    /// Returns a new gate with updated parameter at the specified index
409    fn with_parameter_at(
410        &self,
411        index: usize,
412        param: Parameter,
413    ) -> QuantRS2Result<Box<dyn ParametricGate>>;
414
415    /// Assigns values to symbolic parameters
416    fn assign(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>>;
417
418    /// Returns the gate with all parameters set to the specified values
419    fn bind(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>>;
420}
421
422/// Specialized implementation of rotation gates around the X-axis
423#[derive(Debug, Clone)]
424pub struct ParametricRotationX {
425    /// Target qubit
426    pub target: QubitId,
427
428    /// Rotation angle parameter
429    pub theta: Parameter,
430}
431
432impl ParametricRotationX {
433    /// Create a new X-rotation gate with a constant angle
434    pub const fn new(target: QubitId, theta: f64) -> Self {
435        Self {
436            target,
437            theta: Parameter::constant(theta),
438        }
439    }
440
441    /// Create a new X-rotation gate with a symbolic angle
442    pub fn new_symbolic(target: QubitId, name: &str) -> Self {
443        Self {
444            target,
445            theta: Parameter::symbol(name),
446        }
447    }
448}
449
450impl GateOp for ParametricRotationX {
451    fn name(&self) -> &'static str {
452        "RX"
453    }
454
455    fn qubits(&self) -> Vec<QubitId> {
456        vec![self.target]
457    }
458
459    fn is_parameterized(&self) -> bool {
460        true
461    }
462
463    fn matrix(&self) -> QuantRS2Result<Vec<Complex64>> {
464        self.theta.value().map_or_else(
465            || {
466                Err(crate::error::QuantRS2Error::UnsupportedOperation(
467                    "Cannot generate matrix for RX gate with unbound symbolic parameter".into(),
468                ))
469            },
470            |theta| {
471                let cos = (theta / 2.0).cos();
472                let sin = (theta / 2.0).sin();
473                Ok(vec![
474                    Complex64::new(cos, 0.0),
475                    Complex64::new(0.0, -sin),
476                    Complex64::new(0.0, -sin),
477                    Complex64::new(cos, 0.0),
478                ])
479            },
480        )
481    }
482
483    fn as_any(&self) -> &dyn std::any::Any {
484        self
485    }
486
487    fn clone_gate(&self) -> Box<dyn GateOp> {
488        Box::new(self.clone())
489    }
490}
491
492impl ParametricGate for ParametricRotationX {
493    fn parameters(&self) -> Vec<Parameter> {
494        vec![self.theta.clone()]
495    }
496
497    fn parameter_names(&self) -> Vec<String> {
498        match self.theta {
499            Parameter::Symbol(ref sym) => vec![sym.name.clone()],
500            _ => Vec::new(),
501        }
502    }
503
504    fn with_parameters(&self, params: &[Parameter]) -> QuantRS2Result<Box<dyn ParametricGate>> {
505        if params.len() != 1 {
506            return Err(crate::error::QuantRS2Error::InvalidInput(format!(
507                "RotationX expects 1 parameter, got {}",
508                params.len()
509            )));
510        }
511        Ok(Box::new(Self {
512            target: self.target,
513            theta: params[0].clone(),
514        }))
515    }
516
517    fn with_parameter_at(
518        &self,
519        index: usize,
520        param: Parameter,
521    ) -> QuantRS2Result<Box<dyn ParametricGate>> {
522        if index != 0 {
523            return Err(crate::error::QuantRS2Error::InvalidInput(format!(
524                "RotationX has only 1 parameter, got index {index}"
525            )));
526        }
527        Ok(Box::new(Self {
528            target: self.target,
529            theta: param,
530        }))
531    }
532
533    fn assign(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>> {
534        match self.theta {
535            Parameter::Symbol(ref sym) => {
536                for (name, value) in values {
537                    if sym.name == *name {
538                        return Ok(Box::new(Self {
539                            target: self.target,
540                            theta: Parameter::Symbol(SymbolicParameter::with_value(
541                                &sym.name, *value,
542                            )),
543                        }));
544                    }
545                }
546                // Parameter not found in values, return a clone of the original gate
547                Ok(Box::new(self.clone()))
548            }
549            _ => Ok(Box::new(self.clone())), // Not a symbolic parameter, no change needed
550        }
551    }
552
553    fn bind(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>> {
554        self.assign(values)
555    }
556}
557
558/// Specialized implementation of rotation gates around the Y-axis
559#[derive(Debug, Clone)]
560pub struct ParametricRotationY {
561    /// Target qubit
562    pub target: QubitId,
563
564    /// Rotation angle parameter
565    pub theta: Parameter,
566}
567
568impl ParametricRotationY {
569    /// Create a new Y-rotation gate with a constant angle
570    pub const fn new(target: QubitId, theta: f64) -> Self {
571        Self {
572            target,
573            theta: Parameter::constant(theta),
574        }
575    }
576
577    /// Create a new Y-rotation gate with a symbolic angle
578    pub fn new_symbolic(target: QubitId, name: &str) -> Self {
579        Self {
580            target,
581            theta: Parameter::symbol(name),
582        }
583    }
584}
585
586impl GateOp for ParametricRotationY {
587    fn name(&self) -> &'static str {
588        "RY"
589    }
590
591    fn qubits(&self) -> Vec<QubitId> {
592        vec![self.target]
593    }
594
595    fn is_parameterized(&self) -> bool {
596        true
597    }
598
599    fn matrix(&self) -> QuantRS2Result<Vec<Complex64>> {
600        self.theta.value().map_or_else(
601            || {
602                Err(crate::error::QuantRS2Error::UnsupportedOperation(
603                    "Cannot generate matrix for RY gate with unbound symbolic parameter".into(),
604                ))
605            },
606            |theta| {
607                let cos = (theta / 2.0).cos();
608                let sin = (theta / 2.0).sin();
609                Ok(vec![
610                    Complex64::new(cos, 0.0),
611                    Complex64::new(-sin, 0.0),
612                    Complex64::new(sin, 0.0),
613                    Complex64::new(cos, 0.0),
614                ])
615            },
616        )
617    }
618
619    fn as_any(&self) -> &dyn std::any::Any {
620        self
621    }
622
623    fn clone_gate(&self) -> Box<dyn GateOp> {
624        Box::new(self.clone())
625    }
626}
627
628impl ParametricGate for ParametricRotationY {
629    fn parameters(&self) -> Vec<Parameter> {
630        vec![self.theta.clone()]
631    }
632
633    fn parameter_names(&self) -> Vec<String> {
634        match self.theta {
635            Parameter::Symbol(ref sym) => vec![sym.name.clone()],
636            _ => Vec::new(),
637        }
638    }
639
640    fn with_parameters(&self, params: &[Parameter]) -> QuantRS2Result<Box<dyn ParametricGate>> {
641        if params.len() != 1 {
642            return Err(crate::error::QuantRS2Error::InvalidInput(format!(
643                "RotationY expects 1 parameter, got {}",
644                params.len()
645            )));
646        }
647        Ok(Box::new(Self {
648            target: self.target,
649            theta: params[0].clone(),
650        }))
651    }
652
653    fn with_parameter_at(
654        &self,
655        index: usize,
656        param: Parameter,
657    ) -> QuantRS2Result<Box<dyn ParametricGate>> {
658        if index != 0 {
659            return Err(crate::error::QuantRS2Error::InvalidInput(format!(
660                "RotationY has only 1 parameter, got index {index}"
661            )));
662        }
663        Ok(Box::new(Self {
664            target: self.target,
665            theta: param,
666        }))
667    }
668
669    fn assign(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>> {
670        match self.theta {
671            Parameter::Symbol(ref sym) => {
672                for (name, value) in values {
673                    if sym.name == *name {
674                        return Ok(Box::new(Self {
675                            target: self.target,
676                            theta: Parameter::Symbol(SymbolicParameter::with_value(
677                                &sym.name, *value,
678                            )),
679                        }));
680                    }
681                }
682                // Parameter not found in values, return a clone of the original gate
683                Ok(Box::new(self.clone()))
684            }
685            _ => Ok(Box::new(self.clone())), // Not a symbolic parameter, no change needed
686        }
687    }
688
689    fn bind(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>> {
690        self.assign(values)
691    }
692}
693
694/// Specialized implementation of rotation gates around the Z-axis
695#[derive(Debug, Clone)]
696pub struct ParametricRotationZ {
697    /// Target qubit
698    pub target: QubitId,
699
700    /// Rotation angle parameter
701    pub theta: Parameter,
702}
703
704impl ParametricRotationZ {
705    /// Create a new Z-rotation gate with a constant angle
706    pub const fn new(target: QubitId, theta: f64) -> Self {
707        Self {
708            target,
709            theta: Parameter::constant(theta),
710        }
711    }
712
713    /// Create a new Z-rotation gate with a symbolic angle
714    pub fn new_symbolic(target: QubitId, name: &str) -> Self {
715        Self {
716            target,
717            theta: Parameter::symbol(name),
718        }
719    }
720}
721
722impl GateOp for ParametricRotationZ {
723    fn name(&self) -> &'static str {
724        "RZ"
725    }
726
727    fn qubits(&self) -> Vec<QubitId> {
728        vec![self.target]
729    }
730
731    fn is_parameterized(&self) -> bool {
732        true
733    }
734
735    fn matrix(&self) -> QuantRS2Result<Vec<Complex64>> {
736        self.theta.value().map_or_else(
737            || {
738                Err(crate::error::QuantRS2Error::UnsupportedOperation(
739                    "Cannot generate matrix for RZ gate with unbound symbolic parameter".into(),
740                ))
741            },
742            |theta| {
743                let phase = Complex64::new(0.0, -theta / 2.0).exp();
744                let phase_conj = Complex64::new(0.0, theta / 2.0).exp();
745                Ok(vec![
746                    phase_conj,
747                    Complex64::new(0.0, 0.0),
748                    Complex64::new(0.0, 0.0),
749                    phase,
750                ])
751            },
752        )
753    }
754
755    fn as_any(&self) -> &dyn std::any::Any {
756        self
757    }
758
759    fn clone_gate(&self) -> Box<dyn GateOp> {
760        Box::new(self.clone())
761    }
762}
763
764impl ParametricGate for ParametricRotationZ {
765    fn parameters(&self) -> Vec<Parameter> {
766        vec![self.theta.clone()]
767    }
768
769    fn parameter_names(&self) -> Vec<String> {
770        match self.theta {
771            Parameter::Symbol(ref sym) => vec![sym.name.clone()],
772            _ => Vec::new(),
773        }
774    }
775
776    fn with_parameters(&self, params: &[Parameter]) -> QuantRS2Result<Box<dyn ParametricGate>> {
777        if params.len() != 1 {
778            return Err(crate::error::QuantRS2Error::InvalidInput(format!(
779                "RotationZ expects 1 parameter, got {}",
780                params.len()
781            )));
782        }
783        Ok(Box::new(Self {
784            target: self.target,
785            theta: params[0].clone(),
786        }))
787    }
788
789    fn with_parameter_at(
790        &self,
791        index: usize,
792        param: Parameter,
793    ) -> QuantRS2Result<Box<dyn ParametricGate>> {
794        if index != 0 {
795            return Err(crate::error::QuantRS2Error::InvalidInput(format!(
796                "RotationZ has only 1 parameter, got index {index}"
797            )));
798        }
799        Ok(Box::new(Self {
800            target: self.target,
801            theta: param,
802        }))
803    }
804
805    fn assign(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>> {
806        match self.theta {
807            Parameter::Symbol(ref sym) => {
808                for (name, value) in values {
809                    if sym.name == *name {
810                        return Ok(Box::new(Self {
811                            target: self.target,
812                            theta: Parameter::Symbol(SymbolicParameter::with_value(
813                                &sym.name, *value,
814                            )),
815                        }));
816                    }
817                }
818                // Parameter not found in values, return a clone of the original gate
819                Ok(Box::new(self.clone()))
820            }
821            _ => Ok(Box::new(self.clone())), // Not a symbolic parameter, no change needed
822        }
823    }
824
825    fn bind(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>> {
826        self.assign(values)
827    }
828}
829
830/// Specialized implementation of a general U-gate (parameterized single-qubit gate)
831#[derive(Debug, Clone)]
832pub struct ParametricU {
833    /// Target qubit
834    pub target: QubitId,
835
836    /// Theta parameter (rotation angle)
837    pub theta: Parameter,
838
839    /// Phi parameter (phase angle)
840    pub phi: Parameter,
841
842    /// Lambda parameter (phase angle)
843    pub lambda: Parameter,
844}
845
846impl ParametricU {
847    /// Create a new U-gate with constant angles
848    pub const fn new(target: QubitId, theta: f64, phi: f64, lambda: f64) -> Self {
849        Self {
850            target,
851            theta: Parameter::constant(theta),
852            phi: Parameter::constant(phi),
853            lambda: Parameter::constant(lambda),
854        }
855    }
856
857    /// Create a new U-gate with symbolic angles
858    pub fn new_symbolic(
859        target: QubitId,
860        theta_name: &str,
861        phi_name: &str,
862        lambda_name: &str,
863    ) -> Self {
864        Self {
865            target,
866            theta: Parameter::symbol(theta_name),
867            phi: Parameter::symbol(phi_name),
868            lambda: Parameter::symbol(lambda_name),
869        }
870    }
871}
872
873impl GateOp for ParametricU {
874    fn name(&self) -> &'static str {
875        "U"
876    }
877
878    fn qubits(&self) -> Vec<QubitId> {
879        vec![self.target]
880    }
881
882    fn is_parameterized(&self) -> bool {
883        true
884    }
885
886    fn matrix(&self) -> QuantRS2Result<Vec<Complex64>> {
887        if let (Some(theta), Some(phi), Some(lambda)) =
888            (self.theta.value(), self.phi.value(), self.lambda.value())
889        {
890            let cos = (theta / 2.0).cos();
891            let sin = (theta / 2.0).sin();
892
893            let e_phi = Complex64::new(0.0, phi).exp();
894            let e_lambda = Complex64::new(0.0, lambda).exp();
895
896            Ok(vec![
897                Complex64::new(cos, 0.0),
898                -sin * e_lambda,
899                sin * e_phi,
900                cos * e_phi * e_lambda,
901            ])
902        } else {
903            Err(crate::error::QuantRS2Error::UnsupportedOperation(
904                "Cannot generate matrix for U gate with unbound symbolic parameters".into(),
905            ))
906        }
907    }
908
909    fn as_any(&self) -> &dyn std::any::Any {
910        self
911    }
912
913    fn clone_gate(&self) -> Box<dyn GateOp> {
914        Box::new(self.clone())
915    }
916}
917
918impl ParametricGate for ParametricU {
919    fn parameters(&self) -> Vec<Parameter> {
920        vec![self.theta.clone(), self.phi.clone(), self.lambda.clone()]
921    }
922
923    fn parameter_names(&self) -> Vec<String> {
924        let mut names = Vec::new();
925
926        if let Parameter::Symbol(ref sym) = self.theta {
927            names.push(sym.name.clone());
928        }
929
930        if let Parameter::Symbol(ref sym) = self.phi {
931            names.push(sym.name.clone());
932        }
933
934        if let Parameter::Symbol(ref sym) = self.lambda {
935            names.push(sym.name.clone());
936        }
937
938        names
939    }
940
941    fn with_parameters(&self, params: &[Parameter]) -> QuantRS2Result<Box<dyn ParametricGate>> {
942        if params.len() != 3 {
943            return Err(crate::error::QuantRS2Error::InvalidInput(format!(
944                "U gate expects 3 parameters, got {}",
945                params.len()
946            )));
947        }
948        Ok(Box::new(Self {
949            target: self.target,
950            theta: params[0].clone(),
951            phi: params[1].clone(),
952            lambda: params[2].clone(),
953        }))
954    }
955
956    fn with_parameter_at(
957        &self,
958        index: usize,
959        param: Parameter,
960    ) -> QuantRS2Result<Box<dyn ParametricGate>> {
961        match index {
962            0 => Ok(Box::new(Self {
963                target: self.target,
964                theta: param,
965                phi: self.phi.clone(),
966                lambda: self.lambda.clone(),
967            })),
968            1 => Ok(Box::new(Self {
969                target: self.target,
970                theta: self.theta.clone(),
971                phi: param,
972                lambda: self.lambda.clone(),
973            })),
974            2 => Ok(Box::new(Self {
975                target: self.target,
976                theta: self.theta.clone(),
977                phi: self.phi.clone(),
978                lambda: param,
979            })),
980            _ => Err(crate::error::QuantRS2Error::InvalidInput(format!(
981                "U gate has only 3 parameters, got index {index}"
982            ))),
983        }
984    }
985
986    fn assign(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>> {
987        let mut result = self.clone();
988
989        // Update theta if it's a symbolic parameter
990        if let Parameter::Symbol(ref sym) = self.theta {
991            for (name, value) in values {
992                if sym.name == *name {
993                    result.theta =
994                        Parameter::Symbol(SymbolicParameter::with_value(&sym.name, *value));
995                    break;
996                }
997            }
998        }
999
1000        // Update phi if it's a symbolic parameter
1001        if let Parameter::Symbol(ref sym) = self.phi {
1002            for (name, value) in values {
1003                if sym.name == *name {
1004                    result.phi =
1005                        Parameter::Symbol(SymbolicParameter::with_value(&sym.name, *value));
1006                    break;
1007                }
1008            }
1009        }
1010
1011        // Update lambda if it's a symbolic parameter
1012        if let Parameter::Symbol(ref sym) = self.lambda {
1013            for (name, value) in values {
1014                if sym.name == *name {
1015                    result.lambda =
1016                        Parameter::Symbol(SymbolicParameter::with_value(&sym.name, *value));
1017                    break;
1018                }
1019            }
1020        }
1021
1022        Ok(Box::new(result))
1023    }
1024
1025    fn bind(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>> {
1026        self.assign(values)
1027    }
1028}
1029
1030/// Specialized implementation of controlled parametric rotation around X-axis
1031#[derive(Debug, Clone)]
1032pub struct ParametricCRX {
1033    /// Control qubit
1034    pub control: QubitId,
1035
1036    /// Target qubit
1037    pub target: QubitId,
1038
1039    /// Rotation angle parameter
1040    pub theta: Parameter,
1041}
1042
1043impl ParametricCRX {
1044    /// Create a new CRX gate with a constant angle
1045    pub const fn new(control: QubitId, target: QubitId, theta: f64) -> Self {
1046        Self {
1047            control,
1048            target,
1049            theta: Parameter::constant(theta),
1050        }
1051    }
1052
1053    /// Create a new CRX gate with a symbolic angle
1054    pub fn new_symbolic(control: QubitId, target: QubitId, name: &str) -> Self {
1055        Self {
1056            control,
1057            target,
1058            theta: Parameter::symbol(name),
1059        }
1060    }
1061}
1062
1063impl GateOp for ParametricCRX {
1064    fn name(&self) -> &'static str {
1065        "CRX"
1066    }
1067
1068    fn qubits(&self) -> Vec<QubitId> {
1069        vec![self.control, self.target]
1070    }
1071
1072    fn is_parameterized(&self) -> bool {
1073        true
1074    }
1075
1076    fn matrix(&self) -> QuantRS2Result<Vec<Complex64>> {
1077        self.theta.value().map_or_else(
1078            || {
1079                Err(crate::error::QuantRS2Error::UnsupportedOperation(
1080                    "Cannot generate matrix for CRX gate with unbound symbolic parameter".into(),
1081                ))
1082            },
1083            |theta| {
1084                let cos = (theta / 2.0).cos();
1085                let sin = (theta / 2.0).sin();
1086
1087                Ok(vec![
1088                    Complex64::new(1.0, 0.0),  // (0,0)
1089                    Complex64::new(0.0, 0.0),  // (0,1)
1090                    Complex64::new(0.0, 0.0),  // (0,2)
1091                    Complex64::new(0.0, 0.0),  // (0,3)
1092                    Complex64::new(0.0, 0.0),  // (1,0)
1093                    Complex64::new(1.0, 0.0),  // (1,1)
1094                    Complex64::new(0.0, 0.0),  // (1,2)
1095                    Complex64::new(0.0, 0.0),  // (1,3)
1096                    Complex64::new(0.0, 0.0),  // (2,0)
1097                    Complex64::new(0.0, 0.0),  // (2,1)
1098                    Complex64::new(cos, 0.0),  // (2,2)
1099                    Complex64::new(0.0, -sin), // (2,3)
1100                    Complex64::new(0.0, 0.0),  // (3,0)
1101                    Complex64::new(0.0, 0.0),  // (3,1)
1102                    Complex64::new(0.0, -sin), // (3,2)
1103                    Complex64::new(cos, 0.0),  // (3,3)
1104                ])
1105            },
1106        )
1107    }
1108
1109    fn as_any(&self) -> &dyn std::any::Any {
1110        self
1111    }
1112
1113    fn clone_gate(&self) -> Box<dyn GateOp> {
1114        Box::new(self.clone())
1115    }
1116}
1117
1118impl ParametricGate for ParametricCRX {
1119    fn parameters(&self) -> Vec<Parameter> {
1120        vec![self.theta.clone()]
1121    }
1122
1123    fn parameter_names(&self) -> Vec<String> {
1124        match self.theta {
1125            Parameter::Symbol(ref sym) => vec![sym.name.clone()],
1126            _ => Vec::new(),
1127        }
1128    }
1129
1130    fn with_parameters(&self, params: &[Parameter]) -> QuantRS2Result<Box<dyn ParametricGate>> {
1131        if params.len() != 1 {
1132            return Err(crate::error::QuantRS2Error::InvalidInput(format!(
1133                "CRX expects 1 parameter, got {}",
1134                params.len()
1135            )));
1136        }
1137        Ok(Box::new(Self {
1138            control: self.control,
1139            target: self.target,
1140            theta: params[0].clone(),
1141        }))
1142    }
1143
1144    fn with_parameter_at(
1145        &self,
1146        index: usize,
1147        param: Parameter,
1148    ) -> QuantRS2Result<Box<dyn ParametricGate>> {
1149        if index != 0 {
1150            return Err(crate::error::QuantRS2Error::InvalidInput(format!(
1151                "CRX has only 1 parameter, got index {index}"
1152            )));
1153        }
1154        Ok(Box::new(Self {
1155            control: self.control,
1156            target: self.target,
1157            theta: param,
1158        }))
1159    }
1160
1161    fn assign(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>> {
1162        match self.theta {
1163            Parameter::Symbol(ref sym) => {
1164                for (name, value) in values {
1165                    if sym.name == *name {
1166                        return Ok(Box::new(Self {
1167                            control: self.control,
1168                            target: self.target,
1169                            theta: Parameter::Symbol(SymbolicParameter::with_value(
1170                                &sym.name, *value,
1171                            )),
1172                        }));
1173                    }
1174                }
1175                // Parameter not found in values, return a clone of the original gate
1176                Ok(Box::new(self.clone()))
1177            }
1178            _ => Ok(Box::new(self.clone())), // Not a symbolic parameter, no change needed
1179        }
1180    }
1181
1182    fn bind(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>> {
1183        self.assign(values)
1184    }
1185}
1186
1187/// Phase shift gate with a parameterized phase
1188#[derive(Debug, Clone)]
1189pub struct ParametricPhaseShift {
1190    /// Target qubit
1191    pub target: QubitId,
1192
1193    /// Phase parameter
1194    pub phi: Parameter,
1195}
1196
1197impl ParametricPhaseShift {
1198    /// Create a new phase shift gate with a constant phase
1199    pub const fn new(target: QubitId, phi: f64) -> Self {
1200        Self {
1201            target,
1202            phi: Parameter::constant(phi),
1203        }
1204    }
1205
1206    /// Create a new phase shift gate with a symbolic phase
1207    pub fn new_symbolic(target: QubitId, name: &str) -> Self {
1208        Self {
1209            target,
1210            phi: Parameter::symbol(name),
1211        }
1212    }
1213}
1214
1215impl GateOp for ParametricPhaseShift {
1216    fn name(&self) -> &'static str {
1217        "P"
1218    }
1219
1220    fn qubits(&self) -> Vec<QubitId> {
1221        vec![self.target]
1222    }
1223
1224    fn is_parameterized(&self) -> bool {
1225        true
1226    }
1227
1228    fn matrix(&self) -> QuantRS2Result<Vec<Complex64>> {
1229        self.phi.value().map_or_else(
1230            || {
1231                Err(crate::error::QuantRS2Error::UnsupportedOperation(
1232                    "Cannot generate matrix for phase shift gate with unbound symbolic parameter"
1233                        .into(),
1234                ))
1235            },
1236            |phi| {
1237                let phase = Complex64::new(phi.cos(), phi.sin());
1238                Ok(vec![
1239                    Complex64::new(1.0, 0.0),
1240                    Complex64::new(0.0, 0.0),
1241                    Complex64::new(0.0, 0.0),
1242                    phase,
1243                ])
1244            },
1245        )
1246    }
1247
1248    fn as_any(&self) -> &dyn std::any::Any {
1249        self
1250    }
1251
1252    fn clone_gate(&self) -> Box<dyn GateOp> {
1253        Box::new(self.clone())
1254    }
1255}
1256
1257impl ParametricGate for ParametricPhaseShift {
1258    fn parameters(&self) -> Vec<Parameter> {
1259        vec![self.phi.clone()]
1260    }
1261
1262    fn parameter_names(&self) -> Vec<String> {
1263        match self.phi {
1264            Parameter::Symbol(ref sym) => vec![sym.name.clone()],
1265            _ => Vec::new(),
1266        }
1267    }
1268
1269    fn with_parameters(&self, params: &[Parameter]) -> QuantRS2Result<Box<dyn ParametricGate>> {
1270        if params.len() != 1 {
1271            return Err(crate::error::QuantRS2Error::InvalidInput(format!(
1272                "Phase shift gate expects 1 parameter, got {}",
1273                params.len()
1274            )));
1275        }
1276        Ok(Box::new(Self {
1277            target: self.target,
1278            phi: params[0].clone(),
1279        }))
1280    }
1281
1282    fn with_parameter_at(
1283        &self,
1284        index: usize,
1285        param: Parameter,
1286    ) -> QuantRS2Result<Box<dyn ParametricGate>> {
1287        if index != 0 {
1288            return Err(crate::error::QuantRS2Error::InvalidInput(format!(
1289                "Phase shift gate has only 1 parameter, got index {index}"
1290            )));
1291        }
1292        Ok(Box::new(Self {
1293            target: self.target,
1294            phi: param,
1295        }))
1296    }
1297
1298    fn assign(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>> {
1299        match self.phi {
1300            Parameter::Symbol(ref sym) => {
1301                for (name, value) in values {
1302                    if sym.name == *name {
1303                        return Ok(Box::new(Self {
1304                            target: self.target,
1305                            phi: Parameter::Symbol(SymbolicParameter::with_value(
1306                                &sym.name, *value,
1307                            )),
1308                        }));
1309                    }
1310                }
1311                // Parameter not found in values, return a clone of the original gate
1312                Ok(Box::new(self.clone()))
1313            }
1314            _ => Ok(Box::new(self.clone())), // Not a symbolic parameter, no change needed
1315        }
1316    }
1317
1318    fn bind(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>> {
1319        self.assign(values)
1320    }
1321}
1322
1323/// Module for utilities related to parametric gates
1324pub mod utils {
1325    use super::*;
1326    use crate::gate::{multi, single};
1327
1328    /// Helper function to create a parametric gate from a standard gate
1329    pub fn parametrize_rotation_gate(gate: &dyn GateOp) -> Option<Box<dyn ParametricGate>> {
1330        if !gate.is_parameterized() {
1331            return None;
1332        }
1333
1334        if let Some(rx) = gate.as_any().downcast_ref::<single::RotationX>() {
1335            Some(Box::new(ParametricRotationX::new(rx.target, rx.theta)))
1336        } else if let Some(ry) = gate.as_any().downcast_ref::<single::RotationY>() {
1337            Some(Box::new(ParametricRotationY::new(ry.target, ry.theta)))
1338        } else if let Some(rz) = gate.as_any().downcast_ref::<single::RotationZ>() {
1339            Some(Box::new(ParametricRotationZ::new(rz.target, rz.theta)))
1340        } else if let Some(crx) = gate.as_any().downcast_ref::<multi::CRX>() {
1341            Some(Box::new(ParametricCRX::new(
1342                crx.control,
1343                crx.target,
1344                crx.theta,
1345            )))
1346        } else {
1347            None
1348        }
1349    }
1350
1351    /// Convert a parameter value to a symbolic parameter
1352    pub fn symbolize_parameter(param: f64, name: &str) -> Parameter {
1353        Parameter::symbol_with_value(name, param)
1354    }
1355
1356    /// Check if two parameters are approximately equal
1357    pub fn parameters_approx_eq(p1: &Parameter, p2: &Parameter, epsilon: f64) -> bool {
1358        match (p1.value(), p2.value()) {
1359            (Some(v1), Some(v2)) => (v1 - v2).abs() < epsilon,
1360            _ => false,
1361        }
1362    }
1363}