diff --git a/tests/function.rs b/tests/function.rs index 42be887..c2623aa 100644 --- a/tests/function.rs +++ b/tests/function.rs @@ -271,3 +271,252 @@ fn middle_function() { fn right_function() { do_test(Riemann::Right, 0.8800000000000001); } + +#[test] +fn test_extrema() { + let mut settings = app_settings_constructor(Riemann::Middle, -2.0, 2.0, 100, 100, -2.0, 2.0); + settings.do_extrema = true; + + let mut function = FunctionEntry::default(); + function.update_string("x^2 - 4"); // Parabola with vertex at (0, -4) + function.integral = false; + function.derivative = false; + + function.calculate(true, true, false, settings); + + // For f(x) = x^2 - 4, f'(x) = 2x + // Extrema occurs where f'(x) = 0, so at x = 0 + assert!(!function.extrema_data.is_empty()); + + // Should have exactly one extremum at x = 0 + assert_eq!(function.extrema_data.len(), 1); + + let extremum = function.extrema_data[0]; + assert!(emath::almost_equal(extremum.x as f32, 0.0, f32::EPSILON)); + assert!(emath::almost_equal(extremum.y as f32, -4.0, f32::EPSILON)); +} + +#[test] +fn test_extrema_multiple() { + let mut settings = app_settings_constructor(Riemann::Middle, -3.0, 3.0, 200, 200, -3.0, 3.0); + settings.do_extrema = true; + + let mut function = FunctionEntry::default(); + function.update_string("x^3 - 3*x"); // Cubic with local max and min + function.integral = false; + function.derivative = false; + + function.calculate(true, true, false, settings); + + // For f(x) = x^3 - 3x, f'(x) = 3x^2 - 3 + // Extrema occur where f'(x) = 0, so at x = ±1 + assert!(!function.extrema_data.is_empty()); + + // Should have exactly two extrema + assert_eq!(function.extrema_data.len(), 2); + + // Sort by x coordinate for consistent testing + let mut extrema = function.extrema_data.clone(); + extrema.sort_by(|a, b| a.x.partial_cmp(&b.x).unwrap()); + + // First extremum at x = -1, f(-1) = -1 + 3 = 2 + assert!(emath::almost_equal(extrema[0].x as f32, -1.0, 0.01)); + assert!(emath::almost_equal(extrema[0].y as f32, 2.0, 0.01)); + + // Second extremum at x = 1, f(1) = 1 - 3 = -2 + assert!(emath::almost_equal(extrema[1].x as f32, 1.0, 0.01)); + assert!(emath::almost_equal(extrema[1].y as f32, -2.0, 0.01)); +} + +#[test] +fn test_extrema_disabled() { + let mut settings = app_settings_constructor(Riemann::Middle, -2.0, 2.0, 100, 100, -2.0, 2.0); + settings.do_extrema = false; // Disable extrema + + let mut function = FunctionEntry::default(); + function.update_string("x^2 - 4"); + function.integral = false; + function.derivative = false; + + function.calculate(true, true, false, settings); + + // Extrema data should be empty when disabled + assert!(function.extrema_data.is_empty()); +} + +#[test] +fn test_roots() { + let mut settings = app_settings_constructor(Riemann::Middle, -3.0, 3.0, 200, 200, -3.0, 3.0); + settings.do_roots = true; + + let mut function = FunctionEntry::default(); + function.update_string("x^2 - 4"); // Parabola crossing x-axis at ±2 + function.integral = false; + function.derivative = false; + + function.calculate(true, true, false, settings); + + // For f(x) = x^2 - 4, roots occur where x^2 = 4, so at x = ±2 + assert!(!function.root_data.is_empty()); + + // Should have exactly two roots + assert_eq!(function.root_data.len(), 2); + + // Sort by x coordinate for consistent testing + let mut roots = function.root_data.clone(); + roots.sort_by(|a, b| a.x.partial_cmp(&b.x).unwrap()); + + // First root at x = -2 + assert!(emath::almost_equal(roots[0].x as f32, -2.0, 0.01)); + assert!(emath::almost_equal(roots[0].y as f32, 0.0, 0.001)); + + // Second root at x = 2 + assert!(emath::almost_equal(roots[1].x as f32, 2.0, 0.01)); + assert!(emath::almost_equal(roots[1].y as f32, 0.0, 0.001)); +} + +#[test] +fn test_roots_single() { + let mut settings = app_settings_constructor(Riemann::Middle, -2.0, 2.0, 100, 100, -2.0, 2.0); + settings.do_roots = true; + + let mut function = FunctionEntry::default(); + function.update_string("x - 1"); // Linear function crossing x-axis at x = 1 + function.integral = false; + function.derivative = false; + + function.calculate(true, true, false, settings); + + // For f(x) = x - 1, root occurs at x = 1 + assert!(!function.root_data.is_empty()); + + // Should have exactly one root + assert_eq!(function.root_data.len(), 1); + + let root = function.root_data[0]; + assert!(emath::almost_equal(root.x as f32, 1.0, 0.01)); + assert!(emath::almost_equal(root.y as f32, 0.0, f32::EPSILON)); +} + +#[test] +fn test_roots_disabled() { + let mut settings = app_settings_constructor(Riemann::Middle, -3.0, 3.0, 200, 200, -3.0, 3.0); + settings.do_roots = false; // Disable roots + + let mut function = FunctionEntry::default(); + function.update_string("x^2 - 4"); + function.integral = false; + function.derivative = false; + + function.calculate(true, true, false, settings); + + // Root data should be empty when disabled + assert!(function.root_data.is_empty()); +} + +#[test] +fn test_extrema_and_roots_together() { + let mut settings = app_settings_constructor(Riemann::Middle, -3.0, 3.0, 200, 200, -3.0, 3.0); + settings.do_extrema = true; + settings.do_roots = true; + + let mut function = FunctionEntry::default(); + function.update_string("x^2 - 1"); // Parabola with vertex at (0, -1) and roots at ±1 + function.integral = false; + function.derivative = false; + + function.calculate(true, true, false, settings); + + // Should have one extremum at x = 0 + assert!(!function.extrema_data.is_empty()); + assert_eq!(function.extrema_data.len(), 1); + let extremum = function.extrema_data[0]; + assert!(emath::almost_equal(extremum.x as f32, 0.0, 0.01)); + assert!(emath::almost_equal(extremum.y as f32, -1.0, 0.01)); + + // Should have two roots at x = ±1 + assert!(!function.root_data.is_empty()); + assert_eq!(function.root_data.len(), 2); + + let mut roots = function.root_data.clone(); + roots.sort_by(|a, b| a.x.partial_cmp(&b.x).unwrap()); + + assert!(emath::almost_equal(roots[0].x as f32, -1.0, 0.01)); + assert!(emath::almost_equal(roots[1].x as f32, 1.0, 0.01)); +} + +#[test] +fn test_extrema_no_extrema() { + let mut settings = app_settings_constructor(Riemann::Middle, -2.0, 2.0, 100, 100, -2.0, 2.0); + settings.do_extrema = true; + + let mut function = FunctionEntry::default(); + function.update_string("x"); // Linear function has no extrema + function.integral = false; + function.derivative = false; + + function.calculate(true, true, false, settings); + + // Linear function should have no extrema + assert!(function.extrema_data.is_empty()); +} + +#[test] +fn test_roots_no_roots() { + let mut settings = app_settings_constructor(Riemann::Middle, -2.0, 2.0, 100, 100, -2.0, 2.0); + settings.do_roots = true; + + let mut function = FunctionEntry::default(); + function.update_string("x^2 + 1"); // Parabola that never crosses x-axis + function.integral = false; + function.derivative = false; + + function.calculate(true, true, false, settings); + + // Function that never crosses x-axis should have no roots + assert!(function.root_data.is_empty()); +} + +#[test] +fn test_extrema_and_roots_with_trig() { + let mut settings = app_settings_constructor(Riemann::Middle, -4.0, 4.0, 300, 300, -4.0, 4.0); + settings.do_extrema = true; + settings.do_roots = true; + + let mut function = FunctionEntry::default(); + function.update_string("sin(x)"); // Sine function has extrema at odd multiples of π/2 + function.integral = false; + function.derivative = false; + + function.calculate(true, true, false, settings); + + // Sine function should have extrema in the given range + assert!(!function.extrema_data.is_empty()); + + // Should have multiple extrema (local max/min) + assert!(function.extrema_data.len() >= 2); + + // Check that extrema are at approximately the right locations + // Local max at π/2 ≈ 1.57, local min at 3π/2 ≈ 4.71 (outside range) + // Local min at -π/2 ≈ -1.57, local max at -3π/2 ≈ -4.71 (outside range) + let extrema_x: Vec = function.extrema_data.iter().map(|p| p.x as f32).collect(); + + // Should have extrema near ±π/2 + assert!(extrema_x + .iter() + .any(|&x| emath::almost_equal(x, std::f32::consts::PI / 2.0, 0.1))); + assert!(extrema_x + .iter() + .any(|&x| emath::almost_equal(x, -std::f32::consts::PI / 2.0, 0.1))); + + let roots_x: Vec = function.root_data.iter().map(|p| p.x as f32).collect(); + + assert!(roots_x + .iter() + .any(|&x| emath::almost_equal(x, std::f32::consts::PI, 0.1))); + assert!(roots_x + .iter() + .any(|&x| emath::almost_equal(x, -std::f32::consts::PI, 0.1))); + + assert!(roots_x.iter().any(|&x| emath::almost_equal(x, 0.0, 0.1))); +}