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, visitor::ExprView};
44
45// The engine's map() treats polynomials as opaque and visits rebuilt nodes.
46// Walk the public view instead: it exposes polynomial terms, and replacement
47// expressions are returned immediately without being traversed themselves.
48fn substitute_parameter_expr<'a>(expr: &Expr, lookup: &impl Fn(&str) -> Option<&'a Expr>) -> Expr {
49    match expr.view() {
50        ExprView::Number(_) => expr.clone(),
51        ExprView::Symbol(name) => lookup(&name).cloned().unwrap_or_else(|| expr.clone()),
52        ExprView::Sum(children) | ExprView::Product(children) => {
53            let mapped: Vec<_> = children
54                .iter()
55                .map(|child| substitute_parameter_expr(child, lookup))
56                .collect();
57            if children.iter().zip(&mapped).all(|(a, b)| a.as_ref() == b) {
58                return expr.clone();
59            }
60
61            // Smart sum/product constructors can prune tiny coefficients.
62            // Construct a template of distinct function calls first, then use
63            // map() only on that template, whose children have no polynomials.
64            // Inserted expressions never pass through the smart constructors.
65            let slots = (0..mapped.len())
66                .map(|i| Expr::func("__cqlib_substitution_slot", Expr::number(i as f64)))
67                .collect();
68            let template = if matches!(expr.view(), ExprView::Sum(_)) {
69                Expr::sum(slots)
70            } else {
71                Expr::product(slots)
72            };
73            let slots = match template.view() {
74                ExprView::Sum(slots) | ExprView::Product(slots) => slots.into_owned(),
75                _ => return mapped.into_iter().next().unwrap_or(template),
76            };
77            // Match in template order so reconstruction preserves the original
78            // child order even if construction sorted the placeholder nodes.
79            let replacements: HashMap<_, _> = slots
80                .into_iter()
81                .map(|slot| (*slot).clone())
82                .zip(mapped)
83                .collect();
84            template.map(|node| {
85                replacements
86                    .get(node)
87                    .cloned()
88                    .unwrap_or_else(|| node.clone())
89            })
90        }
91        ExprView::Div(lhs, rhs) => {
92            let a = substitute_parameter_expr(lhs, lookup);
93            let b = substitute_parameter_expr(rhs, lookup);
94            if &a == lhs && &b == rhs {
95                expr.clone()
96            } else {
97                div_parameter_expr(a, b)
98            }
99        }
100        ExprView::Pow(base, exponent) => {
101            let a = substitute_parameter_expr(base, lookup);
102            let b = substitute_parameter_expr(exponent, lookup);
103            if &a == base && &b == exponent {
104                expr.clone()
105            } else {
106                Expr::pow_static(a, b)
107            }
108        }
109        ExprView::Function { name, args } => {
110            let mapped: Vec<_> = args
111                .iter()
112                .map(|arg| substitute_parameter_expr(arg, lookup))
113                .collect();
114            if args.iter().zip(&mapped).all(|(a, b)| a.as_ref() == b) {
115                expr.clone()
116            } else {
117                Expr::func_multi(name, mapped)
118            }
119        }
120        ExprView::Derivative { inner, var, order } => {
121            let mapped = substitute_parameter_expr(inner, lookup);
122            if &mapped == inner {
123                expr.clone()
124            } else {
125                Expr::derivative(mapped, var, order)
126            }
127        }
128    }
129}
130
131// Integral coefficients in the exactly representable integer range cannot
132// trigger the engine's near-zero/near-one pruning. Ordinary sums of symbols
133// therefore avoid coefficient collection and expression rebuilding entirely.
134fn needs_affine_protection(expr: &Expr) -> bool {
135    match expr.view() {
136        ExprView::Number(value) => value.fract() != 0.0 || value.abs() > (1u64 << 53) as f64,
137        ExprView::Sum(children) | ExprView::Product(children) => {
138            children.iter().any(|child| needs_affine_protection(child))
139        }
140        ExprView::Div(numerator, denominator) => {
141            needs_affine_protection(numerator)
142                || denominator.as_number().is_some_and(|value| {
143                    value != 0.0 && needs_affine_protection(&Expr::number(1.0 / value))
144                })
145        }
146        _ => false,
147    }
148}
149
150type AffineCoefficients = HashMap<Option<Expr>, f64>;
151
152// Coefficients are collected once per input, using hashed symbol keys. Building
153// a replacement AST is deferred until the engine actually changes a coefficient.
154fn affine_coefficients(inputs: &[(&Expr, f64)]) -> Option<AffineCoefficients> {
155    fn collect(expr: &Expr, scale: f64, terms: &mut AffineCoefficients) -> Option<()> {
156        match expr.view() {
157            ExprView::Number(value) => *terms.entry(None).or_default() += scale * value,
158            ExprView::Symbol(_) => *terms.entry(Some(expr.clone())).or_default() += scale,
159            ExprView::Sum(children) => {
160                for child in children.iter() {
161                    collect(child, scale, terms)?;
162                }
163            }
164            ExprView::Product(children) => {
165                let mut scale = scale;
166                let mut symbolic = None;
167                for child in children.iter() {
168                    if let Some(value) = child.as_number() {
169                        scale *= value;
170                    } else if symbolic.replace(child).is_some() {
171                        return None;
172                    }
173                }
174                if let Some(child) = symbolic {
175                    collect(child, scale, terms)?;
176                } else {
177                    *terms.entry(None).or_default() += scale;
178                }
179            }
180            ExprView::Div(numerator, denominator) => {
181                let divisor = denominator.as_number()?;
182                if divisor == 0.0 {
183                    return None;
184                }
185                collect(numerator, scale / divisor, terms)?;
186            }
187            _ => return None,
188        }
189        Some(())
190    }
191
192    let mut terms = HashMap::new();
193    for (expr, scale) in inputs {
194        collect(expr, *scale, &mut terms)?;
195    }
196    if terms.values().any(|coefficient| !coefficient.is_finite()) {
197        return None;
198    }
199    terms.retain(|_, coefficient| *coefficient != 0.0);
200    Some(terms)
201}
202
203fn weighted_symbol(symbol: Expr, coefficient: f64) -> Expr {
204    if coefficient == 0.0 {
205        return Expr::number(0.0);
206    }
207    if coefficient == 1.0 {
208        return symbol;
209    }
210    let product = Expr::product(vec![Expr::number(coefficient), symbol.clone()]);
211    if let ExprView::Product(factors) = product.view()
212        && factors
213            .iter()
214            .any(|factor| factor.as_number() == Some(coefficient))
215    {
216        return product;
217    }
218    // Public map() reconstructs products without approximate numeric pruning.
219    // If construction rounded a near-unit coefficient, insert its exact value
220    // into a non-unit product template after construction.
221    Expr::product(vec![Expr::number(2.0), symbol]).map(|factor| {
222        if factor.as_number() == Some(2.0) {
223            Expr::number(coefficient)
224        } else {
225            factor.clone()
226        }
227    })
228}
229
230fn affine_expr(terms: &AffineCoefficients) -> Expr {
231    // Construct a sum of distinct unit-weight symbols before inserting weights,
232    // preventing automatic polynomial conversion from deleting small terms.
233    let unit_sum = Expr::sum(
234        terms
235            .keys()
236            .map(|symbol| symbol.clone().unwrap_or_else(|| Expr::number(1.0)))
237            .collect(),
238    );
239    unit_sum.map(|node| {
240        if node.as_number() == Some(1.0) {
241            Expr::number(terms.get(&None).copied().unwrap_or(1.0))
242        } else if matches!(node.view(), ExprView::Symbol(_)) {
243            weighted_symbol(
244                node.clone(),
245                terms.get(&Some(node.clone())).copied().unwrap_or(1.0),
246            )
247        } else {
248            node.clone()
249        }
250    })
251}
252
253fn preserve_affine(expected: Option<AffineCoefficients>, fallback: Expr) -> Expr {
254    match expected {
255        Some(expected) if expected.keys().all(Option::is_none) => {
256            Expr::number(expected.get(&None).copied().unwrap_or(0.0))
257        }
258        Some(expected) if affine_coefficients(&[(&fallback, 1.0)]).as_ref() != Some(&expected) => {
259            affine_expr(&expected)
260        }
261        _ => fallback,
262    }
263}
264
265fn add_parameter_expr(lhs: Expr, rhs: Expr) -> Expr {
266    if let (Some(a), Some(b)) = (lhs.as_number(), rhs.as_number()) {
267        return Expr::number(a + b);
268    }
269    if !needs_affine_protection(&lhs) && !needs_affine_protection(&rhs) {
270        return lhs + rhs;
271    }
272    let affine = affine_coefficients(&[(&lhs, 1.0), (&rhs, 1.0)]);
273    preserve_affine(affine, lhs + rhs)
274}
275
276fn sub_parameter_expr(lhs: Expr, rhs: Expr) -> Expr {
277    if let (Some(a), Some(b)) = (lhs.as_number(), rhs.as_number()) {
278        return Expr::number(a - b);
279    }
280    if !needs_affine_protection(&lhs) && !needs_affine_protection(&rhs) {
281        return lhs - rhs;
282    }
283    let affine = affine_coefficients(&[(&lhs, 1.0), (&rhs, -1.0)]);
284    preserve_affine(affine, lhs - rhs)
285}
286
287// The common rotation-angle case needs neither a coefficient map nor a second
288// symbolic operation. Handle one weighted symbol directly, including rescaling.
289fn scaled_symbol(expr: &Expr, scale: f64) -> Option<Expr> {
290    let (symbol, coefficient) = match expr.view() {
291        ExprView::Symbol(_) => (expr.clone(), scale),
292        ExprView::Product(children) if children.len() == 2 => {
293            let (number, symbol) = if let Some(number) = children[0].as_number() {
294                (number, &children[1])
295            } else {
296                (children[1].as_number()?, &children[0])
297            };
298            if !matches!(symbol.view(), ExprView::Symbol(_)) {
299                return None;
300            }
301            ((**symbol).clone(), number * scale)
302        }
303        _ => return None,
304    };
305    coefficient
306        .is_finite()
307        .then(|| weighted_symbol(symbol, coefficient))
308}
309
310fn mul_parameter_expr(lhs: Expr, rhs: Expr) -> Expr {
311    if let (Some(a), Some(b)) = (lhs.as_number(), rhs.as_number()) {
312        return Expr::number(a * b);
313    }
314    let affine = if let Some(scale) = lhs.as_number() {
315        if let Some(expr) = scaled_symbol(&rhs, scale) {
316            return expr;
317        }
318        if !needs_affine_protection(&lhs) && !needs_affine_protection(&rhs) {
319            return lhs * rhs;
320        }
321        affine_coefficients(&[(&rhs, scale)])
322    } else if let Some(scale) = rhs.as_number() {
323        if let Some(expr) = scaled_symbol(&lhs, scale) {
324            return expr;
325        }
326        if !needs_affine_protection(&lhs) && !needs_affine_protection(&rhs) {
327            return lhs * rhs;
328        }
329        affine_coefficients(&[(&lhs, scale)])
330    } else {
331        None
332    };
333    preserve_affine(affine, lhs * rhs)
334}
335
336fn div_parameter_expr(lhs: Expr, rhs: Expr) -> Expr {
337    if let (Some(a), Some(b)) = (lhs.as_number(), rhs.as_number())
338        && b != 0.0
339    {
340        return Expr::number(a / b);
341    }
342    let affine = if let Some(divisor) = rhs.as_number().filter(|value| *value != 0.0) {
343        affine_coefficients(&[(&lhs, 1.0)]).and_then(|mut terms| {
344            // Divide coefficients directly: the reciprocal of a subnormal
345            // divisor can overflow even when the quotient is representable.
346            for coefficient in terms.values_mut() {
347                *coefficient /= divisor;
348            }
349            if terms.values().any(|coefficient| !coefficient.is_finite()) {
350                return None;
351            }
352            terms.retain(|_, coefficient| *coefficient != 0.0);
353            Some(terms)
354        })
355    } else {
356        None
357    };
358    preserve_affine(affine, lhs / rhs)
359}
360
361/// A symbolic or numeric parameter used to represent gate angles and other
362/// continuous values in a quantum circuit.
363///
364/// `Parameter` is a thin, cheaply-cloneable wrapper around [`symb_anafis::Expr`].
365/// It supports the full suite of arithmetic operators (`+`, `-`, `*`, `/`)
366/// between `Parameter` values as well as between `Parameter` and primitive numeric types
367/// (`f64`, `f32`, `i32`, `u32`).
368///
369/// The mathematical constants `π` and `e` are always available as free
370/// symbols and are automatically resolved during [`Parameter::evaluate`].
371#[derive(Clone, Debug)]
372pub struct Parameter {
373    /// The root node of the mathematical expression tree.
374    expr: Expr,
375}
376
377impl From<Expr> for Parameter {
378    fn from(expr: Expr) -> Self {
379        Parameter { expr }
380    }
381}
382
383impl From<Parameter> for Expr {
384    fn from(p: Parameter) -> Self {
385        p.expr
386    }
387}
388
389impl fmt::Display for Parameter {
390    /// Formats the parameter as a human-readable mathematical string.
391    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
392        fmt::Display::fmt(&self.expr, f)
393    }
394}
395
396impl Default for Parameter {
397    /// Creates a default parameter representing the integer `0`.
398    fn default() -> Self {
399        Self {
400            expr: Expr::from(0),
401        }
402    }
403}
404
405impl std::hash::Hash for Parameter {
406    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
407        self.expr.hash(state);
408    }
409}
410
411impl PartialEq for Parameter {
412    /// Checks equality based on the underlying expression structure.
413    ///
414    /// Note: This performs **structural** equality of the AST, not semantic
415    /// mathematical equality.  For example, `x + y` and `y + x` are
416    /// structurally different and will compare as unequal.
417    fn eq(&self, other: &Self) -> bool {
418        self.expr == other.expr
419    }
420}
421
422impl Eq for Parameter {}
423
424impl TryFrom<&str> for Parameter {
425    type Error = ParameterError;
426
427    /// Parses a symbolic expression from a string slice.
428    ///
429    /// Returns [`ParameterError::ParseError`] if the string cannot be parsed
430    /// as a valid expression.
431    ///
432    /// # Examples
433    ///
434    /// ```rust
435    /// use cqlib_core::circuit::Parameter;
436    ///
437    /// let p = Parameter::try_from("θ * 2 + π").unwrap();
438    /// let bad = Parameter::try_from("@@@");
439    /// assert!(bad.is_err());
440    /// ```
441    fn try_from(expr: &str) -> Result<Self, Self::Error> {
442        if let Ok(expr) = parse(expr, &HashSet::new(), &HashSet::new(), None) {
443            Ok(Self { expr })
444        } else {
445            Err(ParameterError::ParseError(expr.to_string()))
446        }
447    }
448}
449
450impl FromStr for Parameter {
451    type Err = ParameterError;
452
453    /// Parses a symbolic expression from a string.
454    ///
455    /// Returns [`ParameterError::ParseError`] if the string cannot be parsed
456    /// as a valid expression.
457    ///
458    /// # Examples
459    ///
460    /// ```rust
461    /// use cqlib_core::circuit::Parameter;
462    /// use std::str::FromStr;
463    ///
464    /// let p = Parameter::from_str("θ * 2 + π").unwrap();
465    /// let bad = Parameter::from_str("@@@");
466    /// assert!(bad.is_err());
467    /// ```
468    fn from_str(expr: &str) -> Result<Self, Self::Err> {
469        Self::try_from(expr)
470    }
471}
472
473impl Parameter {
474    /// Creates a new `Parameter` directly from a [`symb_anafis::Expr`] node.
475    ///
476    /// Prefer [`Parameter::symbol`] for named variables or the `From<f64>` / `From<i32>`
477    /// impls for numeric constants.
478    pub fn new(expr: Expr) -> Self {
479        Self { expr }
480    }
481
482    /// Returns a reference to the underlying expression.
483    pub fn as_expr(&self) -> &Expr {
484        &self.expr
485    }
486
487    /// Consumes the parameter and returns the underlying expression.
488    pub fn into_expr(self) -> Expr {
489        self.expr
490    }
491
492    /// Creates a symbolic parameter representing a free variable with the
493    /// given `name`.
494    ///
495    /// # Examples
496    ///
497    /// ```rust
498    /// use cqlib_core::circuit::Parameter;
499    ///
500    /// let theta = Parameter::symbol("θ");
501    /// assert_eq!(theta.get_symbols().contains("θ"), true);
502    /// ```
503    pub fn symbol(name: &str) -> Self {
504        Self::new(Expr::from(symb(name)))
505    }
506
507    /// Evaluates the expression to a concrete `f64` value.
508    ///
509    /// The mathematical constants `π` and `e` are injected automatically, so
510    /// callers do not need to supply them.  Additional variable bindings are
511    /// provided through `bindings`.
512    ///
513    /// Returns [`ParameterError::NaN`] if the expression still contains
514    /// unbound free variables after applying the supplied bindings.
515    ///
516    /// # Examples
517    ///
518    /// ```rust
519    /// use cqlib_core::circuit::Parameter;
520    /// use std::collections::HashMap;
521    ///
522    /// // Numeric constant — no bindings needed.
523    /// let p = Parameter::from(std::f64::consts::PI);
524    /// assert!((p.evaluate(&None).unwrap() - std::f64::consts::PI).abs() < 1e-10);
525    ///
526    /// // Symbolic expression with a user-supplied binding.
527    /// let theta = Parameter::symbol("θ");
528    /// let expr  = theta * Parameter::from(2.0);
529    /// let mut bindings = HashMap::new();
530    /// bindings.insert("θ", 1.0_f64);
531    /// assert!((expr.evaluate(&Some(bindings)).unwrap() - 2.0).abs() < 1e-10);
532    /// ```
533    pub fn evaluate(&self, bindings: &Option<HashMap<&str, f64>>) -> Result<f64, ParameterError> {
534        // Evaluate directly first. Numeric expressions and fully-bound user
535        // expressions avoid constructing a merged binding table entirely.
536        let mut evaluated = match bindings {
537            Some(bindings) => self.expr.evaluate(bindings, &HashMap::new()),
538            None => self.expr.evaluate(&(), &HashMap::new()),
539        };
540        // `symb_anafis` recognizes ASCII `pi`, while `Parameter::pi` uses the
541        // public Unicode spelling `π`. Only retry unresolved expressions with
542        // the compatibility constants, preserving the historical ability for
543        // explicit caller bindings to override them.
544        if evaluated.as_number().is_none() {
545            let mut actual_bindings =
546                HashMap::with_capacity(bindings.as_ref().map_or(2, |bindings| bindings.len() + 2));
547            actual_bindings.insert("π", std::f64::consts::PI);
548            actual_bindings.insert("e", std::f64::consts::E);
549            if let Some(bindings) = bindings {
550                actual_bindings.extend(bindings);
551            }
552            evaluated = self.expr.evaluate(&actual_bindings, &HashMap::new());
553        }
554
555        if let Some(n) = evaluated.as_number() {
556            // Check for NaN
557            if n.is_nan() {
558                return Err(ParameterError::DomainError(format!(
559                    "Evaluation of '{}' resulted in NaN",
560                    self.expr
561                )));
562            }
563            // Check for infinity (positive or negative)
564            if n.is_infinite() {
565                return Err(ParameterError::DomainError(format!(
566                    "Evaluation of '{}' resulted in infinity",
567                    self.expr
568                )));
569            }
570            Ok(n)
571        } else {
572            Err(ParameterError::NaN(self.expr.to_string()))
573        }
574    }
575
576    /// Returns an algebraically simplified form of this parameter.
577    ///
578    /// Simplification is performed in a domain-safe mode to avoid invalid
579    /// transformations (e.g. taking the log of a potentially negative number).
580    /// For affine sums, scalar products and division by numeric constants,
581    /// nonzero `f64` coefficients and cancellation results are preserved without
582    /// tolerance pruning. Numeric literals are retained exactly. Floating-point
583    /// rounding, overflow and underflow still apply. Parsing and general
584    /// nonlinear simplification use the symbolic engine's own numeric policy.
585    ///
586    /// # Examples
587    ///
588    /// ```rust
589    /// use cqlib_core::circuit::Parameter;
590    ///
591    /// let theta = Parameter::symbol("θ");
592    /// let expr  = theta.clone() * Parameter::from(1.0);   // θ * 1
593    /// let simplified = expr.simplify().unwrap();
594    /// assert_eq!(simplified.to_string(), "θ");
595    /// ```
596    pub fn simplify(&self) -> Result<Self, ParameterError> {
597        if self.expr.as_number().is_some() {
598            return Ok(self.clone());
599        }
600        let affine = needs_affine_protection(&self.expr)
601            .then(|| affine_coefficients(&[(&self.expr, 1.0)]))
602            .flatten();
603        let expr = Simplify::new().domain_safe(true).simplify(&self.expr)?;
604        Ok(Self {
605            expr: preserve_affine(affine, expr),
606        })
607    }
608
609    /// Returns the numeric value of this parameter without symbolic
610    /// simplification, if it can be determined directly.
611    ///
612    /// Plain number nodes are read at zero cost; other constant expressions
613    /// (e.g. `pi/2`) fall back to evaluation.  Non-finite values and
614    /// expressions with unbound symbols return `None`.
615    fn numeric_value(&self) -> Option<f64> {
616        self.expr
617            .as_number()
618            .filter(|n| n.is_finite())
619            .or_else(|| self.evaluate(&None).ok())
620    }
621
622    /// Returns the canonical storage form of this parameter expression.
623    ///
624    /// A constant expression is evaluated before symbolic simplification. If
625    /// simplification eliminates all symbols, the result is also evaluated and
626    /// rebuilt as a finite numeric parameter. This also normalizes `-0.0` to
627    /// `0.0`, giving circuit operation storage a stable representation for
628    /// equivalent constants.
629    ///
630    /// Symbolic expressions are returned in their simplified form. This method
631    /// does not intern symbolic parameters into any circuit; use
632    /// [`Circuit::map_param`](crate::circuit::Circuit::map_param) when a
633    /// [`CircuitParam`](crate::circuit::CircuitParam) is needed for operation
634    /// storage.
635    ///
636    /// # Errors
637    ///
638    /// Returns [`ParameterError`] if simplification fails, a constant expression
639    /// cannot be evaluated, or the evaluated constant is not finite.
640    pub fn canonicalized(&self) -> Result<Self, ParameterError> {
641        // Evaluate constant expressions before any approximate symbolic rewrite.
642        if self.get_symbols().is_empty() {
643            let value = self.evaluate(&None)?;
644            return Ok(Parameter::from(if value == 0.0 { 0.0 } else { value }));
645        }
646        let simplified = self.simplify()?;
647
648        if simplified.get_symbols().is_empty() {
649            let value = simplified.evaluate(&None)?;
650            if !value.is_finite() {
651                return Err(ParameterError::DomainError(format!(
652                    "constant parameter evaluates to non-finite value {value}"
653                )));
654            }
655            let value = if value == 0.0 { 0.0 } else { value };
656            Ok(Parameter::from(value))
657        } else {
658            Ok(simplified)
659        }
660    }
661
662    /// Returns whether this parameter is exactly the numeric constant `0.0`.
663    ///
664    /// This is stricter than [`Parameter::is_zero`]: symbolic parameters return
665    /// `false`, and constants are compared exactly after evaluation rather than
666    /// with an epsilon tolerance. Compiler canonicalization uses this predicate
667    /// when deciding whether a no-op is represented exactly as zero.
668    ///
669    /// # Errors
670    ///
671    /// Returns [`ParameterError`] if a constant parameter cannot be evaluated.
672    pub fn is_exact_zero(&self) -> Result<bool, ParameterError> {
673        if self.get_symbols().is_empty() {
674            Ok(self.evaluate(&None)? == 0.0)
675        } else {
676            Ok(false)
677        }
678    }
679
680    /// Returns whether this is already the canonical numeric `+0.0` node.
681    ///
682    /// Unlike [`Parameter::is_exact_zero`], this does not evaluate constant
683    /// expressions and deliberately rejects `-0.0`. Compiler hot paths use it
684    /// only to skip work that is provably an exact representation no-op.
685    pub(crate) fn is_canonical_positive_zero(&self) -> bool {
686        self.expr
687            .as_number()
688            .is_some_and(|value| value.to_bits() == 0.0_f64.to_bits())
689    }
690
691    /// Computes the symbolic partial derivative of this expression with
692    /// respect to the variable `var`.
693    ///
694    /// Returns [`ParameterError`] if differentiation is not supported for
695    /// the expression.
696    ///
697    /// # Examples
698    ///
699    /// ```rust
700    /// use cqlib_core::circuit::Parameter;
701    ///
702    /// let theta = Parameter::symbol("θ");
703    /// let expr  = theta.clone() * theta.clone();  // θ²
704    /// let deriv = expr.derivative("θ").unwrap();  // 2θ
705    /// let val   = deriv.evaluate(&Some([("θ", 3.0)].iter().cloned().collect())).unwrap();
706    /// assert!((val - 6.0).abs() < 1e-10);
707    /// ```
708    pub fn derivative(&self, var: &str) -> Result<Self, ParameterError> {
709        let expr = self.expr.diff(var)?;
710        Ok(Self { expr })
711    }
712
713    /// Returns the set of free variable names present in this expression.
714    ///
715    /// The built-in constants `π` and `e` may appear here if the expression
716    /// was constructed symbolically (e.g. via [`Parameter::pi`]), but they are
717    /// resolved automatically during [`Parameter::evaluate`].
718    ///
719    /// # Examples
720    ///
721    /// ```rust
722    /// use cqlib_core::circuit::Parameter;
723    ///
724    /// let expr = Parameter::symbol("θ") + Parameter::symbol("φ");
725    /// let syms = expr.get_symbols();
726    /// assert!(syms.contains("θ"));
727    /// assert!(syms.contains("φ"));
728    /// ```
729    pub fn get_symbols(&self) -> HashSet<String> {
730        let mut symbols = self.expr.variables();
731        symbols.remove("π");
732        symbols.remove("pi");
733        symbols.remove("e");
734        symbols
735    }
736
737    /// Returns a new parameter representing the absolute value `|x|`.
738    pub fn abs(&self) -> Self {
739        Self {
740            expr: self.expr.clone().abs(),
741        }
742    }
743
744    /// Returns a new parameter representing the square root `√x`.
745    pub fn sqrt(&self) -> Self {
746        Self {
747            expr: self.expr.clone().sqrt(),
748        }
749    }
750
751    /// Returns a new parameter representing the exponential `eˣ`.
752    pub fn exp(&self) -> Self {
753        Self {
754            expr: self.expr.clone().exp(),
755        }
756    }
757
758    /// Returns a new parameter representing the sine `sin(x)`.
759    pub fn sin(&self) -> Self {
760        Self {
761            expr: self.expr.clone().sin(),
762        }
763    }
764
765    /// Returns a new parameter representing the inverse sine `asin(x)`.
766    pub fn asin(&self) -> Self {
767        Self {
768            expr: self.expr.clone().asin(),
769        }
770    }
771
772    /// Returns a new parameter representing the cosine `cos(x)`.
773    pub fn cos(&self) -> Self {
774        Self {
775            expr: self.expr.clone().cos(),
776        }
777    }
778
779    /// Returns a new parameter representing the inverse cosine `acos(x)`.
780    pub fn acos(&self) -> Self {
781        Self {
782            expr: self.expr.clone().acos(),
783        }
784    }
785
786    /// Returns a new parameter representing the tangent `tan(x)`.
787    pub fn tan(&self) -> Self {
788        Self {
789            expr: self.expr.clone().tan(),
790        }
791    }
792
793    /// Returns a new parameter representing the inverse tangent `atan(x)`.
794    pub fn atan(&self) -> Self {
795        Self {
796            expr: self.expr.clone().atan(),
797        }
798    }
799
800    /// Returns a new parameter representing the natural logarithm `ln(x)`.
801    pub fn ln(&self) -> Self {
802        Self {
803            expr: self.expr.clone().ln(),
804        }
805    }
806
807    /// Returns a new parameter representing the logarithm `log_base(x)`.
808    ///
809    /// # Examples
810    ///
811    /// ```rust
812    /// use cqlib_core::circuit::Parameter;
813    ///
814    /// // log base 2 of x
815    /// let x    = Parameter::symbol("x");
816    /// let log2 = x.log(Parameter::from(2.0));
817    /// let val  = log2.evaluate(&Some([("x", 8.0)].iter().cloned().collect())).unwrap();
818    /// assert!((val - 3.0).abs() < 1e-10);
819    /// ```
820    pub fn log(&self, base: impl Into<Self>) -> Self {
821        Self {
822            expr: self.expr.clone().log(base.into()),
823        }
824    }
825
826    /// Returns the mathematical constant π as a symbolic parameter.
827    ///
828    /// The symbol `"π"` is resolved to [`std::f64::consts::PI`] automatically
829    /// inside [`Parameter::evaluate`].
830    pub fn pi() -> Self {
831        Self::symbol("π")
832    }
833
834    /// Returns the mathematical constant *e* (Euler's number) as a symbolic
835    /// parameter.
836    ///
837    /// The symbol `"e"` is resolved to [`std::f64::consts::E`] automatically
838    /// inside [`Parameter::evaluate`].
839    pub fn e() -> Self {
840        Self::symbol("e")
841    }
842
843    /// Returns a new parameter representing `self` raised to the power `exp`.
844    ///
845    /// # Examples
846    ///
847    /// ```rust
848    /// use cqlib_core::circuit::Parameter;
849    ///
850    /// let x   = Parameter::symbol("x");
851    /// let x3  = x.pow(Parameter::from(3.0));   // x³
852    /// let val = x3.evaluate(&Some([("x", 2.0)].iter().cloned().collect())).unwrap();
853    /// assert!((val - 8.0).abs() < 1e-10);
854    /// ```
855    pub fn pow(&self, exp: impl Into<Self>) -> Self {
856        Self {
857            expr: self.expr.clone().pow(exp.into()),
858        }
859    }
860
861    /// Substitutes every occurrence of `symbol` in this expression with
862    /// `param`, returning the resulting expression.
863    /// The inserted expression is not visited again; the original is unchanged.
864    ///
865    /// # Examples
866    ///
867    /// ```rust
868    /// use cqlib_core::circuit::Parameter;
869    ///
870    /// let expr     = Parameter::symbol("θ") * Parameter::from(2.0);  // θ * 2
871    /// let replaced = expr.replace("θ", Parameter::pi());      // π * 2
872    /// let val      = replaced.evaluate(&None).unwrap();
873    /// assert!((val - 2.0 * std::f64::consts::PI).abs() < 1e-10);
874    /// ```
875    pub fn replace(&self, symbol: &str, param: impl Into<Self>) -> Self {
876        let p: Self = param.into();
877        Self {
878            expr: substitute_parameter_expr(&self.expr, &|name| {
879                (name == symbol).then_some(&p.expr)
880            }),
881        }
882    }
883
884    /// Returns the symbol name if this parameter is exactly one free symbol.
885    ///
886    /// This is intentionally conservative: expressions containing one symbol
887    /// are accepted only when they are structurally equal to that symbol after
888    /// optional simplification.
889    pub fn as_symbol(&self) -> Option<String> {
890        let symbols = self.get_symbols();
891        if symbols.len() != 1 {
892            return None;
893        }
894
895        let symbol = symbols.into_iter().next()?;
896        let direct = Self::symbol(&symbol);
897        if self == &direct {
898            return Some(symbol);
899        }
900
901        self.simplify()
902            .ok()
903            .filter(|simplified| simplified == &direct)
904            .map(|_| symbol)
905    }
906
907    /// Simultaneously substitutes bound symbols and simplifies when possible.
908    ///
909    /// Replacement expressions are inserted as-is, without applying other
910    /// bindings to them. The result is independent of binding iteration order,
911    /// and the original expression is unchanged.
912    pub fn substitute_many(&self, bindings: &HashMap<String, Parameter>) -> Self {
913        let substituted = self.substitute_many_simultaneous(bindings);
914        substituted.simplify().unwrap_or(substituted)
915    }
916
917    /// Simultaneously substitutes symbols in a single expression-tree pass.
918    ///
919    /// Replacement expressions are inserted as-is and are not visited again,
920    /// so overlapping bindings such as `a -> b` and `b -> c` do not cascade.
921    pub(crate) fn substitute_many_simultaneous(
922        &self,
923        bindings: &HashMap<String, Parameter>,
924    ) -> Self {
925        if bindings.is_empty() {
926            return self.clone();
927        }
928        Self::new(substitute_parameter_expr(&self.expr, &|name| {
929            bindings.get(name).map(|replacement| &replacement.expr)
930        }))
931    }
932
933    /// Returns whether two parameters are provably equal within `tolerance`.
934    ///
935    /// This is a conservative symbolic check.  It accepts structural equality
936    /// after simplification and concrete numeric equality, but it does not try
937    /// to prove arbitrary symbolic identities.
938    pub fn provably_equal(&self, other: &Self, tolerance: f64) -> bool {
939        if self == other {
940            return true;
941        }
942        if let (Ok(lhs), Ok(rhs)) = (self.evaluate(&None), other.evaluate(&None))
943            && (lhs - rhs).abs() <= tolerance
944        {
945            return true;
946        }
947
948        let lhs = self.simplify().unwrap_or_else(|_| self.clone());
949        let rhs = other.simplify().unwrap_or_else(|_| other.clone());
950        if lhs == rhs {
951            return true;
952        }
953
954        match (lhs.evaluate(&None), rhs.evaluate(&None)) {
955            (Ok(lhs), Ok(rhs)) => (lhs - rhs).abs() <= tolerance,
956            _ => false,
957        }
958    }
959
960    /// Returns whether two parameters are provably equal modulo `modulus`.
961    ///
962    /// Fully numeric parameters take a fast path with plain floating-point
963    /// math, without building or simplifying a symbolic difference expression.
964    /// Only expressions with unbound symbols fall back to the symbolic path,
965    /// where the expression difference and modulus must both evaluate without
966    /// extra bindings for the numeric modulo check to apply.
967    pub fn provably_equal_modulo(&self, other: &Self, modulus: &Self, tolerance: f64) -> bool {
968        if let (Some(lhs), Some(rhs)) = (self.numeric_value(), other.numeric_value()) {
969            let diff_value = lhs - rhs;
970            if diff_value.abs() <= tolerance {
971                return true;
972            }
973            let Some(modulus_value) = modulus.numeric_value() else {
974                return false;
975            };
976            if modulus_value.abs() <= tolerance {
977                return false;
978            }
979            let ratio = diff_value / modulus_value;
980            return (ratio - ratio.round()).abs() <= tolerance;
981        }
982
983        let raw_diff = self.clone() - other.clone();
984        let diff = raw_diff.simplify().unwrap_or(raw_diff);
985        if diff.provably_equal(&Self::from(0.0), tolerance) {
986            return true;
987        }
988
989        let Ok(diff_value) = diff.evaluate(&None) else {
990            return false;
991        };
992        let Ok(modulus_value) = modulus.evaluate(&None) else {
993            return false;
994        };
995        if modulus_value.abs() <= tolerance {
996            return false;
997        }
998
999        let ratio = diff_value / modulus_value;
1000        (ratio - ratio.round()).abs() <= tolerance
1001    }
1002
1003    /// Returns `true` if this parameter is a constant (has no free variables).
1004    ///
1005    /// # Examples
1006    ///
1007    /// ```rust
1008    /// use cqlib_core::circuit::Parameter;
1009    ///
1010    /// let constant = Parameter::from(3.14);
1011    /// assert!(constant.is_constant());
1012    ///
1013    /// let symbolic = Parameter::symbol("x");
1014    /// assert!(!symbolic.is_constant());
1015    /// ```
1016    pub fn is_constant(&self) -> bool {
1017        self.get_symbols().is_empty()
1018    }
1019
1020    /// Returns `true` if this parameter evaluates to zero.
1021    ///
1022    /// Returns `false` if the parameter cannot be evaluated (contains unbound symbols).
1023    ///
1024    /// # Examples
1025    ///
1026    /// ```rust
1027    /// use cqlib_core::circuit::Parameter;
1028    ///
1029    /// let zero = Parameter::from(0.0);
1030    /// assert!(zero.is_zero());
1031    ///
1032    /// let non_zero = Parameter::from(1.0);
1033    /// assert!(!non_zero.is_zero());
1034    /// ```
1035    pub fn is_zero(&self) -> bool {
1036        self.evaluate(&None)
1037            .map(|v| v.abs() < f64::EPSILON)
1038            .unwrap_or(false)
1039    }
1040
1041    /// Returns `true` if this parameter evaluates to one.
1042    ///
1043    /// Returns `false` if the parameter cannot be evaluated (contains unbound symbols).
1044    ///
1045    /// # Examples
1046    ///
1047    /// ```rust
1048    /// use cqlib_core::circuit::Parameter;
1049    ///
1050    /// let one = Parameter::from(1.0);
1051    /// assert!(one.is_one());
1052    ///
1053    /// let non_one = Parameter::from(2.0);
1054    /// assert!(!non_one.is_one());
1055    /// ```
1056    pub fn is_one(&self) -> bool {
1057        self.evaluate(&None)
1058            .map(|v| (v - 1.0).abs() < f64::EPSILON)
1059            .unwrap_or(false)
1060    }
1061
1062    /// Returns the hyperbolic sine `sinh(x)`.
1063    pub fn sinh(&self) -> Self {
1064        Self {
1065            expr: self.expr.clone().sinh(),
1066        }
1067    }
1068
1069    /// Returns the hyperbolic cosine `cosh(x)`.
1070    pub fn cosh(&self) -> Self {
1071        Self {
1072            expr: self.expr.clone().cosh(),
1073        }
1074    }
1075
1076    /// Returns the hyperbolic tangent `tanh(x)`.
1077    pub fn tanh(&self) -> Self {
1078        Self {
1079            expr: self.expr.clone().tanh(),
1080        }
1081    }
1082
1083    /// Returns the floor of the expression.
1084    pub fn floor(&self) -> Self {
1085        Self {
1086            expr: self.expr.clone().floor(),
1087        }
1088    }
1089
1090    /// Returns the ceiling of the expression.
1091    pub fn ceil(&self) -> Self {
1092        Self {
1093            expr: self.expr.clone().ceil(),
1094        }
1095    }
1096
1097    /// Returns the rounded value of the expression.
1098    pub fn round(&self) -> Self {
1099        Self {
1100            expr: self.expr.clone().round(),
1101        }
1102    }
1103}
1104
1105impl From<f64> for Parameter {
1106    fn from(value: f64) -> Self {
1107        if !value.is_finite() {
1108            panic!("Parameter numeric literal must be finite, got {value}");
1109        }
1110        Self {
1111            expr: Expr::from(value),
1112        }
1113    }
1114}
1115
1116impl From<f32> for Parameter {
1117    fn from(value: f32) -> Self {
1118        if !value.is_finite() {
1119            panic!("Parameter numeric literal must be finite, got {value}");
1120        }
1121        Self {
1122            expr: Expr::from(f64::from(value)),
1123        }
1124    }
1125}
1126
1127impl From<u32> for Parameter {
1128    fn from(value: u32) -> Self {
1129        Self {
1130            expr: Expr::from(f64::from(value)),
1131        }
1132    }
1133}
1134
1135impl From<i32> for Parameter {
1136    fn from(value: i32) -> Self {
1137        Self {
1138            expr: Expr::from(value),
1139        }
1140    }
1141}
1142
1143/// Implements the four arithmetic operators between `Parameter` and a primitive numeric
1144/// type `$t` in both directions (e.g. `Parameter + f64` and `f64 + Parameter`).
1145///
1146/// The implementation converts `$t` to `Parameter` via `Parameter::from`, extracts the
1147/// underlying `Expr`, and preserves affine coefficients before falling back to
1148/// the corresponding symbolic engine operator.
1149macro_rules! impl_ops_for_type {
1150    ($t:ty) => {
1151        // Parameter + T
1152        impl Add<$t> for Parameter {
1153            type Output = Parameter;
1154            fn add(self, rhs: $t) -> Self::Output {
1155                Self {
1156                    // 1. `Parameter::from(rhs)` converts the primitive to `Parameter`.
1157                    // 2. `.expr` unwraps the underlying `Expr` node.
1158                    // 3. Preserve affine coefficients before general symbolic addition.
1159                    expr: add_parameter_expr(self.expr, Parameter::from(rhs).expr),
1160                }
1161            }
1162        }
1163
1164        // T + Parameter
1165        impl Add<Parameter> for $t {
1166            type Output = Parameter;
1167            fn add(self, rhs: Parameter) -> Self::Output {
1168                Parameter {
1169                    expr: add_parameter_expr(Parameter::from(self).expr, rhs.expr),
1170                }
1171            }
1172        }
1173
1174        // Parameter - T
1175        impl Sub<$t> for Parameter {
1176            type Output = Parameter;
1177            fn sub(self, rhs: $t) -> Self::Output {
1178                Self {
1179                    expr: sub_parameter_expr(self.expr, Parameter::from(rhs).expr),
1180                }
1181            }
1182        }
1183
1184        // T - Parameter
1185        impl Sub<Parameter> for $t {
1186            type Output = Parameter;
1187            fn sub(self, rhs: Parameter) -> Self::Output {
1188                Parameter {
1189                    expr: sub_parameter_expr(Parameter::from(self).expr, rhs.expr),
1190                }
1191            }
1192        }
1193
1194        // Parameter * T
1195        impl Mul<$t> for Parameter {
1196            type Output = Parameter;
1197            fn mul(self, rhs: $t) -> Self::Output {
1198                Self {
1199                    expr: mul_parameter_expr(self.expr, Parameter::from(rhs).expr),
1200                }
1201            }
1202        }
1203
1204        // T * Parameter
1205        impl Mul<Parameter> for $t {
1206            type Output = Parameter;
1207            fn mul(self, rhs: Parameter) -> Self::Output {
1208                Parameter {
1209                    expr: mul_parameter_expr(Parameter::from(self).expr, rhs.expr),
1210                }
1211            }
1212        }
1213
1214        // Parameter / T
1215        impl Div<$t> for Parameter {
1216            type Output = Parameter;
1217            fn div(self, rhs: $t) -> Self::Output {
1218                Self {
1219                    expr: div_parameter_expr(self.expr, Parameter::from(rhs).expr),
1220                }
1221            }
1222        }
1223
1224        // T / Parameter
1225        impl Div<Parameter> for $t {
1226            type Output = Parameter;
1227            fn div(self, rhs: Parameter) -> Self::Output {
1228                Parameter {
1229                    expr: div_parameter_expr(Parameter::from(self).expr, rhs.expr),
1230                }
1231            }
1232        }
1233    };
1234}
1235
1236// Apply the operator impls to all supported primitive types.
1237impl_ops_for_type!(f64);
1238impl_ops_for_type!(f32);
1239impl_ops_for_type!(i32);
1240impl_ops_for_type!(u32);
1241
1242/// Implements all four combinations of owned/borrowed operands for a binary
1243/// operator between two `Parameter` values.
1244///
1245/// For each `(Trait, method)` pair this macro generates:
1246///
1247/// 1. `Parameter  op Parameter`  — both operands consumed.
1248/// 2. `&Parameter op &Parameter` — neither operand consumed (most common in loop contexts).
1249/// 3. `Parameter  op &Parameter` — left consumed, right borrowed.
1250/// 4. `&Parameter op Parameter`  — left borrowed, right consumed.
1251///
1252/// Cloning `Expr` is inexpensive because `symb_anafis` uses reference-counted
1253/// node sharing internally.
1254macro_rules! impl_binary_op_ref {
1255    ($($trait:ident, $method:ident, $expr_method:ident);* $(;)?) => {
1256        $(
1257            // 1. Parameter op Parameter — both sides consumed.
1258            impl $trait<Parameter> for Parameter {
1259                type Output = Parameter;
1260                fn $method(self, rhs: Parameter) -> Self::Output {
1261                    Parameter {
1262                        // Preserve affine coefficients before general symbolic arithmetic.
1263                        expr: $expr_method(self.expr, rhs.expr),
1264                    }
1265                }
1266            }
1267
1268            // 2. &Parameter op &Parameter — no ownership transferred; clone the inner `Expr`.
1269            impl<'a, 'b> $trait<&'b Parameter> for &'a Parameter {
1270                type Output = Parameter;
1271                fn $method(self, rhs: &'b Parameter) -> Self::Output {
1272                    Parameter {
1273                        // `Expr::clone` is cheap — the tree uses Arc-based
1274                        // node sharing inside `symb_anafis`.
1275                        expr: $expr_method(self.expr.clone(), rhs.expr.clone()),
1276                    }
1277                }
1278            }
1279
1280            // 3. Parameter op &Parameter — left consumed, right cloned.
1281            impl<'a> $trait<&'a Parameter> for Parameter {
1282                type Output = Parameter;
1283                fn $method(self, rhs: &'a Parameter) -> Self::Output {
1284                    Parameter {
1285                        expr: $expr_method(self.expr, rhs.expr.clone()),
1286                    }
1287                }
1288            }
1289
1290            // 4. &Parameter op Parameter — left cloned, right consumed.
1291            impl<'a> $trait<Parameter> for &'a Parameter {
1292                type Output = Parameter;
1293                fn $method(self, rhs: Parameter) -> Self::Output {
1294                    Parameter {
1295                        expr: $expr_method(self.expr.clone(), rhs.expr),
1296                    }
1297                }
1298            }
1299        )*
1300    };
1301}
1302
1303impl_binary_op_ref! {
1304    Add, add, add_parameter_expr;
1305    Sub, sub, sub_parameter_expr;
1306    Mul, mul, mul_parameter_expr;
1307    Div, div, div_parameter_expr;
1308}
1309
1310// Implement Neg for owned Parameter
1311impl Neg for Parameter {
1312    type Output = Parameter;
1313    fn neg(self) -> Self::Output {
1314        Parameter {
1315            expr: mul_parameter_expr(Expr::number(-1.0), self.expr),
1316        }
1317    }
1318}
1319
1320// Implement Neg for borrowed Parameter
1321impl Neg for &Parameter {
1322    type Output = Parameter;
1323    fn neg(self) -> Self::Output {
1324        Parameter {
1325            expr: mul_parameter_expr(Expr::number(-1.0), self.expr.clone()),
1326        }
1327    }
1328}
1329
1330#[cfg(test)]
1331#[path = "parameter_test.rs"]
1332mod parameter_test;