diff --git a/src/main/java/com/thealgorithms/physics/DampedOscillator.java b/src/main/java/com/thealgorithms/physics/DampedOscillator.java
new file mode 100644
index 000000000..84028b628
--- /dev/null
+++ b/src/main/java/com/thealgorithms/physics/DampedOscillator.java
@@ -0,0 +1,109 @@
+package com.thealgorithms.physics;
+
+/**
+ * Models a damped harmonic oscillator, capturing the behavior of a mass-spring-damper system.
+ *
+ *
The system is defined by the second-order differential equation:
+ * x'' + 2 * gamma * x' + omega₀² * x = 0
+ * where:
+ *
+ * - omega₀ is the natural (undamped) angular frequency in radians per second.
+ * - gamma is the damping coefficient in inverse seconds.
+ *
+ *
+ * This implementation provides:
+ *
+ * - An analytical solution for the underdamped case (γ < ω₀).
+ * - A numerical integrator based on the explicit Euler method for simulation purposes.
+ *
+ *
+ * Usage Example:
+ *
{@code
+ * DampedOscillator oscillator = new DampedOscillator(10.0, 0.5);
+ * double displacement = oscillator.displacementAnalytical(1.0, 0.0, 0.1);
+ * double[] nextState = oscillator.stepEuler(new double[]{1.0, 0.0}, 0.001);
+ * }
+ *
+ * @author [Yash Rajput](https://github.com/the-yash-rajput)
+ */
+public final class DampedOscillator {
+
+ /** Natural (undamped) angular frequency (rad/s). */
+ private final double omega0;
+
+ /** Damping coefficient (s⁻¹). */
+ private final double gamma;
+
+ private DampedOscillator() {
+ throw new AssertionError("No instances.");
+ }
+
+ /**
+ * Constructs a damped oscillator model.
+ *
+ * @param omega0 the natural frequency (rad/s), must be positive
+ * @param gamma the damping coefficient (s⁻¹), must be non-negative
+ * @throws IllegalArgumentException if parameters are invalid
+ */
+ public DampedOscillator(double omega0, double gamma) {
+ if (omega0 <= 0) {
+ throw new IllegalArgumentException("Natural frequency must be positive.");
+ }
+ if (gamma < 0) {
+ throw new IllegalArgumentException("Damping coefficient must be non-negative.");
+ }
+ this.omega0 = omega0;
+ this.gamma = gamma;
+ }
+
+ /**
+ * Computes the analytical displacement of an underdamped oscillator.
+ * Formula: x(t) = A * exp(-γt) * cos(ω_d t + φ)
+ *
+ * @param amplitude the initial amplitude A
+ * @param phase the initial phase φ (radians)
+ * @param time the time t (seconds)
+ * @return the displacement x(t)
+ */
+ public double displacementAnalytical(double amplitude, double phase, double time) {
+ double omegaD = Math.sqrt(Math.max(0.0, omega0 * omega0 - gamma * gamma));
+ return amplitude * Math.exp(-gamma * time) * Math.cos(omegaD * time + phase);
+ }
+
+ /**
+ * Performs a single integration step using the explicit Euler method.
+ * State vector format: [x, v], where v = dx/dt.
+ *
+ * @param state the current state [x, v]
+ * @param dt the time step (seconds)
+ * @return the next state [x_next, v_next]
+ * @throws IllegalArgumentException if the state array is invalid or dt is non-positive
+ */
+ public double[] stepEuler(double[] state, double dt) {
+ if (state == null || state.length != 2) {
+ throw new IllegalArgumentException("State must be a non-null array of length 2.");
+ }
+ if (dt <= 0) {
+ throw new IllegalArgumentException("Time step must be positive.");
+ }
+
+ double x = state[0];
+ double v = state[1];
+ double acceleration = -2.0 * gamma * v - omega0 * omega0 * x;
+
+ double xNext = x + dt * v;
+ double vNext = v + dt * acceleration;
+
+ return new double[] {xNext, vNext};
+ }
+
+ /** @return the natural (undamped) angular frequency (rad/s). */
+ public double getOmega0() {
+ return omega0;
+ }
+
+ /** @return the damping coefficient (s⁻¹). */
+ public double getGamma() {
+ return gamma;
+ }
+}
diff --git a/src/test/java/com/thealgorithms/physics/DampedOscillatorTest.java b/src/test/java/com/thealgorithms/physics/DampedOscillatorTest.java
new file mode 100644
index 000000000..4b3e9fafe
--- /dev/null
+++ b/src/test/java/com/thealgorithms/physics/DampedOscillatorTest.java
@@ -0,0 +1,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}.
+ *
+ * Tests focus on:
+ *
+ * - Constructor validation
+ * - Analytical displacement for underdamped and overdamped parameterizations
+ * - Basic numeric integration sanity using explicit Euler for small step sizes
+ * - Method argument validation (null/invalid inputs)
+ *
+ */
+@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)");
+ }
+}