use symplex::prelude::*;
fn p(ctx: &Context, s: &str) -> Ex {
ctx.parse(s).unwrap_or_else(|e| panic!("{s}: {e}"))
}
fn show(e: &Ex) -> String {
format!("{e}")
}
#[test]
fn substitution_at_a_removable_singularity_is_undefined_not_the_limit() {
let ctx = Context::new();
let x = ctx.symbol("x");
let zero = ctx.int(0);
let cases: [(&str, Ex, &str); 10] = [
("sin(8*x)/(8*sin(x))", zero.clone(), "1/8"),
("(1-cos(x))/(1-cos(2*x))", zero.clone(), "1"),
("(exp(x)-1)/(exp(2*x)-1)", zero.clone(), "1"),
("(1-cos(x))/x^2", zero.clone(), "zoo"),
("ln(cos(x))/ln(cos(2*x))", zero.clone(), "1"),
("sinh(2*x)/sinh(x)", zero.clone(), "1"),
("x*ln(x)", zero.clone(), "0"),
("tan(x)*cos(x)", ctx.pi() / 2, "cos(1/2*pi)*tan(1/2*pi)"),
("atanh(x)-atanh(x^2)", ctx.int(1), "0"),
("gamma(x)*sin(pi*x)", zero.clone(), "sin(0)*Gamma(0)"),
];
for (s, at, before) in cases {
let v = p(&ctx, s).subs(&x, &at);
assert_eq!(show(&v), "nan", "{s} at x = {at} (before 0.30: {before})");
}
assert_eq!(show(&p(&ctx, "sin(x)/sin(x)").subs(&x, &zero)), "1");
}
#[test]
fn constant_applications_fold_at_construction() {
let ctx = Context::new();
let cases = [
("ln(0)", "zoo"),
("sin(0)", "0"),
("ln(0)/ln(0)", "nan"),
("ln(0)-ln(0)", "nan"),
("cos(pi/2)", "0"),
("tan(pi/2)", "zoo"),
("gamma(0)", "zoo"),
("gamma(-3)", "zoo"),
("ln(1)", "0"),
("exp(0)", "1"),
("exp(-oo)", "0"),
("exp(oo)", "oo"),
("exp(zoo)", "nan"),
("atanh(1)", "oo"),
("atanh(-1)", "-oo"),
("sinh(I*pi)", "0"),
("cosh(I*pi/2)", "0"),
("tanh(I*pi/2)", "zoo"),
("acosh(1)", "0"),
("asin(0)", "0"),
("loggamma(0)", "oo"),
("loggamma(1)", "0"),
("digamma(0)", "zoo"),
("erfc(oo)", "0"),
("LambertW(0)", "0"),
("LambertW(-exp(-1))", "-1"),
("floor(1/2)", "0"),
("factorial(-1)", "zoo"),
("binomial(2,3)", "0"),
("binomial(2,-1)", "0"),
("beta(0,1)", "zoo"),
("Heaviside(-1)", "0"),
("DiracDelta(1)", "0"),
("besselj(1,0)", "0"),
("bessely(0,0)", "-oo"),
("besselk(0,0)", "oo"),
("erfinv(1)", "oo"),
("elliptic_k(1)", "zoo"),
("polylog(2,0)", "0"),
("cos(pi)", "-1"),
("sin(pi/6)", "1/2"),
];
let mut bad = Vec::new();
for (s, want) in cases {
let got = show(&p(&ctx, s));
if got != want {
bad.push(format!("{s}: got {got}, want {want}"));
}
}
assert!(bad.is_empty(), "{}", bad.join("\n"));
for (s, want) in [
("exp(I*pi/2)", "I"),
("exp(3*I*pi/2)", "-I"),
("sinh(I*pi/6)", "1/2*I"),
("tanh(I*pi/4)", "I"),
("sqrt(-I + sinh(I*pi/2))", "0"),
("exp(I*pi/3)", "exp(1/3*pi*I)"),
] {
assert_eq!(show(&p(&ctx, s)), want, "{s}");
}
assert_eq!(show(&p(&ctx, "atan(I)")), "I*oo");
let s = p(&ctx, "sin(pi/4)");
assert_eq!(show(&s), "sin(1/4*pi)");
assert_eq!(show(&s.eval()), "1/2*sqrt(2)");
let g = p(&ctx, "gamma(5)");
assert_eq!(show(&g), "Gamma(5)");
assert_eq!(show(&g.eval()), "24");
}
#[test]
fn a_function_of_an_exact_inverse_trig_value_folds() {
let ctx = Context::new();
for (s, want) in [
("tan(2*atan(1))", "zoo"),
("sin(2*asin(1))", "0"),
("cos(acos(0))", "0"),
("tan(asin(1))/tan(asin(1))", "nan"),
("exp(ln(-1))", "-1"),
("exp(2*ln(-1))", "1"),
("sinh(ln(-1))", "0"),
("sin(3*acos(1/2))", "0"),
("tan(atan(oo))", "zoo"),
("asin(1)", "asin(1)"),
] {
assert_eq!(show(&p(&ctx, s)), want, "{s}");
}
for (s, want) in [
("atan(oo)", "1/2*pi"),
("atan(-oo)", "-1/2*pi"),
("atanh(oo)", "-1/2*pi*I"),
("atanh(-oo)", "1/2*pi*I"),
] {
assert_eq!(show(&p(&ctx, s).eval()), want, "{s}");
}
}
#[test]
fn modulus_and_sign_of_exact_constants_fold() {
let ctx = Context::new();
for (s, want) in [
("abs(pi)", "pi"),
("sign(-2*E)", "-1"),
("abs(1/sqrt(I))", "1"),
("abs((-1)^(1/3))", "1"),
("abs(exp(I*pi/3))", "1"),
("acosh(sin(abs(pi)/2))", "0"),
("abs(pi*I)", "pi"),
("abs(-2*pi*I)", "2*pi"),
("abs(sqrt(2)*I)", "sqrt(2)"),
("acosh(sin(abs(pi*I)/2))", "0"),
] {
assert_eq!(show(&p(&ctx, s)), want, "{s}");
}
assert!(
p(&ctx, "acos(asin(abs(1/sqrt(I))))")
.eval_complex64()
.is_ok()
);
}
#[test]
fn every_path_that_builds_a_node_folds() {
let ctx = Context::new();
let x = ctx.symbol("x");
let e = x.sinh() * x.gamma();
assert_eq!(show(&e.subs(&x, &ctx.int(0))), "nan");
assert_eq!(show(&x.atanh().subs(&x, &ctx.int(1))), "oo");
}
#[test]
fn exp_of_the_logarithm_of_zero_has_no_value() {
let ctx = Context::new();
let e = p(&ctx, "exp(ln(0))");
assert_eq!(show(&e), "nan");
let hidden = p(&ctx, "(1+sqrt(2))^2-3-2*sqrt(2)");
assert_eq!(hidden.eval_decimal(20).as_deref().ok(), Some("0"));
let e = hidden.ln().exp();
assert!(e.eval_decimal(20).is_err(), "{:?}", e.eval_decimal(20));
assert!(e.eval_f64().is_err());
assert_eq!(show(&p(&ctx, "exp(ln(pi))").eval()), "pi");
assert_eq!(show(&p(&ctx, "exp(ln(x))").eval()), "x");
}
#[test]
fn atan2_of_the_origin_is_undefined() {
let ctx = Context::new();
assert_eq!(show(&p(&ctx, "atan2(0, 0)")), "nan");
let e = p(&ctx, "atan2(sin(pi/4) - cos(pi/4), 0)");
assert_eq!(show(&e.eval()), "nan", "before 0.30: 0");
assert_eq!(show(&p(&ctx, "atan2(0, 1)")), "0");
assert_eq!(show(&p(&ctx, "atan2(0, -1)").eval()), "pi");
}
#[test]
fn zero_to_a_non_numeric_power() {
let ctx = Context::new();
for (s, want) in [
("0^pi", "0"),
("0^sqrt(2)", "0"),
("0^(-pi)", "zoo"),
("0^I", "nan"),
("0^(2*I)", "nan"),
("0^(1+I)", "nan"),
("0^(-1+I)", "nan"),
] {
assert_eq!(show(&p(&ctx, s)), want, "{s}");
}
assert_eq!(show(&p(&ctx, "0^x")), "0^x");
let pos = ctx.symbol_with("q", &[Assumption::Positive]).unwrap();
assert_eq!(show(&ctx.int(0).pow(&pos)), "0");
}
#[test]
fn infinity_times_a_factor_of_unknown_sign_is_complex_infinity() {
let ctx = Context::new();
let x = ctx.symbol("x");
let oo = ctx.infinity();
assert_eq!(show(&(&x * &oo)), "x*oo");
assert_eq!(show(&(&x * &oo).subs(&x, &ctx.int(-1))), "-oo");
assert_eq!(show(&(ctx.i_unit() * &oo)), "I*oo");
assert_eq!(show(&p(&ctx, "ln(2)*oo")), "oo");
assert_eq!(show(&p(&ctx, "(3-pi)*oo")), "-oo");
assert_eq!(show(&p(&ctx, "pi*oo")), "oo");
let q = ctx.symbol_with("q", &[Assumption::Negative]).unwrap();
assert_eq!(show(&(&q * &oo)), "-oo");
}
#[test]
fn evalf_resolves_a_deep_cancellation_instead_of_printing_zero() {
let ctx = Context::new();
let x = ctx.symbol("x");
let r = p(&ctx, "exp(exp(2*I*x))").rewrite_as_trig();
let at = ctx.rational(1, 3) + ctx.i_unit() * 4;
let v = r.subs(&x, &at).eval_complex64().expect("evaluates");
let want = Complex64::new(1.000_263_649_039_367_2, 0.000_207_494_653_475_547_3);
assert!((v - want).norm() < 1e-14, "{v}");
let s = r.subs(&x, &at).eval_decimal(30).expect("evaluates");
assert_eq!(
s,
"1.00026364903936722788163162223 + 0.000207494653475547299727957721481*i"
);
}
#[test]
fn a_combined_infinite_factor_is_multiplied_like_a_written_one() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = ctx.apply("f", &[&x]).unwrap();
let s = ctx.neg_infinity().sqrt();
let e = &s * &(&(&x * &f.sin()) * &s);
assert_eq!(e, -(&x * &f.sin() * ctx.infinity()));
assert_eq!(ctx.parse(&e.to_string()).unwrap(), e);
assert_eq!(&(&ctx.int(2) * &s) * &s, ctx.neg_infinity());
}