带电粒子在匀强磁场中的圆周运动交互课件

 

圆周运动的基础上,顺便做一个带电粒子在匀强磁场中的圆周运动课件,实现的是沪科版选择性必修二第五章 第三节 带电粒子在匀强磁场中的圆周运动中的图 5–31。如下图所示。

图 5–31  带电粒子在匀强磁场中运动

原本以为只要在原有的基础上绘制磁场就可以了,类似于火花学院的这种,后来在飞象老师上看到了这个课件挺好的,就多花了点时间实现了类似的轨迹线效果。

做出的成品如下所示:

数据设置

为了避免犯科学性错误,查了一下真实情况中的典型数值:

小型实验/教学用回旋加速器:B 可能在 0.5 T 到 1.5 T 之间,质子能量在 10  MeV 以下。

医用质子回旋加速器(用于癌症治疗):能量通常 70  MeV 到 250  MeV,采用紧凑型设计时可达更高。现代紧凑型回旋加速器主要用于生产医用放射性同位素(如氟-18 用于PET-CT)或进行中子活化分析,其典型半径:约 0.3 m 到 1.2 m,磁场强度约 1 T 到 2 T,质子能量通常在 10 MeV 到 30 MeV 之间。整个加速器系统(包括磁铁、真空室、高频系统、屏蔽等)通常像一个房间里的中型设备,占地面积从几平方米到几十平方米不等。核心的磁铁直径大概在 1 ~ 3 m。

若采用小型回旋加速器的数值,在不考虑相对论的情况下,质子的速度

\[v = \sqrt {\frac{{2{E_{\rm{k}}}}}{m}} = \sqrt {\frac{{2 \times 10 \times {{10}^6} \times 1.6 \times {{10}^{ - 19}}}}{{1.67 \times {{10}^{ - 27}}}}} \;{\rm{m/s}} \approx 4.37 \times {10^6}\;{\rm{m/s}} \approx 0.146c\]

考虑相对论时求出的速度会再小一点。因此程序中磁感应强度的取值范围为 – 1 ~ 1 T,速度取值范围为 1 ~ 5×107 m/s。

质子的比荷 \(\dfrac{e}{{{m_{\rm{p}}}}}\) ≈ 9.58×107 C/kg,是电子比荷的 \(\dfrac{1}{{1\;836}}\),在稳定、长寿命的常见带电粒子中,除了电子(及其反粒子正电子)以外,质子的荷质比是最大的。因此比荷的取值范围为 − 1×108 ~ 1×108 C/kg。

若 v = 5×107 m/s,B = 1 T,q/m = 1×108 C/kg,求得运转半径 r = 0.5 m,周期约 0.628 ns。

核心代码解读

这次 AI 竟然还创建了一个 Particle 类,使代码逻辑更清晰一点。在这个类中保存了带电粒子水平方向、竖直方向的速度和位置信息:vxvyxy

在代码中先求出当前角速度 ω = \(\frac{m}{{qB}}\),由 θ = ωΔt 求出一帧中转过的角度,根据当前帧的速度 v0xv0y 和 θ 求出下一帧的速度 v1xv2y,进一步求出下一帧粒子的位置 x1y1

绘制轨迹线的逻辑是:由 200 个点组成轨迹,在 update 方法中每隔 5 帧将粒子的位置信息保存到 trail 数组中,若点数超过 200,则移除第 1 个。在 draw 方法中由这 200 点生成 199 条线段,并根据顺序设置透明度,这样就实现了轨迹线渐隐的效果。

// 粒子类
class Particle {
    constructor(x, y, vx, vy, qm) {
      this.pos = { x, y, opacity: 1 };
      this.vel = { x: vx, y: vy };
      this.qm = qm;
      this.trail = [];
      this.color = qm > 0 ? '#f43f5e' : (qm < 0 ? '#3b82f6' : '#94a3b8');
      this.trailFrameCounter = 0; // 轨迹记录帧计数器
    }

    update(dt, B) {
      // 速度因子,用于在不改变物理参数的情况下加快粒子在屏幕上的移动速度
      const speedFactor = 2.0;

      // 如果比荷为0或B为0,不受力
      if (Math.abs(this.qm) < 0.1 || Math.abs(B) < 0.1) {
        this.pos.x += this.vel.x * dt * CONFIG.scale * speedFactor;
        this.pos.y += this.vel.y * dt * CONFIG.scale * speedFactor;
      } else {
        // omega = -(q/m) * B * direction
        const omega = -this.qm * B;

        const angle = omega * dt * speedFactor;
        const cos = Math.cos(angle);
        const sin = Math.sin(angle);

        const newVx = this.vel.x * cos - this.vel.y * sin;
        const newVy = this.vel.x * sin + this.vel.y * cos;

        this.vel.x = newVx;
        this.vel.y = newVy;

        // 更新位置
        this.pos.x += this.vel.x * dt * CONFIG.scale * speedFactor;
        this.pos.y += this.vel.y * dt * CONFIG.scale * speedFactor;
      }

      // 记录轨迹 - 每5帧记录一次,减少粒子密度但保持轨迹长度不变
      // 每个轨迹点携带自己的颜色信息
      this.trailFrameCounter++;
      if (this.trailFrameCounter % 5 === 0) {
        this.trail.push({
          x: this.pos.x,
          y: this.pos.y,
          color: this.color // 记录当前颜色
        });

        // 限制轨迹点数量
        if (this.trail.length > CONFIG.trailLength) {
          this.trail.shift();
        }
      }
     }

