diff --git a/src/main/java/com/thealgorithms/physics/ProjectileMotion.java b/src/main/java/com/thealgorithms/physics/ProjectileMotion.java new file mode 100644 index 000000000000..3651e534f9d1 --- /dev/null +++ b/src/main/java/com/thealgorithms/physics/ProjectileMotion.java @@ -0,0 +1,96 @@ +package com.thealgorithms.physics; + +/** + * + * This implementation calculates the flight path of a projectile launched from any INITIAL HEIGHT. + * It is a more flexible version of the ground-to-ground model. + * + * @see Wikipedia - Projectile Motion + * @author [Priyanshu Kumar Singh](https://github.com/Priyanshu1303d) + */ +public final class ProjectileMotion { + + private ProjectileMotion() { + } + + /** Standard Earth gravity constant*/ + private static final double GRAVITY = 9.80665; + + /** + * A simple container for the results of a projectile motion calculation. + */ + public static final class Result { + private final double timeOfFlight; + private final double horizontalRange; + private final double maxHeight; + + public Result(double timeOfFlight, double horizontalRange, double maxHeight) { + this.timeOfFlight = timeOfFlight; + this.horizontalRange = horizontalRange; + this.maxHeight = maxHeight; + } + + /** @return The total time the projectile is in the air (seconds). */ + public double getTimeOfFlight() { + return timeOfFlight; + } + + /** @return The total horizontal distance traveled (meters). */ + public double getHorizontalRange() { + return horizontalRange; + } + + /** @return The maximum vertical height from the ground (meters). */ + public double getMaxHeight() { + return maxHeight; + } + } + + /** + * Calculates projectile trajectory using standard Earth gravity. + * + * @param initialVelocity Initial speed of the projectile (m/s). + * @param launchAngleDegrees Launch angle from the horizontal (degrees). + * @param initialHeight Starting height of the projectile (m). + * @return A {@link Result} object with the trajectory data. + */ + public static Result calculateTrajectory(double initialVelocity, double launchAngleDegrees, double initialHeight) { + return calculateTrajectory(initialVelocity, launchAngleDegrees, initialHeight, GRAVITY); + } + + /** + * Calculates projectile trajectory with a custom gravity value. + * + * @param initialVelocity Initial speed (m/s). Must be non-negative. + * @param launchAngleDegrees Launch angle (degrees). + * @param initialHeight Starting height (m). Must be non-negative. + * @param gravity Acceleration due to gravity (m/s^2). Must be positive. + * @return A {@link Result} object with the trajectory data. + */ + public static Result calculateTrajectory(double initialVelocity, double launchAngleDegrees, double initialHeight, double gravity) { + if (initialVelocity < 0 || initialHeight < 0 || gravity <= 0) { + throw new IllegalArgumentException("Velocity, height, and gravity must be non-negative, and gravity must be positive."); + } + + double launchAngleRadians = Math.toRadians(launchAngleDegrees); + double v_iy = initialVelocity * Math.sin(launchAngleRadians); // Initial vertical velocity + double v_ix = initialVelocity * Math.cos(launchAngleRadians); // Initial horizontal velocity + + // Correctly calculate total time of flight using the quadratic formula for vertical motion. + // y(t) = y0 + v_iy*t - 0.5*g*t^2. We solve for t when y(t) = 0. + double totalTimeOfFlight = (v_iy + Math.sqrt(v_iy * v_iy + 2 * gravity * initialHeight)) / gravity; + + // Calculate max height. If launched downwards, max height is the initial height. + double maxHeight; + if (v_iy > 0) { + double heightGained = (v_iy * v_iy) / (2 * gravity); + maxHeight = initialHeight + heightGained; + } else { + maxHeight = initialHeight; + } + + double horizontalRange = v_ix * totalTimeOfFlight; + + return new Result(totalTimeOfFlight, horizontalRange, maxHeight); + } +} diff --git a/src/test/java/com/thealgorithms/physics/ProjectileMotionTest.java b/src/test/java/com/thealgorithms/physics/ProjectileMotionTest.java new file mode 100644 index 000000000000..25e7cfa87f9e --- /dev/null +++ b/src/test/java/com/thealgorithms/physics/ProjectileMotionTest.java @@ -0,0 +1,69 @@ +package com.thealgorithms.physics; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Test class for the general-purpose ProjectileMotion calculator. + * + */ +final class ProjectileMotionTest { + + private static final double DELTA = 1e-4; // Tolerance for comparing double values + + @Test + @DisplayName("Test ground-to-ground launch (initial height is zero)") + void testGroundToGroundLaunch() { + ProjectileMotion.Result result = ProjectileMotion.calculateTrajectory(50, 30, 0); + assertEquals(5.0986, result.getTimeOfFlight(), DELTA); + assertEquals(220.7750, result.getHorizontalRange(), DELTA); + assertEquals(31.8661, result.getMaxHeight(), DELTA); + } + + @Test + @DisplayName("Test launch from an elevated position") + void testElevatedLaunch() { + ProjectileMotion.Result result = ProjectileMotion.calculateTrajectory(30, 45, 100); + assertEquals(7.1705, result.getTimeOfFlight(), DELTA); + assertEquals(152.1091, result.getHorizontalRange(), DELTA); + assertEquals(122.9436, result.getMaxHeight(), DELTA); // Final corrected value + } + + @Test + @DisplayName("Test launch straight up (90 degrees)") + void testVerticalLaunch() { + ProjectileMotion.Result result = ProjectileMotion.calculateTrajectory(40, 90, 20); + assertEquals(8.6303, result.getTimeOfFlight(), DELTA); + assertEquals(0.0, result.getHorizontalRange(), DELTA); + assertEquals(101.5773, result.getMaxHeight(), DELTA); + } + + @Test + @DisplayName("Test horizontal launch from a height (0 degrees)") + void testHorizontalLaunch() { + ProjectileMotion.Result result = ProjectileMotion.calculateTrajectory(25, 0, 80); + assertEquals(4.0392, result.getTimeOfFlight(), DELTA); + assertEquals(100.9809, result.getHorizontalRange(), DELTA); + assertEquals(80.0, result.getMaxHeight(), DELTA); + } + + @Test + @DisplayName("Test downward launch from a height (negative angle)") + void testDownwardLaunchFromHeight() { + ProjectileMotion.Result result = ProjectileMotion.calculateTrajectory(20, -30, 100); + assertEquals(3.6100, result.getTimeOfFlight(), DELTA); + assertEquals(62.5268, result.getHorizontalRange(), DELTA); + assertEquals(100.0, result.getMaxHeight(), DELTA); + } + + @Test + @DisplayName("Test invalid arguments throw an exception") + void testInvalidInputs() { + assertThrows(IllegalArgumentException.class, () -> ProjectileMotion.calculateTrajectory(-10, 45, 100)); + assertThrows(IllegalArgumentException.class, () -> ProjectileMotion.calculateTrajectory(10, 45, -100)); + assertThrows(IllegalArgumentException.class, () -> ProjectileMotion.calculateTrajectory(10, 45, 100, 0)); + } +} diff --git a/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java b/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java index 2a3a84b5ceea..ed8b7725f379 100644 --- a/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java +++ b/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java @@ -56,8 +56,9 @@ void testReproducibility() { void testNegativeInterval() { // Integral of f(x) = x from -1 to 1 is 0 Function linear = Function.identity(); - double result = approximate(linear, -1, 1, 10000); - assertEquals(0.0, result, EPSILON); + // The integral of x from 2 to 1 is -1.5 + double result = approximate(linear, 2, 1, 10000); + assertEquals(-1.5, result, EPSILON); } @Test