use tracing::debug_span;
use crate::api::expr::{Ex, Expr, Numeric};
use crate::base::errors::SymplexError;
use crate::base::node::ExprNode;
use crate::calculus::limit::Direction;
use crate::calculus::summation::{self, SumOutcome};
impl Expr<Numeric> {
#[must_use = "returns the evaluated sum; does not modify in place"]
pub fn summation(&self, var: &Ex, lower: &Ex, upper: &Ex) -> Ex {
let var_id = self.checked_id(var);
let lo_id = self.checked_id(lower);
let hi_id = self.checked_id(upper);
let _span = debug_span!("summation", body = ?self.raw_id()).entered();
let mut inner = self.inner.write();
let outcome = summation::summation(&mut inner.arena, self.raw_id(), var_id, lo_id, hi_id);
let id = match outcome {
SumOutcome::Closed(id) => id,
SumOutcome::Divergent(Some(inf)) => inf,
SumOutcome::Divergent(None) | SumOutcome::Unevaluated => inner
.arena
.intern(ExprNode::Sum(self.raw_id(), var_id, lo_id, hi_id)),
};
drop(inner);
self.wrap(id)
}
pub fn try_summation(&self, var: &Ex, lower: &Ex, upper: &Ex) -> Result<Ex, SymplexError> {
let var_id = self.checked_id(var);
let lo_id = self.checked_id(lower);
let hi_id = self.checked_id(upper);
let _span = debug_span!("try_summation", body = ?self.raw_id()).entered();
let mut inner = self.inner.write();
let outcome = summation::summation(&mut inner.arena, self.raw_id(), var_id, lo_id, hi_id);
drop(inner);
match outcome {
SumOutcome::Closed(id) => {
let result = self.wrap(id);
if result.has_unevaluated() {
Err(SymplexError::ComputationFailed {
operation: "summation",
reason: "no closed form for part of the sum".into(),
})
} else {
Ok(result)
}
}
SumOutcome::Divergent(Some(inf)) => Err(SymplexError::Divergent {
operation: "summation",
reason: format!("the series diverges to {}", self.wrap(inf)),
}),
SumOutcome::Divergent(None) => Err(SymplexError::Divergent {
operation: "summation",
reason: "the series diverges (terms do not tend to zero)".into(),
}),
SumOutcome::Unevaluated => Err(SymplexError::ComputationFailed {
operation: "summation",
reason: "no closed form found".into(),
}),
}
}
#[must_use = "returns the evaluated product; does not modify in place"]
pub fn product_over(&self, var: &Ex, lower: &Ex, upper: &Ex) -> Ex {
let var_id = self.checked_id(var);
let lo_id = self.checked_id(lower);
let hi_id = self.checked_id(upper);
let _span = debug_span!("product_over", body = ?self.raw_id()).entered();
let mut inner = self.inner.write();
let outcome = summation::product(&mut inner.arena, self.raw_id(), var_id, lo_id, hi_id);
let id = match outcome {
SumOutcome::Closed(id) => id,
SumOutcome::Divergent(Some(inf)) => inf,
SumOutcome::Divergent(None) | SumOutcome::Unevaluated => inner
.arena
.intern(ExprNode::Product_(self.raw_id(), var_id, lo_id, hi_id)),
};
drop(inner);
self.wrap(id)
}
pub fn try_product_over(&self, var: &Ex, lower: &Ex, upper: &Ex) -> Result<Ex, SymplexError> {
let var_id = self.checked_id(var);
let lo_id = self.checked_id(lower);
let hi_id = self.checked_id(upper);
let mut inner = self.inner.write();
let outcome = summation::product(&mut inner.arena, self.raw_id(), var_id, lo_id, hi_id);
drop(inner);
match outcome {
SumOutcome::Closed(id) => {
let result = self.wrap(id);
if result.has_unevaluated() {
Err(SymplexError::ComputationFailed {
operation: "product",
reason: "no closed form for part of the product".into(),
})
} else {
Ok(result)
}
}
SumOutcome::Divergent(_) => Err(SymplexError::Divergent {
operation: "product",
reason: "the infinite product diverges".into(),
}),
SumOutcome::Unevaluated => Err(SymplexError::ComputationFailed {
operation: "product",
reason: "no closed form found".into(),
}),
}
}
#[must_use]
pub fn hypergeometric_ratio(&self, var: &Ex) -> Option<Ex> {
let var_id = self.checked_id(var);
let mut inner = self.inner.write();
let r = crate::calculus::gosper::is_hypergeometric(&mut inner.arena, self.raw_id(), var_id);
drop(inner);
r.map(|id| self.wrap(id))
}
#[must_use]
pub fn is_absolutely_convergent(&self, var: &Ex) -> Option<bool> {
let var_id = self.checked_id(var);
let _span = debug_span!("is_absolutely_convergent").entered();
let mut inner = self.inner.write();
crate::calculus::convergence::is_absolutely_convergent(
&mut inner.arena,
self.raw_id(),
var_id,
)
}
#[must_use = "returns the expansion; does not modify in place"]
pub fn series_at_infinity(&self, var: &Ex, n_terms: u32) -> Ex {
self.series_at_infinity_impl(var, n_terms, false)
}
#[must_use = "returns the expansion; does not modify in place"]
pub fn series_at_neg_infinity(&self, var: &Ex, n_terms: u32) -> Ex {
self.series_at_infinity_impl(var, n_terms, true)
}
pub fn try_series_at_infinity(&self, var: &Ex, n_terms: u32) -> Result<Ex, SymplexError> {
let r = self.series_at_infinity(var, n_terms);
if r.has_unevaluated() {
Err(SymplexError::ComputationFailed {
operation: "series_at_infinity",
reason: "could not compute asymptotic expansion".into(),
})
} else {
Ok(r)
}
}
#[must_use = "returns the expansion; does not modify in place"]
pub fn series_dir(&self, var: &Ex, point: &Ex, order: u32, dir: Direction) -> Ex {
let var_id = self.checked_id(var);
let point_id = self.checked_id(point);
let _span = debug_span!("series_dir", expr = ?self.raw_id(), order = order).entered();
let mut inner = self.inner.write();
let id = match crate::calculus::series::series_dir(
&mut inner.arena,
self.raw_id(),
var_id,
point_id,
order,
dir,
) {
Ok(id) => id,
Err(_) => {
let order_id = inner.arena.int(order as i64);
inner
.arena
.intern(ExprNode::Series(self.raw_id(), var_id, point_id, order_id))
}
};
drop(inner);
self.wrap(id)
}
pub fn try_series_dir(
&self,
var: &Ex,
point: &Ex,
order: u32,
dir: Direction,
) -> Result<Ex, SymplexError> {
let r = self.series_dir(var, point, order, dir);
if r.has_unevaluated() {
Err(SymplexError::ComputationFailed {
operation: "series_dir",
reason: "no expansion from this side".into(),
})
} else {
Ok(r)
}
}
fn series_at_infinity_impl(&self, var: &Ex, n_terms: u32, negative: bool) -> Ex {
let var_id = self.checked_id(var);
let _span = debug_span!("series_at_infinity", expr = ?self.raw_id()).entered();
let mut inner = self.inner.write();
let id = crate::calculus::series::series_at_infinity(
&mut inner.arena,
self.raw_id(),
var_id,
n_terms,
negative,
);
let id = match id {
Ok(id) => id,
Err(_) => {
let point = if negative {
inner.arena.neg_infinity
} else {
inner.arena.infinity
};
let order = inner.arena.int(n_terms as i64);
inner
.arena
.intern(ExprNode::Series(self.raw_id(), var_id, point, order))
}
};
drop(inner);
self.wrap(id)
}
}