cqlib_core/circuit/parameter.rs
1// This code is part of Cqlib.
2//
3// (C) Copyright China Telecom Quantum Group 2026
4//
5// This code is licensed under the Apache License, Version 2.0. You may
6// obtain a copy of this license in the LICENSE.txt file in the root directory
7// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8//
9// Any modifications or derivative works of this code must retain this
10// copyright notice, and modified files need to carry a notice indicating
11// that they have been altered from the originals.
12
13//! Symbolic and numeric parameter type for quantum circuit gate angles.
14//!
15//! [`Parameter`] wraps a [`symb_anafis::Expr`] expression tree, allowing gate parameters
16//! to be either concrete floating-point numbers or symbolic expressions containing
17//! free variables (e.g. `θ`, `φ`). Symbolic parameters can be composed with
18//! arithmetic operators, differentiated, simplified, and finally evaluated by
19//! supplying variable bindings.
20//!
21//! # Quick start
22//!
23//! ```rust
24//! use cqlib_core::circuit::Parameter;
25//!
26//! // Numeric parameter
27//! let angle = Parameter::from(std::f64::consts::PI / 2.0);
28//!
29//! // Symbolic parameter
30//! let theta = Parameter::symbol("θ");
31//! let expr = theta.clone() * Parameter::from(2.0) + Parameter::pi();
32//!
33//! // Evaluate with a concrete binding
34//! let val = expr.evaluate(&Some([("θ", 0.5)].iter().cloned().collect())).unwrap();
35//! ```
36
37use crate::circuit::error::ParameterError;
38use core::fmt::Debug;
39use std::collections::{HashMap, HashSet};
40use std::fmt;
41use std::ops::{Add, Div, Mul, Neg, Sub};
42use std::str::FromStr;
43use symb_anafis::{Expr, Simplify, parse, symb};
44
45/// A symbolic or numeric parameter used to represent gate angles and other
46/// continuous values in a quantum circuit.
47///
48/// `Parameter` is a thin, cheaply-cloneable wrapper around [`symb_anafis::Expr`].
49/// It supports the full suite of arithmetic operators (`+`, `-`, `*`, `/`)
50/// between `Parameter` values as well as between `Parameter` and primitive numeric types
51/// (`f64`, `f32`, `i32`, `u32`).
52///
53/// The mathematical constants `π` and `e` are always available as free
54/// symbols and are automatically resolved during [`Parameter::evaluate`].
55#[derive(Clone, Debug)]
56pub struct Parameter {
57 /// The root node of the mathematical expression tree.
58 expr: Expr,
59}
60
61impl From<Expr> for Parameter {
62 fn from(expr: Expr) -> Self {
63 Parameter { expr }
64 }
65}
66
67impl From<Parameter> for Expr {
68 fn from(p: Parameter) -> Self {
69 p.expr
70 }
71}
72
73impl fmt::Display for Parameter {
74 /// Formats the parameter as a human-readable mathematical string.
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 fmt::Display::fmt(&self.expr, f)
77 }
78}
79
80impl Default for Parameter {
81 /// Creates a default parameter representing the integer `0`.
82 fn default() -> Self {
83 Self {
84 expr: Expr::from(0),
85 }
86 }
87}
88
89impl std::hash::Hash for Parameter {
90 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
91 self.expr.hash(state);
92 }
93}
94
95impl PartialEq for Parameter {
96 /// Checks equality based on the underlying expression structure.
97 ///
98 /// Note: This performs **structural** equality of the AST, not semantic
99 /// mathematical equality. For example, `x + y` and `y + x` are
100 /// structurally different and will compare as unequal.
101 fn eq(&self, other: &Self) -> bool {
102 self.expr == other.expr
103 }
104}
105
106impl Eq for Parameter {}
107
108impl TryFrom<&str> for Parameter {
109 type Error = ParameterError;
110
111 /// Parses a symbolic expression from a string slice.
112 ///
113 /// Returns [`ParameterError::ParseError`] if the string cannot be parsed
114 /// as a valid expression.
115 ///
116 /// # Examples
117 ///
118 /// ```rust
119 /// use cqlib_core::circuit::Parameter;
120 ///
121 /// let p = Parameter::try_from("θ * 2 + π").unwrap();
122 /// let bad = Parameter::try_from("@@@");
123 /// assert!(bad.is_err());
124 /// ```
125 fn try_from(expr: &str) -> Result<Self, Self::Error> {
126 if let Ok(expr) = parse(expr, &HashSet::new(), &HashSet::new(), None) {
127 Ok(Self { expr })
128 } else {
129 Err(ParameterError::ParseError(expr.to_string()))
130 }
131 }
132}
133
134impl FromStr for Parameter {
135 type Err = ParameterError;
136
137 /// Parses a symbolic expression from a string.
138 ///
139 /// Returns [`ParameterError::ParseError`] if the string cannot be parsed
140 /// as a valid expression.
141 ///
142 /// # Examples
143 ///
144 /// ```rust
145 /// use cqlib_core::circuit::Parameter;
146 /// use std::str::FromStr;
147 ///
148 /// let p = Parameter::from_str("θ * 2 + π").unwrap();
149 /// let bad = Parameter::from_str("@@@");
150 /// assert!(bad.is_err());
151 /// ```
152 fn from_str(expr: &str) -> Result<Self, Self::Err> {
153 Self::try_from(expr)
154 }
155}
156
157impl Parameter {
158 /// Creates a new `Parameter` directly from a [`symb_anafis::Expr`] node.
159 ///
160 /// Prefer [`Parameter::symbol`] for named variables or the `From<f64>` / `From<i32>`
161 /// impls for numeric constants.
162 pub fn new(expr: Expr) -> Self {
163 Self { expr }
164 }
165
166 /// Returns a reference to the underlying expression.
167 pub fn as_expr(&self) -> &Expr {
168 &self.expr
169 }
170
171 /// Consumes the parameter and returns the underlying expression.
172 pub fn into_expr(self) -> Expr {
173 self.expr
174 }
175
176 /// Creates a symbolic parameter representing a free variable with the
177 /// given `name`.
178 ///
179 /// # Examples
180 ///
181 /// ```rust
182 /// use cqlib_core::circuit::Parameter;
183 ///
184 /// let theta = Parameter::symbol("θ");
185 /// assert_eq!(theta.get_symbols().contains("θ"), true);
186 /// ```
187 pub fn symbol(name: &str) -> Self {
188 Self::new(Expr::from(symb(name)))
189 }
190
191 /// Evaluates the expression to a concrete `f64` value.
192 ///
193 /// The mathematical constants `π` and `e` are injected automatically, so
194 /// callers do not need to supply them. Additional variable bindings are
195 /// provided through `bindings`.
196 ///
197 /// Returns [`ParameterError::NaN`] if the expression still contains
198 /// unbound free variables after applying the supplied bindings.
199 ///
200 /// # Examples
201 ///
202 /// ```rust
203 /// use cqlib_core::circuit::Parameter;
204 /// use std::collections::HashMap;
205 ///
206 /// // Numeric constant — no bindings needed.
207 /// let p = Parameter::from(std::f64::consts::PI);
208 /// assert!((p.evaluate(&None).unwrap() - std::f64::consts::PI).abs() < 1e-10);
209 ///
210 /// // Symbolic expression with a user-supplied binding.
211 /// let theta = Parameter::symbol("θ");
212 /// let expr = theta * Parameter::from(2.0);
213 /// let mut bindings = HashMap::new();
214 /// bindings.insert("θ", 1.0_f64);
215 /// assert!((expr.evaluate(&Some(bindings)).unwrap() - 2.0).abs() < 1e-10);
216 /// ```
217 pub fn evaluate(&self, bindings: &Option<HashMap<&str, f64>>) -> Result<f64, ParameterError> {
218 // Evaluate directly first. Numeric expressions and fully-bound user
219 // expressions avoid constructing a merged binding table entirely.
220 let mut evaluated = match bindings {
221 Some(bindings) => self.expr.evaluate(bindings, &HashMap::new()),
222 None => self.expr.evaluate(&(), &HashMap::new()),
223 };
224 // `symb_anafis` recognizes ASCII `pi`, while `Parameter::pi` uses the
225 // public Unicode spelling `π`. Only retry unresolved expressions with
226 // the compatibility constants, preserving the historical ability for
227 // explicit caller bindings to override them.
228 if evaluated.as_number().is_none() {
229 let mut actual_bindings =
230 HashMap::with_capacity(bindings.as_ref().map_or(2, |bindings| bindings.len() + 2));
231 actual_bindings.insert("π", std::f64::consts::PI);
232 actual_bindings.insert("e", std::f64::consts::E);
233 if let Some(bindings) = bindings {
234 actual_bindings.extend(bindings);
235 }
236 evaluated = self.expr.evaluate(&actual_bindings, &HashMap::new());
237 }
238
239 if let Some(n) = evaluated.as_number() {
240 // Check for NaN
241 if n.is_nan() {
242 return Err(ParameterError::DomainError(format!(
243 "Evaluation of '{}' resulted in NaN",
244 self.expr
245 )));
246 }
247 // Check for infinity (positive or negative)
248 if n.is_infinite() {
249 return Err(ParameterError::DomainError(format!(
250 "Evaluation of '{}' resulted in infinity",
251 self.expr
252 )));
253 }
254 Ok(n)
255 } else {
256 Err(ParameterError::NaN(self.expr.to_string()))
257 }
258 }
259
260 /// Returns an algebraically simplified form of this parameter.
261 ///
262 /// Simplification is performed in a domain-safe mode to avoid invalid
263 /// transformations (e.g. taking the log of a potentially negative number).
264 ///
265 /// # Examples
266 ///
267 /// ```rust
268 /// use cqlib_core::circuit::Parameter;
269 ///
270 /// let theta = Parameter::symbol("θ");
271 /// let expr = theta.clone() * Parameter::from(1.0); // θ * 1
272 /// let simplified = expr.simplify().unwrap();
273 /// assert_eq!(simplified.to_string(), "θ");
274 /// ```
275 pub fn simplify(&self) -> Result<Self, ParameterError> {
276 let expr = Simplify::new().domain_safe(true).simplify(&self.expr)?;
277 Ok(Self { expr })
278 }
279
280 /// Returns the numeric value of this parameter without symbolic
281 /// simplification, if it can be determined directly.
282 ///
283 /// Plain number nodes are read at zero cost; other constant expressions
284 /// (e.g. `pi/2`) fall back to evaluation. Non-finite values and
285 /// expressions with unbound symbols return `None`.
286 fn numeric_value(&self) -> Option<f64> {
287 self.expr
288 .as_number()
289 .filter(|n| n.is_finite())
290 .or_else(|| self.evaluate(&None).ok())
291 }
292
293 /// Returns the canonical storage form of this parameter expression.
294 ///
295 /// The parameter is first simplified. If the simplified expression has no
296 /// free symbols, it is evaluated to a concrete finite value and rebuilt as
297 /// a numeric parameter. This also normalizes `-0.0` to `0.0`, giving circuit
298 /// operation storage a stable representation for equivalent constants.
299 ///
300 /// Symbolic expressions are returned in their simplified form. This method
301 /// does not intern symbolic parameters into any circuit; use
302 /// [`Circuit::map_param`](crate::circuit::Circuit::map_param) when a
303 /// [`CircuitParam`](crate::circuit::CircuitParam) is needed for operation
304 /// storage.
305 ///
306 /// # Errors
307 ///
308 /// Returns [`ParameterError`] if simplification fails, a constant expression
309 /// cannot be evaluated, or the evaluated constant is not finite.
310 pub fn canonicalized(&self) -> Result<Self, ParameterError> {
311 let simplified = self.simplify()?;
312
313 if simplified.get_symbols().is_empty() {
314 let value = simplified.evaluate(&None)?;
315 if !value.is_finite() {
316 return Err(ParameterError::DomainError(format!(
317 "constant parameter evaluates to non-finite value {value}"
318 )));
319 }
320 let value = if value == 0.0 { 0.0 } else { value };
321 Ok(Parameter::from(value))
322 } else {
323 Ok(simplified)
324 }
325 }
326
327 /// Returns whether this parameter is exactly the numeric constant `0.0`.
328 ///
329 /// This is stricter than [`Parameter::is_zero`]: symbolic parameters return
330 /// `false`, and constants are compared exactly after evaluation rather than
331 /// with an epsilon tolerance. Compiler canonicalization uses this predicate
332 /// when deciding whether a no-op is represented exactly as zero.
333 ///
334 /// # Errors
335 ///
336 /// Returns [`ParameterError`] if a constant parameter cannot be evaluated.
337 pub fn is_exact_zero(&self) -> Result<bool, ParameterError> {
338 if self.get_symbols().is_empty() {
339 Ok(self.evaluate(&None)? == 0.0)
340 } else {
341 Ok(false)
342 }
343 }
344
345 /// Returns whether this is already the canonical numeric `+0.0` node.
346 ///
347 /// Unlike [`Parameter::is_exact_zero`], this does not evaluate constant
348 /// expressions and deliberately rejects `-0.0`. Compiler hot paths use it
349 /// only to skip work that is provably an exact representation no-op.
350 pub(crate) fn is_canonical_positive_zero(&self) -> bool {
351 self.expr
352 .as_number()
353 .is_some_and(|value| value.to_bits() == 0.0_f64.to_bits())
354 }
355
356 /// Computes the symbolic partial derivative of this expression with
357 /// respect to the variable `var`.
358 ///
359 /// Returns [`ParameterError`] if differentiation is not supported for
360 /// the expression.
361 ///
362 /// # Examples
363 ///
364 /// ```rust
365 /// use cqlib_core::circuit::Parameter;
366 ///
367 /// let theta = Parameter::symbol("θ");
368 /// let expr = theta.clone() * theta.clone(); // θ²
369 /// let deriv = expr.derivative("θ").unwrap(); // 2θ
370 /// let val = deriv.evaluate(&Some([("θ", 3.0)].iter().cloned().collect())).unwrap();
371 /// assert!((val - 6.0).abs() < 1e-10);
372 /// ```
373 pub fn derivative(&self, var: &str) -> Result<Self, ParameterError> {
374 let expr = self.expr.diff(var)?;
375 Ok(Self { expr })
376 }
377
378 /// Returns the set of free variable names present in this expression.
379 ///
380 /// The built-in constants `π` and `e` may appear here if the expression
381 /// was constructed symbolically (e.g. via [`Parameter::pi`]), but they are
382 /// resolved automatically during [`Parameter::evaluate`].
383 ///
384 /// # Examples
385 ///
386 /// ```rust
387 /// use cqlib_core::circuit::Parameter;
388 ///
389 /// let expr = Parameter::symbol("θ") + Parameter::symbol("φ");
390 /// let syms = expr.get_symbols();
391 /// assert!(syms.contains("θ"));
392 /// assert!(syms.contains("φ"));
393 /// ```
394 pub fn get_symbols(&self) -> HashSet<String> {
395 let mut symbols = self.expr.variables();
396 symbols.remove("π");
397 symbols.remove("pi");
398 symbols.remove("e");
399 symbols
400 }
401
402 /// Returns a new parameter representing the absolute value `|x|`.
403 pub fn abs(&self) -> Self {
404 Self {
405 expr: self.expr.clone().abs(),
406 }
407 }
408
409 /// Returns a new parameter representing the square root `√x`.
410 pub fn sqrt(&self) -> Self {
411 Self {
412 expr: self.expr.clone().sqrt(),
413 }
414 }
415
416 /// Returns a new parameter representing the exponential `eˣ`.
417 pub fn exp(&self) -> Self {
418 Self {
419 expr: self.expr.clone().exp(),
420 }
421 }
422
423 /// Returns a new parameter representing the sine `sin(x)`.
424 pub fn sin(&self) -> Self {
425 Self {
426 expr: self.expr.clone().sin(),
427 }
428 }
429
430 /// Returns a new parameter representing the inverse sine `asin(x)`.
431 pub fn asin(&self) -> Self {
432 Self {
433 expr: self.expr.clone().asin(),
434 }
435 }
436
437 /// Returns a new parameter representing the cosine `cos(x)`.
438 pub fn cos(&self) -> Self {
439 Self {
440 expr: self.expr.clone().cos(),
441 }
442 }
443
444 /// Returns a new parameter representing the inverse cosine `acos(x)`.
445 pub fn acos(&self) -> Self {
446 Self {
447 expr: self.expr.clone().acos(),
448 }
449 }
450
451 /// Returns a new parameter representing the tangent `tan(x)`.
452 pub fn tan(&self) -> Self {
453 Self {
454 expr: self.expr.clone().tan(),
455 }
456 }
457
458 /// Returns a new parameter representing the inverse tangent `atan(x)`.
459 pub fn atan(&self) -> Self {
460 Self {
461 expr: self.expr.clone().atan(),
462 }
463 }
464
465 /// Returns a new parameter representing the natural logarithm `ln(x)`.
466 pub fn ln(&self) -> Self {
467 Self {
468 expr: self.expr.clone().ln(),
469 }
470 }
471
472 /// Returns a new parameter representing the logarithm `log_base(x)`.
473 ///
474 /// # Examples
475 ///
476 /// ```rust
477 /// use cqlib_core::circuit::Parameter;
478 ///
479 /// // log base 2 of x
480 /// let x = Parameter::symbol("x");
481 /// let log2 = x.log(Parameter::from(2.0));
482 /// let val = log2.evaluate(&Some([("x", 8.0)].iter().cloned().collect())).unwrap();
483 /// assert!((val - 3.0).abs() < 1e-10);
484 /// ```
485 pub fn log(&self, base: impl Into<Self>) -> Self {
486 Self {
487 expr: self.expr.clone().log(base.into()),
488 }
489 }
490
491 /// Returns the mathematical constant π as a symbolic parameter.
492 ///
493 /// The symbol `"π"` is resolved to [`std::f64::consts::PI`] automatically
494 /// inside [`Parameter::evaluate`].
495 pub fn pi() -> Self {
496 Self::symbol("π")
497 }
498
499 /// Returns the mathematical constant *e* (Euler's number) as a symbolic
500 /// parameter.
501 ///
502 /// The symbol `"e"` is resolved to [`std::f64::consts::E`] automatically
503 /// inside [`Parameter::evaluate`].
504 pub fn e() -> Self {
505 Self::symbol("e")
506 }
507
508 /// Returns a new parameter representing `self` raised to the power `exp`.
509 ///
510 /// # Examples
511 ///
512 /// ```rust
513 /// use cqlib_core::circuit::Parameter;
514 ///
515 /// let x = Parameter::symbol("x");
516 /// let x3 = x.pow(Parameter::from(3.0)); // x³
517 /// let val = x3.evaluate(&Some([("x", 2.0)].iter().cloned().collect())).unwrap();
518 /// assert!((val - 8.0).abs() < 1e-10);
519 /// ```
520 pub fn pow(&self, exp: impl Into<Self>) -> Self {
521 Self {
522 expr: self.expr.clone().pow(exp.into()),
523 }
524 }
525
526 /// Substitutes every occurrence of `symbol` in this expression with
527 /// `param`, returning the resulting expression.
528 ///
529 /// # Examples
530 ///
531 /// ```rust
532 /// use cqlib_core::circuit::Parameter;
533 ///
534 /// let expr = Parameter::symbol("θ") * Parameter::from(2.0); // θ * 2
535 /// let replaced = expr.replace("θ", Parameter::pi()); // π * 2
536 /// let val = replaced.evaluate(&None).unwrap();
537 /// assert!((val - 2.0 * std::f64::consts::PI).abs() < 1e-10);
538 /// ```
539 pub fn replace(&self, symbol: &str, param: impl Into<Self>) -> Self {
540 let p: Self = param.into();
541 Self {
542 expr: self.expr.substitute(symbol, &p.expr),
543 }
544 }
545
546 /// Returns the symbol name if this parameter is exactly one free symbol.
547 ///
548 /// This is intentionally conservative: expressions containing one symbol
549 /// are accepted only when they are structurally equal to that symbol after
550 /// optional simplification.
551 pub fn as_symbol(&self) -> Option<String> {
552 let symbols = self.get_symbols();
553 if symbols.len() != 1 {
554 return None;
555 }
556
557 let symbol = symbols.into_iter().next()?;
558 let direct = Self::symbol(&symbol);
559 if self == &direct {
560 return Some(symbol);
561 }
562
563 self.simplify()
564 .ok()
565 .filter(|simplified| simplified == &direct)
566 .map(|_| symbol)
567 }
568
569 /// Substitutes every bound symbol in this expression and simplifies when possible.
570 pub fn substitute_many(&self, bindings: &HashMap<String, Parameter>) -> Self {
571 let mut substituted = self.clone();
572 for (symbol, value) in bindings {
573 substituted = substituted.replace(symbol, value.clone());
574 }
575 substituted.simplify().unwrap_or(substituted)
576 }
577
578 /// Simultaneously substitutes symbols in a single expression-tree pass.
579 ///
580 /// Replacement expressions are inserted as-is and are not visited again,
581 /// so overlapping bindings such as `a -> b` and `b -> c` do not cascade.
582 pub(crate) fn substitute_many_simultaneous(
583 &self,
584 bindings: &HashMap<String, Parameter>,
585 ) -> Self {
586 if bindings.is_empty() {
587 return self.clone();
588 }
589 let replacements = bindings
590 .iter()
591 .map(|(symbol, replacement)| (Self::symbol(symbol).expr, &replacement.expr))
592 .collect::<Vec<_>>();
593 Self::new(self.expr.map(|node| {
594 replacements
595 .iter()
596 .find(|(symbol, _)| node == symbol)
597 .map_or_else(|| node.clone(), |(_, replacement)| (*replacement).clone())
598 }))
599 }
600
601 /// Returns whether two parameters are provably equal within `tolerance`.
602 ///
603 /// This is a conservative symbolic check. It accepts structural equality
604 /// after simplification and concrete numeric equality, but it does not try
605 /// to prove arbitrary symbolic identities.
606 pub fn provably_equal(&self, other: &Self, tolerance: f64) -> bool {
607 if self == other {
608 return true;
609 }
610 if let (Ok(lhs), Ok(rhs)) = (self.evaluate(&None), other.evaluate(&None))
611 && (lhs - rhs).abs() <= tolerance
612 {
613 return true;
614 }
615
616 let lhs = self.simplify().unwrap_or_else(|_| self.clone());
617 let rhs = other.simplify().unwrap_or_else(|_| other.clone());
618 if lhs == rhs {
619 return true;
620 }
621
622 match (lhs.evaluate(&None), rhs.evaluate(&None)) {
623 (Ok(lhs), Ok(rhs)) => (lhs - rhs).abs() <= tolerance,
624 _ => false,
625 }
626 }
627
628 /// Returns whether two parameters are provably equal modulo `modulus`.
629 ///
630 /// Fully numeric parameters take a fast path with plain floating-point
631 /// math, without building or simplifying a symbolic difference expression.
632 /// Only expressions with unbound symbols fall back to the symbolic path,
633 /// where the expression difference and modulus must both evaluate without
634 /// extra bindings for the numeric modulo check to apply.
635 pub fn provably_equal_modulo(&self, other: &Self, modulus: &Self, tolerance: f64) -> bool {
636 if let (Some(lhs), Some(rhs)) = (self.numeric_value(), other.numeric_value()) {
637 let diff_value = lhs - rhs;
638 if diff_value.abs() <= tolerance {
639 return true;
640 }
641 let Some(modulus_value) = modulus.numeric_value() else {
642 return false;
643 };
644 if modulus_value.abs() <= tolerance {
645 return false;
646 }
647 let ratio = diff_value / modulus_value;
648 return (ratio - ratio.round()).abs() <= tolerance;
649 }
650
651 let raw_diff = self.clone() - other.clone();
652 let diff = raw_diff.simplify().unwrap_or(raw_diff);
653 if diff.provably_equal(&Self::from(0.0), tolerance) {
654 return true;
655 }
656
657 let Ok(diff_value) = diff.evaluate(&None) else {
658 return false;
659 };
660 let Ok(modulus_value) = modulus.evaluate(&None) else {
661 return false;
662 };
663 if modulus_value.abs() <= tolerance {
664 return false;
665 }
666
667 let ratio = diff_value / modulus_value;
668 (ratio - ratio.round()).abs() <= tolerance
669 }
670
671 /// Returns `true` if this parameter is a constant (has no free variables).
672 ///
673 /// # Examples
674 ///
675 /// ```rust
676 /// use cqlib_core::circuit::Parameter;
677 ///
678 /// let constant = Parameter::from(3.14);
679 /// assert!(constant.is_constant());
680 ///
681 /// let symbolic = Parameter::symbol("x");
682 /// assert!(!symbolic.is_constant());
683 /// ```
684 pub fn is_constant(&self) -> bool {
685 self.get_symbols().is_empty()
686 }
687
688 /// Returns `true` if this parameter evaluates to zero.
689 ///
690 /// Returns `false` if the parameter cannot be evaluated (contains unbound symbols).
691 ///
692 /// # Examples
693 ///
694 /// ```rust
695 /// use cqlib_core::circuit::Parameter;
696 ///
697 /// let zero = Parameter::from(0.0);
698 /// assert!(zero.is_zero());
699 ///
700 /// let non_zero = Parameter::from(1.0);
701 /// assert!(!non_zero.is_zero());
702 /// ```
703 pub fn is_zero(&self) -> bool {
704 self.evaluate(&None)
705 .map(|v| v.abs() < f64::EPSILON)
706 .unwrap_or(false)
707 }
708
709 /// Returns `true` if this parameter evaluates to one.
710 ///
711 /// Returns `false` if the parameter cannot be evaluated (contains unbound symbols).
712 ///
713 /// # Examples
714 ///
715 /// ```rust
716 /// use cqlib_core::circuit::Parameter;
717 ///
718 /// let one = Parameter::from(1.0);
719 /// assert!(one.is_one());
720 ///
721 /// let non_one = Parameter::from(2.0);
722 /// assert!(!non_one.is_one());
723 /// ```
724 pub fn is_one(&self) -> bool {
725 self.evaluate(&None)
726 .map(|v| (v - 1.0).abs() < f64::EPSILON)
727 .unwrap_or(false)
728 }
729
730 /// Returns the hyperbolic sine `sinh(x)`.
731 pub fn sinh(&self) -> Self {
732 Self {
733 expr: self.expr.clone().sinh(),
734 }
735 }
736
737 /// Returns the hyperbolic cosine `cosh(x)`.
738 pub fn cosh(&self) -> Self {
739 Self {
740 expr: self.expr.clone().cosh(),
741 }
742 }
743
744 /// Returns the hyperbolic tangent `tanh(x)`.
745 pub fn tanh(&self) -> Self {
746 Self {
747 expr: self.expr.clone().tanh(),
748 }
749 }
750
751 /// Returns the floor of the expression.
752 pub fn floor(&self) -> Self {
753 Self {
754 expr: self.expr.clone().floor(),
755 }
756 }
757
758 /// Returns the ceiling of the expression.
759 pub fn ceil(&self) -> Self {
760 Self {
761 expr: self.expr.clone().ceil(),
762 }
763 }
764
765 /// Returns the rounded value of the expression.
766 pub fn round(&self) -> Self {
767 Self {
768 expr: self.expr.clone().round(),
769 }
770 }
771}
772
773impl From<f64> for Parameter {
774 fn from(value: f64) -> Self {
775 if !value.is_finite() {
776 panic!("Parameter numeric literal must be finite, got {value}");
777 }
778 Self {
779 expr: Expr::from(value),
780 }
781 }
782}
783
784impl From<f32> for Parameter {
785 fn from(value: f32) -> Self {
786 if !value.is_finite() {
787 panic!("Parameter numeric literal must be finite, got {value}");
788 }
789 Self {
790 expr: Expr::from(f64::from(value)),
791 }
792 }
793}
794
795impl From<u32> for Parameter {
796 fn from(value: u32) -> Self {
797 Self {
798 expr: Expr::from(f64::from(value)),
799 }
800 }
801}
802
803impl From<i32> for Parameter {
804 fn from(value: i32) -> Self {
805 Self {
806 expr: Expr::from(value),
807 }
808 }
809}
810
811/// Implements the four arithmetic operators between `Parameter` and a primitive numeric
812/// type `$t` in both directions (e.g. `Parameter + f64` and `f64 + Parameter`).
813///
814/// The implementation converts `$t` to `Parameter` via `Parameter::from`, extracts the
815/// underlying `Expr`, and delegates to the corresponding `Expr` operator.
816macro_rules! impl_ops_for_type {
817 ($t:ty) => {
818 // Parameter + T
819 impl Add<$t> for Parameter {
820 type Output = Parameter;
821 fn add(self, rhs: $t) -> Self::Output {
822 Self {
823 // 1. `Parameter::from(rhs)` converts the primitive to `Parameter`.
824 // 2. `.expr` unwraps the underlying `Expr` node.
825 // 3. `self.expr + ...` invokes `symb_anafis`'s `Add` impl.
826 expr: self.expr + Parameter::from(rhs).expr,
827 }
828 }
829 }
830
831 // T + Parameter
832 impl Add<Parameter> for $t {
833 type Output = Parameter;
834 fn add(self, rhs: Parameter) -> Self::Output {
835 Parameter {
836 expr: Parameter::from(self).expr + rhs.expr,
837 }
838 }
839 }
840
841 // Parameter - T
842 impl Sub<$t> for Parameter {
843 type Output = Parameter;
844 fn sub(self, rhs: $t) -> Self::Output {
845 Self {
846 expr: self.expr - Parameter::from(rhs).expr,
847 }
848 }
849 }
850
851 // T - Parameter
852 impl Sub<Parameter> for $t {
853 type Output = Parameter;
854 fn sub(self, rhs: Parameter) -> Self::Output {
855 Parameter {
856 expr: Parameter::from(self).expr - rhs.expr,
857 }
858 }
859 }
860
861 // Parameter * T
862 impl Mul<$t> for Parameter {
863 type Output = Parameter;
864 fn mul(self, rhs: $t) -> Self::Output {
865 Self {
866 expr: self.expr * Parameter::from(rhs).expr,
867 }
868 }
869 }
870
871 // T * Parameter
872 impl Mul<Parameter> for $t {
873 type Output = Parameter;
874 fn mul(self, rhs: Parameter) -> Self::Output {
875 Parameter {
876 expr: Parameter::from(self).expr * rhs.expr,
877 }
878 }
879 }
880
881 // Parameter / T
882 impl Div<$t> for Parameter {
883 type Output = Parameter;
884 fn div(self, rhs: $t) -> Self::Output {
885 Self {
886 expr: self.expr / Parameter::from(rhs).expr,
887 }
888 }
889 }
890
891 // T / Parameter
892 impl Div<Parameter> for $t {
893 type Output = Parameter;
894 fn div(self, rhs: Parameter) -> Self::Output {
895 Parameter {
896 expr: Parameter::from(self).expr / rhs.expr,
897 }
898 }
899 }
900 };
901}
902
903// Apply the operator impls to all supported primitive types.
904impl_ops_for_type!(f64);
905impl_ops_for_type!(f32);
906impl_ops_for_type!(i32);
907impl_ops_for_type!(u32);
908
909/// Implements all four combinations of owned/borrowed operands for a binary
910/// operator between two `Parameter` values.
911///
912/// For each `(Trait, method)` pair this macro generates:
913///
914/// 1. `Parameter op Parameter` — both operands consumed.
915/// 2. `&Parameter op &Parameter` — neither operand consumed (most common in loop contexts).
916/// 3. `Parameter op &Parameter` — left consumed, right borrowed.
917/// 4. `&Parameter op Parameter` — left borrowed, right consumed.
918///
919/// Cloning `Expr` is inexpensive because `symb_anafis` uses reference-counted
920/// node sharing internally.
921macro_rules! impl_binary_op_ref {
922 ($($trait:ident, $method:ident),* $(,)?) => {
923 $(
924 // 1. Parameter op Parameter — both sides consumed.
925 impl $trait<Parameter> for Parameter {
926 type Output = Parameter;
927 fn $method(self, rhs: Parameter) -> Self::Output {
928 Parameter {
929 // Delegate directly to the underlying `Expr` operator.
930 expr: self.expr.$method(rhs.expr),
931 }
932 }
933 }
934
935 // 2. &Parameter op &Parameter — no ownership transferred; clone the inner `Expr`.
936 impl<'a, 'b> $trait<&'b Parameter> for &'a Parameter {
937 type Output = Parameter;
938 fn $method(self, rhs: &'b Parameter) -> Self::Output {
939 Parameter {
940 // `Expr::clone` is cheap — the tree uses Arc-based
941 // node sharing inside `symb_anafis`.
942 expr: self.expr.clone().$method(rhs.expr.clone()),
943 }
944 }
945 }
946
947 // 3. Parameter op &Parameter — left consumed, right cloned.
948 impl<'a> $trait<&'a Parameter> for Parameter {
949 type Output = Parameter;
950 fn $method(self, rhs: &'a Parameter) -> Self::Output {
951 Parameter {
952 expr: self.expr.$method(rhs.expr.clone()),
953 }
954 }
955 }
956
957 // 4. &Parameter op Parameter — left cloned, right consumed.
958 impl<'a> $trait<Parameter> for &'a Parameter {
959 type Output = Parameter;
960 fn $method(self, rhs: Parameter) -> Self::Output {
961 Parameter {
962 expr: self.expr.clone().$method(rhs.expr),
963 }
964 }
965 }
966 )*
967 };
968}
969
970impl_binary_op_ref! {
971 Add, add,
972 Sub, sub,
973 Mul, mul,
974 Div, div,
975}
976
977// Implement Neg for owned Parameter
978impl Neg for Parameter {
979 type Output = Parameter;
980 fn neg(self) -> Self::Output {
981 Parameter {
982 expr: self.expr.neg(),
983 }
984 }
985}
986
987// Implement Neg for borrowed Parameter
988impl Neg for &Parameter {
989 type Output = Parameter;
990 fn neg(self) -> Self::Output {
991 Parameter {
992 expr: self.expr.clone().neg(),
993 }
994 }
995}
996
997#[cfg(test)]
998#[path = "parameter_test.rs"]
999mod parameter_test;