单摆与阻尼
模拟受空气阻力影响的单摆,展示振幅随时间的衰减。
import numpy as np import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation import matplotlib.patches as patches # 设置中文字体 plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC", "sans-serif"] plt.rcParams["axes.unicode_minus"] = False # 解决负号显示问题 class DampedPendulum: def __init__(self, length=1.0, mass=1.0, damping=0.1, gravity=9.81, initial_angle=np.pi / 4, initial_velocity=0.0): """ 初始化阻尼单摆 参数: length: 摆长 (米) mass: 摆锤质量 (千克) damping: 阻尼系数 gravity: 重力加速度 (米/秒²) initial_angle: 初始角度 (弧度) initial_velocity: 初始角速度 (弧度/秒) """ self.length = length self.mass = mass self.damping = damping self.gravity = gravity self.angle = initial_angle self.angular_velocity = initial_velocity self.time = 0.0 # 记录角度随时间的变化,用于绘制振幅衰减曲线 self.angles_history = [] self.time_history = [] # 记录最大振幅,用于绘制衰减包络线 self.amplitudes = [] self.amplitude_times = [] self.last_peak_angle = initial_angle self.peak_direction = 1 # 1表示寻找正峰值,-1表示寻找负峰值 def update(self, dt): """使用欧拉方法更新单摆状态""" # 计算角加速度 (考虑阻尼) angular_acceleration = -(self.gravity / self.length) * np.sin(self.angle) - \ (self.damping / self.mass) * self.angular_velocity # 更新角速度和角度 self.angular_velocity += angular_acceleration * dt self.angle += self.angular_velocity * dt self.time += dt # 记录角度和时间 self.angles_history.append(self.angle) self.time_history.append(self.time) # 检测振幅峰值 (用于绘制衰减包络线) if len(self.angles_history) >= 3: current_angle = self.angles_history[-1] prev_angle = self.angles_history[-2] prev_prev_angle = self.angles_history[-3] # 检测正峰值 if self.peak_direction == 1 and prev_angle > current_angle and prev_angle > prev_prev_angle: self.amplitudes.append(prev_angle) self.amplitude_times.append(self.time_history[-2]) self.peak_direction = -1 # 切换到寻找负峰值 # 检测负峰值 elif self.peak_direction == -1 and prev_angle < current_angle and prev_angle < prev_prev_angle: self.amplitudes.append(-prev_angle) # 存储绝对值 self.amplitude_times.append(self.time_history[-2]) self.peak_direction = 1 # 切换到寻找正峰值 def get_pendulum_position(self): """计算单摆位置""" x = self.length * np.sin(self.angle) y = -self.length * np.cos(self.angle) return x, y def run_simulation(damping=0.1, simulation_time=20, dt=0.01): """ 运行单摆阻尼模拟 参数: damping: 阻尼系数 simulation_time: 模拟总时间 (秒) dt: 时间步长 (秒) """ # 创建单摆对象 pendulum = DampedPendulum(damping=damping, initial_angle=np.pi / 3) # 创建图表 fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 10)) # 设置单摆动画轴 ax1.set_xlim(-1.2, 1.2) ax1.set_ylim(-1.2, 0.2) ax1.set_aspect('equal') ax1.set_title('阻尼单摆运动') ax1.grid(True) # 设置振幅衰减图轴 ax2.set_xlim(0, simulation_time) ax2.set_ylim(-np.pi / 2, np.pi / 2) ax2.set_xlabel('时间 (秒)') ax2.set_ylabel('角度 (弧度)') ax2.set_title(f'振幅随时间的衰减 (阻尼系数: {damping})') ax2.grid(True) # 创建单摆元素 pivot = patches.Circle((0, 0), 0.02, color='black') ax1.add_patch(pivot) pendulum_rod, = ax1.plot([0, 0], [0, -pendulum.length], 'k-', lw=2) pendulum_bob = patches.Circle((0, -pendulum.length), 0.05, color='red') ax1.add_patch(pendulum_bob) # 创建振幅衰减图元素 angle_line, = ax2.plot([], [], 'b-', label='角度') envelope_line, = ax2.plot([], [], 'r--', label='振幅包络线') ax2.axhline(y=0, color='gray', linestyle='-', alpha=0.3) ax2.legend() # 动画更新函数 def update(frame): # 更新单摆状态 pendulum.update(dt) # 更新单摆位置 x, y = pendulum.get_pendulum_position() pendulum_rod.set_data([0, x], [0, y]) pendulum_bob.center = (x, y) # 更新振幅衰减图 angle_line.set_data(pendulum.time_history, pendulum.angles_history) # 更新振幅包络线 if len(pendulum.amplitudes) > 1: # 创建平滑的包络线 t_envelope = np.array(pendulum.amplitude_times) amp_envelope = np.array(pendulum.amplitudes) # 用指数曲线拟合包络线 if len(t_envelope) > 3: # 确保有足够的数据点进行拟合 # 只使用最近的几个点进行拟合,避免早期数据影响 recent_points = min(10, len(t_envelope)) t_fit = t_envelope[-recent_points:] amp_fit = amp_envelope[-recent_points:] # 对振幅取对数,以便进行线性拟合 log_amp = np.log(amp_fit) fit = np.polyfit(t_fit, log_amp, 1) decay_constant = fit[0] initial_amp = np.exp(fit[1]) # 生成拟合的包络线 t_smooth = np.linspace(0, pendulum.time, 200) envelope = initial_amp * np.exp(decay_constant * t_smooth) # 同时显示正负包络线 ax2.set_ylim(min(-envelope[-1] * 1.1, min(pendulum.angles_history) * 1.1), max(envelope[-1] * 1.1, max(pendulum.angles_history) * 1.1)) envelope_line.set_data(np.concatenate([t_smooth, t_smooth]), np.concatenate([envelope, -envelope])) # 更新标题,显示当前时间和角度 ax1.set_title(f'阻尼单摆运动 - 时间: {pendulum.time:.1f}s, 角度: {pendulum.angle:.2f}rad') return pendulum_rod, pendulum_bob, angle_line, envelope_line # 创建动画 num_frames = int(simulation_time / dt) ani = FuncAnimation(fig, update, frames=num_frames, interval=dt * 1000, blit=True, repeat=False) plt.tight_layout() plt.show() return ani # 运行模拟 if __name__ == "__main__": # 可以修改阻尼系数来观察不同阻尼效果 # 小阻尼: 0.1, 中等阻尼: 0.5, 大阻尼: 1.0 run_simulation(damping=0.2, simulation_time=30)