forked from TheAlgorithms/Java
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDampedOscillatorTest.java
More file actions
143 lines (115 loc) · 5.93 KB
/
DampedOscillatorTest.java
File metadata and controls
143 lines (115 loc) · 5.93 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
package com.thealgorithms.physics;
import static org.junit.jupiter.api.Assertions.assertAll;
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;
/**
* Unit tests for {@link DampedOscillator}.
*
* <p>Tests focus on:
* <ul>
* <li>Constructor validation</li>
* <li>Analytical displacement for underdamped and overdamped parameterizations</li>
* <li>Basic numeric integration sanity using explicit Euler for small step sizes</li>
* <li>Method argument validation (null/invalid inputs)</li>
* </ul>
*/
@DisplayName("DampedOscillator — unit tests")
public class DampedOscillatorTest {
private static final double TOLERANCE = 1e-3;
@Test
@DisplayName("Constructor rejects invalid parameters")
void constructorValidation() {
assertAll("invalid-constructor-params",
()
-> assertThrows(IllegalArgumentException.class, () -> new DampedOscillator(0.0, 0.1), "omega0 == 0 should throw"),
() -> assertThrows(IllegalArgumentException.class, () -> new DampedOscillator(-1.0, 0.1), "negative omega0 should throw"), () -> assertThrows(IllegalArgumentException.class, () -> new DampedOscillator(1.0, -0.1), "negative gamma should throw"));
}
@Test
@DisplayName("Analytical displacement matches expected formula for underdamped case")
void analyticalUnderdamped() {
double omega0 = 10.0;
double gamma = 0.5;
DampedOscillator d = new DampedOscillator(omega0, gamma);
double a = 1.0;
double phi = 0.2;
double t = 0.123;
// expected: a * exp(-gamma * t) * cos(omega_d * t + phi)
double omegaD = Math.sqrt(Math.max(0.0, omega0 * omega0 - gamma * gamma));
double expected = a * Math.exp(-gamma * t) * Math.cos(omegaD * t + phi);
double actual = d.displacementAnalytical(a, phi, t);
assertEquals(expected, actual, 1e-12, "Analytical underdamped displacement should match closed-form value");
}
@Test
@DisplayName("Analytical displacement gracefully handles overdamped parameters (omegaD -> 0)")
void analyticalOverdamped() {
double omega0 = 1.0;
double gamma = 2.0; // gamma > omega0 => omega_d = 0 in our implementation (Math.max)
DampedOscillator d = new DampedOscillator(omega0, gamma);
double a = 2.0;
double phi = Math.PI / 4.0;
double t = 0.5;
// With omegaD forced to 0 by implementation, expected simplifies to:
double expected = a * Math.exp(-gamma * t) * Math.cos(phi);
double actual = d.displacementAnalytical(a, phi, t);
assertEquals(expected, actual, 1e-12, "Overdamped handling should reduce to exponential * cos(phase)");
}
@Test
@DisplayName("Explicit Euler step approximates analytical solution for small dt over short time")
void eulerApproximatesAnalyticalSmallDt() {
double omega0 = 10.0;
double gamma = 0.5;
DampedOscillator d = new DampedOscillator(omega0, gamma);
double a = 1.0;
double phi = 0.0;
// initial conditions consistent with amplitude a and zero phase:
// x(0) = a, v(0) = -a * gamma * cos(phi) + a * omegaD * sin(phi)
double omegaD = Math.sqrt(Math.max(0.0, omega0 * omega0 - gamma * gamma));
double x0 = a * Math.cos(phi);
double v0 = -a * gamma * Math.cos(phi) - a * omegaD * Math.sin(phi); // small general form
double dt = 1e-4;
int steps = 1000; // simulate to t = 0.1s
double tFinal = steps * dt;
double[] state = new double[] {x0, v0};
for (int i = 0; i < steps; i++) {
state = d.stepEuler(state, dt);
}
double analyticAtT = d.displacementAnalytical(a, phi, tFinal);
double numericAtT = state[0];
// Euler is low-order — allow a small tolerance but assert it remains close for small dt + short time.
assertEquals(analyticAtT, numericAtT, TOLERANCE, String.format("Numeric Euler should approximate analytical solution at t=%.6f (tolerance=%g)", tFinal, TOLERANCE));
}
@Test
@DisplayName("stepEuler validates inputs and throws on null/invalid dt/state")
void eulerInputValidation() {
DampedOscillator d = new DampedOscillator(5.0, 0.1);
assertAll("invalid-stepEuler-args",
()
-> assertThrows(IllegalArgumentException.class, () -> d.stepEuler(null, 0.01), "null state should throw"),
()
-> assertThrows(IllegalArgumentException.class, () -> d.stepEuler(new double[] {1.0}, 0.01), "state array with invalid length should throw"),
() -> assertThrows(IllegalArgumentException.class, () -> d.stepEuler(new double[] {1.0, 0.0}, 0.0), "non-positive dt should throw"), () -> assertThrows(IllegalArgumentException.class, () -> d.stepEuler(new double[] {1.0, 0.0}, -1e-3), "negative dt should throw"));
}
@Test
@DisplayName("Getter methods return configured parameters")
void gettersReturnConfiguration() {
double omega0 = Math.PI;
double gamma = 0.01;
DampedOscillator d = new DampedOscillator(omega0, gamma);
assertAll("getters", () -> assertEquals(omega0, d.getOmega0(), 0.0, "getOmega0 should return configured omega0"), () -> assertEquals(gamma, d.getGamma(), 0.0, "getGamma should return configured gamma"));
}
@Test
@DisplayName("Analytical displacement at t=0 returns initial amplitude * cos(phase)")
void analyticalAtZeroTime() {
double omega0 = 5.0;
double gamma = 0.2;
DampedOscillator d = new DampedOscillator(omega0, gamma);
double a = 2.0;
double phi = Math.PI / 3.0;
double t = 0.0;
double expected = a * Math.cos(phi);
double actual = d.displacementAnalytical(a, phi, t);
assertEquals(expected, actual, 1e-12, "Displacement at t=0 should be a * cos(phase)");
}
}