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                // IBM/Qiskit/OpenQASM-3 convention: Rz(θ) = diag(e^{-iθ/2}, e^{+iθ/2})
746                Ok(vec![
747                    phase,
748                    Complex64::new(0.0, 0.0),
749                    Complex64::new(0.0, 0.0),
750                    phase_conj,
751                ])
752            },
753        )
754    }
755
756    fn as_any(&self) -> &dyn std::any::Any {
757        self
758    }
759
760    fn clone_gate(&self) -> Box<dyn GateOp> {
761        Box::new(self.clone())
762    }
763}
764
765impl ParametricGate for ParametricRotationZ {
766    fn parameters(&self) -> Vec<Parameter> {
767        vec![self.theta.clone()]
768    }
769
770    fn parameter_names(&self) -> Vec<String> {
771        match self.theta {
772            Parameter::Symbol(ref sym) => vec![sym.name.clone()],
773            _ => Vec::new(),
774        }
775    }
776
777    fn with_parameters(&self, params: &[Parameter]) -> QuantRS2Result<Box<dyn ParametricGate>> {
778        if params.len() != 1 {
779            return Err(crate::error::QuantRS2Error::InvalidInput(format!(
780                "RotationZ expects 1 parameter, got {}",
781                params.len()
782            )));
783        }
784        Ok(Box::new(Self {
785            target: self.target,
786            theta: params[0].clone(),
787        }))
788    }
789
790    fn with_parameter_at(
791        &self,
792        index: usize,
793        param: Parameter,
794    ) -> QuantRS2Result<Box<dyn ParametricGate>> {
795        if index != 0 {
796            return Err(crate::error::QuantRS2Error::InvalidInput(format!(
797                "RotationZ has only 1 parameter, got index {index}"
798            )));
799        }
800        Ok(Box::new(Self {
801            target: self.target,
802            theta: param,
803        }))
804    }
805
806    fn assign(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>> {
807        match self.theta {
808            Parameter::Symbol(ref sym) => {
809                for (name, value) in values {
810                    if sym.name == *name {
811                        return Ok(Box::new(Self {
812                            target: self.target,
813                            theta: Parameter::Symbol(SymbolicParameter::with_value(
814                                &sym.name, *value,
815                            )),
816                        }));
817                    }
818                }
819                // Parameter not found in values, return a clone of the original gate
820                Ok(Box::new(self.clone()))
821            }
822            _ => Ok(Box::new(self.clone())), // Not a symbolic parameter, no change needed
823        }
824    }
825
826    fn bind(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>> {
827        self.assign(values)
828    }
829}
830
831/// Specialized implementation of a general U-gate (parameterized single-qubit gate)
832#[derive(Debug, Clone)]
833pub struct ParametricU {
834    /// Target qubit
835    pub target: QubitId,
836
837    /// Theta parameter (rotation angle)
838    pub theta: Parameter,
839
840    /// Phi parameter (phase angle)
841    pub phi: Parameter,
842
843    /// Lambda parameter (phase angle)
844    pub lambda: Parameter,
845}
846
847impl ParametricU {
848    /// Create a new U-gate with constant angles
849    pub const fn new(target: QubitId, theta: f64, phi: f64, lambda: f64) -> Self {
850        Self {
851            target,
852            theta: Parameter::constant(theta),
853            phi: Parameter::constant(phi),
854            lambda: Parameter::constant(lambda),
855        }
856    }
857
858    /// Create a new U-gate with symbolic angles
859    pub fn new_symbolic(
860        target: QubitId,
861        theta_name: &str,
862        phi_name: &str,
863        lambda_name: &str,
864    ) -> Self {
865        Self {
866            target,
867            theta: Parameter::symbol(theta_name),
868            phi: Parameter::symbol(phi_name),
869            lambda: Parameter::symbol(lambda_name),
870        }
871    }
872}
873
874impl GateOp for ParametricU {
875    fn name(&self) -> &'static str {
876        "U"
877    }
878
879    fn qubits(&self) -> Vec<QubitId> {
880        vec![self.target]
881    }
882
883    fn is_parameterized(&self) -> bool {
884        true
885    }
886
887    fn matrix(&self) -> QuantRS2Result<Vec<Complex64>> {
888        if let (Some(theta), Some(phi), Some(lambda)) =
889            (self.theta.value(), self.phi.value(), self.lambda.value())
890        {
891            let cos = (theta / 2.0).cos();
892            let sin = (theta / 2.0).sin();
893
894            let e_phi = Complex64::new(0.0, phi).exp();
895            let e_lambda = Complex64::new(0.0, lambda).exp();
896
897            Ok(vec![
898                Complex64::new(cos, 0.0),
899                -sin * e_lambda,
900                sin * e_phi,
901                cos * e_phi * e_lambda,
902            ])
903        } else {
904            Err(crate::error::QuantRS2Error::UnsupportedOperation(
905                "Cannot generate matrix for U gate with unbound symbolic parameters".into(),
906            ))
907        }
908    }
909
910    fn as_any(&self) -> &dyn std::any::Any {
911        self
912    }
913
914    fn clone_gate(&self) -> Box<dyn GateOp> {
915        Box::new(self.clone())
916    }
917}
918
919impl ParametricGate for ParametricU {
920    fn parameters(&self) -> Vec<Parameter> {
921        vec![self.theta.clone(), self.phi.clone(), self.lambda.clone()]
922    }
923
924    fn parameter_names(&self) -> Vec<String> {
925        let mut names = Vec::new();
926
927        if let Parameter::Symbol(ref sym) = self.theta {
928            names.push(sym.name.clone());
929        }
930
931        if let Parameter::Symbol(ref sym) = self.phi {
932            names.push(sym.name.clone());
933        }
934
935        if let Parameter::Symbol(ref sym) = self.lambda {
936            names.push(sym.name.clone());
937        }
938
939        names
940    }
941
942    fn with_parameters(&self, params: &[Parameter]) -> QuantRS2Result<Box<dyn ParametricGate>> {
943        if params.len() != 3 {
944            return Err(crate::error::QuantRS2Error::InvalidInput(format!(
945                "U gate expects 3 parameters, got {}",
946                params.len()
947            )));
948        }
949        Ok(Box::new(Self {
950            target: self.target,
951            theta: params[0].clone(),
952            phi: params[1].clone(),
953            lambda: params[2].clone(),
954        }))
955    }
956
957    fn with_parameter_at(
958        &self,
959        index: usize,
960        param: Parameter,
961    ) -> QuantRS2Result<Box<dyn ParametricGate>> {
962        match index {
963            0 => Ok(Box::new(Self {
964                target: self.target,
965                theta: param,
966                phi: self.phi.clone(),
967                lambda: self.lambda.clone(),
968            })),
969            1 => Ok(Box::new(Self {
970                target: self.target,
971                theta: self.theta.clone(),
972                phi: param,
973                lambda: self.lambda.clone(),
974            })),
975            2 => Ok(Box::new(Self {
976                target: self.target,
977                theta: self.theta.clone(),
978                phi: self.phi.clone(),
979                lambda: param,
980            })),
981            _ => Err(crate::error::QuantRS2Error::InvalidInput(format!(
982                "U gate has only 3 parameters, got index {index}"
983            ))),
984        }
985    }
986
987    fn assign(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>> {
988        let mut result = self.clone();
989
990        // Update theta if it's a symbolic parameter
991        if let Parameter::Symbol(ref sym) = self.theta {
992            for (name, value) in values {
993                if sym.name == *name {
994                    result.theta =
995                        Parameter::Symbol(SymbolicParameter::with_value(&sym.name, *value));
996                    break;
997                }
998            }
999        }
1000
1001        // Update phi if it's a symbolic parameter
1002        if let Parameter::Symbol(ref sym) = self.phi {
1003            for (name, value) in values {
1004                if sym.name == *name {
1005                    result.phi =
1006                        Parameter::Symbol(SymbolicParameter::with_value(&sym.name, *value));
1007                    break;
1008                }
1009            }
1010        }
1011
1012        // Update lambda if it's a symbolic parameter
1013        if let Parameter::Symbol(ref sym) = self.lambda {
1014            for (name, value) in values {
1015                if sym.name == *name {
1016                    result.lambda =
1017                        Parameter::Symbol(SymbolicParameter::with_value(&sym.name, *value));
1018                    break;
1019                }
1020            }
1021        }
1022
1023        Ok(Box::new(result))
1024    }
1025
1026    fn bind(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>> {
1027        self.assign(values)
1028    }
1029}
1030
1031/// Specialized implementation of controlled parametric rotation around X-axis
1032#[derive(Debug, Clone)]
1033pub struct ParametricCRX {
1034    /// Control qubit
1035    pub control: QubitId,
1036
1037    /// Target qubit
1038    pub target: QubitId,
1039
1040    /// Rotation angle parameter
1041    pub theta: Parameter,
1042}
1043
1044impl ParametricCRX {
1045    /// Create a new CRX gate with a constant angle
1046    pub const fn new(control: QubitId, target: QubitId, theta: f64) -> Self {
1047        Self {
1048            control,
1049            target,
1050            theta: Parameter::constant(theta),
1051        }
1052    }
1053
1054    /// Create a new CRX gate with a symbolic angle
1055    pub fn new_symbolic(control: QubitId, target: QubitId, name: &str) -> Self {
1056        Self {
1057            control,
1058            target,
1059            theta: Parameter::symbol(name),
1060        }
1061    }
1062}
1063
1064impl GateOp for ParametricCRX {
1065    fn name(&self) -> &'static str {
1066        "CRX"
1067    }
1068
1069    fn qubits(&self) -> Vec<QubitId> {
1070        vec![self.control, self.target]
1071    }
1072
1073    fn is_parameterized(&self) -> bool {
1074        true
1075    }
1076
1077    fn matrix(&self) -> QuantRS2Result<Vec<Complex64>> {
1078        self.theta.value().map_or_else(
1079            || {
1080                Err(crate::error::QuantRS2Error::UnsupportedOperation(
1081                    "Cannot generate matrix for CRX gate with unbound symbolic parameter".into(),
1082                ))
1083            },
1084            |theta| {
1085                let cos = (theta / 2.0).cos();
1086                let sin = (theta / 2.0).sin();
1087
1088                Ok(vec![
1089                    Complex64::new(1.0, 0.0),  // (0,0)
1090                    Complex64::new(0.0, 0.0),  // (0,1)
1091                    Complex64::new(0.0, 0.0),  // (0,2)
1092                    Complex64::new(0.0, 0.0),  // (0,3)
1093                    Complex64::new(0.0, 0.0),  // (1,0)
1094                    Complex64::new(1.0, 0.0),  // (1,1)
1095                    Complex64::new(0.0, 0.0),  // (1,2)
1096                    Complex64::new(0.0, 0.0),  // (1,3)
1097                    Complex64::new(0.0, 0.0),  // (2,0)
1098                    Complex64::new(0.0, 0.0),  // (2,1)
1099                    Complex64::new(cos, 0.0),  // (2,2)
1100                    Complex64::new(0.0, -sin), // (2,3)
1101                    Complex64::new(0.0, 0.0),  // (3,0)
1102                    Complex64::new(0.0, 0.0),  // (3,1)
1103                    Complex64::new(0.0, -sin), // (3,2)
1104                    Complex64::new(cos, 0.0),  // (3,3)
1105                ])
1106            },
1107        )
1108    }
1109
1110    fn as_any(&self) -> &dyn std::any::Any {
1111        self
1112    }
1113
1114    fn clone_gate(&self) -> Box<dyn GateOp> {
1115        Box::new(self.clone())
1116    }
1117}
1118
1119impl ParametricGate for ParametricCRX {
1120    fn parameters(&self) -> Vec<Parameter> {
1121        vec![self.theta.clone()]
1122    }
1123
1124    fn parameter_names(&self) -> Vec<String> {
1125        match self.theta {
1126            Parameter::Symbol(ref sym) => vec![sym.name.clone()],
1127            _ => Vec::new(),
1128        }
1129    }
1130
1131    fn with_parameters(&self, params: &[Parameter]) -> QuantRS2Result<Box<dyn ParametricGate>> {
1132        if params.len() != 1 {
1133            return Err(crate::error::QuantRS2Error::InvalidInput(format!(
1134                "CRX expects 1 parameter, got {}",
1135                params.len()
1136            )));
1137        }
1138        Ok(Box::new(Self {
1139            control: self.control,
1140            target: self.target,
1141            theta: params[0].clone(),
1142        }))
1143    }
1144
1145    fn with_parameter_at(
1146        &self,
1147        index: usize,
1148        param: Parameter,
1149    ) -> QuantRS2Result<Box<dyn ParametricGate>> {
1150        if index != 0 {
1151            return Err(crate::error::QuantRS2Error::InvalidInput(format!(
1152                "CRX has only 1 parameter, got index {index}"
1153            )));
1154        }
1155        Ok(Box::new(Self {
1156            control: self.control,
1157            target: self.target,
1158            theta: param,
1159        }))
1160    }
1161
1162    fn assign(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>> {
1163        match self.theta {
1164            Parameter::Symbol(ref sym) => {
1165                for (name, value) in values {
1166                    if sym.name == *name {
1167                        return Ok(Box::new(Self {
1168                            control: self.control,
1169                            target: self.target,
1170                            theta: Parameter::Symbol(SymbolicParameter::with_value(
1171                                &sym.name, *value,
1172                            )),
1173                        }));
1174                    }
1175                }
1176                // Parameter not found in values, return a clone of the original gate
1177                Ok(Box::new(self.clone()))
1178            }
1179            _ => Ok(Box::new(self.clone())), // Not a symbolic parameter, no change needed
1180        }
1181    }
1182
1183    fn bind(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>> {
1184        self.assign(values)
1185    }
1186}
1187
1188/// Phase shift gate with a parameterized phase
1189#[derive(Debug, Clone)]
1190pub struct ParametricPhaseShift {
1191    /// Target qubit
1192    pub target: QubitId,
1193
1194    /// Phase parameter
1195    pub phi: Parameter,
1196}
1197
1198impl ParametricPhaseShift {
1199    /// Create a new phase shift gate with a constant phase
1200    pub const fn new(target: QubitId, phi: f64) -> Self {
1201        Self {
1202            target,
1203            phi: Parameter::constant(phi),
1204        }
1205    }
1206
1207    /// Create a new phase shift gate with a symbolic phase
1208    pub fn new_symbolic(target: QubitId, name: &str) -> Self {
1209        Self {
1210            target,
1211            phi: Parameter::symbol(name),
1212        }
1213    }
1214}
1215
1216impl GateOp for ParametricPhaseShift {
1217    fn name(&self) -> &'static str {
1218        "P"
1219    }
1220
1221    fn qubits(&self) -> Vec<QubitId> {
1222        vec![self.target]
1223    }
1224
1225    fn is_parameterized(&self) -> bool {
1226        true
1227    }
1228
1229    fn matrix(&self) -> QuantRS2Result<Vec<Complex64>> {
1230        self.phi.value().map_or_else(
1231            || {
1232                Err(crate::error::QuantRS2Error::UnsupportedOperation(
1233                    "Cannot generate matrix for phase shift gate with unbound symbolic parameter"
1234                        .into(),
1235                ))
1236            },
1237            |phi| {
1238                let phase = Complex64::new(phi.cos(), phi.sin());
1239                Ok(vec![
1240                    Complex64::new(1.0, 0.0),
1241                    Complex64::new(0.0, 0.0),
1242                    Complex64::new(0.0, 0.0),
1243                    phase,
1244                ])
1245            },
1246        )
1247    }
1248
1249    fn as_any(&self) -> &dyn std::any::Any {
1250        self
1251    }
1252
1253    fn clone_gate(&self) -> Box<dyn GateOp> {
1254        Box::new(self.clone())
1255    }
1256}
1257
1258impl ParametricGate for ParametricPhaseShift {
1259    fn parameters(&self) -> Vec<Parameter> {
1260        vec![self.phi.clone()]
1261    }
1262
1263    fn parameter_names(&self) -> Vec<String> {
1264        match self.phi {
1265            Parameter::Symbol(ref sym) => vec![sym.name.clone()],
1266            _ => Vec::new(),
1267        }
1268    }
1269
1270    fn with_parameters(&self, params: &[Parameter]) -> QuantRS2Result<Box<dyn ParametricGate>> {
1271        if params.len() != 1 {
1272            return Err(crate::error::QuantRS2Error::InvalidInput(format!(
1273                "Phase shift gate expects 1 parameter, got {}",
1274                params.len()
1275            )));
1276        }
1277        Ok(Box::new(Self {
1278            target: self.target,
1279            phi: params[0].clone(),
1280        }))
1281    }
1282
1283    fn with_parameter_at(
1284        &self,
1285        index: usize,
1286        param: Parameter,
1287    ) -> QuantRS2Result<Box<dyn ParametricGate>> {
1288        if index != 0 {
1289            return Err(crate::error::QuantRS2Error::InvalidInput(format!(
1290                "Phase shift gate has only 1 parameter, got index {index}"
1291            )));
1292        }
1293        Ok(Box::new(Self {
1294            target: self.target,
1295            phi: param,
1296        }))
1297    }
1298
1299    fn assign(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>> {
1300        match self.phi {
1301            Parameter::Symbol(ref sym) => {
1302                for (name, value) in values {
1303                    if sym.name == *name {
1304                        return Ok(Box::new(Self {
1305                            target: self.target,
1306                            phi: Parameter::Symbol(SymbolicParameter::with_value(
1307                                &sym.name, *value,
1308                            )),
1309                        }));
1310                    }
1311                }
1312                // Parameter not found in values, return a clone of the original gate
1313                Ok(Box::new(self.clone()))
1314            }
1315            _ => Ok(Box::new(self.clone())), // Not a symbolic parameter, no change needed
1316        }
1317    }
1318
1319    fn bind(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>> {
1320        self.assign(values)
1321    }
1322}
1323
1324/// Module for utilities related to parametric gates
1325pub mod utils {
1326    use super::*;
1327    use crate::gate::{multi, single};
1328
1329    /// Helper function to create a parametric gate from a standard gate
1330    pub fn parametrize_rotation_gate(gate: &dyn GateOp) -> Option<Box<dyn ParametricGate>> {
1331        if !gate.is_parameterized() {
1332            return None;
1333        }
1334
1335        if let Some(rx) = gate.as_any().downcast_ref::<single::RotationX>() {
1336            Some(Box::new(ParametricRotationX::new(rx.target, rx.theta)))
1337        } else if let Some(ry) = gate.as_any().downcast_ref::<single::RotationY>() {
1338            Some(Box::new(ParametricRotationY::new(ry.target, ry.theta)))
1339        } else if let Some(rz) = gate.as_any().downcast_ref::<single::RotationZ>() {
1340            Some(Box::new(ParametricRotationZ::new(rz.target, rz.theta)))
1341        } else if let Some(crx) = gate.as_any().downcast_ref::<multi::CRX>() {
1342            Some(Box::new(ParametricCRX::new(
1343                crx.control,
1344                crx.target,
1345                crx.theta,
1346            )))
1347        } else {
1348            None
1349        }
1350    }
1351
1352    /// Convert a parameter value to a symbolic parameter
1353    pub fn symbolize_parameter(param: f64, name: &str) -> Parameter {
1354        Parameter::symbol_with_value(name, param)
1355    }
1356
1357    /// Check if two parameters are approximately equal
1358    pub fn parameters_approx_eq(p1: &Parameter, p2: &Parameter, epsilon: f64) -> bool {
1359        match (p1.value(), p2.value()) {
1360            (Some(v1), Some(v2)) => (v1 - v2).abs() < epsilon,
1361            _ => false,
1362        }
1363    }
1364}
1365
1366#[cfg(test)]
1367mod issue_32_parametric_rz_tests {
1368    use super::ParametricRotationZ;
1369    use crate::gate::single::RotationZ;
1370    use crate::gate::GateOp;
1371    use crate::qubit::QubitId;
1372    use std::f64::consts::PI;
1373
1374    /// Regression test for GitHub issue #32.
1375    ///
1376    /// A `ParametricRotationZ` bound to θ = π/2 must produce exactly the same
1377    /// matrix as the non-parametric `RotationZ(π/2)` — both in the
1378    /// IBM/Qiskit/OpenQASM-3 convention `diag(e^{-iθ/2}, e^{+iθ/2})`.
1379    #[test]
1380    fn test_issue_32_parametric_rz_matches_rotation_z() {
1381        let theta = PI / 2.0;
1382        let parametric = ParametricRotationZ::new(QubitId::new(0), theta)
1383            .matrix()
1384            .expect("ParametricRotationZ matrix");
1385        let reference = RotationZ {
1386            target: QubitId::new(0),
1387            theta,
1388        }
1389        .matrix()
1390        .expect("RotationZ matrix");
1391
1392        assert_eq!(parametric.len(), reference.len());
1393        for (i, (p, r)) in parametric.iter().zip(reference.iter()).enumerate() {
1394            assert!(
1395                (p - r).norm() < 1e-12,
1396                "mismatch at index {i}: {p:?} vs {r:?}"
1397            );
1398        }
1399        // Guard the sign convention explicitly.
1400        assert!(parametric[0].im < 0.0, "index 0 must be e^(-iθ/2)");
1401        assert!(parametric[3].im > 0.0, "index 3 must be e^(+iθ/2)");
1402    }
1403}