Domain: Physics — Physics (general)

Problem:
Consider a simple pendulum with quadratic air resistance, governed by the equation:


\[
\ddot{\theta} = -\frac{g}{L} \sin \theta - \gamma \omega |\omega|
\]


where \(\omega = \dot{\theta}\).

Given \(L = 10 \, \text{m}, g = 10 \, \text{m/s}^2\), and a damping coefficient \(\gamma = 0.05\), the pendulum is released from rest at an initial angle of \(\theta_0 = 100^\circ\).

Using a 4th order Runge-Kutta method with a sufficiently small timestep for a convergent solution, determine the time in seconds it takes for the peak angular displacement of the pendulum to first fall below \(30^\circ\). Report your answer to two decimal places.

Answer: \(63.28\)

Solution code:
import numpy as np

def solve_damped_pendulum(theta0_deg=100.0, L=10.0, g=10.0, gamma=0.05, tmax=200.0, dt=0.01):
    
    theta0 = np.radians(theta0_deg)
    # Initial angular velocity is zero (released from rest)
    omega0 = 0.0

    # Lists to store the results
    t_pts = [0.0]
    theta_pts = [theta0]
    omega_pts = [omega0]

    
    def rhs(theta, omega):
        """Returns the derivatives (d(theta)/dt, d(omega)/dt)"""
        d_theta_dt = omega
        d_omega_dt = -(g / L) * np.sin(theta) - gamma * omega * np.abs(omega)
        return d_theta_dt, d_omega_dt

    # 2. INTEGRATION LOOP (RK4)
    t = 0.0
    while t < tmax:
        # Get the current state
        theta, omega = theta_pts[-1], omega_pts[-1]

        # Ensure we don't step past tmax
        dt_eff = min(dt, tmax - t)

        # Calculate RK4 coefficients (k1, k2, k3, k4)
        k1_theta, k1_omega = rhs(theta, omega)
        k2_theta, k2_omega = rhs(theta + 0.5 * dt_eff * k1_theta, omega + 0.5 * dt_eff * k1_omega)
        k3_theta, k3_omega = rhs(theta + 0.5 * dt_eff * k2_theta, omega + 0.5 * dt_eff * k2_omega)
        k4_theta, k4_omega = rhs(theta + dt_eff * k3_theta, omega + dt_eff * k3_omega)

        # Calculate the new state
        theta_new = theta + (dt_eff / 6.0) * (k1_theta + 2*k2_theta + 2*k3_theta + k4_theta)
        omega_new = omega + (dt_eff / 6.0) * (k1_omega + 2*k2_omega + 2*k3_omega + k4_omega)

        # Advance time and store results
        t += dt_eff
        t_pts.append(t)
        theta_pts.append(theta_new)
        omega_pts.append(omega_new)

    return np.array(t_pts), np.array(theta_pts)


def find_decay_time(t_arr, theta_arr_rad, peak_threshold_deg=30.0):
    """
    Analyzes the pendulum's motion to find the time when its peak
    amplitude first drops below a given threshold.

    Args:
        t_arr (np.array): Array of time points.
        theta_arr_rad (np.array): Array of angular displacements in radians.
        peak_threshold_deg (float): The threshold angle in degrees.

    Returns:
        float or None: The time when the peak drops below the threshold, or None if not found.
    """
    peak_threshold_rad = np.radians(peak_threshold_deg)
    
    # We are only interested in positive peaks since it's released from a positive angle
    theta_arr_abs_rad = np.abs(theta_arr_rad)

    
 
    peak_indices = []
    for i in range(1, len(theta_arr_abs_rad) - 1):
        if theta_arr_abs_rad[i-1] < theta_arr_abs_rad[i] > theta_arr_abs_rad[i+1]:
            peak_indices.append(i)

    if not peak_indices:
        print("No peaks were found in the simulation time.")
        return None

    # Iterate through the peaks to find the first one below the threshold
    for idx in peak_indices:
        peak_time = t_arr[idx]
        peak_angle_rad = theta_arr_abs_rad[idx]
        peak_angle_deg = np.degrees(peak_angle_rad)

        

        if peak_angle_rad < peak_threshold_rad:
            
            return peak_time

   
    return None

# --- Main execution ---
if __name__ == "__main__":
    # Parameters from the problem
    L_param = 10.0      # m
    g_param = 10.0      # m/s^2
    gamma_param = 0.05  # damping coefficient
    theta0_param = 100.0# degrees

    
    time_step = 0.01
    max_time = 200.0

   
    # Run the simulation
    t_solution, theta_solution_rad = solve_damped_pendulum(
        theta0_deg=theta0_param,
        L=L_param,
        g=g_param,
        gamma=gamma_param,
        tmax=max_time,
        dt=time_step
    )
  

    # Analyze the results to find the answer
    decay_time = find_decay_time(t_solution, theta_solution_rad, peak_threshold_deg=30.0)

    # Print the final answer
    if decay_time is not None:
        
        print(f"{decay_time:.2f}")