From 29c18a39b7c52d1b376cdd96757b5eec46ac09ce Mon Sep 17 00:00:00 2001 From: the-yash-rajput Date: Thu, 16 Oct 2025 20:01:52 +0530 Subject: [PATCH 1/4] Added SimplePendulumRK4 --- .../physics/SimplePendulumRK4.java | 124 ++++++ .../physics/SimplePendulumRK4Test.java | 354 ++++++++++++++++++ 2 files changed, 478 insertions(+) create mode 100644 src/main/java/com/thealgorithms/physics/SimplePendulumRK4.java create mode 100644 src/test/java/com/thealgorithms/physics/SimplePendulumRK4Test.java diff --git a/src/main/java/com/thealgorithms/physics/SimplePendulumRK4.java b/src/main/java/com/thealgorithms/physics/SimplePendulumRK4.java new file mode 100644 index 000000000000..ef0d6262c650 --- /dev/null +++ b/src/main/java/com/thealgorithms/physics/SimplePendulumRK4.java @@ -0,0 +1,124 @@ +package com.thealgorithms.physics; + +/** + * Simulates a simple pendulum using the Runge-Kutta 4th order method. + * The pendulum is modeled with the nonlinear differential equation. + */ +public final class SimplePendulumRK4 { + + private SimplePendulumRK4() { + throw new AssertionError("No instances."); + } + + private final double length; // meters + private final double g; // acceleration due to gravity (m/s^2) + + /** + * Constructs a simple pendulum simulator. + * + * @param length the length of the pendulum in meters + * @param g the acceleration due to gravity in m/s^2 + */ + public SimplePendulumRK4(double length, double g) { + if (length <= 0) { + throw new IllegalArgumentException("Length must be positive"); + } + if (g <= 0) { + throw new IllegalArgumentException("Gravity must be positive"); + } + this.length = length; + this.g = g; + } + + /** + * Computes the derivatives of the state vector. + * State: [theta, omega] where theta is angle and omega is angular velocity. + * + * @param state the current state [theta, omega] + * @return the derivatives [dtheta/dt, domega/dt] + */ + private double[] derivatives(double[] state) { + double theta = state[0]; + double omega = state[1]; + double dtheta = omega; + double domega = -(g / length) * Math.sin(theta); + return new double[] {dtheta, domega}; + } + + /** + * Performs one time step using the RK4 method. + * + * @param state the current state [theta, omega] + * @param dt the time step size + * @return the new state after time dt + */ + public double[] stepRK4(double[] state, double dt) { + if (state == null || state.length != 2) { + throw new IllegalArgumentException("State must be array of length 2"); + } + if (dt <= 0) { + throw new IllegalArgumentException("Time step must be positive"); + } + + double[] k1 = derivatives(state); + double[] s2 = new double[] {state[0] + 0.5 * dt * k1[0], state[1] + 0.5 * dt * k1[1]}; + + double[] k2 = derivatives(s2); + double[] s3 = new double[] {state[0] + 0.5 * dt * k2[0], state[1] + 0.5 * dt * k2[1]}; + + double[] k3 = derivatives(s3); + double[] s4 = new double[] {state[0] + dt * k3[0], state[1] + dt * k3[1]}; + + double[] k4 = derivatives(s4); + + double thetaNext = state[0] + (dt / 6.0) * (k1[0] + 2 * k2[0] + 2 * k3[0] + k4[0]); + double omegaNext = state[1] + (dt / 6.0) * (k1[1] + 2 * k2[1] + 2 * k3[1] + k4[1]); + + return new double[] {thetaNext, omegaNext}; + } + + /** + * Simulates the pendulum for a given duration. + * + * @param initialState the initial state [theta, omega] + * @param dt the time step size + * @param steps the number of steps to simulate + * @return array of states at each step + */ + public double[][] simulate(double[] initialState, double dt, int steps) { + double[][] trajectory = new double[steps + 1][2]; + trajectory[0] = initialState.clone(); + + double[] currentState = initialState.clone(); + for (int i = 1; i <= steps; i++) { + currentState = stepRK4(currentState, dt); + trajectory[i] = currentState.clone(); + } + + return trajectory; + } + + /** + * Calculates the total energy of the pendulum. + * E = (1/2) * m * L^2 * omega^2 + m * g * L * (1 - cos(theta)) + * We use m = 1 for simplicity. + * + * @param state the current state [theta, omega] + * @return the total energy + */ + public double calculateEnergy(double[] state) { + double theta = state[0]; + double omega = state[1]; + double kineticEnergy = 0.5 * length * length * omega * omega; + double potentialEnergy = g * length * (1 - Math.cos(theta)); + return kineticEnergy + potentialEnergy; + } + + public double getLength() { + return length; + } + + public double getGravity() { + return g; + } +} diff --git a/src/test/java/com/thealgorithms/physics/SimplePendulumRK4Test.java b/src/test/java/com/thealgorithms/physics/SimplePendulumRK4Test.java new file mode 100644 index 000000000000..be4165dc0f19 --- /dev/null +++ b/src/test/java/com/thealgorithms/physics/SimplePendulumRK4Test.java @@ -0,0 +1,354 @@ +package com.thealgorithms.physics; + +/** + * Comprehensive test suite for SimplePendulumRK4. + * Tests numerical accuracy, physical correctness, and edge cases. + */ +public class SimplePendulumRK4Test { + + private static final double EPSILON = 1e-6; + + // ANSI color codes for terminal output + private static final String GREEN = "\u001B[32m"; + private static final String RED = "\u001B[31m"; + private static final String YELLOW = "\u001B[33m"; + private static final String BLUE = "\u001B[34m"; + private static final String RESET = "\u001B[0m"; + + private static int testCount = 0; + private static int passCount = 0; + + public static void main(String[] args) { + printHeader(); + + // Basic functionality tests + runTest("Constructor Validation", SimplePendulumRK4Test::testConstructor); + runTest("Getters", SimplePendulumRK4Test::testGetters); + runTest("Single Step RK4", SimplePendulumRK4Test::testSingleStep); + + // Physics tests + runTest("Equilibrium Stability", SimplePendulumRK4Test::testEquilibrium); + runTest("Small Angle Oscillation", SimplePendulumRK4Test::testSmallAngle); + runTest("Large Angle Oscillation", SimplePendulumRK4Test::testLargeAngle); + runTest("Energy Conservation (Small Angle)", SimplePendulumRK4Test::testEnergySmallAngle); + runTest("Energy Conservation (Large Angle)", SimplePendulumRK4Test::testEnergyLargeAngle); + + // Method tests + runTest("Simulate Method", SimplePendulumRK4Test::testSimulate); + runTest("Energy Calculation", SimplePendulumRK4Test::testEnergyCalculation); + + // Edge cases and error handling + runTest("Invalid Constructor Arguments", SimplePendulumRK4Test::testInvalidConstructor); + runTest("Invalid Step Arguments", SimplePendulumRK4Test::testInvalidStep); + runTest("Extreme Initial Conditions", SimplePendulumRK4Test::testExtremeConditions); + + printSummary(); + } + + private static void runTest(String name, TestCase test) { + testCount++; + System.out.print(BLUE + "Test " + testCount + ": " + RESET + name + " ... "); + + try { + test.run(); + passCount++; + System.out.println(GREEN + "✓ PASSED" + RESET); + } catch (AssertionError e) { + System.out.println(RED + "✗ FAILED" + RESET); + System.out.println(" " + RED + "Error: " + e.getMessage() + RESET); + } catch (Exception e) { + System.out.println(RED + "✗ ERROR" + RESET); + System.out.println(" " + RED + "Exception: " + e.getMessage() + RESET); + } + } + + private static void printHeader() { + System.out.println("\n" + + "=".repeat(60)); + System.out.println(YELLOW + " SimplePendulumRK4 Test Suite" + RESET); + System.out.println("=".repeat(60) + "\n"); + } + + private static void printSummary() { + System.out.println("\n" + + "=".repeat(60)); + double percentage = (passCount * 100.0) / testCount; + String color = percentage == 100 ? GREEN : (percentage >= 80 ? YELLOW : RED); + System.out.println(color + " Results: " + passCount + "/" + testCount + " tests passed (" + String.format("%.1f", percentage) + "%)" + RESET); + System.out.println("=".repeat(60) + "\n"); + } + + // ==================== Test Cases ==================== + + private static void testConstructor() { + SimplePendulumRK4 p = new SimplePendulumRK4(1.5, 9.81); + assertTrue(p != null, "Constructor should create object"); + } + + private static void testGetters() { + SimplePendulumRK4 p = new SimplePendulumRK4(2.5, 10.0); + assertEquals(2.5, p.getLength(), EPSILON, "Length getter"); + assertEquals(10.0, p.getGravity(), EPSILON, "Gravity getter"); + } + + private static void testSingleStep() { + SimplePendulumRK4 p = new SimplePendulumRK4(1.0, 9.81); + double[] state = {0.1, 0.0}; + double[] newState = p.stepRK4(state, 0.01); + + assertTrue(newState != null, "Step should return non-null state"); + assertTrue(newState.length == 2, "State should have length 2"); + assertTrue(newState != state, "Step should return new array"); + } + + private static void testEquilibrium() { + SimplePendulumRK4 p = new SimplePendulumRK4(1.0, 9.81); + double[] state = {0.0, 0.0}; + + for (int i = 0; i < 100; i++) { + state = p.stepRK4(state, 0.01); + } + + assertTrue(Math.abs(state[0]) < EPSILON, "Theta should remain at equilibrium"); + assertTrue(Math.abs(state[1]) < EPSILON, "Omega should remain zero"); + } + + private static void testSmallAngle() { + SimplePendulumRK4 p = new SimplePendulumRK4(1.0, 9.81); + double[] state = {Math.toRadians(5.0), 0.0}; + double dt = 0.01; + + // Theoretical period for small angles + double expectedPeriod = 2 * Math.PI * Math.sqrt(1.0 / 9.81); + int stepsPerPeriod = (int) (expectedPeriod / dt); + + double[][] trajectory = p.simulate(state, dt, stepsPerPeriod); + double finalTheta = trajectory[stepsPerPeriod][0]; + + // After one period, should return close to initial position + double error = Math.abs(finalTheta - state[0]) / Math.abs(state[0]); + assertTrue(error < 0.05, "Small angle approximation error: " + error); + } + + private static void testLargeAngle() { + SimplePendulumRK4 p = new SimplePendulumRK4(1.0, 9.81); + double[] state = {Math.toRadians(120.0), 0.0}; + + double[][] trajectory = p.simulate(state, 0.01, 500); + + // Check that pendulum oscillates + double maxTheta = Double.NEGATIVE_INFINITY; + double minTheta = Double.POSITIVE_INFINITY; + + for (double[] s : trajectory) { + maxTheta = Math.max(maxTheta, s[0]); + minTheta = Math.min(minTheta, s[0]); + } + + assertTrue(maxTheta > 0, "Should have positive excursions"); + assertTrue(minTheta < 0, "Should have negative excursions"); + } + + private static void testEnergySmallAngle() { + SimplePendulumRK4 p = new SimplePendulumRK4(1.0, 9.81); + double[] state = {Math.toRadians(15.0), 0.0}; + + testEnergyConservation(p, state, 0.01, 1000, 0.001); + } + + private static void testEnergyLargeAngle() { + SimplePendulumRK4 p = new SimplePendulumRK4(1.0, 9.81); + double[] state = {Math.toRadians(90.0), 0.0}; + + testEnergyConservation(p, state, 0.01, 1000, 0.001); + } + + private static void testEnergyConservation(SimplePendulumRK4 p, double[] initialState, double dt, int steps, double maxDrift) { + double initialEnergy = p.calculateEnergy(initialState); + double[] state = initialState.clone(); + + for (int i = 0; i < steps; i++) { + state = p.stepRK4(state, dt); + } + + double finalEnergy = p.calculateEnergy(state); + double drift = Math.abs(finalEnergy - initialEnergy) / initialEnergy; + + assertTrue(drift < maxDrift, String.format("Energy drift %.6f exceeds limit %.6f", drift, maxDrift)); + } + + private static void testPeriodAccuracy(double angleDegrees) { + SimplePendulumRK4 p = new SimplePendulumRK4(1.0, 9.81); + double[] state = {Math.toRadians(angleDegrees), 0.0}; + double dt = 0.001; + + // Measure period by detecting zero crossings with positive velocity + double prevTheta = state[0]; + int zeroCrossings = 0; + int periodSteps = 0; + + for (int i = 0; i < 10000; i++) { + state = p.stepRK4(state, dt); + + // Detect zero crossing with positive velocity (moving right) + if (prevTheta < 0 && state[0] >= 0 && state[1] > 0) { + zeroCrossings++; + if (zeroCrossings == 2) { + // Two zero crossings = one complete period + periodSteps = i + 1; + break; + } + } + + prevTheta = state[0]; + } + + assertTrue(periodSteps > 0, "Could not measure period"); + + double measuredPeriod = periodSteps * dt; + double theoreticalPeriod = 2 * Math.PI * Math.sqrt(1.0 / 9.81); + + // For small angles, should match theoretical period closely + if (angleDegrees < 10) { + double error = Math.abs(measuredPeriod - theoreticalPeriod) / theoreticalPeriod; + assertTrue(error < 0.02, "Period error: " + error); + } else { + // For larger angles, just verify we got a reasonable period + assertTrue(measuredPeriod > theoreticalPeriod * 0.8, "Period too short"); + assertTrue(measuredPeriod < theoreticalPeriod * 1.5, "Period too long"); + } + } + + private static void testSimulate() { + SimplePendulumRK4 p = new SimplePendulumRK4(1.0, 9.81); + double[] initialState = {Math.toRadians(20.0), 0.0}; + int steps = 100; + + double[][] trajectory = p.simulate(initialState, 0.01, steps); + + assertEquals(steps + 1, trajectory.length, 0, "Trajectory length"); + assertEquals(initialState[0], trajectory[0][0], EPSILON, "Initial theta"); + assertEquals(initialState[1], trajectory[0][1], EPSILON, "Initial omega"); + + // Verify state changes + boolean changed = false; + for (int i = 1; i <= steps; i++) { + if (Math.abs(trajectory[i][0] - initialState[0]) > EPSILON) { + changed = true; + break; + } + } + assertTrue(changed, "Simulation should progress"); + } + + private static void testEnergyCalculation() { + SimplePendulumRK4 p = new SimplePendulumRK4(1.0, 9.81); + + // At equilibrium with no velocity: E = 0 + double[] state1 = {0.0, 0.0}; + double E1 = p.calculateEnergy(state1); + assertEquals(0.0, E1, EPSILON, "Energy at equilibrium"); + + // At rest at max angle: E = potential only + double[] state2 = {Math.PI / 2, 0.0}; + double E2 = p.calculateEnergy(state2); + assertTrue(E2 > 0, "Energy should be positive at max angle"); + + // Energy with angular velocity + double[] state3 = {0.0, 1.0}; + double E3 = p.calculateEnergy(state3); + assertTrue(E3 > 0, "Energy should be positive with velocity"); + } + + private static void testInvalidConstructor() { + boolean caught = false; + try { + new SimplePendulumRK4(-1.0, 9.81); + } catch (IllegalArgumentException e) { + caught = true; + } + assertTrue(caught, "Should reject negative length"); + + caught = false; + try { + new SimplePendulumRK4(1.0, -9.81); + } catch (IllegalArgumentException e) { + caught = true; + } + assertTrue(caught, "Should reject negative gravity"); + + caught = false; + try { + new SimplePendulumRK4(0.0, 9.81); + } catch (IllegalArgumentException e) { + caught = true; + } + assertTrue(caught, "Should reject zero length"); + } + + private static void testInvalidStep() { + SimplePendulumRK4 p = new SimplePendulumRK4(1.0, 9.81); + + boolean caught = false; + try { + p.stepRK4(null, 0.01); + } catch (IllegalArgumentException e) { + caught = true; + } + assertTrue(caught, "Should reject null state"); + + caught = false; + try { + p.stepRK4(new double[] {0.1}, 0.01); + } catch (IllegalArgumentException e) { + caught = true; + } + assertTrue(caught, "Should reject wrong state length"); + + caught = false; + try { + p.stepRK4(new double[] {0.1, 0.2}, -0.01); + } catch (IllegalArgumentException e) { + caught = true; + } + assertTrue(caught, "Should reject negative dt"); + } + + private static void testExtremeConditions() { + SimplePendulumRK4 p = new SimplePendulumRK4(1.0, 9.81); + + // Very large angle + double[] state1 = {Math.toRadians(179.0), 0.0}; + double[] result1 = p.stepRK4(state1, 0.01); + assertTrue(!Double.isNaN(result1[0]), "Should handle large angles"); + + // Very high angular velocity + double[] state2 = {0.0, 10.0}; + double[] result2 = p.stepRK4(state2, 0.01); + assertTrue(!Double.isNaN(result2[0]), "Should handle high velocity"); + + // Very small time step + double[] state3 = {Math.toRadians(10.0), 0.0}; + double[] result3 = p.stepRK4(state3, 1e-6); + assertTrue(!Double.isNaN(result3[0]), "Should handle small dt"); + } + + // ==================== Helper Methods ==================== + + private static void assertTrue(boolean condition, String message) { + if (!condition) { + throw new AssertionError(message); + } + } + + private static void assertEquals(double expected, double actual, double tolerance, String message) { + if (Math.abs(expected - actual) > tolerance) { + throw new AssertionError(message + String.format(": expected %.6f but got %.6f", expected, actual)); + } + } + + @FunctionalInterface + interface TestCase { + void run(); + } +} From 7a565c738841cb5de502f327038b6fa665106e5b Mon Sep 17 00:00:00 2001 From: the-yash-rajput Date: Thu, 16 Oct 2025 20:09:45 +0530 Subject: [PATCH 2/4] Fixed build issue. --- .../physics/SimplePendulumRK4Test.java | 63 ++++--------------- 1 file changed, 13 insertions(+), 50 deletions(-) diff --git a/src/test/java/com/thealgorithms/physics/SimplePendulumRK4Test.java b/src/test/java/com/thealgorithms/physics/SimplePendulumRK4Test.java index be4165dc0f19..635ee3aaac5c 100644 --- a/src/test/java/com/thealgorithms/physics/SimplePendulumRK4Test.java +++ b/src/test/java/com/thealgorithms/physics/SimplePendulumRK4Test.java @@ -4,10 +4,15 @@ * Comprehensive test suite for SimplePendulumRK4. * Tests numerical accuracy, physical correctness, and edge cases. */ -public class SimplePendulumRK4Test { +public final class SimplePendulumRK4Test { private static final double EPSILON = 1e-6; + // Private constructor to prevent instantiation + private SimplePendulumRK4Test() { + throw new UnsupportedOperationException("Utility class"); + } + // ANSI color codes for terminal output private static final String GREEN = "\u001B[32m"; private static final String RED = "\u001B[31m"; @@ -33,7 +38,7 @@ public static void main(String[] args) { runTest("Energy Conservation (Small Angle)", SimplePendulumRK4Test::testEnergySmallAngle); runTest("Energy Conservation (Large Angle)", SimplePendulumRK4Test::testEnergyLargeAngle); - // Method tests + // Method tests` runTest("Simulate Method", SimplePendulumRK4Test::testSimulate); runTest("Energy Calculation", SimplePendulumRK4Test::testEnergyCalculation); @@ -177,48 +182,6 @@ private static void testEnergyConservation(SimplePendulumRK4 p, double[] initial assertTrue(drift < maxDrift, String.format("Energy drift %.6f exceeds limit %.6f", drift, maxDrift)); } - private static void testPeriodAccuracy(double angleDegrees) { - SimplePendulumRK4 p = new SimplePendulumRK4(1.0, 9.81); - double[] state = {Math.toRadians(angleDegrees), 0.0}; - double dt = 0.001; - - // Measure period by detecting zero crossings with positive velocity - double prevTheta = state[0]; - int zeroCrossings = 0; - int periodSteps = 0; - - for (int i = 0; i < 10000; i++) { - state = p.stepRK4(state, dt); - - // Detect zero crossing with positive velocity (moving right) - if (prevTheta < 0 && state[0] >= 0 && state[1] > 0) { - zeroCrossings++; - if (zeroCrossings == 2) { - // Two zero crossings = one complete period - periodSteps = i + 1; - break; - } - } - - prevTheta = state[0]; - } - - assertTrue(periodSteps > 0, "Could not measure period"); - - double measuredPeriod = periodSteps * dt; - double theoreticalPeriod = 2 * Math.PI * Math.sqrt(1.0 / 9.81); - - // For small angles, should match theoretical period closely - if (angleDegrees < 10) { - double error = Math.abs(measuredPeriod - theoreticalPeriod) / theoreticalPeriod; - assertTrue(error < 0.02, "Period error: " + error); - } else { - // For larger angles, just verify we got a reasonable period - assertTrue(measuredPeriod > theoreticalPeriod * 0.8, "Period too short"); - assertTrue(measuredPeriod < theoreticalPeriod * 1.5, "Period too long"); - } - } - private static void testSimulate() { SimplePendulumRK4 p = new SimplePendulumRK4(1.0, 9.81); double[] initialState = {Math.toRadians(20.0), 0.0}; @@ -246,18 +209,18 @@ private static void testEnergyCalculation() { // At equilibrium with no velocity: E = 0 double[] state1 = {0.0, 0.0}; - double E1 = p.calculateEnergy(state1); - assertEquals(0.0, E1, EPSILON, "Energy at equilibrium"); + double energy1 = p.calculateEnergy(state1); + assertEquals(0.0, energy1, EPSILON, "Energy at equilibrium"); // At rest at max angle: E = potential only double[] state2 = {Math.PI / 2, 0.0}; - double E2 = p.calculateEnergy(state2); - assertTrue(E2 > 0, "Energy should be positive at max angle"); + double energy2 = p.calculateEnergy(state2); + assertTrue(energy2 > 0, "Energy should be positive at max angle"); // Energy with angular velocity double[] state3 = {0.0, 1.0}; - double E3 = p.calculateEnergy(state3); - assertTrue(E3 > 0, "Energy should be positive with velocity"); + double energy3 = p.calculateEnergy(state3); + assertTrue(energy3 > 0, "Energy should be positive with velocity"); } private static void testInvalidConstructor() { From b7772cb07fd1e6b31218b1c7ee806fff39ae460d Mon Sep 17 00:00:00 2001 From: the-yash-rajput Date: Thu, 16 Oct 2025 20:31:34 +0530 Subject: [PATCH 3/4] Fixed build issue. --- .../physics/SimplePendulumRK4Test.java | 385 ++++++++---------- 1 file changed, 167 insertions(+), 218 deletions(-) diff --git a/src/test/java/com/thealgorithms/physics/SimplePendulumRK4Test.java b/src/test/java/com/thealgorithms/physics/SimplePendulumRK4Test.java index 635ee3aaac5c..714c48b2a4c8 100644 --- a/src/test/java/com/thealgorithms/physics/SimplePendulumRK4Test.java +++ b/src/test/java/com/thealgorithms/physics/SimplePendulumRK4Test.java @@ -1,147 +1,110 @@ package com.thealgorithms.physics; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + /** - * Comprehensive test suite for SimplePendulumRK4. + * Test class for SimplePendulumRK4. * Tests numerical accuracy, physical correctness, and edge cases. */ -public final class SimplePendulumRK4Test { +class SimplePendulumRK4Test { private static final double EPSILON = 1e-6; - - // Private constructor to prevent instantiation - private SimplePendulumRK4Test() { - throw new UnsupportedOperationException("Utility class"); - } - - // ANSI color codes for terminal output - private static final String GREEN = "\u001B[32m"; - private static final String RED = "\u001B[31m"; - private static final String YELLOW = "\u001B[33m"; - private static final String BLUE = "\u001B[34m"; - private static final String RESET = "\u001B[0m"; - - private static int testCount = 0; - private static int passCount = 0; - - public static void main(String[] args) { - printHeader(); - - // Basic functionality tests - runTest("Constructor Validation", SimplePendulumRK4Test::testConstructor); - runTest("Getters", SimplePendulumRK4Test::testGetters); - runTest("Single Step RK4", SimplePendulumRK4Test::testSingleStep); - - // Physics tests - runTest("Equilibrium Stability", SimplePendulumRK4Test::testEquilibrium); - runTest("Small Angle Oscillation", SimplePendulumRK4Test::testSmallAngle); - runTest("Large Angle Oscillation", SimplePendulumRK4Test::testLargeAngle); - runTest("Energy Conservation (Small Angle)", SimplePendulumRK4Test::testEnergySmallAngle); - runTest("Energy Conservation (Large Angle)", SimplePendulumRK4Test::testEnergyLargeAngle); - - // Method tests` - runTest("Simulate Method", SimplePendulumRK4Test::testSimulate); - runTest("Energy Calculation", SimplePendulumRK4Test::testEnergyCalculation); - - // Edge cases and error handling - runTest("Invalid Constructor Arguments", SimplePendulumRK4Test::testInvalidConstructor); - runTest("Invalid Step Arguments", SimplePendulumRK4Test::testInvalidStep); - runTest("Extreme Initial Conditions", SimplePendulumRK4Test::testExtremeConditions); - - printSummary(); + private static final double ENERGY_DRIFT_TOLERANCE = 1e-3; + @Test + @DisplayName("Test constructor creates valid pendulum") + void testConstructor() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.5, 9.81); + assertNotNull(pendulum); + assertEquals(1.5, pendulum.getLength(), EPSILON); + assertEquals(9.81, pendulum.getGravity(), EPSILON); } - private static void runTest(String name, TestCase test) { - testCount++; - System.out.print(BLUE + "Test " + testCount + ": " + RESET + name + " ... "); - - try { - test.run(); - passCount++; - System.out.println(GREEN + "✓ PASSED" + RESET); - } catch (AssertionError e) { - System.out.println(RED + "✗ FAILED" + RESET); - System.out.println(" " + RED + "Error: " + e.getMessage() + RESET); - } catch (Exception e) { - System.out.println(RED + "✗ ERROR" + RESET); - System.out.println(" " + RED + "Exception: " + e.getMessage() + RESET); - } + @Test + @DisplayName("Test constructor rejects negative length") + void testConstructorNegativeLength() { + assertThrows(IllegalArgumentException.class, () -> { new SimplePendulumRK4(-1.0, 9.81); }); } - private static void printHeader() { - System.out.println("\n" - + "=".repeat(60)); - System.out.println(YELLOW + " SimplePendulumRK4 Test Suite" + RESET); - System.out.println("=".repeat(60) + "\n"); + @Test + @DisplayName("Test constructor rejects negative gravity") + void testConstructorNegativeGravity() { + assertThrows(IllegalArgumentException.class, () -> { new SimplePendulumRK4(1.0, -9.81); }); } - private static void printSummary() { - System.out.println("\n" - + "=".repeat(60)); - double percentage = (passCount * 100.0) / testCount; - String color = percentage == 100 ? GREEN : (percentage >= 80 ? YELLOW : RED); - System.out.println(color + " Results: " + passCount + "/" + testCount + " tests passed (" + String.format("%.1f", percentage) + "%)" + RESET); - System.out.println("=".repeat(60) + "\n"); + @Test + @DisplayName("Test constructor rejects zero length") + void testConstructorZeroLength() { + assertThrows(IllegalArgumentException.class, () -> { new SimplePendulumRK4(0.0, 9.81); }); } - // ==================== Test Cases ==================== - - private static void testConstructor() { - SimplePendulumRK4 p = new SimplePendulumRK4(1.5, 9.81); - assertTrue(p != null, "Constructor should create object"); + @Test + @DisplayName("Test getters return correct values") + void testGetters() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(2.5, 10.0); + assertEquals(2.5, pendulum.getLength(), EPSILON); + assertEquals(10.0, pendulum.getGravity(), EPSILON); } - private static void testGetters() { - SimplePendulumRK4 p = new SimplePendulumRK4(2.5, 10.0); - assertEquals(2.5, p.getLength(), EPSILON, "Length getter"); - assertEquals(10.0, p.getGravity(), EPSILON, "Gravity getter"); - } - - private static void testSingleStep() { - SimplePendulumRK4 p = new SimplePendulumRK4(1.0, 9.81); + @Test + @DisplayName("Test single RK4 step returns valid state") + void testSingleStep() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); double[] state = {0.1, 0.0}; - double[] newState = p.stepRK4(state, 0.01); + double[] newState = pendulum.stepRK4(state, 0.01); - assertTrue(newState != null, "Step should return non-null state"); - assertTrue(newState.length == 2, "State should have length 2"); - assertTrue(newState != state, "Step should return new array"); + assertNotNull(newState); + assertEquals(2, newState.length); } - private static void testEquilibrium() { - SimplePendulumRK4 p = new SimplePendulumRK4(1.0, 9.81); + @Test + @DisplayName("Test equilibrium stability (pendulum at rest stays at rest)") + void testEquilibrium() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); double[] state = {0.0, 0.0}; for (int i = 0; i < 100; i++) { - state = p.stepRK4(state, 0.01); + state = pendulum.stepRK4(state, 0.01); } - assertTrue(Math.abs(state[0]) < EPSILON, "Theta should remain at equilibrium"); - assertTrue(Math.abs(state[1]) < EPSILON, "Omega should remain zero"); + assertEquals(0.0, state[0], EPSILON, "Theta should remain at equilibrium"); + assertEquals(0.0, state[1], EPSILON, "Omega should remain zero"); } - private static void testSmallAngle() { - SimplePendulumRK4 p = new SimplePendulumRK4(1.0, 9.81); - double[] state = {Math.toRadians(5.0), 0.0}; + @Test + @DisplayName("Test small angle oscillation returns to initial position") + void testSmallAngleOscillation() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); + double initialAngle = Math.toRadians(5.0); + double[] state = {initialAngle, 0.0}; double dt = 0.01; // Theoretical period for small angles double expectedPeriod = 2 * Math.PI * Math.sqrt(1.0 / 9.81); int stepsPerPeriod = (int) (expectedPeriod / dt); - double[][] trajectory = p.simulate(state, dt, stepsPerPeriod); + double[][] trajectory = pendulum.simulate(state, dt, stepsPerPeriod); double finalTheta = trajectory[stepsPerPeriod][0]; // After one period, should return close to initial position - double error = Math.abs(finalTheta - state[0]) / Math.abs(state[0]); - assertTrue(error < 0.05, "Small angle approximation error: " + error); + double error = Math.abs(finalTheta - initialAngle) / Math.abs(initialAngle); + assertTrue(error < 0.05, "Small angle approximation error should be < 5%"); } - private static void testLargeAngle() { - SimplePendulumRK4 p = new SimplePendulumRK4(1.0, 9.81); + @Test + @DisplayName("Test large angle oscillation is symmetric") + void testLargeAngleOscillation() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); double[] state = {Math.toRadians(120.0), 0.0}; - double[][] trajectory = p.simulate(state, 0.01, 500); + double[][] trajectory = pendulum.simulate(state, 0.01, 500); - // Check that pendulum oscillates double maxTheta = Double.NEGATIVE_INFINITY; double minTheta = Double.POSITIVE_INFINITY; @@ -152,48 +115,61 @@ private static void testLargeAngle() { assertTrue(maxTheta > 0, "Should have positive excursions"); assertTrue(minTheta < 0, "Should have negative excursions"); + + // Check symmetry + double asymmetry = Math.abs((maxTheta + minTheta) / maxTheta); + assertTrue(asymmetry < 0.1, "Oscillation should be symmetric"); } - private static void testEnergySmallAngle() { - SimplePendulumRK4 p = new SimplePendulumRK4(1.0, 9.81); + @Test + @DisplayName("Test energy conservation for small angle") + void testEnergyConservationSmallAngle() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); double[] state = {Math.toRadians(15.0), 0.0}; - testEnergyConservation(p, state, 0.01, 1000, 0.001); - } + double initialEnergy = pendulum.calculateEnergy(state); - private static void testEnergyLargeAngle() { - SimplePendulumRK4 p = new SimplePendulumRK4(1.0, 9.81); - double[] state = {Math.toRadians(90.0), 0.0}; + for (int i = 0; i < 1000; i++) { + state = pendulum.stepRK4(state, 0.01); + } + + double finalEnergy = pendulum.calculateEnergy(state); + double drift = Math.abs(finalEnergy - initialEnergy) / initialEnergy; - testEnergyConservation(p, state, 0.01, 1000, 0.001); + assertTrue(drift < ENERGY_DRIFT_TOLERANCE, "Energy drift should be < 0.1%, got: " + (drift * 100) + "%"); } - private static void testEnergyConservation(SimplePendulumRK4 p, double[] initialState, double dt, int steps, double maxDrift) { - double initialEnergy = p.calculateEnergy(initialState); - double[] state = initialState.clone(); + @Test + @DisplayName("Test energy conservation for large angle") + void testEnergyConservationLargeAngle() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); + double[] state = {Math.toRadians(90.0), 0.0}; + + double initialEnergy = pendulum.calculateEnergy(state); - for (int i = 0; i < steps; i++) { - state = p.stepRK4(state, dt); + for (int i = 0; i < 1000; i++) { + state = pendulum.stepRK4(state, 0.01); } - double finalEnergy = p.calculateEnergy(state); + double finalEnergy = pendulum.calculateEnergy(state); double drift = Math.abs(finalEnergy - initialEnergy) / initialEnergy; - assertTrue(drift < maxDrift, String.format("Energy drift %.6f exceeds limit %.6f", drift, maxDrift)); + assertTrue(drift < ENERGY_DRIFT_TOLERANCE, "Energy drift should be < 0.1%, got: " + (drift * 100) + "%"); } - private static void testSimulate() { - SimplePendulumRK4 p = new SimplePendulumRK4(1.0, 9.81); + @Test + @DisplayName("Test simulate method returns correct trajectory") + void testSimulate() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); double[] initialState = {Math.toRadians(20.0), 0.0}; int steps = 100; - double[][] trajectory = p.simulate(initialState, 0.01, steps); + double[][] trajectory = pendulum.simulate(initialState, 0.01, steps); - assertEquals(steps + 1, trajectory.length, 0, "Trajectory length"); - assertEquals(initialState[0], trajectory[0][0], EPSILON, "Initial theta"); - assertEquals(initialState[1], trajectory[0][1], EPSILON, "Initial omega"); + assertEquals(steps + 1, trajectory.length, "Trajectory should have steps + 1 entries"); + assertArrayEquals(initialState, trajectory[0], EPSILON, "First entry should match initial state"); - // Verify state changes + // Verify state changes over time boolean changed = false; for (int i = 1; i <= steps; i++) { if (Math.abs(trajectory[i][0] - initialState[0]) > EPSILON) { @@ -201,117 +177,90 @@ private static void testSimulate() { break; } } - assertTrue(changed, "Simulation should progress"); + assertTrue(changed, "Simulation should progress from initial state"); } - private static void testEnergyCalculation() { - SimplePendulumRK4 p = new SimplePendulumRK4(1.0, 9.81); - - // At equilibrium with no velocity: E = 0 - double[] state1 = {0.0, 0.0}; - double energy1 = p.calculateEnergy(state1); - assertEquals(0.0, energy1, EPSILON, "Energy at equilibrium"); - - // At rest at max angle: E = potential only - double[] state2 = {Math.PI / 2, 0.0}; - double energy2 = p.calculateEnergy(state2); - assertTrue(energy2 > 0, "Energy should be positive at max angle"); - - // Energy with angular velocity - double[] state3 = {0.0, 1.0}; - double energy3 = p.calculateEnergy(state3); - assertTrue(energy3 > 0, "Energy should be positive with velocity"); + @Test + @DisplayName("Test energy calculation at equilibrium") + void testEnergyAtEquilibrium() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); + double[] state = {0.0, 0.0}; + double energy = pendulum.calculateEnergy(state); + assertEquals(0.0, energy, EPSILON, "Energy at equilibrium should be zero"); } - private static void testInvalidConstructor() { - boolean caught = false; - try { - new SimplePendulumRK4(-1.0, 9.81); - } catch (IllegalArgumentException e) { - caught = true; - } - assertTrue(caught, "Should reject negative length"); - - caught = false; - try { - new SimplePendulumRK4(1.0, -9.81); - } catch (IllegalArgumentException e) { - caught = true; - } - assertTrue(caught, "Should reject negative gravity"); - - caught = false; - try { - new SimplePendulumRK4(0.0, 9.81); - } catch (IllegalArgumentException e) { - caught = true; - } - assertTrue(caught, "Should reject zero length"); + @Test + @DisplayName("Test energy calculation at maximum angle") + void testEnergyAtMaxAngle() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); + double[] state = {Math.PI / 2, 0.0}; + double energy = pendulum.calculateEnergy(state); + assertTrue(energy > 0, "Energy should be positive at max angle"); } - private static void testInvalidStep() { - SimplePendulumRK4 p = new SimplePendulumRK4(1.0, 9.81); - - boolean caught = false; - try { - p.stepRK4(null, 0.01); - } catch (IllegalArgumentException e) { - caught = true; - } - assertTrue(caught, "Should reject null state"); - - caught = false; - try { - p.stepRK4(new double[] {0.1}, 0.01); - } catch (IllegalArgumentException e) { - caught = true; - } - assertTrue(caught, "Should reject wrong state length"); + @Test + @DisplayName("Test energy calculation with angular velocity") + void testEnergyWithVelocity() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); + double[] state = {0.0, 1.0}; + double energy = pendulum.calculateEnergy(state); + assertTrue(energy > 0, "Energy should be positive with velocity"); + } - caught = false; - try { - p.stepRK4(new double[] {0.1, 0.2}, -0.01); - } catch (IllegalArgumentException e) { - caught = true; - } - assertTrue(caught, "Should reject negative dt"); + @Test + @DisplayName("Test stepRK4 rejects null state") + void testStepRejectsNullState() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); + assertThrows(IllegalArgumentException.class, () -> { pendulum.stepRK4(null, 0.01); }); } - private static void testExtremeConditions() { - SimplePendulumRK4 p = new SimplePendulumRK4(1.0, 9.81); + @Test + @DisplayName("Test stepRK4 rejects invalid state length") + void testStepRejectsInvalidStateLength() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); + assertThrows(IllegalArgumentException.class, () -> { pendulum.stepRK4(new double[] {0.1}, 0.01); }); + } - // Very large angle - double[] state1 = {Math.toRadians(179.0), 0.0}; - double[] result1 = p.stepRK4(state1, 0.01); - assertTrue(!Double.isNaN(result1[0]), "Should handle large angles"); + @Test + @DisplayName("Test stepRK4 rejects negative time step") + void testStepRejectsNegativeTimeStep() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); + assertThrows(IllegalArgumentException.class, () -> { pendulum.stepRK4(new double[] {0.1, 0.2}, -0.01); }); + } - // Very high angular velocity - double[] state2 = {0.0, 10.0}; - double[] result2 = p.stepRK4(state2, 0.01); - assertTrue(!Double.isNaN(result2[0]), "Should handle high velocity"); + @Test + @DisplayName("Test extreme condition: very large angle") + void testExtremeLargeAngle() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); + double[] state = {Math.toRadians(179.0), 0.0}; + double[] result = pendulum.stepRK4(state, 0.01); - // Very small time step - double[] state3 = {Math.toRadians(10.0), 0.0}; - double[] result3 = p.stepRK4(state3, 1e-6); - assertTrue(!Double.isNaN(result3[0]), "Should handle small dt"); + assertNotNull(result); + assertTrue(Double.isFinite(result[0]), "Should handle large angles without NaN"); + assertTrue(Double.isFinite(result[1]), "Should handle large angles without NaN"); } - // ==================== Helper Methods ==================== + @Test + @DisplayName("Test extreme condition: high angular velocity") + void testExtremeHighVelocity() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); + double[] state = {0.0, 10.0}; + double[] result = pendulum.stepRK4(state, 0.01); - private static void assertTrue(boolean condition, String message) { - if (!condition) { - throw new AssertionError(message); - } + assertNotNull(result); + assertTrue(Double.isFinite(result[0]), "Should handle high velocity without NaN"); + assertTrue(Double.isFinite(result[1]), "Should handle high velocity without NaN"); } - private static void assertEquals(double expected, double actual, double tolerance, String message) { - if (Math.abs(expected - actual) > tolerance) { - throw new AssertionError(message + String.format(": expected %.6f but got %.6f", expected, actual)); - } - } + @Test + @DisplayName("Test extreme condition: very small time step") + void testExtremeSmallTimeStep() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); + double[] state = {Math.toRadians(10.0), 0.0}; + double[] result = pendulum.stepRK4(state, 1e-6); - @FunctionalInterface - interface TestCase { - void run(); + assertNotNull(result); + assertTrue(Double.isFinite(result[0]), "Should handle small time steps without NaN"); + assertTrue(Double.isFinite(result[1]), "Should handle small time steps without NaN"); } } From 0493bcd70fc6db415a76a0a5e829b0b8725e6a9c Mon Sep 17 00:00:00 2001 From: the-yash-rajput Date: Thu, 16 Oct 2025 20:46:03 +0530 Subject: [PATCH 4/4] Fixed build issue. --- .../physics/SimplePendulumRK4.java | 6 +- .../physics/SimplePendulumRK4Test.java | 79 +++++++++---------- 2 files changed, 41 insertions(+), 44 deletions(-) diff --git a/src/main/java/com/thealgorithms/physics/SimplePendulumRK4.java b/src/main/java/com/thealgorithms/physics/SimplePendulumRK4.java index ef0d6262c650..6de69c103b5a 100644 --- a/src/main/java/com/thealgorithms/physics/SimplePendulumRK4.java +++ b/src/main/java/com/thealgorithms/physics/SimplePendulumRK4.java @@ -3,6 +3,8 @@ /** * Simulates a simple pendulum using the Runge-Kutta 4th order method. * The pendulum is modeled with the nonlinear differential equation. + * + * @author [Yash Rajput](https://github.com/the-yash-rajput) */ public final class SimplePendulumRK4 { @@ -71,8 +73,8 @@ public double[] stepRK4(double[] state, double dt) { double[] k4 = derivatives(s4); - double thetaNext = state[0] + (dt / 6.0) * (k1[0] + 2 * k2[0] + 2 * k3[0] + k4[0]); - double omegaNext = state[1] + (dt / 6.0) * (k1[1] + 2 * k2[1] + 2 * k3[1] + k4[1]); + double thetaNext = state[0] + dt / 6.0 * (k1[0] + 2 * k2[0] + 2 * k3[0] + k4[0]); + double omegaNext = state[1] + dt / 6.0 * (k1[1] + 2 * k2[1] + 2 * k3[1] + k4[1]); return new double[] {thetaNext, omegaNext}; } diff --git a/src/test/java/com/thealgorithms/physics/SimplePendulumRK4Test.java b/src/test/java/com/thealgorithms/physics/SimplePendulumRK4Test.java index 714c48b2a4c8..bec4ad8cbbd7 100644 --- a/src/test/java/com/thealgorithms/physics/SimplePendulumRK4Test.java +++ b/src/test/java/com/thealgorithms/physics/SimplePendulumRK4Test.java @@ -1,11 +1,6 @@ package com.thealgorithms.physics; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -21,35 +16,35 @@ class SimplePendulumRK4Test { @DisplayName("Test constructor creates valid pendulum") void testConstructor() { SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.5, 9.81); - assertNotNull(pendulum); - assertEquals(1.5, pendulum.getLength(), EPSILON); - assertEquals(9.81, pendulum.getGravity(), EPSILON); + Assertions.assertNotNull(pendulum); + Assertions.assertEquals(1.5, pendulum.getLength(), EPSILON); + Assertions.assertEquals(9.81, pendulum.getGravity(), EPSILON); } @Test @DisplayName("Test constructor rejects negative length") void testConstructorNegativeLength() { - assertThrows(IllegalArgumentException.class, () -> { new SimplePendulumRK4(-1.0, 9.81); }); + Assertions.assertThrows(IllegalArgumentException.class, () -> { new SimplePendulumRK4(-1.0, 9.81); }); } @Test @DisplayName("Test constructor rejects negative gravity") void testConstructorNegativeGravity() { - assertThrows(IllegalArgumentException.class, () -> { new SimplePendulumRK4(1.0, -9.81); }); + Assertions.assertThrows(IllegalArgumentException.class, () -> { new SimplePendulumRK4(1.0, -9.81); }); } @Test @DisplayName("Test constructor rejects zero length") void testConstructorZeroLength() { - assertThrows(IllegalArgumentException.class, () -> { new SimplePendulumRK4(0.0, 9.81); }); + Assertions.assertThrows(IllegalArgumentException.class, () -> { new SimplePendulumRK4(0.0, 9.81); }); } @Test @DisplayName("Test getters return correct values") void testGetters() { SimplePendulumRK4 pendulum = new SimplePendulumRK4(2.5, 10.0); - assertEquals(2.5, pendulum.getLength(), EPSILON); - assertEquals(10.0, pendulum.getGravity(), EPSILON); + Assertions.assertEquals(2.5, pendulum.getLength(), EPSILON); + Assertions.assertEquals(10.0, pendulum.getGravity(), EPSILON); } @Test @@ -59,8 +54,8 @@ void testSingleStep() { double[] state = {0.1, 0.0}; double[] newState = pendulum.stepRK4(state, 0.01); - assertNotNull(newState); - assertEquals(2, newState.length); + Assertions.assertNotNull(newState); + Assertions.assertEquals(2, newState.length); } @Test @@ -73,8 +68,8 @@ void testEquilibrium() { state = pendulum.stepRK4(state, 0.01); } - assertEquals(0.0, state[0], EPSILON, "Theta should remain at equilibrium"); - assertEquals(0.0, state[1], EPSILON, "Omega should remain zero"); + Assertions.assertEquals(0.0, state[0], EPSILON, "Theta should remain at equilibrium"); + Assertions.assertEquals(0.0, state[1], EPSILON, "Omega should remain zero"); } @Test @@ -94,7 +89,7 @@ void testSmallAngleOscillation() { // After one period, should return close to initial position double error = Math.abs(finalTheta - initialAngle) / Math.abs(initialAngle); - assertTrue(error < 0.05, "Small angle approximation error should be < 5%"); + Assertions.assertTrue(error < 0.05, "Small angle approximation error should be < 5%"); } @Test @@ -113,12 +108,12 @@ void testLargeAngleOscillation() { minTheta = Math.min(minTheta, s[0]); } - assertTrue(maxTheta > 0, "Should have positive excursions"); - assertTrue(minTheta < 0, "Should have negative excursions"); + Assertions.assertTrue(maxTheta > 0, "Should have positive excursions"); + Assertions.assertTrue(minTheta < 0, "Should have negative excursions"); // Check symmetry double asymmetry = Math.abs((maxTheta + minTheta) / maxTheta); - assertTrue(asymmetry < 0.1, "Oscillation should be symmetric"); + Assertions.assertTrue(asymmetry < 0.1, "Oscillation should be symmetric"); } @Test @@ -136,7 +131,7 @@ void testEnergyConservationSmallAngle() { double finalEnergy = pendulum.calculateEnergy(state); double drift = Math.abs(finalEnergy - initialEnergy) / initialEnergy; - assertTrue(drift < ENERGY_DRIFT_TOLERANCE, "Energy drift should be < 0.1%, got: " + (drift * 100) + "%"); + Assertions.assertTrue(drift < ENERGY_DRIFT_TOLERANCE, "Energy drift should be < 0.1%, got: " + (drift * 100) + "%"); } @Test @@ -154,7 +149,7 @@ void testEnergyConservationLargeAngle() { double finalEnergy = pendulum.calculateEnergy(state); double drift = Math.abs(finalEnergy - initialEnergy) / initialEnergy; - assertTrue(drift < ENERGY_DRIFT_TOLERANCE, "Energy drift should be < 0.1%, got: " + (drift * 100) + "%"); + Assertions.assertTrue(drift < ENERGY_DRIFT_TOLERANCE, "Energy drift should be < 0.1%, got: " + (drift * 100) + "%"); } @Test @@ -166,8 +161,8 @@ void testSimulate() { double[][] trajectory = pendulum.simulate(initialState, 0.01, steps); - assertEquals(steps + 1, trajectory.length, "Trajectory should have steps + 1 entries"); - assertArrayEquals(initialState, trajectory[0], EPSILON, "First entry should match initial state"); + Assertions.assertEquals(steps + 1, trajectory.length, "Trajectory should have steps + 1 entries"); + Assertions.assertArrayEquals(initialState, trajectory[0], EPSILON, "First entry should match initial state"); // Verify state changes over time boolean changed = false; @@ -177,7 +172,7 @@ void testSimulate() { break; } } - assertTrue(changed, "Simulation should progress from initial state"); + Assertions.assertTrue(changed, "Simulation should progress from initial state"); } @Test @@ -186,7 +181,7 @@ void testEnergyAtEquilibrium() { SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); double[] state = {0.0, 0.0}; double energy = pendulum.calculateEnergy(state); - assertEquals(0.0, energy, EPSILON, "Energy at equilibrium should be zero"); + Assertions.assertEquals(0.0, energy, EPSILON, "Energy at equilibrium should be zero"); } @Test @@ -195,7 +190,7 @@ void testEnergyAtMaxAngle() { SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); double[] state = {Math.PI / 2, 0.0}; double energy = pendulum.calculateEnergy(state); - assertTrue(energy > 0, "Energy should be positive at max angle"); + Assertions.assertTrue(energy > 0, "Energy should be positive at max angle"); } @Test @@ -204,28 +199,28 @@ void testEnergyWithVelocity() { SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); double[] state = {0.0, 1.0}; double energy = pendulum.calculateEnergy(state); - assertTrue(energy > 0, "Energy should be positive with velocity"); + Assertions.assertTrue(energy > 0, "Energy should be positive with velocity"); } @Test @DisplayName("Test stepRK4 rejects null state") void testStepRejectsNullState() { SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); - assertThrows(IllegalArgumentException.class, () -> { pendulum.stepRK4(null, 0.01); }); + Assertions.assertThrows(IllegalArgumentException.class, () -> { pendulum.stepRK4(null, 0.01); }); } @Test @DisplayName("Test stepRK4 rejects invalid state length") void testStepRejectsInvalidStateLength() { SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); - assertThrows(IllegalArgumentException.class, () -> { pendulum.stepRK4(new double[] {0.1}, 0.01); }); + Assertions.assertThrows(IllegalArgumentException.class, () -> { pendulum.stepRK4(new double[] {0.1}, 0.01); }); } @Test @DisplayName("Test stepRK4 rejects negative time step") void testStepRejectsNegativeTimeStep() { SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); - assertThrows(IllegalArgumentException.class, () -> { pendulum.stepRK4(new double[] {0.1, 0.2}, -0.01); }); + Assertions.assertThrows(IllegalArgumentException.class, () -> { pendulum.stepRK4(new double[] {0.1, 0.2}, -0.01); }); } @Test @@ -235,9 +230,9 @@ void testExtremeLargeAngle() { double[] state = {Math.toRadians(179.0), 0.0}; double[] result = pendulum.stepRK4(state, 0.01); - assertNotNull(result); - assertTrue(Double.isFinite(result[0]), "Should handle large angles without NaN"); - assertTrue(Double.isFinite(result[1]), "Should handle large angles without NaN"); + Assertions.assertNotNull(result); + Assertions.assertTrue(Double.isFinite(result[0]), "Should handle large angles without NaN"); + Assertions.assertTrue(Double.isFinite(result[1]), "Should handle large angles without NaN"); } @Test @@ -247,9 +242,9 @@ void testExtremeHighVelocity() { double[] state = {0.0, 10.0}; double[] result = pendulum.stepRK4(state, 0.01); - assertNotNull(result); - assertTrue(Double.isFinite(result[0]), "Should handle high velocity without NaN"); - assertTrue(Double.isFinite(result[1]), "Should handle high velocity without NaN"); + Assertions.assertNotNull(result); + Assertions.assertTrue(Double.isFinite(result[0]), "Should handle high velocity without NaN"); + Assertions.assertTrue(Double.isFinite(result[1]), "Should handle high velocity without NaN"); } @Test @@ -259,8 +254,8 @@ void testExtremeSmallTimeStep() { double[] state = {Math.toRadians(10.0), 0.0}; double[] result = pendulum.stepRK4(state, 1e-6); - assertNotNull(result); - assertTrue(Double.isFinite(result[0]), "Should handle small time steps without NaN"); - assertTrue(Double.isFinite(result[1]), "Should handle small time steps without NaN"); + Assertions.assertNotNull(result); + Assertions.assertTrue(Double.isFinite(result[0]), "Should handle small time steps without NaN"); + Assertions.assertTrue(Double.isFinite(result[1]), "Should handle small time steps without NaN"); } }