use crate::circuit::error::ParameterError;
use core::fmt::Debug;
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::ops::{Add, Div, Mul, Neg, Sub};
use std::str::FromStr;
use symb_anafis::{Expr, Simplify, parse, symb, visitor::ExprView};
fn substitute_parameter_expr<'a>(expr: &Expr, lookup: &impl Fn(&str) -> Option<&'a Expr>) -> Expr {
match expr.view() {
ExprView::Number(_) => expr.clone(),
ExprView::Symbol(name) => lookup(&name).cloned().unwrap_or_else(|| expr.clone()),
ExprView::Sum(children) | ExprView::Product(children) => {
let mapped: Vec<_> = children
.iter()
.map(|child| substitute_parameter_expr(child, lookup))
.collect();
if children.iter().zip(&mapped).all(|(a, b)| a.as_ref() == b) {
return expr.clone();
}
let slots = (0..mapped.len())
.map(|i| Expr::func("__cqlib_substitution_slot", Expr::number(i as f64)))
.collect();
let template = if matches!(expr.view(), ExprView::Sum(_)) {
Expr::sum(slots)
} else {
Expr::product(slots)
};
let slots = match template.view() {
ExprView::Sum(slots) | ExprView::Product(slots) => slots.into_owned(),
_ => return mapped.into_iter().next().unwrap_or(template),
};
let replacements: HashMap<_, _> = slots
.into_iter()
.map(|slot| (*slot).clone())
.zip(mapped)
.collect();
template.map(|node| {
replacements
.get(node)
.cloned()
.unwrap_or_else(|| node.clone())
})
}
ExprView::Div(lhs, rhs) => {
let a = substitute_parameter_expr(lhs, lookup);
let b = substitute_parameter_expr(rhs, lookup);
if &a == lhs && &b == rhs {
expr.clone()
} else {
div_parameter_expr(a, b)
}
}
ExprView::Pow(base, exponent) => {
let a = substitute_parameter_expr(base, lookup);
let b = substitute_parameter_expr(exponent, lookup);
if &a == base && &b == exponent {
expr.clone()
} else {
Expr::pow_static(a, b)
}
}
ExprView::Function { name, args } => {
let mapped: Vec<_> = args
.iter()
.map(|arg| substitute_parameter_expr(arg, lookup))
.collect();
if args.iter().zip(&mapped).all(|(a, b)| a.as_ref() == b) {
expr.clone()
} else {
Expr::func_multi(name, mapped)
}
}
ExprView::Derivative { inner, var, order } => {
let mapped = substitute_parameter_expr(inner, lookup);
if &mapped == inner {
expr.clone()
} else {
Expr::derivative(mapped, var, order)
}
}
}
}
fn needs_affine_protection(expr: &Expr) -> bool {
match expr.view() {
ExprView::Number(value) => value.fract() != 0.0 || value.abs() > (1u64 << 53) as f64,
ExprView::Sum(children) | ExprView::Product(children) => {
children.iter().any(|child| needs_affine_protection(child))
}
ExprView::Div(numerator, denominator) => {
needs_affine_protection(numerator)
|| denominator.as_number().is_some_and(|value| {
value != 0.0 && needs_affine_protection(&Expr::number(1.0 / value))
})
}
_ => false,
}
}
type AffineCoefficients = HashMap<Option<Expr>, f64>;
fn affine_coefficients(inputs: &[(&Expr, f64)]) -> Option<AffineCoefficients> {
fn collect(expr: &Expr, scale: f64, terms: &mut AffineCoefficients) -> Option<()> {
match expr.view() {
ExprView::Number(value) => *terms.entry(None).or_default() += scale * value,
ExprView::Symbol(_) => *terms.entry(Some(expr.clone())).or_default() += scale,
ExprView::Sum(children) => {
for child in children.iter() {
collect(child, scale, terms)?;
}
}
ExprView::Product(children) => {
let mut scale = scale;
let mut symbolic = None;
for child in children.iter() {
if let Some(value) = child.as_number() {
scale *= value;
} else if symbolic.replace(child).is_some() {
return None;
}
}
if let Some(child) = symbolic {
collect(child, scale, terms)?;
} else {
*terms.entry(None).or_default() += scale;
}
}
ExprView::Div(numerator, denominator) => {
let divisor = denominator.as_number()?;
if divisor == 0.0 {
return None;
}
collect(numerator, scale / divisor, terms)?;
}
_ => return None,
}
Some(())
}
let mut terms = HashMap::new();
for (expr, scale) in inputs {
collect(expr, *scale, &mut terms)?;
}
if terms.values().any(|coefficient| !coefficient.is_finite()) {
return None;
}
terms.retain(|_, coefficient| *coefficient != 0.0);
Some(terms)
}
fn weighted_symbol(symbol: Expr, coefficient: f64) -> Expr {
if coefficient == 0.0 {
return Expr::number(0.0);
}
if coefficient == 1.0 {
return symbol;
}
let product = Expr::product(vec![Expr::number(coefficient), symbol.clone()]);
if let ExprView::Product(factors) = product.view()
&& factors
.iter()
.any(|factor| factor.as_number() == Some(coefficient))
{
return product;
}
Expr::product(vec![Expr::number(2.0), symbol]).map(|factor| {
if factor.as_number() == Some(2.0) {
Expr::number(coefficient)
} else {
factor.clone()
}
})
}
fn affine_expr(terms: &AffineCoefficients) -> Expr {
let unit_sum = Expr::sum(
terms
.keys()
.map(|symbol| symbol.clone().unwrap_or_else(|| Expr::number(1.0)))
.collect(),
);
unit_sum.map(|node| {
if node.as_number() == Some(1.0) {
Expr::number(terms.get(&None).copied().unwrap_or(1.0))
} else if matches!(node.view(), ExprView::Symbol(_)) {
weighted_symbol(
node.clone(),
terms.get(&Some(node.clone())).copied().unwrap_or(1.0),
)
} else {
node.clone()
}
})
}
fn preserve_affine(expected: Option<AffineCoefficients>, fallback: Expr) -> Expr {
match expected {
Some(expected) if expected.keys().all(Option::is_none) => {
Expr::number(expected.get(&None).copied().unwrap_or(0.0))
}
Some(expected) if affine_coefficients(&[(&fallback, 1.0)]).as_ref() != Some(&expected) => {
affine_expr(&expected)
}
_ => fallback,
}
}
fn add_parameter_expr(lhs: Expr, rhs: Expr) -> Expr {
if let (Some(a), Some(b)) = (lhs.as_number(), rhs.as_number()) {
return Expr::number(a + b);
}
if !needs_affine_protection(&lhs) && !needs_affine_protection(&rhs) {
return lhs + rhs;
}
let affine = affine_coefficients(&[(&lhs, 1.0), (&rhs, 1.0)]);
preserve_affine(affine, lhs + rhs)
}
fn sub_parameter_expr(lhs: Expr, rhs: Expr) -> Expr {
if let (Some(a), Some(b)) = (lhs.as_number(), rhs.as_number()) {
return Expr::number(a - b);
}
if !needs_affine_protection(&lhs) && !needs_affine_protection(&rhs) {
return lhs - rhs;
}
let affine = affine_coefficients(&[(&lhs, 1.0), (&rhs, -1.0)]);
preserve_affine(affine, lhs - rhs)
}
fn scaled_symbol(expr: &Expr, scale: f64) -> Option<Expr> {
let (symbol, coefficient) = match expr.view() {
ExprView::Symbol(_) => (expr.clone(), scale),
ExprView::Product(children) if children.len() == 2 => {
let (number, symbol) = if let Some(number) = children[0].as_number() {
(number, &children[1])
} else {
(children[1].as_number()?, &children[0])
};
if !matches!(symbol.view(), ExprView::Symbol(_)) {
return None;
}
((**symbol).clone(), number * scale)
}
_ => return None,
};
coefficient
.is_finite()
.then(|| weighted_symbol(symbol, coefficient))
}
fn mul_parameter_expr(lhs: Expr, rhs: Expr) -> Expr {
if let (Some(a), Some(b)) = (lhs.as_number(), rhs.as_number()) {
return Expr::number(a * b);
}
let affine = if let Some(scale) = lhs.as_number() {
if let Some(expr) = scaled_symbol(&rhs, scale) {
return expr;
}
if !needs_affine_protection(&lhs) && !needs_affine_protection(&rhs) {
return lhs * rhs;
}
affine_coefficients(&[(&rhs, scale)])
} else if let Some(scale) = rhs.as_number() {
if let Some(expr) = scaled_symbol(&lhs, scale) {
return expr;
}
if !needs_affine_protection(&lhs) && !needs_affine_protection(&rhs) {
return lhs * rhs;
}
affine_coefficients(&[(&lhs, scale)])
} else {
None
};
preserve_affine(affine, lhs * rhs)
}
fn div_parameter_expr(lhs: Expr, rhs: Expr) -> Expr {
if let (Some(a), Some(b)) = (lhs.as_number(), rhs.as_number())
&& b != 0.0
{
return Expr::number(a / b);
}
let affine = if let Some(divisor) = rhs.as_number().filter(|value| *value != 0.0) {
affine_coefficients(&[(&lhs, 1.0)]).and_then(|mut terms| {
for coefficient in terms.values_mut() {
*coefficient /= divisor;
}
if terms.values().any(|coefficient| !coefficient.is_finite()) {
return None;
}
terms.retain(|_, coefficient| *coefficient != 0.0);
Some(terms)
})
} else {
None
};
preserve_affine(affine, lhs / rhs)
}
#[derive(Clone, Debug)]
pub struct Parameter {
expr: Expr,
}
impl From<Expr> for Parameter {
fn from(expr: Expr) -> Self {
Parameter { expr }
}
}
impl From<Parameter> for Expr {
fn from(p: Parameter) -> Self {
p.expr
}
}
impl fmt::Display for Parameter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.expr, f)
}
}
impl Default for Parameter {
fn default() -> Self {
Self {
expr: Expr::from(0),
}
}
}
impl std::hash::Hash for Parameter {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.expr.hash(state);
}
}
impl PartialEq for Parameter {
fn eq(&self, other: &Self) -> bool {
self.expr == other.expr
}
}
impl Eq for Parameter {}
impl TryFrom<&str> for Parameter {
type Error = ParameterError;
fn try_from(expr: &str) -> Result<Self, Self::Error> {
if let Ok(expr) = parse(expr, &HashSet::new(), &HashSet::new(), None) {
Ok(Self { expr })
} else {
Err(ParameterError::ParseError(expr.to_string()))
}
}
}
impl FromStr for Parameter {
type Err = ParameterError;
fn from_str(expr: &str) -> Result<Self, Self::Err> {
Self::try_from(expr)
}
}
impl Parameter {
pub fn new(expr: Expr) -> Self {
Self { expr }
}
pub fn as_expr(&self) -> &Expr {
&self.expr
}
pub fn into_expr(self) -> Expr {
self.expr
}
pub fn symbol(name: &str) -> Self {
Self::new(Expr::from(symb(name)))
}
pub fn evaluate(&self, bindings: &Option<HashMap<&str, f64>>) -> Result<f64, ParameterError> {
let mut evaluated = match bindings {
Some(bindings) => self.expr.evaluate(bindings, &HashMap::new()),
None => self.expr.evaluate(&(), &HashMap::new()),
};
if evaluated.as_number().is_none() {
let mut actual_bindings =
HashMap::with_capacity(bindings.as_ref().map_or(2, |bindings| bindings.len() + 2));
actual_bindings.insert("π", std::f64::consts::PI);
actual_bindings.insert("e", std::f64::consts::E);
if let Some(bindings) = bindings {
actual_bindings.extend(bindings);
}
evaluated = self.expr.evaluate(&actual_bindings, &HashMap::new());
}
if let Some(n) = evaluated.as_number() {
if n.is_nan() {
return Err(ParameterError::DomainError(format!(
"Evaluation of '{}' resulted in NaN",
self.expr
)));
}
if n.is_infinite() {
return Err(ParameterError::DomainError(format!(
"Evaluation of '{}' resulted in infinity",
self.expr
)));
}
Ok(n)
} else {
Err(ParameterError::NaN(self.expr.to_string()))
}
}
pub fn simplify(&self) -> Result<Self, ParameterError> {
if self.expr.as_number().is_some() {
return Ok(self.clone());
}
let affine = needs_affine_protection(&self.expr)
.then(|| affine_coefficients(&[(&self.expr, 1.0)]))
.flatten();
let expr = Simplify::new().domain_safe(true).simplify(&self.expr)?;
Ok(Self {
expr: preserve_affine(affine, expr),
})
}
fn numeric_value(&self) -> Option<f64> {
self.expr
.as_number()
.filter(|n| n.is_finite())
.or_else(|| self.evaluate(&None).ok())
}
pub fn canonicalized(&self) -> Result<Self, ParameterError> {
if self.get_symbols().is_empty() {
let value = self.evaluate(&None)?;
return Ok(Parameter::from(if value == 0.0 { 0.0 } else { value }));
}
let simplified = self.simplify()?;
if simplified.get_symbols().is_empty() {
let value = simplified.evaluate(&None)?;
if !value.is_finite() {
return Err(ParameterError::DomainError(format!(
"constant parameter evaluates to non-finite value {value}"
)));
}
let value = if value == 0.0 { 0.0 } else { value };
Ok(Parameter::from(value))
} else {
Ok(simplified)
}
}
pub fn is_exact_zero(&self) -> Result<bool, ParameterError> {
if self.get_symbols().is_empty() {
Ok(self.evaluate(&None)? == 0.0)
} else {
Ok(false)
}
}
pub(crate) fn is_canonical_positive_zero(&self) -> bool {
self.expr
.as_number()
.is_some_and(|value| value.to_bits() == 0.0_f64.to_bits())
}
pub fn derivative(&self, var: &str) -> Result<Self, ParameterError> {
let expr = self.expr.diff(var)?;
Ok(Self { expr })
}
pub fn get_symbols(&self) -> HashSet<String> {
let mut symbols = self.expr.variables();
symbols.remove("π");
symbols.remove("pi");
symbols.remove("e");
symbols
}
pub fn abs(&self) -> Self {
Self {
expr: self.expr.clone().abs(),
}
}
pub fn sqrt(&self) -> Self {
Self {
expr: self.expr.clone().sqrt(),
}
}
pub fn exp(&self) -> Self {
Self {
expr: self.expr.clone().exp(),
}
}
pub fn sin(&self) -> Self {
Self {
expr: self.expr.clone().sin(),
}
}
pub fn asin(&self) -> Self {
Self {
expr: self.expr.clone().asin(),
}
}
pub fn cos(&self) -> Self {
Self {
expr: self.expr.clone().cos(),
}
}
pub fn acos(&self) -> Self {
Self {
expr: self.expr.clone().acos(),
}
}
pub fn tan(&self) -> Self {
Self {
expr: self.expr.clone().tan(),
}
}
pub fn atan(&self) -> Self {
Self {
expr: self.expr.clone().atan(),
}
}
pub fn ln(&self) -> Self {
Self {
expr: self.expr.clone().ln(),
}
}
pub fn log(&self, base: impl Into<Self>) -> Self {
Self {
expr: self.expr.clone().log(base.into()),
}
}
pub fn pi() -> Self {
Self::symbol("π")
}
pub fn e() -> Self {
Self::symbol("e")
}
pub fn pow(&self, exp: impl Into<Self>) -> Self {
Self {
expr: self.expr.clone().pow(exp.into()),
}
}
pub fn replace(&self, symbol: &str, param: impl Into<Self>) -> Self {
let p: Self = param.into();
Self {
expr: substitute_parameter_expr(&self.expr, &|name| {
(name == symbol).then_some(&p.expr)
}),
}
}
pub fn as_symbol(&self) -> Option<String> {
let symbols = self.get_symbols();
if symbols.len() != 1 {
return None;
}
let symbol = symbols.into_iter().next()?;
let direct = Self::symbol(&symbol);
if self == &direct {
return Some(symbol);
}
self.simplify()
.ok()
.filter(|simplified| simplified == &direct)
.map(|_| symbol)
}
pub fn substitute_many(&self, bindings: &HashMap<String, Parameter>) -> Self {
let substituted = self.substitute_many_simultaneous(bindings);
substituted.simplify().unwrap_or(substituted)
}
pub(crate) fn substitute_many_simultaneous(
&self,
bindings: &HashMap<String, Parameter>,
) -> Self {
if bindings.is_empty() {
return self.clone();
}
Self::new(substitute_parameter_expr(&self.expr, &|name| {
bindings.get(name).map(|replacement| &replacement.expr)
}))
}
pub fn provably_equal(&self, other: &Self, tolerance: f64) -> bool {
if self == other {
return true;
}
if let (Ok(lhs), Ok(rhs)) = (self.evaluate(&None), other.evaluate(&None))
&& (lhs - rhs).abs() <= tolerance
{
return true;
}
let lhs = self.simplify().unwrap_or_else(|_| self.clone());
let rhs = other.simplify().unwrap_or_else(|_| other.clone());
if lhs == rhs {
return true;
}
match (lhs.evaluate(&None), rhs.evaluate(&None)) {
(Ok(lhs), Ok(rhs)) => (lhs - rhs).abs() <= tolerance,
_ => false,
}
}
pub fn provably_equal_modulo(&self, other: &Self, modulus: &Self, tolerance: f64) -> bool {
if let (Some(lhs), Some(rhs)) = (self.numeric_value(), other.numeric_value()) {
let diff_value = lhs - rhs;
if diff_value.abs() <= tolerance {
return true;
}
let Some(modulus_value) = modulus.numeric_value() else {
return false;
};
if modulus_value.abs() <= tolerance {
return false;
}
let ratio = diff_value / modulus_value;
return (ratio - ratio.round()).abs() <= tolerance;
}
let raw_diff = self.clone() - other.clone();
let diff = raw_diff.simplify().unwrap_or(raw_diff);
if diff.provably_equal(&Self::from(0.0), tolerance) {
return true;
}
let Ok(diff_value) = diff.evaluate(&None) else {
return false;
};
let Ok(modulus_value) = modulus.evaluate(&None) else {
return false;
};
if modulus_value.abs() <= tolerance {
return false;
}
let ratio = diff_value / modulus_value;
(ratio - ratio.round()).abs() <= tolerance
}
pub fn is_constant(&self) -> bool {
self.get_symbols().is_empty()
}
pub fn is_zero(&self) -> bool {
self.evaluate(&None)
.map(|v| v.abs() < f64::EPSILON)
.unwrap_or(false)
}
pub fn is_one(&self) -> bool {
self.evaluate(&None)
.map(|v| (v - 1.0).abs() < f64::EPSILON)
.unwrap_or(false)
}
pub fn sinh(&self) -> Self {
Self {
expr: self.expr.clone().sinh(),
}
}
pub fn cosh(&self) -> Self {
Self {
expr: self.expr.clone().cosh(),
}
}
pub fn tanh(&self) -> Self {
Self {
expr: self.expr.clone().tanh(),
}
}
pub fn floor(&self) -> Self {
Self {
expr: self.expr.clone().floor(),
}
}
pub fn ceil(&self) -> Self {
Self {
expr: self.expr.clone().ceil(),
}
}
pub fn round(&self) -> Self {
Self {
expr: self.expr.clone().round(),
}
}
}
impl From<f64> for Parameter {
fn from(value: f64) -> Self {
if !value.is_finite() {
panic!("Parameter numeric literal must be finite, got {value}");
}
Self {
expr: Expr::from(value),
}
}
}
impl From<f32> for Parameter {
fn from(value: f32) -> Self {
if !value.is_finite() {
panic!("Parameter numeric literal must be finite, got {value}");
}
Self {
expr: Expr::from(f64::from(value)),
}
}
}
impl From<u32> for Parameter {
fn from(value: u32) -> Self {
Self {
expr: Expr::from(f64::from(value)),
}
}
}
impl From<i32> for Parameter {
fn from(value: i32) -> Self {
Self {
expr: Expr::from(value),
}
}
}
macro_rules! impl_ops_for_type {
($t:ty) => {
impl Add<$t> for Parameter {
type Output = Parameter;
fn add(self, rhs: $t) -> Self::Output {
Self {
expr: add_parameter_expr(self.expr, Parameter::from(rhs).expr),
}
}
}
impl Add<Parameter> for $t {
type Output = Parameter;
fn add(self, rhs: Parameter) -> Self::Output {
Parameter {
expr: add_parameter_expr(Parameter::from(self).expr, rhs.expr),
}
}
}
impl Sub<$t> for Parameter {
type Output = Parameter;
fn sub(self, rhs: $t) -> Self::Output {
Self {
expr: sub_parameter_expr(self.expr, Parameter::from(rhs).expr),
}
}
}
impl Sub<Parameter> for $t {
type Output = Parameter;
fn sub(self, rhs: Parameter) -> Self::Output {
Parameter {
expr: sub_parameter_expr(Parameter::from(self).expr, rhs.expr),
}
}
}
impl Mul<$t> for Parameter {
type Output = Parameter;
fn mul(self, rhs: $t) -> Self::Output {
Self {
expr: mul_parameter_expr(self.expr, Parameter::from(rhs).expr),
}
}
}
impl Mul<Parameter> for $t {
type Output = Parameter;
fn mul(self, rhs: Parameter) -> Self::Output {
Parameter {
expr: mul_parameter_expr(Parameter::from(self).expr, rhs.expr),
}
}
}
impl Div<$t> for Parameter {
type Output = Parameter;
fn div(self, rhs: $t) -> Self::Output {
Self {
expr: div_parameter_expr(self.expr, Parameter::from(rhs).expr),
}
}
}
impl Div<Parameter> for $t {
type Output = Parameter;
fn div(self, rhs: Parameter) -> Self::Output {
Parameter {
expr: div_parameter_expr(Parameter::from(self).expr, rhs.expr),
}
}
}
};
}
impl_ops_for_type!(f64);
impl_ops_for_type!(f32);
impl_ops_for_type!(i32);
impl_ops_for_type!(u32);
macro_rules! impl_binary_op_ref {
($($trait:ident, $method:ident, $expr_method:ident);* $(;)?) => {
$(
impl $trait<Parameter> for Parameter {
type Output = Parameter;
fn $method(self, rhs: Parameter) -> Self::Output {
Parameter {
expr: $expr_method(self.expr, rhs.expr),
}
}
}
impl<'a, 'b> $trait<&'b Parameter> for &'a Parameter {
type Output = Parameter;
fn $method(self, rhs: &'b Parameter) -> Self::Output {
Parameter {
expr: $expr_method(self.expr.clone(), rhs.expr.clone()),
}
}
}
impl<'a> $trait<&'a Parameter> for Parameter {
type Output = Parameter;
fn $method(self, rhs: &'a Parameter) -> Self::Output {
Parameter {
expr: $expr_method(self.expr, rhs.expr.clone()),
}
}
}
impl<'a> $trait<Parameter> for &'a Parameter {
type Output = Parameter;
fn $method(self, rhs: Parameter) -> Self::Output {
Parameter {
expr: $expr_method(self.expr.clone(), rhs.expr),
}
}
}
)*
};
}
impl_binary_op_ref! {
Add, add, add_parameter_expr;
Sub, sub, sub_parameter_expr;
Mul, mul, mul_parameter_expr;
Div, div, div_parameter_expr;
}
impl Neg for Parameter {
type Output = Parameter;
fn neg(self) -> Self::Output {
Parameter {
expr: mul_parameter_expr(Expr::number(-1.0), self.expr),
}
}
}
impl Neg for &Parameter {
type Output = Parameter;
fn neg(self) -> Self::Output {
Parameter {
expr: mul_parameter_expr(Expr::number(-1.0), self.expr.clone()),
}
}
}
#[cfg(test)]
#[path = "parameter_test.rs"]
mod parameter_test;