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;