1use 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#[derive(Debug, Clone)]
18pub enum Parameter {
19 Constant(f64),
21
22 ComplexConstant(Complex64),
24
25 Symbol(SymbolicParameter),
27
28 Symbolic(SymbolicExpression),
30}
31
32impl Parameter {
33 pub const fn constant(value: f64) -> Self {
35 Self::Constant(value)
36 }
37
38 pub const fn complex_constant(value: Complex64) -> Self {
40 Self::ComplexConstant(value)
41 }
42
43 pub fn symbol(name: &str) -> Self {
45 Self::Symbol(SymbolicParameter::new(name))
46 }
47
48 pub fn symbol_with_value(name: &str, value: f64) -> Self {
50 Self::Symbol(SymbolicParameter::with_value(name, value))
51 }
52
53 pub const fn symbolic(expr: SymbolicExpression) -> Self {
55 Self::Symbolic(expr)
56 }
57
58 pub fn variable(name: &str) -> Self {
60 Self::Symbolic(SymbolicExpression::variable(name))
61 }
62
63 pub fn parse(expr: &str) -> QuantRS2Result<Self> {
65 if let Ok(value) = expr.parse::<f64>() {
67 return Ok(Self::Constant(value));
68 }
69
70 let symbolic_expr = SymbolicExpression::parse(expr)?;
72 Ok(Self::Symbolic(symbolic_expr))
73 }
74
75 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 }
85 }
86 Self::Symbol(sym) => sym.value,
87 Self::Symbolic(expr) => {
88 expr.evaluate(&HashMap::new()).ok()
90 }
91 }
92 }
93
94 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 expr.evaluate_complex(&HashMap::new()).ok()
103 }
104 }
105 }
106
107 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 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 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 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 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 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 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 #[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 #[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 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 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 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 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#[derive(Debug, Clone)]
358pub struct SymbolicParameter {
359 pub name: String,
361
362 pub value: Option<f64>,
364}
365
366impl SymbolicParameter {
367 pub fn new(name: &str) -> Self {
369 Self {
370 name: name.to_string(),
371 value: None,
372 }
373 }
374
375 pub fn with_value(name: &str, value: f64) -> Self {
377 Self {
378 name: name.to_string(),
379 value: Some(value),
380 }
381 }
382
383 pub const fn set_value(&mut self, value: f64) {
385 self.value = Some(value);
386 }
387
388 pub const fn clear_value(&mut self) {
390 self.value = None;
391 }
392}
393
394pub trait ParametricGate: GateOp {
399 fn parameters(&self) -> Vec<Parameter>;
401
402 fn parameter_names(&self) -> Vec<String>;
404
405 fn with_parameters(&self, params: &[Parameter]) -> QuantRS2Result<Box<dyn ParametricGate>>;
407
408 fn with_parameter_at(
410 &self,
411 index: usize,
412 param: Parameter,
413 ) -> QuantRS2Result<Box<dyn ParametricGate>>;
414
415 fn assign(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>>;
417
418 fn bind(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>>;
420}
421
422#[derive(Debug, Clone)]
424pub struct ParametricRotationX {
425 pub target: QubitId,
427
428 pub theta: Parameter,
430}
431
432impl ParametricRotationX {
433 pub const fn new(target: QubitId, theta: f64) -> Self {
435 Self {
436 target,
437 theta: Parameter::constant(theta),
438 }
439 }
440
441 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 Ok(Box::new(self.clone()))
548 }
549 _ => Ok(Box::new(self.clone())), }
551 }
552
553 fn bind(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>> {
554 self.assign(values)
555 }
556}
557
558#[derive(Debug, Clone)]
560pub struct ParametricRotationY {
561 pub target: QubitId,
563
564 pub theta: Parameter,
566}
567
568impl ParametricRotationY {
569 pub const fn new(target: QubitId, theta: f64) -> Self {
571 Self {
572 target,
573 theta: Parameter::constant(theta),
574 }
575 }
576
577 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 Ok(Box::new(self.clone()))
684 }
685 _ => Ok(Box::new(self.clone())), }
687 }
688
689 fn bind(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>> {
690 self.assign(values)
691 }
692}
693
694#[derive(Debug, Clone)]
696pub struct ParametricRotationZ {
697 pub target: QubitId,
699
700 pub theta: Parameter,
702}
703
704impl ParametricRotationZ {
705 pub const fn new(target: QubitId, theta: f64) -> Self {
707 Self {
708 target,
709 theta: Parameter::constant(theta),
710 }
711 }
712
713 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![
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 Ok(Box::new(self.clone()))
821 }
822 _ => Ok(Box::new(self.clone())), }
824 }
825
826 fn bind(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>> {
827 self.assign(values)
828 }
829}
830
831#[derive(Debug, Clone)]
833pub struct ParametricU {
834 pub target: QubitId,
836
837 pub theta: Parameter,
839
840 pub phi: Parameter,
842
843 pub lambda: Parameter,
845}
846
847impl ParametricU {
848 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 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 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 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 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#[derive(Debug, Clone)]
1033pub struct ParametricCRX {
1034 pub control: QubitId,
1036
1037 pub target: QubitId,
1039
1040 pub theta: Parameter,
1042}
1043
1044impl ParametricCRX {
1045 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 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), Complex64::new(0.0, 0.0), Complex64::new(0.0, 0.0), Complex64::new(0.0, 0.0), Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0), Complex64::new(0.0, 0.0), Complex64::new(0.0, 0.0), Complex64::new(0.0, 0.0), Complex64::new(cos, 0.0), Complex64::new(0.0, -sin), Complex64::new(0.0, 0.0), Complex64::new(0.0, 0.0), Complex64::new(0.0, -sin), Complex64::new(cos, 0.0), ])
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 Ok(Box::new(self.clone()))
1178 }
1179 _ => Ok(Box::new(self.clone())), }
1181 }
1182
1183 fn bind(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>> {
1184 self.assign(values)
1185 }
1186}
1187
1188#[derive(Debug, Clone)]
1190pub struct ParametricPhaseShift {
1191 pub target: QubitId,
1193
1194 pub phi: Parameter,
1196}
1197
1198impl ParametricPhaseShift {
1199 pub const fn new(target: QubitId, phi: f64) -> Self {
1201 Self {
1202 target,
1203 phi: Parameter::constant(phi),
1204 }
1205 }
1206
1207 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 Ok(Box::new(self.clone()))
1314 }
1315 _ => Ok(Box::new(self.clone())), }
1317 }
1318
1319 fn bind(&self, values: &[(String, f64)]) -> QuantRS2Result<Box<dyn ParametricGate>> {
1320 self.assign(values)
1321 }
1322}
1323
1324pub mod utils {
1326 use super::*;
1327 use crate::gate::{multi, single};
1328
1329 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 pub fn symbolize_parameter(param: f64, name: &str) -> Parameter {
1354 Parameter::symbol_with_value(name, param)
1355 }
1356
1357 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 #[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 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}