mod alg_tower;
pub(super) mod decompose;
pub mod elliptic;
pub mod elliptic_output;
pub mod find_order;
pub mod genus1_log;
pub(super) mod genus_zero;
pub mod hermite_curve;
pub mod integral_basis;
mod jacobian_torsion;
pub(super) mod parametrize;
pub(super) mod poly_utils;
pub mod residues;
#[allow(dead_code)]
mod trager_log;
pub mod vanhoeij;
use crate::deriv::log::{DerivationLog, DerivedExpr, RewriteStep};
use crate::integrate::engine::IntegrationError;
use crate::kernel::{ExprData, ExprId, ExprPool};
use crate::simplify::engine::simplify;
use decompose::decompose_sqrt;
use genus_zero::integrate_with_sqrt;
use poly_utils::is_zero_expr;
pub fn contains_algebraic_subterm(expr: ExprId, pool: &ExprPool) -> bool {
match pool.get(expr) {
ExprData::Func { ref name, ref args } => {
if name == "sqrt" {
return true;
}
args.iter().any(|&a| contains_algebraic_subterm(a, pool))
}
ExprData::Pow { base, exp } => {
if matches!(pool.get(exp), ExprData::Rational(_)) {
return true;
}
contains_algebraic_subterm(base, pool)
}
ExprData::Add(args) | ExprData::Mul(args) => {
args.iter().any(|&a| contains_algebraic_subterm(a, pool))
}
_ => false,
}
}
pub fn contains_algebraic_func_of_var(expr: ExprId, var: ExprId, pool: &ExprPool) -> bool {
use crate::integrate::risch::poly_rde::is_free_of_var;
match pool.get(expr) {
ExprData::Func { ref name, ref args } if args.len() == 1 => {
if name == "cbrt" && !is_free_of_var(args[0], var, pool) {
return true;
}
contains_algebraic_func_of_var(args[0], var, pool)
}
ExprData::Pow { base, .. } => contains_algebraic_func_of_var(base, var, pool),
ExprData::Add(args) | ExprData::Mul(args) => args
.iter()
.any(|&a| contains_algebraic_func_of_var(a, var, pool)),
_ => false,
}
}
fn collect_generators(expr: ExprId, pool: &ExprPool, out: &mut Vec<ExprId>) {
match pool.get(expr) {
ExprData::Func { ref name, ref args } => {
if name == "sqrt" && args.len() == 1 {
out.push(expr);
} else {
for &a in args.iter() {
collect_generators(a, pool, out);
}
}
}
ExprData::Pow { base, exp } => {
if matches!(pool.get(exp), ExprData::Rational(_)) {
out.push(expr);
} else {
collect_generators(base, pool, out);
}
}
ExprData::Add(args) | ExprData::Mul(args) => {
for &a in args.iter() {
collect_generators(a, pool, out);
}
}
_ => {}
}
}
fn get_radicand(expr: ExprId, pool: &ExprPool) -> Option<ExprId> {
match pool.get(expr) {
ExprData::Func { ref name, ref args } if name == "sqrt" && args.len() == 1 => Some(args[0]),
ExprData::Pow { base, exp } => {
match pool.get(exp) {
ExprData::Rational(r) if r.0 == rug::Rational::from((1u32, 2u32)) => Some(base),
_ => None,
}
}
_ => None,
}
}
fn find_generator(expr: ExprId, pool: &ExprPool) -> Option<(ExprId, ExprId)> {
let mut generators = Vec::new();
collect_generators(expr, pool, &mut generators);
generators.sort_unstable();
generators.dedup();
if generators.len() != 1 {
return None;
}
let sqrt_id = generators[0];
let radicand = get_radicand(sqrt_id, pool)?;
Some((sqrt_id, radicand))
}
pub fn integrate_algebraic(
expr: ExprId,
var: ExprId,
pool: &ExprPool,
) -> Result<DerivedExpr<ExprId>, IntegrationError> {
if let Some(res) = parametrize::try_parametrize_genus0(expr, var, pool) {
return res;
}
if let Some(res) =
crate::integrate::risch::simple_radical::try_integrate_simple_radical(expr, var, pool)
{
return res;
}
match integrate_via_decompose(expr, var, pool) {
Err(IntegrationError::NotImplemented(_)) => {
if let Some(res) = parametrize::try_euler_quadratic(expr, var, pool) {
return res;
}
integrate_via_decompose(expr, var, pool)
}
other => other,
}
}
fn integrate_via_decompose(
expr: ExprId,
var: ExprId,
pool: &ExprPool,
) -> Result<DerivedExpr<ExprId>, IntegrationError> {
let mut log = DerivationLog::new();
let (sqrt_id, p_expr) = find_generator(expr, pool).ok_or_else(|| {
IntegrationError::NotImplemented(
"algebraic integrator requires exactly one sqrt(P(x)) generator; \
multiple or nested generators are not supported in v1.1"
.to_string(),
)
})?;
if let ExprData::Pow { exp, .. } = pool.get(sqrt_id) {
if let ExprData::Rational(r) = pool.get(exp) {
let q = r.0.denom().to_u32().unwrap_or(0);
if q != 2 {
return Err(IntegrationError::UnsupportedExtensionDegree(q));
}
}
}
let (a_raw, b_raw) = decompose_sqrt(expr, sqrt_id, p_expr, pool).ok_or_else(|| {
IntegrationError::NotImplemented(
"could not decompose integrand into A(x) + B(x)·sqrt(P(x)); \
expression structure is not supported"
.to_string(),
)
})?;
let a_part = simplify(a_raw, pool).value;
let b_part = simplify(b_raw, pool).value;
if !is_zero_expr(b_part, pool) {
if let Some(f) = try_genus1_log(a_part, b_part, p_expr, var, pool) {
let simplified = simplify(f, pool);
log = log.merge(simplified.log);
log.push(RewriteStep::simple(
"genus1_elliptic_log",
expr,
simplified.value,
));
return Ok(DerivedExpr::with_log(simplified.value, log));
}
if let Some(f) = elliptic_output::try_elliptic_output(a_part, b_part, p_expr, var, pool) {
let simplified = simplify(f, pool);
log = log.merge(simplified.log);
log.push(RewriteStep::simple(
"genus1_elliptic_firstkind_output",
expr,
simplified.value,
));
return Ok(DerivedExpr::with_log(simplified.value, log));
}
if let Some(f) =
elliptic_output::try_elliptic_output_higher_kind(a_part, b_part, p_expr, var, pool)
{
let simplified = simplify(f, pool);
log = log.merge(simplified.log);
log.push(RewriteStep::simple(
"genus1_elliptic_higherkind_output",
expr,
simplified.value,
));
return Ok(DerivedExpr::with_log(simplified.value, log));
}
}
let zero = pool.integer(0_i32);
let int_a = if is_zero_expr(a_part, pool) {
zero
} else {
crate::integrate::engine::integrate_raw(a_part, var, pool, &mut log)?
};
let int_b = if is_zero_expr(b_part, pool) {
zero
} else {
integrate_with_sqrt(b_part, p_expr, sqrt_id, var, pool, &mut log)?
};
let raw = match (is_zero_expr(int_a, pool), is_zero_expr(int_b, pool)) {
(true, true) => zero,
(false, true) => int_a,
(true, false) => int_b,
(false, false) => pool.add(vec![int_a, int_b]),
};
let simplified = simplify(raw, pool);
log = log.merge(simplified.log);
log.push(RewriteStep::simple(
"algebraic_risch",
expr,
simplified.value,
));
Ok(DerivedExpr::with_log(simplified.value, log))
}
fn try_genus1_log(
a_part: ExprId,
b_part: ExprId,
p_expr: ExprId,
var: ExprId,
pool: &ExprPool,
) -> Option<ExprId> {
use crate::integrate::risch::alg_field::RatFn;
use crate::integrate::risch::poly_rde::expr_to_qpoly;
use crate::integrate::risch::rational_rde::expr_to_qrational;
let p_poly = expr_to_qpoly(p_expr, var, pool)?;
let a_rat = if is_zero_expr(a_part, pool) {
RatFn::int(0)
} else {
let (num, den) = expr_to_qrational(a_part, var, pool)?;
RatFn::new(num, den)
};
let (b_num, b_den) = expr_to_qrational(b_part, var, pool)?;
let integrand = vec![a_rat, RatFn::new(b_num, b_den)];
genus1_log::integrate_genus1_log(&p_poly, &integrand, var, pool)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kernel::Domain;
fn eval(expr: ExprId, x: ExprId, xv: f64, pool: &ExprPool) -> Option<f64> {
if expr == x {
return Some(xv);
}
match pool.get(expr) {
ExprData::Integer(n) => Some(n.0.to_f64()),
ExprData::Rational(r) => Some(r.0.to_f64()),
ExprData::Add(args) => args
.iter()
.try_fold(0.0, |s, &a| Some(s + eval(a, x, xv, pool)?)),
ExprData::Mul(args) => args
.iter()
.try_fold(1.0, |s, &a| Some(s * eval(a, x, xv, pool)?)),
ExprData::Pow { base, exp } => {
Some(eval(base, x, xv, pool)?.powf(eval(exp, x, xv, pool)?))
}
ExprData::Func { ref name, ref args } if args.len() == 1 => {
let v = eval(args[0], x, xv, pool)?;
match name.as_str() {
"sqrt" => Some(v.sqrt()),
"log" => Some(v.ln()),
"exp" => Some(v.exp()),
"cbrt" => Some(v.cbrt()),
_ => None,
}
}
_ => None,
}
}
#[test]
fn genus1_elliptic_log_via_engine() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let x3p1 = pool.add(vec![pool.pow(x, pool.integer(3_i32)), pool.integer(1_i32)]);
let sq = pool.func("sqrt", vec![x3p1]);
let half = pool.pow(pool.integer(2_i32), pool.integer(-1_i32));
let a_part = pool.mul(vec![half, pool.pow(x, pool.integer(-1_i32))]);
let denom = pool.mul(vec![pool.integer(2_i32), x, x3p1]);
let b_term = pool.mul(vec![sq, pool.pow(denom, pool.integer(-1_i32))]);
let integrand = pool.add(vec![a_part, b_term]);
let res = crate::integrate::engine::integrate(integrand, x, &pool)
.expect("genus-1 integrand should integrate elementarily");
let f = res.value;
let df = simplify(crate::diff::diff(f, x, &pool).unwrap().value, &pool).value;
let mut checked = 0;
for &xv in &[0.7_f64, 1.5, 2.9] {
let lhs = eval(df, x, xv, &pool).unwrap();
let rhs = eval(integrand, x, xv, &pool).unwrap();
assert!(
(lhs - rhs).abs() < 1e-6 * (1.0 + rhs.abs()),
"x={xv}: d/dx F = {lhs}, integrand = {rhs}\n F = {}",
pool.display(f)
);
checked += 1;
}
assert!(checked >= 2);
}
#[test]
fn quintic_poly_b_integral_part_elementary() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let x5p1 = pool.add(vec![pool.pow(x, pool.integer(5_i32)), pool.integer(1_i32)]);
let sq = pool.func("sqrt", vec![x5p1]);
let integrand = pool.mul(vec![
pool.integer(5_i32),
pool.pow(x, pool.integer(4_i32)),
sq,
]);
let res = crate::integrate::engine::integrate(integrand, x, &pool)
.expect("polynomial-B integral part is elementary");
let df = simplify(crate::diff::diff(res.value, x, &pool).unwrap().value, &pool).value;
for &xv in &[0.3_f64, 0.8, 1.4] {
let lhs = eval(df, x, xv, &pool).unwrap();
let rhs = eval(integrand, x, xv, &pool).unwrap();
assert!(
(lhs - rhs).abs() < 1e-6 * (1.0 + rhs.abs()),
"x={xv}: d/dx F = {lhs}, integrand = {rhs}"
);
}
}
#[test]
fn quintic_poly_b_genuinely_non_elementary() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let x5p1 = pool.add(vec![pool.pow(x, pool.integer(5_i32)), pool.integer(1_i32)]);
let sq = pool.func("sqrt", vec![x5p1]);
let integrand = pool.mul(vec![x, sq]);
let res = crate::integrate::engine::integrate(integrand, x, &pool);
assert!(
matches!(res, Err(IntegrationError::NonElementary(_))),
"got {res:?}"
);
}
#[test]
fn quintic_rational_b_integral_part_elementary() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let x5p1 = pool.add(vec![pool.pow(x, pool.integer(5_i32)), pool.integer(1_i32)]);
let inv_sqrt = pool.pow(pool.func("sqrt", vec![x5p1]), pool.integer(-1_i32));
let half = pool.pow(pool.integer(2_i32), pool.integer(-1_i32));
let integrand = pool.mul(vec![
pool.integer(5_i32),
pool.pow(x, pool.integer(4_i32)),
half,
inv_sqrt,
]);
let res = crate::integrate::engine::integrate(integrand, x, &pool)
.expect("rational-B integral part is elementary");
let df = simplify(crate::diff::diff(res.value, x, &pool).unwrap().value, &pool).value;
for &xv in &[0.4_f64, 0.9, 1.6] {
let lhs = eval(df, x, xv, &pool).unwrap();
let rhs = eval(integrand, x, xv, &pool).unwrap();
assert!(
(lhs - rhs).abs() < 1e-6 * (1.0 + rhs.abs()),
"x={xv}: d/dx F = {lhs}, integrand = {rhs}"
);
}
}
#[test]
fn quintic_first_kind_non_elementary() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let x5p1 = pool.add(vec![pool.pow(x, pool.integer(5_i32)), pool.integer(1_i32)]);
let integrand = pool.pow(pool.func("sqrt", vec![x5p1]), pool.integer(-1_i32));
let res = crate::integrate::engine::integrate(integrand, x, &pool);
assert!(
matches!(res, Err(IntegrationError::NonElementary(_))),
"got {res:?}"
);
}
#[test]
fn quintic_algebraic_pole_non_elementary() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![
pool.pow(x, pool.integer(5_i32)),
x,
pool.integer(1_i32),
]);
let sq = pool.func("sqrt", vec![p]);
let integrand = pool.mul(vec![
sq,
pool.pow(
pool.add(vec![x, pool.integer(-2_i32)]),
pool.integer(-1_i32),
),
]);
let res = crate::integrate::engine::integrate(integrand, x, &pool);
assert!(
matches!(res, Err(IntegrationError::NonElementary(_))),
"got {res:?}"
);
}
#[test]
fn quintic_compositum_non_elementary() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![
pool.pow(x, pool.integer(5_i32)),
x,
pool.integer(1_i32),
]);
let sq = pool.func("sqrt", vec![p]);
let den = pool.mul(vec![
pool.add(vec![x, pool.integer(-2_i32)]),
pool.add(vec![x, pool.integer(-3_i32)]),
]);
let integrand = pool.mul(vec![sq, pool.pow(den, pool.integer(-1_i32))]);
let res = crate::integrate::engine::integrate(integrand, x, &pool);
assert!(
matches!(res, Err(IntegrationError::NonElementary(_))),
"got {res:?}"
);
}
#[test]
fn quintic_algebraic_pole_weighted_non_elementary() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![
pool.pow(x, pool.integer(5_i32)),
x,
pool.integer(1_i32),
]);
let sq = pool.func("sqrt", vec![p]);
let integrand = pool.mul(vec![
pool.pow(x, pool.integer(3_i32)),
sq,
pool.pow(
pool.add(vec![x, pool.integer(-2_i32)]),
pool.integer(-1_i32),
),
]);
let res = crate::integrate::engine::integrate(integrand, x, &pool);
assert!(
matches!(res, Err(IntegrationError::NonElementary(_))),
"got {res:?}"
);
}
#[test]
fn quintic_algebraic_base_non_elementary() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![
pool.pow(x, pool.integer(5_i32)),
pool.mul(vec![pool.integer(-4_i32), x]),
pool.integer(3_i32),
]);
let sq = pool.func("sqrt", vec![p]);
let den = pool.add(vec![pool.pow(x, pool.integer(2_i32)), pool.integer(-2_i32)]); let integrand = pool.mul(vec![sq, pool.pow(den, pool.integer(-1_i32))]);
let res = crate::integrate::engine::integrate(integrand, x, &pool);
assert!(
matches!(res, Err(IntegrationError::NonElementary(_))),
"got {res:?}"
);
}
#[test]
fn quintic_algebraic_base_non_galois_non_elementary() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![
pool.pow(x, pool.integer(5_i32)),
x,
pool.integer(1_i32),
]);
let sq = pool.func("sqrt", vec![p]);
let den = pool.add(vec![pool.pow(x, pool.integer(2_i32)), pool.integer(-2_i32)]);
let integrand = pool.mul(vec![sq, pool.pow(den, pool.integer(-1_i32))]);
let res = crate::integrate::engine::integrate(integrand, x, &pool);
assert!(
matches!(res, Err(IntegrationError::NonElementary(_))),
"got {res:?}"
);
}
#[test]
fn quintic_general_quadratic_base_galois_non_elementary() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![
pool.pow(x, pool.integer(5_i32)),
pool.mul(vec![pool.integer(5_i32), pool.pow(x, pool.integer(4_i32))]),
pool.mul(vec![pool.integer(10_i32), pool.pow(x, pool.integer(3_i32))]),
pool.mul(vec![pool.integer(10_i32), pool.pow(x, pool.integer(2_i32))]),
x,
]);
let sq = pool.func("sqrt", vec![p]);
let den = pool.add(vec![
pool.pow(x, pool.integer(2_i32)),
pool.mul(vec![pool.integer(2_i32), x]),
pool.integer(-1_i32),
]);
let integrand = pool.mul(vec![sq, pool.pow(den, pool.integer(-1_i32))]);
let res = crate::integrate::engine::integrate(integrand, x, &pool);
assert!(
matches!(res, Err(IntegrationError::NonElementary(_))),
"got {res:?}"
);
}
#[test]
fn quintic_general_quadratic_base_non_galois_non_elementary() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![
pool.pow(x, pool.integer(5_i32)),
pool.mul(vec![pool.integer(5_i32), pool.pow(x, pool.integer(4_i32))]),
pool.mul(vec![pool.integer(10_i32), pool.pow(x, pool.integer(3_i32))]),
pool.mul(vec![pool.integer(10_i32), pool.pow(x, pool.integer(2_i32))]),
pool.mul(vec![pool.integer(6_i32), x]),
pool.integer(3_i32),
]);
let sq = pool.func("sqrt", vec![p]);
let den = pool.add(vec![
pool.pow(x, pool.integer(2_i32)),
pool.mul(vec![pool.integer(2_i32), x]),
pool.integer(-1_i32),
]);
let integrand = pool.mul(vec![sq, pool.pow(den, pool.integer(-1_i32))]);
let res = crate::integrate::engine::integrate(integrand, x, &pool);
assert!(
matches!(res, Err(IntegrationError::NonElementary(_))),
"got {res:?}"
);
}
#[test]
fn genus1_first_kind_emits_ellipticf_via_engine() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let x3p1 = pool.add(vec![pool.pow(x, pool.integer(3_i32)), pool.integer(1_i32)]);
let sq = pool.func("sqrt", vec![x3p1]);
let integrand = pool.pow(sq, pool.integer(-1_i32));
let res = crate::integrate::engine::integrate(integrand, x, &pool)
.expect("∫dx/√(x³+1) should emit a first-kind EllipticF form");
let f = res.value;
assert!(
pool.display(f).to_string().contains("EllipticF"),
"expected EllipticF in {}",
pool.display(f)
);
let df = simplify(crate::diff::diff(f, x, &pool).unwrap().value, &pool).value;
let mut checked = 0;
for &xv in &[0.5_f64, 1.0, 2.0, 3.0] {
let lhs = eval_ell(df, x, xv, &pool).unwrap();
let rhs = 1.0 / (xv * xv * xv + 1.0).sqrt();
assert!(
(lhs - rhs).abs() < 1e-6 * (1.0 + rhs.abs()),
"x={xv}: d/dx F = {lhs}, integrand = {rhs}\n F = {}",
pool.display(f)
);
checked += 1;
}
assert!(checked >= 3);
}
#[test]
fn genus1_three_real_emits_ellipticf_via_engine() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![
pool.pow(x, pool.integer(3_i32)),
pool.mul(vec![pool.integer(-1_i32), x]),
]);
let sq = pool.func("sqrt", vec![p]);
let integrand = pool.pow(sq, pool.integer(-1_i32));
let res = crate::integrate::engine::integrate(integrand, x, &pool)
.expect("∫dx/√(x³−x) should emit EllipticF");
let f = res.value;
assert!(
pool.display(f).to_string().contains("EllipticF"),
"{}",
pool.display(f)
);
let df = simplify(crate::diff::diff(f, x, &pool).unwrap().value, &pool).value;
for &xv in &[1.2_f64, 2.0, 4.0] {
let lhs = eval_ell(df, x, xv, &pool).unwrap();
let rhs = 1.0 / (xv * xv * xv - xv).sqrt();
assert!(
(lhs - rhs).abs() < 1e-6 * (1.0 + rhs.abs()),
"x={xv}: d/dx F = {lhs}, integrand = {rhs}"
);
}
}
#[test]
fn genus1_quartic_emits_ellipticf_via_engine() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![
pool.integer(1_i32),
pool.mul(vec![pool.integer(-1_i32), pool.pow(x, pool.integer(4_i32))]),
]);
let sq = pool.func("sqrt", vec![p]);
let integrand = pool.pow(sq, pool.integer(-1_i32));
let res = crate::integrate::engine::integrate(integrand, x, &pool)
.expect("∫dx/√(1−x⁴) should emit EllipticF");
let f = res.value;
assert!(
pool.display(f).to_string().contains("EllipticF"),
"{}",
pool.display(f)
);
let df = simplify(crate::diff::diff(f, x, &pool).unwrap().value, &pool).value;
for &xv in &[-0.8_f64, -0.2, 0.3, 0.8] {
let lhs = eval_ell(df, x, xv, &pool).unwrap();
let rhs = 1.0 / (1.0 - xv.powi(4)).sqrt();
assert!(
(lhs - rhs).abs() < 1e-6 * (1.0 + rhs.abs()),
"x={xv}: d/dx F = {lhs}, integrand = {rhs}"
);
}
}
#[test]
fn quintic_first_kind_still_non_elementary_via_engine() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let p = pool.add(vec![pool.pow(x, pool.integer(5_i32)), pool.integer(1_i32)]);
let sq = pool.func("sqrt", vec![p]);
let integrand = pool.pow(sq, pool.integer(-1_i32));
let res = crate::integrate::engine::integrate(integrand, x, &pool);
assert!(
matches!(res, Err(IntegrationError::NonElementary(_))),
"∫dx/√(x⁵+1) must stay NonElementary, got {res:?}"
);
}
fn eval_ell(expr: ExprId, x: ExprId, xv: f64, pool: &ExprPool) -> Option<f64> {
if expr == x {
return Some(xv);
}
match pool.get(expr) {
ExprData::Integer(n) => Some(n.0.to_f64()),
ExprData::Rational(r) => Some(r.0.to_f64()),
ExprData::Add(args) => args
.iter()
.try_fold(0.0, |s, &a| Some(s + eval_ell(a, x, xv, pool)?)),
ExprData::Mul(args) => args
.iter()
.try_fold(1.0, |s, &a| Some(s * eval_ell(a, x, xv, pool)?)),
ExprData::Pow { base, exp } => {
Some(eval_ell(base, x, xv, pool)?.powf(eval_ell(exp, x, xv, pool)?))
}
ExprData::Func { ref name, ref args } if args.len() == 1 => {
let v = eval_ell(args[0], x, xv, pool)?;
match name.as_str() {
"sin" => Some(v.sin()),
"cos" => Some(v.cos()),
"tan" => Some(v.tan()),
"asin" => Some(v.asin()),
"acos" => Some(v.acos()),
"atan" => Some(v.atan()),
"sqrt" => Some(v.sqrt()),
"log" => Some(v.ln()),
"exp" => Some(v.exp()),
"cbrt" => Some(v.cbrt()),
_ => None,
}
}
_ => None,
}
}
}