use super::common;
use symplex::prelude::*;
#[test]
fn integrate_completing_square() {
let ctx = Context::new();
let x = ctx.symbol("x");
let two = ctx.int(2);
let five = ctx.int(5);
let x2 = x.powi(2);
let quadratic = &x2 + &(&two * &x) + &five;
let integrand = quadratic.powi(-1);
let result = integrand.integrate(&x);
let s = format!("{result}");
assert!(
s.contains("atan"),
"∫ 1/(x²+2x+5) dx should use atan, got: {s}"
);
assert!(!s.contains("Integral"), "should not be unevaluated: {s}");
common::assert_ftc(&integrand, &x, "1/(x²+2x+5)");
}
#[test]
fn integrate_1_over_x2_plus_4() {
let ctx = Context::new();
let x = ctx.symbol("x");
let four = ctx.int(4);
let base = x.powi(2) + &four;
let integrand = base.powi(-1);
let result = integrand.integrate(&x);
let s = format!("{result}");
assert!(
s.contains("atan"),
"∫ 1/(x²+4) dx should use atan, got: {s}"
);
assert!(!s.contains("Integral"), "should not be unevaluated: {s}");
common::assert_ftc(&integrand, &x, "1/(x²+4)");
}
#[test]
fn integrate_rational_function() {
let ctx = Context::new();
let x = ctx.symbol("x");
let numer = ctx.int(2) * &x + ctx.int(3);
let denom = x.powi(2) + ctx.int(2) * &x + ctx.int(5);
let integrand = &numer / &denom;
let result = integrand.integrate(&x);
let s = format!("{result}");
assert!(
!s.contains("Integral"),
"∫ (2x+3)/(x²+2x+5) dx should not be unevaluated: {s}"
);
common::assert_ftc(&integrand, &x, "(2x+3)/(x²+2x+5)");
}
#[test]
fn integrate_weierstrass_simple() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = (ctx.int(1) + x.sin()).powi(-1);
let result = integrand.integrate(&x);
let s = format!("{result}");
if s.contains("Integral") {
eprintln!(
"NOTE: Weierstrass substitution did not fully resolve for \
1/(1+sin(x)). Got: {s}"
);
} else {
common::assert_ftc_tol(&integrand, &x, 1e-6, "1/(1+sin(x))");
}
}
#[test]
fn integrate_1_over_x2_plus_1() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = (x.powi(2) + ctx.int(1)).powi(-1);
let result = integrand.integrate(&x);
let s = format!("{result}");
assert!(
s.contains("atan"),
"∫ 1/(x²+1) dx should be atan(x), got: {s}"
);
common::assert_ftc(&integrand, &x, "1/(x²+1)");
}
#[test]
fn integrate_completing_square_x2_plus_x_plus_1() {
let ctx = Context::new();
let x = ctx.symbol("x");
let quadratic = x.powi(2) + &x + ctx.int(1);
let integrand = quadratic.powi(-1);
let result = integrand.integrate(&x);
let s = format!("{result}");
assert!(
s.contains("atan"),
"∫ 1/(x²+x+1) dx should use atan, got: {s}"
);
assert!(!s.contains("Integral"), "should not be unevaluated: {s}");
common::assert_ftc(&integrand, &x, "1/(x²+x+1)");
}
#[test]
fn ode_exact_simple() {
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let dy = y.formal_diff(&x);
let m = ctx.int(2) * &x + &y;
let n = &x + ctx.int(2) * &y;
let ode_expr = &m + &n * &dy;
let sol = ode_expr.solve_ode(&y, &x);
assert!(
!sol.has_unevaluated(),
"should solve exact ODE (2x+y) + (x+2y)y' = 0"
);
let s = format!("{sol}");
eprintln!("exact ODE (2x+y)+(x+2y)y' = 0 solution: {s}");
assert!(!s.is_empty(), "should produce a non-empty solution: {s}");
}
#[test]
fn ode_exact_verify() {
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let dy = y.formal_diff(&x);
let ode_expr = &y + &x * &dy;
let sol = ode_expr.solve_ode(&y, &x);
assert!(
!sol.has_unevaluated(),
"should solve y + x·y' = 0 (separable or exact)"
);
let s = format!("{sol}");
eprintln!("y + x·y' = 0 solution: {s}");
assert!(!s.is_empty(), "solution should be non-empty: {s}");
}
#[test]
fn ode_exact_non_trivial() {
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let dy = y.formal_diff(&x);
let m = x.powi(2) + &y;
let n = &x - y.powi(2);
let ode_expr = &m + &n * &dy;
let sol = ode_expr.solve_ode(&y, &x);
assert!(
!sol.has_unevaluated(),
"should solve exact ODE (x²+y) + (x−y²)y' = 0"
);
let s = format!("{sol}");
eprintln!("(x²+y)+(x−y²)y' = 0 solution: {s}");
assert!(!s.is_empty(), "solution string should not be empty");
}
#[test]
fn ode_exact_classification() {
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let dy = y.formal_diff(&x);
let m = ctx.int(2) * &x + &y;
let n = &x + ctx.int(2) * &y;
let ode = &m + &n * &dy;
let kind = ode.classify_ode(&y, &x);
eprintln!("(2x+y)+(x+2y)y' classified as: {kind:?}");
}
#[test]
fn ode_integrating_factor_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let dy = y.formal_diff(&x);
let ode_expr = &dy + &y / &x - &x;
let sol = ode_expr.solve_ode(&y, &x);
if !sol.has_unevaluated() {
let s = format!("{sol}");
eprintln!("y' + y/x = x solution: {s}");
assert!(s.contains("C1"), "should have constant C1: {s}");
} else {
eprintln!(
"NOTE: y' + y/x = x not yet solved (may need exp(ln(x)) \
simplification)"
);
}
}
#[test]
fn ode_existing_simple_separable() {
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let dy = y.formal_diff(&x);
let ode = &dy - &x;
let sol = ode.solve_ode(&y, &x);
assert!(!sol.has_unevaluated(), "should solve y' = x");
let s = format!("{sol}");
assert!(s.contains("C1"), "y' = x solution should have C1: {s}");
assert!(s.contains("x"), "y' = x solution should contain x: {s}");
}
#[test]
fn ode_existing_first_order_linear() {
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let dy = y.formal_diff(&x);
let ode = &dy + ctx.int(2) * &y;
let sol = ode.solve_ode(&y, &x);
assert!(!sol.has_unevaluated(), "should solve y' + 2y = 0");
let s = format!("{sol}");
assert!(s.contains("exp"), "y' + 2y = 0 should have exp: {s}");
assert!(s.contains("C1"), "y' + 2y = 0 should have C1: {s}");
}
#[test]
fn ode_existing_second_order_cc() {
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let dy = y.formal_diff(&x);
let d2y = dy.formal_diff(&x);
let ode = &d2y - ctx.int(3) * &dy + ctx.int(2) * &y;
let sol = ode.solve_ode(&y, &x);
assert!(!sol.has_unevaluated(), "should solve y'' − 3y' + 2y = 0");
let s = format!("{sol}");
assert!(
s.contains("C1") && s.contains("C2"),
"should have C1 and C2: {s}"
);
assert!(s.contains("exp"), "should contain exp: {s}");
}
#[test]
fn ode_existing_separable_xy() {
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let dy = y.formal_diff(&x);
let ode = &dy - &x * &y;
let sol = ode.solve_ode(&y, &x);
assert!(!sol.has_unevaluated(), "should solve y' − xy = 0");
let s = format!("{sol}");
assert!(s.contains("exp"), "y' = xy should have exp: {s}");
}
#[test]
fn ode_existing_variable_coeff_linear() {
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let dy = y.formal_diff(&x);
let ode = &dy + ctx.int(2) * &x * &y;
let sol = ode.solve_ode(&y, &x);
assert!(!sol.has_unevaluated(), "should solve y' + 2xy = 0");
let s = format!("{sol}");
assert!(s.contains("exp"), "y' + 2xy = 0 should have exp: {s}");
assert!(s.contains("C1"), "y' + 2xy = 0 should have C1: {s}");
}