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};
#[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> {
let expr = Simplify::new().domain_safe(true).simplify(&self.expr)?;
Ok(Self { 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> {
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: self.expr.substitute(symbol, &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 mut substituted = self.clone();
for (symbol, value) in bindings {
substituted = substituted.replace(symbol, value.clone());
}
substituted.simplify().unwrap_or(substituted)
}
pub(crate) fn substitute_many_simultaneous(
&self,
bindings: &HashMap<String, Parameter>,
) -> Self {
if bindings.is_empty() {
return self.clone();
}
let replacements = bindings
.iter()
.map(|(symbol, replacement)| (Self::symbol(symbol).expr, &replacement.expr))
.collect::<Vec<_>>();
Self::new(self.expr.map(|node| {
replacements
.iter()
.find(|(symbol, _)| node == symbol)
.map_or_else(|| node.clone(), |(_, replacement)| (*replacement).clone())
}))
}
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: self.expr + Parameter::from(rhs).expr,
}
}
}
impl Add<Parameter> for $t {
type Output = Parameter;
fn add(self, rhs: Parameter) -> Self::Output {
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: self.expr - Parameter::from(rhs).expr,
}
}
}
impl Sub<Parameter> for $t {
type Output = Parameter;
fn sub(self, rhs: Parameter) -> Self::Output {
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: self.expr * Parameter::from(rhs).expr,
}
}
}
impl Mul<Parameter> for $t {
type Output = Parameter;
fn mul(self, rhs: Parameter) -> Self::Output {
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: self.expr / Parameter::from(rhs).expr,
}
}
}
impl Div<Parameter> for $t {
type Output = Parameter;
fn div(self, rhs: Parameter) -> Self::Output {
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),* $(,)?) => {
$(
impl $trait<Parameter> for Parameter {
type Output = Parameter;
fn $method(self, rhs: Parameter) -> Self::Output {
Parameter {
expr: self.expr.$method(rhs.expr),
}
}
}
impl<'a, 'b> $trait<&'b Parameter> for &'a Parameter {
type Output = Parameter;
fn $method(self, rhs: &'b Parameter) -> Self::Output {
Parameter {
expr: self.expr.clone().$method(rhs.expr.clone()),
}
}
}
impl<'a> $trait<&'a Parameter> for Parameter {
type Output = Parameter;
fn $method(self, rhs: &'a Parameter) -> Self::Output {
Parameter {
expr: self.expr.$method(rhs.expr.clone()),
}
}
}
impl<'a> $trait<Parameter> for &'a Parameter {
type Output = Parameter;
fn $method(self, rhs: Parameter) -> Self::Output {
Parameter {
expr: self.expr.clone().$method(rhs.expr),
}
}
}
)*
};
}
impl_binary_op_ref! {
Add, add,
Sub, sub,
Mul, mul,
Div, div,
}
impl Neg for Parameter {
type Output = Parameter;
fn neg(self) -> Self::Output {
Parameter {
expr: self.expr.neg(),
}
}
}
impl Neg for &Parameter {
type Output = Parameter;
fn neg(self) -> Self::Output {
Parameter {
expr: self.expr.clone().neg(),
}
}
}
#[cfg(test)]
#[path = "parameter_test.rs"]
mod parameter_test;