单摆与阻尼

单摆与阻尼
模拟受空气阻力影响的单摆,展示振幅随时间的衰减。

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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值