use num_bigint::BigInt;
use num_traits::One;
use crate::symbolic::calculus::differentiate;
use crate::symbolic::calculus::improper_integral;
use crate::symbolic::calculus::limit;
use crate::symbolic::calculus::substitute;
use crate::symbolic::core::Expr;
use crate::symbolic::elementary::infinity;
use crate::symbolic::simplify::is_zero;
use crate::symbolic::simplify_dag::simplify;
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(C)]
pub enum ConvergenceResult {
Converges,
Diverges,
Inconclusive,
}
pub(crate) fn is_positive(
f_n: &Expr,
n: &str,
) -> bool {
let large_n = Expr::Constant(1000.0);
let val_at_large_n = simplify(&substitute(f_n, n, &large_n));
val_at_large_n.to_f64().is_some_and(|v| v > 0.0)
}
pub(crate) fn is_eventually_decreasing(
f_n: &Expr,
n: &str,
) -> bool {
let derivative = differentiate(f_n, n);
let large_n = Expr::Constant(1000.0);
let deriv_at_large_n = simplify(&substitute(&derivative, n, &large_n));
deriv_at_large_n.to_f64().is_some_and(|v| v <= 0.0)
}
#[must_use]
pub fn analyze_convergence(
a_n: &Expr,
n: &str,
) -> ConvergenceResult {
let simplified = simplify(a_n);
let a_n = match simplified {
| Expr::Dag(ref node) => node.to_expr().unwrap_or_else(|_| simplified.clone()),
| _ => simplified,
};
if let Expr::Power(var, p) = &a_n {
if let Expr::Variable(name) = &**var {
if name == n {
if let Some(p_val) = simplify(&p.as_ref().clone()).to_f64() {
let effective_p = -p_val;
return if effective_p > 1.0 {
ConvergenceResult::Converges
} else {
ConvergenceResult::Diverges
};
}
}
}
}
if let Expr::Div(one, denominator) = &a_n {
let is_one = match &**one {
| Expr::BigInt(b) => b.is_one(),
| Expr::Constant(c) => (*c - 1.0).abs() < f64::EPSILON,
| _ => false,
};
if is_one {
if let Expr::Power(var, p) = &**denominator {
if let Expr::Variable(name) = &**var {
if name == n {
if let Some(p_val) = simplify(&p.as_ref().clone()).to_f64() {
return if p_val > 1.0 {
ConvergenceResult::Converges
} else {
ConvergenceResult::Diverges
};
}
}
}
}
else if let Expr::Variable(name) = &**denominator {
if name == n {
return ConvergenceResult::Diverges;
}
}
}
}
let term_limit = limit(&a_n, n, &infinity());
let simplified_limit = simplify(&term_limit);
if !is_zero(&simplified_limit)
&& (simplified_limit.to_f64().is_some() || matches!(simplified_limit, Expr::Infinity))
{
return ConvergenceResult::Diverges;
}
let mut is_alternating = false;
let mut b_n = a_n.clone();
if let Expr::Mul(factor1, factor2) = &a_n {
if let Expr::Power(neg_one, _) = &**factor1 {
if let Expr::BigInt(base) = &**neg_one {
if base == &BigInt::from(-1) {
is_alternating = true;
b_n = factor2.as_ref().clone();
}
}
}
}
if is_alternating && is_eventually_decreasing(&b_n, n) {
return ConvergenceResult::Converges;
}
let n_plus_1 = Expr::new_add(Expr::Variable(n.to_string()), Expr::BigInt(BigInt::one()));
let a_n_plus_1 = substitute(&a_n, n, &n_plus_1);
let ratio = simplify(&Expr::new_abs(Expr::new_div(a_n_plus_1, a_n.clone())));
let ratio_limit = limit(&ratio, n, &infinity());
if let Some(l) = simplify(&ratio_limit).to_f64() {
if l < 1.0 {
return ConvergenceResult::Converges;
}
if l > 1.0 {
return ConvergenceResult::Diverges;
}
}
let root_expr = simplify(&Expr::new_pow(
Expr::new_abs(a_n.clone()),
Expr::new_div(Expr::BigInt(BigInt::one()), Expr::Variable(n.to_string())),
));
let root_limit = limit(&root_expr, n, &infinity());
if let Some(l) = simplify(&root_limit).to_f64() {
if l < 1.0 {
return ConvergenceResult::Converges;
}
if l > 1.0 {
return ConvergenceResult::Diverges;
}
}
if is_positive(&a_n, n) && is_eventually_decreasing(&a_n, n) {
let integral_result = improper_integral(&a_n, n);
if matches!(integral_result, Expr::Infinity) {
return ConvergenceResult::Diverges;
}
if !matches!(integral_result, Expr::Integral { .. }) {
return ConvergenceResult::Converges;
}
}
ConvergenceResult::Inconclusive
}