use std::sync::Arc;
use num_bigint::BigInt;
use num_traits::One;
use num_traits::Zero;
use crate::symbolic::calculus::definite_integrate;
use crate::symbolic::calculus::differentiate;
use crate::symbolic::calculus::path_integrate;
use crate::symbolic::core::Expr;
use crate::symbolic::simplify::is_zero;
use crate::symbolic::simplify_dag::simplify;
use crate::symbolic::solve::solve;
pub(crate) fn i_complex() -> Expr {
Expr::new_complex(Expr::BigInt(BigInt::zero()), Expr::BigInt(BigInt::one()))
}
#[must_use]
pub fn fourier_time_shift(
f_omega: &Expr,
a: &Expr,
out_var: &str,
) -> Expr {
simplify(&Expr::new_mul(
Expr::new_exp(Expr::new_mul(
Expr::new_mul(i_complex(), Expr::Variable(out_var.to_string())),
Expr::new_neg(a.clone()),
)),
f_omega.clone(),
))
}
#[must_use]
pub fn fourier_frequency_shift(
f_omega: &Expr,
a: &Expr,
out_var: &str,
) -> Expr {
simplify(&Expr::Substitute(
Arc::new(f_omega.clone()),
out_var.to_string(),
Arc::new(Expr::new_sub(
Expr::Variable(out_var.to_string()),
a.clone(),
)),
))
}
#[must_use]
pub fn fourier_scaling(
f_omega: &Expr,
a: &Expr,
out_var: &str,
) -> Expr {
simplify(&Expr::new_mul(
Expr::new_div(Expr::BigInt(BigInt::one()), Expr::new_abs(a.clone())),
Expr::Substitute(
Arc::new(f_omega.clone()),
out_var.to_string(),
Arc::new(Expr::new_div(
Expr::Variable(out_var.to_string()),
a.clone(),
)),
),
))
}
#[must_use]
pub fn fourier_differentiation(
f_omega: &Expr,
out_var: &str,
) -> Expr {
simplify(&Expr::new_mul(
Expr::new_mul(i_complex(), Expr::Variable(out_var.to_string())),
f_omega.clone(),
))
}
#[must_use]
pub fn laplace_time_shift(
f_s: &Expr,
a: &Expr,
out_var: &str,
) -> Expr {
simplify(&Expr::new_mul(
Expr::new_exp(Expr::new_mul(
Expr::new_neg(a.clone()),
Expr::Variable(out_var.to_string()),
)),
f_s.clone(),
))
}
#[must_use]
pub fn laplace_differentiation(
f_s: &Expr,
out_var: &str,
f_zero: &Expr,
) -> Expr {
simplify(&Expr::new_sub(
Expr::new_mul(Expr::Variable(out_var.to_string()), f_s.clone()),
f_zero.clone(),
))
}
#[must_use]
pub fn laplace_frequency_shift(
f_s: &Expr,
a: &Expr,
out_var: &str,
) -> Expr {
simplify(&Expr::Substitute(
Arc::new(f_s.clone()),
out_var.to_string(),
Arc::new(Expr::new_sub(
Expr::Variable(out_var.to_string()),
a.clone(),
)),
))
}
#[must_use]
pub fn laplace_scaling(
f_s: &Expr,
a: &Expr,
out_var: &str,
) -> Expr {
simplify(&Expr::new_mul(
Expr::new_div(Expr::BigInt(BigInt::one()), a.clone()),
Expr::Substitute(
Arc::new(f_s.clone()),
out_var.to_string(),
Arc::new(Expr::new_div(
Expr::Variable(out_var.to_string()),
a.clone(),
)),
),
))
}
#[must_use]
pub fn laplace_integration(
f_s: &Expr,
out_var: &str,
) -> Expr {
simplify(&Expr::new_div(
f_s.clone(),
Expr::Variable(out_var.to_string()),
))
}
#[must_use]
pub fn z_time_shift(
f_z: &Expr,
k: &Expr,
out_var: &str,
) -> Expr {
simplify(&Expr::new_mul(
Expr::new_pow(
Expr::Variable(out_var.to_string()),
Expr::new_neg(k.clone()),
),
f_z.clone(),
))
}
#[must_use]
pub fn z_scaling(
f_z: &Expr,
a: &Expr,
out_var: &str,
) -> Expr {
simplify(&Expr::Substitute(
Arc::new(f_z.clone()),
out_var.to_string(),
Arc::new(Expr::new_div(
Expr::Variable(out_var.to_string()),
a.clone(),
)),
))
}
#[must_use]
pub fn z_differentiation(
f_z: &Expr,
out_var: &str,
) -> Expr {
simplify(&Expr::new_mul(
Expr::new_neg(Expr::Variable(out_var.to_string())),
differentiate(f_z, out_var),
))
}
#[must_use]
pub fn fourier_transform(
expr: &Expr,
in_var: &str,
out_var: &str,
) -> Expr {
let integrand = Expr::new_mul(
expr.clone(),
Expr::new_exp(Expr::new_neg(Expr::new_mul(
i_complex(),
Expr::new_mul(
Expr::Variable(out_var.to_string()),
Expr::Variable(in_var.to_string()),
),
))),
);
definite_integrate(&integrand, in_var, &Expr::NegativeInfinity, &Expr::Infinity)
}
#[must_use]
pub fn inverse_fourier_transform(
expr: &Expr,
in_var: &str,
out_var: &str,
) -> Expr {
let factor = Expr::new_div(
Expr::BigInt(BigInt::one()),
Expr::new_mul(
Expr::BigInt(BigInt::from(2)),
Expr::Variable("pi".to_string()),
),
);
let integrand = Expr::new_mul(
expr.clone(),
Expr::new_exp(Expr::new_mul(
i_complex(),
Expr::new_mul(
Expr::Variable(in_var.to_string()),
Expr::Variable(out_var.to_string()),
),
)),
);
let integral = definite_integrate(&integrand, in_var, &Expr::NegativeInfinity, &Expr::Infinity);
simplify(&Expr::new_mul(factor, integral))
}
#[must_use]
pub fn laplace_transform(
expr: &Expr,
in_var: &str,
out_var: &str,
) -> Expr {
let integrand = Expr::new_mul(
expr.clone(),
Expr::new_exp(Expr::new_neg(Expr::new_mul(
Expr::Variable(out_var.to_string()),
Expr::Variable(in_var.to_string()),
))),
);
definite_integrate(
&integrand,
in_var,
&Expr::BigInt(BigInt::zero()),
&Expr::Infinity,
)
}
#[must_use]
pub fn inverse_laplace_transform(
expr: &Expr,
in_var: &str,
out_var: &str,
) -> Expr {
if let Some(result) = lookup_inverse_laplace(expr, in_var, out_var) {
return result;
}
if let Some(terms) = partial_fraction_decomposition(expr, in_var) {
let mut result_expr = Expr::BigInt(BigInt::zero());
for term in terms {
result_expr = simplify(&Expr::new_add(
result_expr,
inverse_laplace_transform(&term, in_var, out_var),
));
}
return result_expr;
}
let c = Expr::Variable("c".to_string());
let integrand = Expr::new_mul(
expr.clone(),
Expr::new_exp(Expr::new_mul(
Expr::Variable(in_var.to_string()),
Expr::Variable(out_var.to_string()),
)),
);
let factor = Expr::new_div(
Expr::BigInt(BigInt::one()),
Expr::new_mul(
Expr::new_mul(
Expr::BigInt(BigInt::from(2)),
Expr::Variable("pi".to_string()),
),
i_complex(),
),
);
let integral = path_integrate(
&integrand,
in_var,
&Expr::Path(
crate::symbolic::core::PathType::Line,
Arc::new(Expr::new_sub(c.clone(), Expr::Infinity)),
Arc::new(Expr::new_add(c, Expr::Infinity)),
),
);
simplify(&Expr::new_mul(factor, integral))
}
#[must_use]
pub fn z_transform(
expr: &Expr,
in_var: &str,
out_var: &str,
) -> Expr {
let term = Expr::new_mul(
expr.clone(),
Expr::new_pow(
Expr::Variable(out_var.to_string()),
Expr::new_neg(Expr::Variable(in_var.to_string())),
),
);
simplify(&Expr::Summation(
Arc::new(term),
in_var.to_string(),
Arc::new(Expr::NegativeInfinity),
Arc::new(Expr::Infinity),
))
}
#[must_use]
pub fn inverse_z_transform(
expr: &Expr,
in_var: &str,
out_var: &str,
) -> Expr {
let factor = Expr::new_div(
Expr::BigInt(BigInt::one()),
Expr::new_mul(
Expr::new_mul(
Expr::BigInt(BigInt::from(2)),
Expr::Variable("pi".to_string()),
),
i_complex(),
),
);
let integrand = Expr::new_mul(
expr.clone(),
Expr::new_pow(
Expr::Variable(in_var.to_string()),
Expr::new_sub(
Expr::Variable(out_var.to_string()),
Expr::BigInt(BigInt::one()),
),
),
);
let integral = path_integrate(
&integrand,
in_var,
&Expr::Path(
crate::symbolic::core::PathType::Circle,
Arc::new(Expr::BigInt(BigInt::zero())),
Arc::new(Expr::Variable("R".to_string())),
),
);
simplify(&Expr::new_mul(factor, integral))
}
#[must_use]
pub fn partial_fraction_decomposition(
expr: &Expr,
var: &str,
) -> Option<Vec<Expr>> {
if let Expr::Dag(node) = expr {
return node
.to_expr()
.ok()
.and_then(|e| partial_fraction_decomposition(&e, var));
}
if let Expr::Div(num, den) = expr {
let roots: Vec<Expr> = solve(den, var).into_iter().map(|r| simplify(&r)).collect();
if roots.is_empty() || roots.iter().any(|r| matches!(r, Expr::Solve(_, _))) {
return None;
}
let mut terms = Vec::new();
let mut temp_den = den.clone();
for root in roots {
let factor = Expr::new_sub(Expr::Variable(var.to_string()), root.clone());
let mut multiplicity = 0;
while is_zero(&simplify(&crate::symbolic::calculus::evaluate_at_point(
&temp_den, var, &root,
))) {
multiplicity += 1;
let (quotient, _) =
crate::symbolic::polynomial::polynomial_long_division(&temp_den, &factor, var);
temp_den = Arc::new(quotient);
}
for k in 1..=multiplicity {
let mut g = simplify(&Expr::new_div(num.clone(), temp_den.as_ref().clone()));
for _ in 0..(multiplicity - k) {
g = differentiate(&g, var);
}
let c = simplify(&Expr::new_div(
crate::symbolic::calculus::evaluate_at_point(&g, var, &root),
Expr::Constant(crate::symbolic::calculus::factorial(multiplicity - k)),
));
terms.push(simplify(&Expr::new_div(
c,
Expr::new_pow(factor.clone(), Expr::BigInt(BigInt::from(k))),
)));
}
}
return Some(terms);
}
None
}
pub(crate) fn lookup_inverse_laplace(
expr: &Expr,
in_var: &str,
out_var: &str,
) -> Option<Expr> {
match expr {
| Expr::Dag(node) => {
lookup_inverse_laplace(&node.to_expr().expect("Dag Inverse"), in_var, out_var)
},
| Expr::Div(num, den) => {
match (&**num, &**den) {
| (Expr::BigInt(n), Expr::Variable(v)) if n.is_one() && v == in_var => {
Some(Expr::BigInt(BigInt::one()))
},
| (Expr::BigInt(n), Expr::Sub(s_var, a_const)) if n.is_one() => {
if let (Expr::Variable(v), Expr::Constant(a)) = (&**s_var, &**a_const) {
if v == in_var {
return Some(Expr::new_exp(Expr::new_mul(
Expr::Constant(*a),
Expr::Variable(out_var.to_string()),
)));
}
}
None
},
| (Expr::Constant(w), Expr::Add(s_sq, w_sq)) => {
if let (Expr::Power(s_var, s_exp), Expr::Power(w_const, _w_exp)) =
(&**s_sq, &**w_sq)
{
if let (Expr::Variable(v), s_exp_expr) = (&**s_var, s_exp.clone()) {
if let Expr::BigInt(s_exp_val) = &*s_exp_expr {
if s_exp_val == &BigInt::from(2)
&& v == in_var
&& (if let Expr::Constant(val) = **w_const {
val
} else {
return None;
} - *w)
.abs()
< 1e-10
{
return Some(Expr::new_sin(Expr::new_mul(
w_const.clone(),
Expr::Variable(out_var.to_string()),
)));
}
}
}
}
None
},
| (Expr::Variable(v), Expr::Add(s_sq, w_sq)) if v == in_var => {
if let (Expr::Power(s_var, s_exp), Expr::Power(w_const, _w_exp)) =
(&**s_sq, &**w_sq)
{
if let (Expr::Variable(s), s_exp_expr) = (&**s_var, s_exp.clone()) {
if let Expr::BigInt(s_exp_val) = &*s_exp_expr {
if s_exp_val == &BigInt::from(2) && s == in_var {
return Some(Expr::new_cos(Expr::new_mul(
w_const.clone(),
Expr::Variable(out_var.to_string()),
)));
}
}
}
}
None
},
| _ => None,
}
},
| _ => None,
}
}
#[must_use]
pub fn convolution_fourier(
f: &Expr,
g: &Expr,
in_var: &str,
out_var: &str,
) -> Expr {
let ft_f = fourier_transform(f, in_var, out_var);
let ft_g = fourier_transform(g, in_var, out_var);
simplify(&Expr::new_mul(ft_f, ft_g))
}
#[must_use]
pub fn convolution_laplace(
f: &Expr,
g: &Expr,
in_var: &str,
out_var: &str,
) -> Expr {
let lt_f = laplace_transform(f, in_var, out_var);
let lt_g = laplace_transform(g, in_var, out_var);
simplify(&Expr::new_mul(lt_f, lt_g))
}