威尔伯福斯摆:耦合振动建模、实时参数辨识与教学可视化系统 📅 发布时间:2026/9/14 3:32:09 👁 浏览次数: 简介本资源为2021年全国大学生物理实验竞赛一等奖获奖项目——威尔伯福斯摆Wilberforce Pendulum的完整开源实现面向物理类本科生、实验课程教师及对振动与耦合动力学感兴趣的科研初学者。项目聚焦共振耦合现象系统呈现振动能量在纵向与横向模式间周期性转换的物理机制助力理解振动理论、能量守恒、傅里叶分析等核心概念。压缩包共94个文件含39个JavaScript前端交互模块用于数据可视化与参数调节、5个Python脚本承担数值模拟与数据分析、16个XML配置与界面定义文件、7个CSS/JS样式资源及配套文档README.md、LICENSE、ss.md等整体仅912KB轻量易部署。目前已有293人学习下载内容结构清晰backend含Flask服务与模型逻辑src为前端工程cal.py与app.py体现关键算法与控制流程配套说明文档详述实验原理与复现实操路径可直接用于课程演示、创新实验复现或毕业设计参考。1. 威尔伯福斯摆不是“两个摆的简单组合”而是耦合振动系统的教科书级实证载体2021年全国大学生物理实验竞赛一等奖作品.zip 解压后核心是wilberforce_pendulum_publish-master它并非一个演示动画或仿真课件而是一套完整闭环的物理实验系统从硬件信号采集含光电门/加速度计原始数据流、实时数据处理Python 后端服务、动态可视化React 前端界面到理论建模验证含参数辨识与相图重构。这个项目真正解决的是高校物理实验中长期存在的“现象可观、规律难量、模型难验”三重断层——学生能看见摆动但无法精确捕捉能量在纵向拉伸模态与横向扭转模态间的周期性转移教师有理论公式却缺乏可复现、可调节、可对比的实测数据支撑。它适合两类人一是需要将《力学》《振动与波》课程实验升级为“可编程物理实验”的高校教师二是正在准备创新实验设计、需同时体现硬件搭建、数据建模与可视化表达能力的本科生团队。项目中cal.py的相位差计算逻辑、models.py的双自由度微分方程离散化实现、以及app.py中 WebSocket 实时数据管道的设计共同构成了一个可拆解、可替换、可扩展的物理实验数字底座。2. 威尔伯福斯摆的物理建模必须从双自由度耦合方程出发而非单摆近似2.1 为什么不能直接套用单摆公式——耦合项的物理意义与数学表征威尔伯福斯摆的核心特征在于其纵向轴向振动与横向扭转振动存在强动力学耦合。若忽略耦合项仅将两部分视为独立单摆会导致振幅衰减预测偏差超40%相位关系完全失真。真实系统需用如下双自由度微分方程组描述$$ \begin{cases} m\ddot{z} k_z z \kappa (\theta - \alpha z) 0 \ I\ddot{\theta} k_\theta \theta \kappa (\theta - \alpha z) 0 \end{cases} $$其中 $z$ 为纵向位移$\theta$ 为扭转角$m$ 和 $I$ 分别为等效质量与转动惯量$k_z$、$k_\theta$ 为各自刚度$\kappa$ 是耦合刚度系数$\alpha$ 表征几何耦合比例由连杆长度与摆臂偏心距决定。项目models.py中的关键实现正是对这一方程组的无量纲化与四阶龙格-库塔离散化# models.py 片段双自由度系统状态空间建模 def wilberforce_ode(t, y, params): y [z, dz/dt, theta, dtheta/dt] params [m, I, kz, ktheta, kappa, alpha, c_z, c_theta] # 含阻尼项 z, vz, theta, vtheta y m, I, kz, ktheta, kappa, alpha, cz, ctheta params # 耦合项明确体现在两个方程的右侧kappa*(theta - alpha*z) dzdt vz dvzdt (-kz * z - kappa * (theta - alpha * z) - cz * vz) / m dthetadt vtheta dvthetadt (-ktheta * theta - kappa * (theta - alpha * z) - ctheta * vtheta) / I return [dzdt, dvzdt, dthetadt, dvthetadt]提示kappa参数在实际标定时不可设为零。项目cal.py中通过扫频实验获取共振峰分裂间距 $\Delta f f_2 - f_1$再代入公式 $\kappa \approx \frac{1}{2} m (2\pi \Delta f)^2$ 进行初值估计这是避免数值求解发散的关键前置步骤。2.2 实验数据如何反推模型参数——基于最小二乘的在线辨识流程理论模型需与实测数据对齐。项目采用分阶段参数辨识策略先固定几何参数$\alpha$ 由结构测量获得再通过三组独立实验数据联合优化剩余6个参数。cal.py中的identify_parameters()函数执行此过程# cal.py 片段多目标参数辨识主函数 def identify_parameters(raw_data_list): raw_data_list: [ (t1, z1, theta1), (t2, z2, theta2), ... ] 返回最优参数向量及各目标函数残差 def cost_func(params): total_error 0 for t, z_obs, theta_obs in raw_data_list: # 使用当前params积分ODE得到模拟z_sim, theta_sim sol solve_ivp( wilberforce_ode, [t[0], t[-1]], [z_obs[0], 0, theta_obs[0], 0], # 初值取首帧观测值 args(params,), t_evalt, methodRK45, rtol1e-6 ) z_sim, theta_sim sol.y[0], sol.y[2] # 加权误差z通道权重0.6theta通道权重0.4因光电门精度更高 error_z np.mean((z_obs - z_sim)**2) error_theta np.mean((theta_obs - theta_sim)**2) total_error 0.6 * error_z 0.4 * error_theta return total_error # 初始猜测基于文献值与粗略测量 x0 [0.15, 0.002, 85.0, 12.0, 3.2, 0.8, 0.05, 0.03] # m, I, kz, ktheta, kappa, alpha, cz, ctheta result minimize(cost_func, x0, methodL-BFGS-B, bounds[(0.1,0.2), (0.001,0.005), (70,100), (8,15), (1,5), (0.5,1.2), (0.01,0.1), (0.01,0.1)]) return result.x, result.fun # 执行辨识示例调用 optimal_params, min_error identify_parameters([ (t_exp1, z_exp1, theta_exp1), (t_exp2, z_exp2, theta_exp2), (t_exp3, z_exp3, theta_exp3) ])该代码逻辑说明solve_ivp在每次迭代中重新积分微分方程将模拟输出与三组实测数据比对bounds参数强制物理合理性如质量不能为负、阻尼系数需在合理量级最终返回的optimal_params可直接写入config.py供后续仿真使用。失败时常见原因包括初始猜测偏离过远导致minimize收敛至局部极小此时应检查raw_data_list中时间序列是否对齐、初值是否取自同一时刻。2.3 模型验证必须通过相图与能量轨迹——而不仅是时域波形拟合仅比对时域波形z-t, θ-t易掩盖相位误差。项目采用相图Phase Portrait与模态能量轨迹Energy Trajectory双重验证。cal.py中plot_phase_energy()函数生成关键诊断图验证维度计算方法物理意义正常表现z-vz 相图plt.plot(z, vz)纵向振动能量守恒性封闭椭圆长轴方向反映 $k_z/m$θ-vθ 相图plt.plot(theta, vtheta)扭转振动能量守恒性封闭椭圆长轴方向反映 $k_\theta/I$能量交换轨迹E_z 0.5*m*vz² 0.5*kz*z²,E_θ 0.5*I*vθ² 0.5*kθ*θ²,plt.plot(E_z, E_θ)耦合强度与能量守恒近似直线斜率 ≈ -1总能量 $E_zE_θ$ 波动 5%# cal.py 片段能量轨迹绘制关键诊断 def plot_energy_trajectory(z, vz, theta, vtheta, params): m, I, kz, ktheta, *_ params E_z 0.5 * m * vz**2 0.5 * kz * z**2 E_theta 0.5 * I * vtheta**2 0.5 * ktheta * theta**2 E_total E_z E_theta plt.figure(figsize(12,4)) plt.subplot(131) plt.plot(z, vz, b-, alpha0.7) plt.xlabel(z (m)); plt.ylabel(vz (m/s)) plt.title(Longitudinal Phase Portrait) plt.subplot(132) plt.plot(theta, vtheta, r-, alpha0.7) plt.xlabel(θ (rad)); plt.ylabel(vθ (rad/s)) plt.title(Torsional Phase Portrait) plt.subplot(133) plt.plot(E_z, E_theta, g-, alpha0.7) plt.xlabel(E_z (J)); plt.ylabel(E_θ (J)) plt.title(Energy Exchange Trajectory) plt.grid(True) # 添加总能量波动标注 plt.figtext(0.02, 0.02, fTotal energy std: {np.std(E_total):.3e} J, fontsize10) plt.tight_layout() plt.show() # 调用示例使用辨识后的参数 plot_energy_trajectory(z_exp1, vz_exp1, theta_exp1, vtheta_exp1, optimal_params)参数辨识成功的标志是第三幅图中能量轨迹呈高线性度R² 0.995且总能量标准差低于 $10^{-4}$ J。若出现明显弯曲说明耦合项 $\kappa$ 或阻尼系数估计不足若总能量持续下降过快则需增大cz,ctheta。3. 实时数据管道设计从传感器原始信号到前端动态相图的低延迟链路3.1 后端服务如何承载高频振动数据——WebSocket 与异步任务的协同架构威尔伯福斯摆典型振动频率在 1–3 Hz但为精确捕捉相位关系采样率需达 100 Hz 以上。app.py采用 Flask-SocketIO 构建全双工通信避免 HTTP 轮询的延迟累积。其核心是分离「数据接收」与「数据广播」两个异步任务# app.py 片段异步数据管道主干 from flask_socketio import SocketIO, emit, join_room import asyncio from threading import Lock socketio SocketIO(app, async_modeeventlet, cors_allowed_origins*) data_buffer [] # 存储最近1000帧原始数据 buffer_lock Lock() sampling_rate 100 # Hz socketio.on(connect) def handle_connect(): join_room(pendulum_data) # 任务1模拟传感器数据流实际项目中替换为串口/USB读取 async def sensor_reader(): 每10ms生成一帧数据z, theta, timestamp t 0.0 while True: # 模拟真实传感器噪声高斯白噪声量化误差 z_raw 0.02 * np.sin(2*np.pi*1.8*t 0.1) np.random.normal(0, 0.0005) theta_raw 0.015 * np.sin(2*np.pi*1.9*t - 0.3) np.random.normal(0, 0.0003) frame { t: round(t, 3), z: round(z_raw, 6), theta: round(theta_raw, 6), ts: int(time.time() * 1000) # 毫秒级时间戳 } with buffer_lock: data_buffer.append(frame) if len(data_buffer) 1000: data_buffer.pop(0) await asyncio.sleep(0.01) # 10ms间隔 → 100Hz t 0.01 # 任务2向所有客户端广播最新数据每50ms推送一次降低前端压力 async def broadcaster(): while True: await asyncio.sleep(0.05) with buffer_lock: if data_buffer: latest data_buffer[-1] socketio.emit(pendulum_update, latest, roompendulum_data) # 启动异步任务Flask-SocketIO eventlet 模式下 socketio.on(start_stream) def start_stream(): socketio.start_background_task(sensor_reader) socketio.start_background_task(broadcaster)注意sensor_reader中await asyncio.sleep(0.01)是硬性时间约束确保采样间隔稳定。实际部署时需替换为serial.Serial.readline()或pyusb读取但必须保证循环内无阻塞操作如文件I/O、数据库查询否则会拖慢整个事件循环。3.2 前端如何实现毫秒级相图渲染——WebGL 加速的 Canvas 动画src/pages/PendulumView.tsx使用react-konva基于 HTML5 Canvas实现轻量级实时绘图避免 DOM 重排开销。关键优化点在于只重绘新增点不刷新整图使用requestAnimationFrame同步屏幕刷新率// src/pages/PendulumView.tsx 片段高效相图渲染 import { Stage, Layer, Line, Circle } from react-konva; const PendulumView () { const [phasePoints, setPhasePoints] useState{x: number, y: number}[]([]); // WebSocket 监听器仅追加新点不重建数组 useEffect(() { const socket io(); socket.on(pendulum_update, (data: {z: number, theta: number}) { // 坐标归一化z∈[-0.03,0.03]→x∈[100,500], theta∈[-0.02,0.02]→y∈[100,400] const x 100 ((data.z 0.03) / 0.06) * 400; const y 100 ((0.02 - data.theta) / 0.04) * 300; // Y轴翻转 setPhasePoints(prev [ ...prev.slice(-199), // 保留最近200点 { x, y } ]); }); return () socket.close(); }, []); return ( Stage width{600} height{500} Layer {/* 动态相图曲线 */} Line points{phasePoints.flatMap(p [p.x, p.y])} stroke#3b82f6 strokeWidth{2} tension{0.5} // 样条插值平滑 / {/* 当前点高亮 */} {phasePoints.length 0 ( Circle x{phasePoints[phasePoints.length-1].x} y{phasePoints[phasePoints.length-1].y} radius{4} fill#ef4444 / )} /Layer /Stage ); }; export default PendulumView;该实现每帧仅更新points属性Konva 库内部自动进行 Canvas 路径重绘实测在 Chrome 中可稳定维持 60 FPS。若需更高性能如添加频谱图可切换至reglWebGL方案但本项目复杂度下 Canvas 已足够。3.3 数据校准与异常过滤前端预处理保障可视化可靠性原始传感器数据含直流偏移与脉冲噪声。src/utils/dataProcessor.ts在前端完成轻量级校准避免后端重复计算// src/utils/dataProcessor.ts 片段前端实时校准 export class DataProcessor { private zOffset: number 0; private thetaOffset: number 0; private zHistory: number[] []; private thetaHistory: number[] []; // 启动时采集1秒静止数据计算零点偏移 calibrateZero(zStream: number[], thetaStream: number[]): void { this.zOffset zStream.reduce((a, b) a b, 0) / zStream.length; this.thetaOffset thetaStream.reduce((a, b) a b, 0) / thetaStream.length; } // 滑动窗口中位数滤波抗脉冲噪声 filterOutlier(value: number, history: number[], windowSize: number 5): number { history.push(value); if (history.length windowSize) history.shift(); const sorted [...history].sort((a, b) a - b); return sorted[Math.floor(sorted.length / 2)]; } process(raw: {z: number, theta: number}): {z: number, theta: number} { let zClean raw.z - this.zOffset; let thetaClean raw.theta - this.thetaOffset; // 应用中位数滤波z通道更易受冲击干扰 zClean this.filterOutlier(zClean, this.zHistory); thetaClean this.filterOutlier(thetaClean, this.thetaHistory); // 限幅超出±5倍标准差则截断防止传感器饱和 const zStd Math.sqrt(this.zHistory.reduce((sum, x) sum (x - this.zOffset)**2, 0) / this.zHistory.length); const thetaStd Math.sqrt(this.thetaHistory.reduce((sum, x) sum (x - this.thetaOffset)**2, 0) / this.thetaHistory.length); zClean Math.max(-5*zStd, Math.min(5*zStd, zClean)); thetaClean Math.max(-5*thetaStd, Math.min(5*thetaStd, thetaClean)); return { z: parseFloat(zClean.toFixed(6)), theta: parseFloat(thetaClean.toFixed(6)) }; } } // 使用示例 const processor new DataProcessor(); processor.calibrateZero(initialZ, initialTheta); // 静止期调用 socket.on(pendulum_update, (raw) { const cleaned processor.process(raw); // 更新相图... });此校准逻辑使相图中心稳定在原点消除因温度漂移导致的缓慢偏移中位数滤波有效抑制开关机瞬态、桌面震动等脉冲干扰避免相图出现异常飞点。4. 从竞赛作品到教学工具参数扫描与对比实验的快速构建方法4.1 一键生成多组对比实验——修改 config.py 即可切换物理场景项目将所有可调参数集中于config.py教师无需改代码即可开展探究式教学。例如研究耦合强度对能量交换周期的影响只需修改KAPPA值并重启服务# config.py 关键参数节选教学场景常用调整项 # ———————————————————————————————————————————————— # 【基础物理参数】 MASS 0.15 # kg, 摆锤质量 INERTIA 0.002 # kg·m², 摆锤转动惯量 K_Z 85.0 # N/m, 纵向刚度 K_THETA 12.0 # N·m/rad, 扭转刚度 KAPPA 3.2 # N·m/rad, 耦合刚度 ← 修改此处 ALPHA 0.8 # 无量纲, 几何耦合系数 # 【阻尼参数】 C_Z 0.05 # N·s/m, 纵向阻尼 C_THETA 0.03 # N·m·s/rad, 扭转阻尼 # 【仿真与显示】 SIMULATION_DT 0.005 # s, 数值积分步长 PLOT_WINDOW_SEC 10.0 # s, 实时绘图时间窗 PHASE_PLOT_SCALE 200 # px/unit, 相图缩放因子修改KAPPA 1.0后重启app.py前端相图将立即显示能量交换周期显著延长理论周期 $T_{exchange} \propto 1/\kappa$设KAPPA 0.0则两模态完全解耦相图退化为两个独立椭圆。这种即时反馈极大提升课堂演示效果。4.2 快速导出科研级数据包——JSON 格式兼容 MATLAB/Python 分析所有实时采集与仿真数据均按标准 JSON Schema 输出便于导入主流分析工具。views.py中/api/export接口提供三种导出模式# views.py 片段标准化数据导出 from flask import jsonify, send_file import json import io app.route(/api/export, methods[POST]) def export_data(): data_type request.json.get(type) # raw, simulated, both duration request.json.get(duration, 30) # 秒 # 从内存缓冲区或数据库提取指定时长数据 if data_type raw: export_data get_raw_buffer_last(duration) elif data_type simulated: export_data run_simulation_for(duration) else: export_data { raw: get_raw_buffer_last(duration), simulated: run_simulation_for(duration) } # 生成符合物理实验数据规范的JSON output { metadata: { experiment_id: WP_2021_AWARD, timestamp: datetime.now().isoformat(), sampling_rate_hz: 100, units: {z: m, theta: rad, t: s}, parameters_used: current_config_dict() # 读取config.py当前值 }, data: export_data } # 内存中生成JSON文件并返回 json_str json.dumps(output, indent2) json_bytes io.BytesIO(json_str.encode(utf-8)) json_bytes.seek(0) return send_file( json_bytes, mimetypeapplication/json, as_attachmentTrue, download_namefwilberforce_data_{data_type}_{duration}s.json )教师在课堂演示后可立即导出wilberforce_data_raw_30s.json学生用 Pythonpandas.read_json()或 MATLABjsondecode()直接加载无缝接入傅里叶分析、李雅普诺夫指数计算等高阶实验。4.3 教学提示卡三个必做对比实验与预期现象为降低教学实施门槛项目附带ss.md教学提示文档明确列出三个核心对比实验及其现象判据实验编号操作步骤预期现象教学要点EXP-01将KAPPA从 3.2 降至 0.8保持其他参数不变能量交换周期延长约 2.2 倍相图中能量轨迹斜率绝对值减小耦合强度 $\kappa$ 直接决定模态间能量转移速率验证公式 $T_{exchange} \frac{2\pi}{\sqrt{(k_z/m - k_\theta/I)^2 4\kappa^2}}$EXP-02将C_Z从 0.05 增至 0.2C_THETA不变z-vz 相图椭圆迅速坍缩为点θ-vθ 相图仍保持较完整椭圆总能量衰减中 z 分量占比超 70%阻尼不对称性导致能量单向耗散引申讨论非保守系统的哈密顿量破缺EXP-03将K_Z与K_THETA设为相等如均为 50.0KAPPA3.2出现简并模态z 与 θ 振动频率相同相图呈现完美圆形能量轨迹变为严格直线简并条件下系统具有额外对称性是理解量子简并、光子晶体带隙的基础类比每个实验均配有curl命令示例教师可直接在终端执行触发参数变更无需打开编辑器# EXP-01 示例降低耦合强度 curl -X POST http://localhost:5000/api/update_config \ -H Content-Type: application/json \ -d {KAPPA: 0.8}此设计将复杂的物理概念转化为可触摸、可测量、可重复的操作指令真正实现“做中学”。本文还有配套的精品资源点击获取