use tracing::debug_span;
use crate::api::expr::{BoolEx, Ex, Expr, Numeric};
use crate::base::errors::SymplexError;
pub use crate::calculus::fourier_transform::FourierConvention;
pub use crate::calculus::limit::Direction;
impl Expr<Numeric> {
#[must_use = "returns the limit value; does not modify in place"]
pub fn limit_dir(&self, var: &Ex, point: &Ex, dir: Direction) -> Ex {
match self.try_limit_dir(var, point, dir) {
Ok(v) => v,
Err(_) => {
let var_id = self.checked_id(var);
let point_id = self.checked_id(point);
let id = self
.inner
.write()
.arena
.intern(crate::base::node::ExprNode::Limit(
self.raw_id(),
var_id,
point_id,
));
self.wrap(id)
}
}
}
pub fn try_limit_dir(&self, var: &Ex, point: &Ex, dir: Direction) -> Result<Ex, SymplexError> {
let var_id = self.checked_id(var);
let point_id = self.checked_id(point);
let _span =
debug_span!("limit_dir", expr = ?self.raw_id(), var = ?var_id, dir = ?dir).entered();
let result = {
let mut inner = self.inner.write();
crate::calculus::limit::limit_dir(
&mut inner.arena,
self.raw_id(),
var_id,
point_id,
dir,
)
};
result.map(|id| self.wrap(id))
}
#[must_use = "returns the limit value; does not modify in place"]
pub fn limit_left(&self, var: &Ex, point: &Ex) -> Ex {
self.limit_dir(var, point, Direction::Left)
}
#[must_use = "returns the limit value; does not modify in place"]
pub fn limit_right(&self, var: &Ex, point: &Ex) -> Ex {
self.limit_dir(var, point, Direction::Right)
}
pub fn try_limit_left(&self, var: &Ex, point: &Ex) -> Result<Ex, SymplexError> {
self.try_limit_dir(var, point, Direction::Left)
}
pub fn try_limit_right(&self, var: &Ex, point: &Ex) -> Result<Ex, SymplexError> {
self.try_limit_dir(var, point, Direction::Right)
}
}
impl Expr<Numeric> {
pub fn fourier_transform(&self, t: &Ex, omega: &Ex) -> Result<Ex, SymplexError> {
self.fourier_transform_with(t, omega, FourierConvention::NonUnitaryAngular)
}
pub fn fourier_transform_with(
&self,
t: &Ex,
omega: &Ex,
convention: FourierConvention,
) -> Result<Ex, SymplexError> {
let t_id = self.checked_id(t);
let w_id = self.checked_id(omega);
let _span = debug_span!("fourier_transform", expr = ?self.raw_id(), t = ?t_id).entered();
let r = {
let mut inner = self.inner.write();
crate::calculus::fourier_transform::fourier_transform_with(
&mut inner.arena,
self.raw_id(),
t_id,
w_id,
convention,
)
};
r.map(|id| self.wrap(id))
}
pub fn inverse_fourier_transform(&self, omega: &Ex, t: &Ex) -> Result<Ex, SymplexError> {
self.inverse_fourier_transform_with(omega, t, FourierConvention::NonUnitaryAngular)
}
pub fn inverse_fourier_transform_with(
&self,
omega: &Ex,
t: &Ex,
convention: FourierConvention,
) -> Result<Ex, SymplexError> {
let w_id = self.checked_id(omega);
let t_id = self.checked_id(t);
let _span = debug_span!("inverse_fourier_transform", expr = ?self.raw_id(), omega = ?w_id)
.entered();
let r = {
let mut inner = self.inner.write();
crate::calculus::fourier_transform::inverse_fourier_transform_with(
&mut inner.arena,
self.raw_id(),
w_id,
t_id,
convention,
)
};
r.map(|id| self.wrap(id))
}
}
impl Expr<Numeric> {
pub fn laplace_initial_value(&self, s: &Ex) -> Result<Ex, SymplexError> {
let sf = s * self;
sf.try_limit(s, &s.context().infinity())
}
pub fn laplace_final_value(&self, s: &Ex) -> Result<Ex, SymplexError> {
let ctx = s.context();
let sf = (s * self).ratsimp();
let (_, den) = sf.as_numer_denom();
if den.is_polynomial(s) && !den.free_symbols().is_empty() {
let poles = den.solve_or_empty(s);
if poles.is_empty() {
return Err(SymplexError::ComputationFailed {
operation: "laplace_final_value",
reason: format!(
"cannot locate the poles of s·F(s) (denominator {den}) to verify the final value theorem"
),
});
}
for p in &poles {
let re = match p.eval_complex64() {
Ok((re, _)) => re,
Err(_) => match p.re().is_negative() {
Some(true) => -1.0,
Some(false) => 0.0,
None => {
return Err(SymplexError::ComputationFailed {
operation: "laplace_final_value",
reason: format!(
"cannot determine the sign of Re({p}) (a pole of s·F(s))"
),
});
}
},
};
if re >= 0.0 {
return Err(SymplexError::Divergent {
operation: "laplace_final_value",
reason: format!(
"s·F(s) has a pole at s = {p} with non-negative real part, so f(t) has no finite limit"
),
});
}
}
}
sf.try_limit_right(s, &ctx.int(0))
}
}
#[derive(Debug, Clone)]
pub struct FourierSeries {
pub function: Ex,
pub var: Ex,
pub lower: Ex,
pub upper: Ex,
pub period: Ex,
pub a0: Ex,
pub an: Vec<Ex>,
pub bn: Vec<Ex>,
}
impl FourierSeries {
#[must_use]
pub fn omega0(&self) -> Ex {
let ctx = self.var.context();
2 * ctx.pi() / &self.period
}
#[must_use]
pub fn n_terms(&self) -> usize {
self.an.len()
}
fn raw_coefficient(&self, k: u32, sine: bool) -> Ex {
let ctx = self.var.context();
let arg = (ctx.int(i64::from(k)) * self.omega0() * &self.var).eval();
let kernel = if sine { arg.sin() } else { arg.cos() };
let integrand = &self.function * kernel;
let integral = integrand.integrate_definite(&self.var, &self.lower, &self.upper);
(2 * integral / &self.period).simplify().eval()
}
#[must_use]
pub fn coefficient_a(&self, k: u32) -> Ex {
if k == 0 {
return self.a0.clone();
}
if let Some(a) = self.an.get(k as usize - 1) {
return a.clone();
}
self.raw_coefficient(k, false)
}
#[must_use]
pub fn coefficient_b(&self, k: u32) -> Ex {
if k == 0 {
return self.var.context().int(0);
}
if let Some(b) = self.bn.get(k as usize - 1) {
return b.clone();
}
self.raw_coefficient(k, true)
}
#[must_use]
pub fn coefficient_c(&self, k: i64) -> Ex {
let ctx = self.var.context();
if k == 0 {
return (&self.a0 / 2).eval();
}
let m = k.unsigned_abs() as u32;
let a = self.coefficient_a(m);
let b = self.coefficient_b(m);
let i = ctx.i_unit();
let c = if k > 0 {
(&a - &i * &b) / 2
} else {
(&a + &i * &b) / 2
};
c.eval()
}
#[must_use]
pub fn truncate(&self, n: u32) -> Ex {
let ctx = self.var.context();
let mut sum = &self.a0 / 2;
let w0 = self.omega0();
for k in 1..=n {
let arg = (ctx.int(i64::from(k)) * &w0 * &self.var).eval();
let a = self.coefficient_a(k);
let b = self.coefficient_b(k);
if !a.is_zero_structural() {
sum = &sum + &a * arg.cos();
}
if !b.is_zero_structural() {
sum = &sum + &b * arg.sin();
}
}
sum.eval()
}
}
impl Expr<Numeric> {
pub fn fourier_series_on(
&self,
var: &Ex,
lower: &Ex,
upper: &Ex,
n_terms: u32,
) -> Result<FourierSeries, SymplexError> {
let ctx = self.context();
let var_id = self.checked_id(var);
let _ = self.checked_id(lower);
let _ = self.checked_id(upper);
{
let inner = self.inner.read();
if !matches!(
inner.arena.node(var_id),
crate::base::node::ExprNode::Symbol(_)
) {
return Err(SymplexError::InvalidArgument {
operation: "fourier_series_on",
reason: "the expansion variable must be a symbol".into(),
});
}
}
let period = (upper - lower).eval();
if period.is_zero_structural() || period.is_positive() == Some(false) {
return Err(SymplexError::InvalidArgument {
operation: "fourier_series_on",
reason: format!("the interval [{lower}, {upper}] must have positive length"),
});
}
let _span =
debug_span!("fourier_series_on", expr = ?self.raw_id(), var = ?var_id).entered();
let check = |c: Ex, what: String| -> Result<Ex, SymplexError> {
if c.has_unevaluated() {
Err(SymplexError::ComputationFailed {
operation: "fourier_series_on",
reason: format!("the coefficient integral for {what} has no closed form: {c}"),
})
} else {
Ok(c)
}
};
let a0_int = self.integrate_definite(var, lower, upper);
let a0 = check((2 * a0_int / &period).simplify().eval(), "a_0".into())?;
let mut series = FourierSeries {
function: self.clone(),
var: var.clone(),
lower: lower.clone(),
upper: upper.clone(),
period,
a0,
an: Vec::with_capacity(n_terms as usize),
bn: Vec::with_capacity(n_terms as usize),
};
for k in 1..=n_terms {
let a = check(series.raw_coefficient(k, false), format!("a_{k}"))?;
let b = check(series.raw_coefficient(k, true), format!("b_{k}"))?;
series.an.push(a);
series.bn.push(b);
}
let _ = ctx;
Ok(series)
}
}
impl Expr<Numeric> {
pub fn mellin_transform(&self, x: &Ex, s: &Ex) -> Result<(Ex, BoolEx), SymplexError> {
let x_id = self.checked_id(x);
let s_id = self.checked_id(s);
let _span = debug_span!("mellin_transform", expr = ?self.raw_id(), x = ?x_id).entered();
let r = {
let mut inner = self.inner.write();
crate::calculus::mellin::mellin_transform(&mut inner.arena, self.raw_id(), x_id, s_id)
};
r.map(|(f, cond)| (self.wrap(f), self.wrap_as(cond)))
}
pub fn inverse_mellin_transform(&self, s: &Ex, x: &Ex) -> Result<Ex, SymplexError> {
let s_id = self.checked_id(s);
let x_id = self.checked_id(x);
let _span =
debug_span!("inverse_mellin_transform", expr = ?self.raw_id(), s = ?s_id).entered();
let r = {
let mut inner = self.inner.write();
crate::calculus::mellin::inverse_mellin_transform(
&mut inner.arena,
self.raw_id(),
s_id,
x_id,
)
};
r.map(|id| self.wrap(id))
}
}