use symplex::prelude::*;
use symplex::syms;
#[test]
fn diff_integer_is_zero() {
let ctx = Context::new();
let x = ctx.symbol("x");
let five = ctx.int(5);
let result = five.diff(&x);
assert!(result.is_zero_structural(), "d/dx(5) should be 0");
}
#[test]
fn diff_pi_is_zero() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = ctx.pi().diff(&x);
assert!(result.is_zero_structural(), "d/dx(pi) should be 0");
}
#[test]
fn diff_e_is_zero() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = ctx.e().diff(&x);
assert!(result.is_zero_structural(), "d/dx(e) should be 0");
}
#[test]
fn diff_other_symbol_is_zero() {
let ctx = Context::new();
syms!(ctx; x, y);
let result = y.diff(&x);
assert!(result.is_zero_structural(), "d/dx(y) should be 0");
}
#[test]
fn diff_x_wrt_x_is_one() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x.diff(&x);
assert!(result.is_one_structural(), "d/dx(x) should be 1");
}
#[test]
fn diff_sum_is_sum_of_derivatives() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x + 3;
let result = expr.diff(&x);
assert_eq!(format!("{result}"), "1");
}
#[test]
fn diff_2x_is_2() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x * 2;
let result = expr.diff(&x);
assert_eq!(format!("{result}"), "2");
}
#[test]
fn diff_3x_plus_5_is_3() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x * 3 + 5;
let result = expr.diff(&x);
assert_eq!(format!("{result}"), "3");
}
#[test]
fn diff_x_squared() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x.powi(2).diff(&x);
assert_eq!(format!("{result}"), "2*x");
}
#[test]
fn diff_x_cubed() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x.powi(3).diff(&x);
assert_eq!(format!("{result}"), "3*x^2");
}
#[test]
fn diff_x_to_the_fourth() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x.powi(4).diff(&x);
assert_eq!(format!("{result}"), "4*x^3");
}
#[test]
fn diff_x_to_the_minus_one() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x.powi(-1).diff(&x);
let s = format!("{result}");
assert!(
s.contains("-1") || s.contains("-x"),
"d/dx(1/x) should be negative, got: {s}"
);
assert!(s.contains("x"), "should contain x, got: {s}");
let val = result
.subs(&x, &ctx.int(2))
.eval_f64()
.expect("derivative of 1/x should evaluate at x=2");
assert!(
(val - (-0.25)).abs() < 1e-10,
"d/dx(1/x) at x=2: expected -0.25, got {val}"
);
}
#[test]
fn diff_x_times_y() {
let ctx = Context::new();
syms!(ctx; x, y);
let expr = &x * &y;
let result = expr.diff(&x);
assert_eq!(format!("{result}"), "y");
}
#[test]
fn diff_x_times_sin_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x * &x.sin();
let result = expr.diff(&x);
let s = format!("{result}");
assert!(s.contains("sin"), "should contain sin(x), got: {s}");
assert!(s.contains("cos"), "should contain cos(x), got: {s}");
}
#[test]
fn diff_product_three_symbols() {
let ctx = Context::new();
syms!(ctx; x, y, z);
let expr = &(&x * &y) * &z;
let result = expr.diff(&x);
assert_eq!(format!("{result}"), "y*z");
}
#[test]
fn diff_sin_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x.sin().diff(&x);
assert_eq!(format!("{result}"), "cos(x)");
}
#[test]
fn diff_cos_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x.cos().diff(&x);
assert_eq!(format!("{result}"), "-sin(x)");
}
#[test]
fn diff_tan_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x.tan().diff(&x);
let s = format!("{result}");
assert!(
s.contains("tan"),
"d/dx(tan(x)) should involve tan, got: {s}"
);
let val = result
.subs(&x, &ctx.rational(1, 2))
.eval_f64()
.expect("derivative of tan(x) should evaluate at x=1/2");
let expected = 1.0 / (0.5f64.cos().powi(2));
assert!(
(val - expected).abs() < 1e-8,
"d/dx(tan(x)) at x=0.5: expected {expected}, got {val}"
);
}
#[test]
fn diff_sin_of_x_squared() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x.powi(2).sin().diff(&x);
let val = result
.subs(&x, &ctx.int(1))
.eval_f64()
.expect("derivative of sin(x²) should evaluate at x=1");
let expected = 2.0 * 1.0f64.cos();
assert!(
(val - expected).abs() < 1e-10,
"d/dx(sin(x²)) at x=1: expected {expected}, got {val}"
);
let s = format!("{result}");
assert!(s.contains("2"), "should contain 2, got: {s}");
assert!(s.contains("cos"), "should contain cos, got: {s}");
assert!(s.contains("x"), "should contain x, got: {s}");
}
#[test]
fn diff_cos_of_3x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let three_x = &x * 3;
let result = three_x.cos().diff(&x);
let s = format!("{result}");
assert!(s.contains("3"), "should contain 3, got: {s}");
assert!(s.contains("sin"), "should contain sin, got: {s}");
}
#[test]
fn diff_exp_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x.exp().diff(&x);
assert_eq!(format!("{result}"), "exp(x)");
}
#[test]
fn diff_ln_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x.ln().diff(&x);
assert_eq!(format!("{result}"), "1/x");
}
#[test]
fn diff_exp_of_2x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let two_x = &x * 2;
let result = two_x.exp().diff(&x);
let s = format!("{result}");
assert!(s.contains("2"), "should contain 2, got: {s}");
assert!(s.contains("exp"), "should contain exp, got: {s}");
}
#[test]
fn diff_ln_of_x_squared() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x.powi(2).ln().diff(&x);
let s = format!("{result}");
assert!(s.contains("2"), "should contain 2, got: {s}");
assert!(s.contains("x"), "should contain x, got: {s}");
}
#[test]
fn diff_sqrt_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x.sqrt().diff(&x);
let val = result
.subs(&x, &ctx.int(4))
.eval_f64()
.expect("derivative of sqrt(x) should evaluate at x=4");
assert!(
(val - 0.25).abs() < 1e-10,
"d/dx(√x) at x=4: expected 0.25, got {val}"
);
let s = format!("{result}");
assert!(
s.contains("sqrt") || s.contains("1/2"),
"should involve sqrt or 1/2, got: {s}"
);
}
#[test]
fn diff_neg_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = (-&x).diff(&x);
assert_eq!(format!("{result}"), "-1");
}
#[test]
fn diff_neg_x_squared() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = (-&x.powi(2)).diff(&x);
assert_eq!(format!("{result}"), "-2*x");
}
#[test]
fn diff_quadratic() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x.powi(2) + &x * 2 + 1;
let result = expr.diff(&x);
let s = format!("{result}");
assert!(s.contains("2"), "should contain 2, got: {s}");
assert!(s.contains("x"), "should contain x, got: {s}");
}
#[test]
fn diff_cubic_polynomial() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x.powi(3) + &x.powi(2) * 2 + &x + 5;
let result = expr.diff(&x);
let s = format!("{result}");
assert!(s.contains("3*x^2"), "should contain 3*x^2, got: {s}");
assert!(s.contains("4*x"), "should contain 4*x, got: {s}");
assert!(s.contains('1'), "should contain 1, got: {s}");
}
#[test]
fn second_derivative_of_x_cubed() {
let ctx = Context::new();
let x = ctx.symbol("x");
let first = x.powi(3).diff(&x);
let second = first.diff(&x);
assert_eq!(format!("{second}"), "6*x");
}
#[test]
fn third_derivative_of_x_cubed_is_constant() {
let ctx = Context::new();
let x = ctx.symbol("x");
let first = x.powi(3).diff(&x);
let second = first.diff(&x);
let third = second.diff(&x);
assert_eq!(format!("{third}"), "6");
}
#[test]
fn fourth_derivative_of_x_cubed_is_zero() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x.powi(3).diff(&x).diff(&x).diff(&x).diff(&x);
assert!(
result.is_zero_structural(),
"d⁴/dx⁴(x³) should be 0, got: {result}"
);
}
#[test]
fn partial_derivative_x() {
let ctx = Context::new();
syms!(ctx; x, y);
let expr = &x.powi(2) * &y;
let result = expr.diff(&x);
assert_eq!(format!("{result}"), "2*x*y");
}
#[test]
fn partial_derivative_y() {
let ctx = Context::new();
syms!(ctx; x, y);
let expr = &x.powi(2) * &y;
let result = expr.diff(&y);
assert_eq!(format!("{result}"), "x^2");
}
#[test]
fn mixed_partial_derivative() {
let ctx = Context::new();
syms!(ctx; x, y);
let expr = &x.powi(2) * &y;
let result = expr.diff(&y).diff(&x);
assert_eq!(format!("{result}"), "2*x");
}
#[test]
fn diff_then_substitute() {
let ctx = Context::new();
let x = ctx.symbol("x");
let deriv = x.powi(2).diff(&x);
let result = deriv.subs(&x, &ctx.int(3));
assert_eq!(format!("{result}"), "6");
}
#[test]
fn diff_then_substitute_polynomial() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x.powi(3) + &x;
let deriv = expr.diff(&x);
let result = deriv.subs(&x, &ctx.int(2));
assert_eq!(format!("{result}"), "13");
}
#[test]
fn diff_zero_is_zero() {
let ctx = Context::new();
let x = ctx.symbol("x");
let zero = ctx.int(0);
assert!(zero.diff(&x).is_zero_structural());
}
#[test]
fn diff_one_is_zero() {
let ctx = Context::new();
let x = ctx.symbol("x");
let one = ctx.int(1);
assert!(one.diff(&x).is_zero_structural());
}
#[test]
fn diff_constant_sum_is_zero() {
let ctx = Context::new();
let x = ctx.symbol("x");
syms!(ctx; y, z);
let expr = &y + &z + 5;
assert!(
expr.diff(&x).is_zero_structural(),
"d/dx(y + z + 5) should be 0"
);
}
#[test]
fn diff_preserves_assumptions() {
let ctx = Context::new();
let x = ctx.symbol_with("x", &[Assumption::Positive, Assumption::Real]);
let deriv = x.powi(2).diff(&x);
assert_eq!(
ctx.query(&deriv, Props::POSITIVE),
Some(true),
"2*x should be positive when x is positive"
);
}