forked from TheAlgorithms/Java
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSimplePendulumRK4Test.java
More file actions
261 lines (213 loc) · 10.2 KB
/
SimplePendulumRK4Test.java
File metadata and controls
261 lines (213 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
package com.thealgorithms.physics;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
/**
* Test class for SimplePendulumRK4.
* Tests numerical accuracy, physical correctness, and edge cases.
*/
class SimplePendulumRK4Test {
private static final double EPSILON = 1e-6;
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);
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() {
Assertions.assertThrows(IllegalArgumentException.class, () -> { new SimplePendulumRK4(-1.0, 9.81); });
}
@Test
@DisplayName("Test constructor rejects negative gravity")
void testConstructorNegativeGravity() {
Assertions.assertThrows(IllegalArgumentException.class, () -> { new SimplePendulumRK4(1.0, -9.81); });
}
@Test
@DisplayName("Test constructor rejects zero length")
void testConstructorZeroLength() {
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);
Assertions.assertEquals(2.5, pendulum.getLength(), EPSILON);
Assertions.assertEquals(10.0, pendulum.getGravity(), EPSILON);
}
@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 = pendulum.stepRK4(state, 0.01);
Assertions.assertNotNull(newState);
Assertions.assertEquals(2, newState.length);
}
@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 = pendulum.stepRK4(state, 0.01);
}
Assertions.assertEquals(0.0, state[0], EPSILON, "Theta should remain at equilibrium");
Assertions.assertEquals(0.0, state[1], EPSILON, "Omega should remain zero");
}
@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 = pendulum.simulate(state, dt, stepsPerPeriod);
double finalTheta = trajectory[stepsPerPeriod][0];
// After one period, should return close to initial position
double error = Math.abs(finalTheta - initialAngle) / Math.abs(initialAngle);
Assertions.assertTrue(error < 0.05, "Small angle approximation error should be < 5%");
}
@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 = pendulum.simulate(state, 0.01, 500);
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]);
}
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);
Assertions.assertTrue(asymmetry < 0.1, "Oscillation should be symmetric");
}
@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};
double initialEnergy = pendulum.calculateEnergy(state);
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;
Assertions.assertTrue(drift < ENERGY_DRIFT_TOLERANCE, "Energy drift should be < 0.1%, got: " + (drift * 100) + "%");
}
@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 < 1000; i++) {
state = pendulum.stepRK4(state, 0.01);
}
double finalEnergy = pendulum.calculateEnergy(state);
double drift = Math.abs(finalEnergy - initialEnergy) / initialEnergy;
Assertions.assertTrue(drift < ENERGY_DRIFT_TOLERANCE, "Energy drift should be < 0.1%, got: " + (drift * 100) + "%");
}
@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 = pendulum.simulate(initialState, 0.01, steps);
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;
for (int i = 1; i <= steps; i++) {
if (Math.abs(trajectory[i][0] - initialState[0]) > EPSILON) {
changed = true;
break;
}
}
Assertions.assertTrue(changed, "Simulation should progress from initial state");
}
@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);
Assertions.assertEquals(0.0, energy, EPSILON, "Energy at equilibrium should be zero");
}
@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);
Assertions.assertTrue(energy > 0, "Energy should be positive at max angle");
}
@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);
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);
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);
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);
Assertions.assertThrows(IllegalArgumentException.class, () -> { pendulum.stepRK4(new double[] {0.1, 0.2}, -0.01); });
}
@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);
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
@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);
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
@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);
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");
}
}