Skip to main content

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;