    draw() {
      // 更新粒子位置
      const cx = this.pos.x;
      const cy = this.pos.y;

      this.particleEl.setAttribute('cx', cx);
      this.particleEl.setAttribute('cy', cy);
      this.particleEl.setAttribute('opacity', this.pos.opacity);


      // 绘制轨迹 - 使用多个线段,每段根据生命周期设置透明度
      if (this.trail.length > 1) {
        // 先清空旧的轨迹线段
        this.trailContainer.innerHTML = '';

        // 创建轨迹线段
        for (let i = 1; i < this.trail.length; i++) {
          const prevPoint = this.trail[i - 1];
          const currPoint = this.trail[i];

          // 计算透明度:根据轨迹点在数组中的位置
          // 最旧的点(索引0)透明度为0,最新的点(索引length-1)透明度为1
          const opacity = i / (this.trail.length - 1);

          // 创建线段
          const line = document.createElementNS(NS, 'line');
          line.setAttribute('x1', prevPoint.x);
          line.setAttribute('y1', prevPoint.y);
          line.setAttribute('x2', currPoint.x);
          line.setAttribute('y2', currPoint.y);
          line.setAttribute('stroke', currPoint.color); // 使用当前点的颜色
          line.setAttribute('stroke-width', '5');
          line.setAttribute('opacity', opacity);

          this.trailContainer.appendChild(line);
        }
    }
}

欧拉积分法

高中阶段解决一维匀变速直线运动的思路如下:

但在真实场景中,通常不会有像这样美好的解析解,对于更通用的问题,我们必须用数值方法进行积分,才能求得速度及位置。最基本的方法叫做欧拉积分法。基本的欧拉积分法有 2 种。

1.显式欧拉法(Explicit Euler)

用当前点的切线方向来估计下一个点,就像只看脚下走路。它的缺点是稳定性差、能量不守恒,模拟物理系统会发散。若用 n 表示当前状态,n + 1 表示下一时刻的状态,则积分运算过程可以表示为:

vn+1 = vn + anΔt

xn+1 = xn + vnΔt

2.半隐式欧拉法(Semi-Implicit Euler)

又称辛欧拉法,是一种改进的欧拉方法,它部分使用未来信息,但保持显式可解性。是显式欧拉和完全隐式欧拉之间的折中方案。积分运算过程可以表示为:

vn+1 = vn + anΔt

xn+1 = xn + vn+1Δt

与显式欧拉法相比,唯一的区别是新位置 xn+1 是用新速度 vn+1 隐式更新的。用物理直观解释,就像一个谨慎的行人,先看路计算下一步的速度(基于当前位置),用新速度走——用刚算出的速度决定走多远,比显式更安全:不会“走过头”。它更接近真实物理:物体确实是先加速,然后用新速度位移。显式欧拉法能量单调增长,可能会数值爆炸,而半隐式欧拉法能量有界振荡,可以保持长期稳定。推荐看一下Bilibili上的视频——颠覆轨道模拟的代码:辛积分与相空间不变量如何革新小行星带长期模拟?——加深理解。

若用这两种方法分析匀加速直线运动,显式欧拉法相当于 x = v0t,位移偏小,隐式欧拉法相当于x = (v0 + at)t,位移偏大,而精确值为 x = \(\dfrac{{{ v_0} + ({v_0} + at)}}{2}\)t

在本例的代码中:

const newVx = this.vel.x * cos - this.vel.y * sin;
const newVy = this.vel.x * sin + this.vel.y * cos;
this.vel.x = newVx;
this.vel.y = newVy;
// 更新位置
this.pos.x += this.vel.x * dt * CONFIG.scale * speedFactor;
this.pos.y += this.vel.y * dt * CONFIG.scale * speedFactor;

可见,用的是半隐式欧拉法。

完整代码

code

发布时间:2026/2/1 下午4:58:15  阅读次数:70

2006 - 2026,推荐分辨率 1024*768 以上,推荐浏览器 Chrome、Edge 等现代浏览器,截止 2021 年 12 月 5 日的访问次数:1872 万 9823 站长邮箱

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号