Files
dynamics/examples/case04/Readme.html
T
admin 7078cbc744 fix: 轨道示意图太阳移至左焦点,默认 e=0.017(地球真实 0.0167)
- 椭圆中心改为 cx+f,使左焦点=cx 为太阳
- 偏心距标注从椭圆中心指向左焦点
- a/b 标注、近日点/远日点位置同步修正
- 滑块映射改为 e=value/1000(精度 0.001)
- 滑块默认值 17 → e=0.017 ≈ 地球真实 0.0167
2026-06-18 23:05:23 +08:00

594 lines
19 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>case04 — 日地月三体系统</title>
<script>
MathJax = {
tex: { inlineMath: [['$','$'], ['\\(','\\)']], displayMath: [['$$','$$'], ['\\[','\\]']] },
svg: { fontCache: 'global' }
};
</script>
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js" async></script>
<style>
:root {
--bg: #0d1117;
--surface: #161b22;
--border: #30363d;
--text: #c9d1d9;
--text-dim: #8b949e;
--accent: #58a6ff;
--green: #3fb950;
--orange: #d29922;
--red: #f85149;
--teal: #56d4dd;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans SC", Helvetica, Arial, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.8;
padding: 40px 24px;
}
.container { max-width: 860px; margin: 0 auto; }
h1 { font-size: 1.8rem; margin-bottom: 6px; color: #f0f6fc; }
h1 small { font-size: 1rem; color: var(--text-dim); font-weight: 400; }
.subtitle { color: var(--text-dim); margin-bottom: 32px; }
h2 {
font-size: 1.35rem;
margin-top: 36px;
margin-bottom: 14px;
padding-bottom: 6px;
border-bottom: 1px solid var(--border);
color: #f0f6fc;
}
h3 { font-size: 1.1rem; margin-top: 24px; margin-bottom: 10px; color: #f0f6fc; }
p { margin-bottom: 12px; color: var(--text); }
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
padding: 20px 24px;
margin-bottom: 20px;
}
table {
width: 100%;
border-collapse: collapse;
margin: 12px 0;
font-size: 0.9rem;
}
th, td {
text-align: left;
padding: 8px 14px;
border-bottom: 1px solid var(--border);
}
th { color: var(--text-dim); font-weight: 600; background: #1c2128; }
.highlight {
background: #1f242e;
border-left: 3px solid var(--accent);
padding: 10px 16px;
margin: 12px 0;
border-radius: 0 6px 6px 0;
font-size: 0.9rem;
}
.highlight-eq {
background: #1f242e;
border-left: 3px solid var(--teal);
padding: 14px 18px;
margin: 14px 0;
border-radius: 0 6px 6px 0;
}
.highlight-eq p { margin: 6px 0; }
code {
background: #1c2128;
padding: 2px 6px;
border-radius: 4px;
font-family: "SF Mono", "Fira Code", monospace;
font-size: 0.85em;
color: var(--green);
}
.tag {
display: inline-block;
padding: 2px 10px;
border-radius: 4px;
font-size: 0.78rem;
font-weight: 500;
margin: 2px;
}
.tag-python { background: #3572A533; color: #3572A5; }
.tag-gravity { background: #d2992233; color: var(--orange); }
ul, ol { margin: 8px 0 12px 24px; }
li { margin-bottom: 4px; }
.formula-block {
overflow-x: auto;
padding: 8px 0;
}
.diagram-wrap {
background: #0d1117;
border: 1px solid var(--border);
border-radius: 8px;
padding: 16px;
margin: 16px 0;
text-align: center;
}
.diagram-wrap canvas {
display: block;
margin: 0 auto;
max-width: 100%;
height: auto;
}
.diagram-controls {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 16px;
margin-top: 12px;
}
.diagram-controls label {
font-size: 0.85rem;
color: var(--text-dim);
}
.diagram-controls input[type="range"] {
width: 200px;
accent-color: var(--accent);
}
.diagram-controls .val {
font-size: 0.9rem;
font-weight: 500;
color: var(--teal);
min-width: 48px;
display: inline-block;
text-align: center;
}
.diagram-info {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 20px;
margin-top: 10px;
font-size: 0.82rem;
color: var(--text-dim);
}
.diagram-info span {
display: inline-flex;
align-items: center;
gap: 4px;
}
.diagram-info .dot {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 2px;
}
@media (max-width: 640px) {
body { padding: 16px; }
.diagram-controls { flex-direction: column; gap: 8px; }
}
</style>
</head>
<body>
<div class="container">
<h1>case04 — 日地月三体系统 <small>稳定轨道版本</small></h1>
<p class="subtitle">太阳、地球、月球三体系统,采用真实比例的质量和万有引力模拟,地球和月球维持稳定椭圆轨道。</p>
<div class="card" style="display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin-bottom:20px;">
<span class="tag tag-python">Python 引擎</span>
<span class="tag tag-gravity">万有引力</span>
<span style="color:var(--text-dim); font-size:0.85rem; margin-left:8px;">3 粒子 | leapfrog 算法 | 稳定轨道</span>
</div>
<h2>一、物理系统概览</h2>
<div class="card">
<p>本案例模拟真实的日-地-月三体系统:</p>
<ul>
<li><strong>太阳</strong>:位于原点,质量为 $M_\odot$,固定不动(fix_x=fix_y=fix_z=1</li>
<li><strong>地球</strong>:绕太阳公转,质量为 $M_\oplus$,近日点出发</li>
<li><strong>月球</strong>:绕地球公转(同时跟随地球绕太阳),质量为 $M_\text{moon}$</li>
</ul>
<p>原子间万有引力由 <code>gravity_strength</code> 缩放控制,$F = G \dfrac{m_1 m_2}{r^2}$。</p>
</div>
<h2>二、天体真实参数</h2>
<h3>2.1 质量</h3>
<table>
<tr><th>天体</th><th>质量 / kg</th><th>模拟质量(月球=1</th></tr>
<tr><td>太阳</td><td>$1.989 \times 10^{30}$</td><td>$27\,000$</td></tr>
<tr><td>地球</td><td>$5.972 \times 10^{24}$</td><td>$81$</td></tr>
<tr><td>月球</td><td>$7.35 \times 10^{22}$</td><td>$1$</td></tr>
</table>
<div class="highlight">
<strong>质量比</strong>$M_\odot : M_\oplus : M_\text{moon} \approx 27\,100\,000 : 81.3 : 1$<br>
模拟中采用缩放比例 $27\,000 : 81 : 1$(太阳质量缩至 $1/1000$ 以保持数值稳定)。
</div>
<h3>2.2 轨道参数</h3>
<table>
<tr><th>轨道</th><th>半长轴 $a$</th><th>偏心率 $e$</th><th>半短轴 $b$</th><th>周期</th></tr>
<tr><td>地球绕太阳</td><td>$1.4960 \times 10^8$ km (1 AU)</td><td>$0.0167$</td><td>$1.4958 \times 10^8$ km</td><td>365.25 天</td></tr>
<tr><td>月球绕地球</td><td>$3.844 \times 10^5$ km</td><td>$0.0549$</td><td>$3.838 \times 10^5$ km</td><td>27.32 天</td></tr>
</table>
<div class="highlight">
<strong>比例</strong>$R_{\text{日地}} : R_{\text{地月}} \approx 389 : 1$<br>
地月距离约为日地距离的 $1/389$。
</div>
<h2>三、轨道力学</h2>
<h3>3.1 交互式轨道示意图</h3>
<p>拖动下方滑块改变偏心率 $e$,观察轨道形状的变化和参数标注:</p>
<div class="diagram-wrap">
<canvas id="orbitCanvas" width="760" height="400"></canvas>
<div class="diagram-controls">
<label>偏心率 e = <span class="val" id="eDisplay">0.0170</span>(地球真实值 0.0167</label>
<input type="range" id="eSlider" min="0" max="850" value="17">
</div>
<div class="diagram-info" id="diagramInfo"></div>
</div>
<h3>3.2 偏心率定义</h3>
<div class="highlight-eq">
<p>偏心率 $e$ 描述椭圆轨道偏离正圆的程度:</p>
<div class="formula-block">
$$e = \frac{c}{a}$$
</div>
<p>其中 $c = ea$ 为偏心距(焦点到椭圆中心的距离),$a$ 为半长轴。</p>
</div>
<table>
<tr><th>$e$ 值</th><th>轨道形状</th><th>说明</th></tr>
<tr><td>$e = 0$</td><td>正圆形</td><td>速度恒定,距离恒定</td></tr>
<tr><td>$0 &lt; e &lt; 1$</td><td>椭圆</td><td>近日点速度最大,远日点速度最小</td></tr>
<tr><td>$e = 1$</td><td>抛物线</td><td>逃逸轨道,速度恰好达到逃逸速度</td></tr>
<tr><td>$e &gt; 1$</td><td>双曲线</td><td>飞越轨道,速度超过逃逸速度</td></tr>
</table>
<h3>3.3 半长轴与半短轴的关系</h3>
<div class="highlight-eq">
<div class="formula-block">
$$b = a\sqrt{1 - e^2}$$
</div>
<p>当 $e \ll 1$ 时,$b \approx a\left(1 - \dfrac{e^2}{2}\right)$,椭圆度非常微小。</p>
</div>
<h3>3.4 近日点与远日点</h3>
<div class="highlight-eq">
<p>近日点(距太阳最近)和远日点(距太阳最远)的距离分别为:</p>
<div class="formula-block">
$$r_{\text{peri}} = a(1 - e), \qquad r_{\text{ap}} = a(1 + e)$$
</div>
<p>在近日点轨道速度最大,在远日点轨道速度最小:</p>
<div class="formula-block">
$$v_{\text{peri}} = \sqrt{\frac{GM(1+e)}{a(1-e)}}, \qquad
v_{\text{ap}} = \sqrt{\frac{GM(1-e)}{a(1+e)}}$$
</div>
</div>
<h3>3.5 开普勒三大定律</h3>
<ol>
<li><strong>椭圆定律</strong>:行星轨道是椭圆,太阳位于椭圆的一个焦点上。</li>
<li><strong>面积定律</strong>:行星与太阳的连线在相等时间内扫过相等的面积。</li>
<li><strong>周期定律</strong>:公转周期的平方与半长轴的立方成正比:$T^2 \propto a^3$。</li>
</ol>
<div class="highlight">
<strong>验证</strong>:在本模拟中,你可以通过轨迹图观察面积定律是否成立——地球在近日点移动更快,远日点移动更慢。
</div>
<h2>四、模拟参数</h2>
<h3>4.1 缩放说明</h3>
<p>真实尺度无法直接用于模拟(日地距离 1.5 亿 km),因此采用缩放参数。当前 case04 的配置为教学演示而简化:</p>
<table>
<tr><th>参数</th><th></th><th>说明</th></tr>
<tr><td><code>gravity_strength</code></td><td>100.0</td><td>万有引力强度</td></tr>
<tr><td><code>box_a</code></td><td>30.0</td><td>盒子半边长</td></tr>
<tr><td><code>method</code></td><td>leapfrog</td><td>蛙跳法(辛积分器,能量守恒)</td></tr>
<tr><td><code>T_total</code></td><td>10.0 s</td><td>总模拟时间</td></tr>
<tr><td><code>NSTEP</code></td><td>2</td><td>抽帧间隔(密采帧)</td></tr>
</table>
<h3>4.2 初始构型</h3>
<table>
<tr><th>天体</th><th>质量</th><th>位置 $(x,y,z)$</th><th>速度 $(v_x,v_y,v_z)$</th><th>固定约束</th></tr>
<tr><td>太阳</td><td>$27\,000$</td><td>$(0,0,0)$</td><td>$(0,0,0)$</td><td>全部固定</td></tr>
<tr><td>地球</td><td>$81$</td><td>$(10,0,0)$</td><td>$(0,0,520)$</td><td></td></tr>
<tr><td>月球</td><td>$1$</td><td>$(11.5,0,0)$</td><td>$(0,0,526)$</td><td></td></tr>
</table>
<p>地球在 $z$ 方向获得初速度 $v=520$,产生绕太阳的轨道运动;月球在地球基础上附加 $v=6$ 的绕地速度,形成绕地球的微小轨道。速度由圆形轨道公式 $v = \sqrt{G_{\text{eff}} M / r}$ 计算,其中 $G_{\text{eff}} = \text{gravity\_strength} = 100$。</p>
<h2>五、使用方法</h2>
<div class="card">
<code style="display:block; padding:14px 18px; margin-bottom:10px;">
# 进入 case04 目录并运行<br>
cd examples/case04<br>
python run_dynamics.py<br><br>
# 仅输出轨迹图,跳过动画<br>
python run_dynamics.py --no-plot
</code>
<p>配置文件:<code>input/input.txt</code>(物理参数)、<code>input/coord.txt</code>(初始位置/速度)。</p>
</div>
<h2>六、与 case03 的对比</h2>
<table>
<tr><th></th><th>case03(失败案例)</th><th>case04(成功案例)</th></tr>
<tr><td>轨道状态</td><td>轨道发散或碰撞</td><td>稳定椭圆轨道</td></tr>
<tr><td>关键差异</td><td>初始速度或质量比不恰当</td><td>合理的初值和参数</td></tr>
<tr><td>教学意义</td><td>展示参数选择的重要性</td><td>展示正确的三体运动</td></tr>
</table>
<div class="highlight">
<strong>教学建议</strong>:先运行 case03 观察失稳,再运行 case04 对比稳定轨道,理解初始条件对数值模拟的关键影响。
</div>
<h2>七、已知局限</h2>
<ul>
<li>太阳真实质量为月球 $27\,100\,000$ 倍,模拟中缩至 $27\,000$ 倍以保持数值稳定($1/1000$)</li>
<li>轨道半径未按真实比例缩放(真实日地距是地月距的 389 倍,模拟中仅为 6.7 倍)</li>
<li>未考虑月球轨道倾角(真实地月轨道有约 5° 的倾角)</li>
<li>leapfrog 算法为能量守恒的辛积分器,长期稳定,但步长过大时仍可能偏离真实轨道</li>
</ul>
</div>
<script>
(function() {
var canvas = document.getElementById('orbitCanvas');
var ctx = canvas.getContext('2d');
var slider = document.getElementById('eSlider');
var eDisplay = document.getElementById('eDisplay');
var infoDiv = document.getElementById('diagramInfo');
var W = 760, H = 400;
var cx = 380, cy = 200;
// 实际绘图区域半径
var A = 140;
function draw(e) {
ctx.clearRect(0, 0, W, H);
var b = A * Math.sqrt(1 - e * e);
var f = e * A;
// 长轴辅助线
ctx.beginPath();
ctx.moveTo(cx - A - 20, cy);
ctx.lineTo(cx + A + 20, cy);
ctx.strokeStyle = '#30363d';
ctx.lineWidth = 0.5;
ctx.setLineDash([4, 3]);
ctx.stroke();
ctx.setLineDash([]);
// 椭圆轨道(中心在 cx+f,左焦点=cx 为太阳)
ctx.beginPath();
ctx.ellipse(cx + f, cy, A, b, 0, 0, Math.PI * 2);
ctx.strokeStyle = '#58a6ff';
ctx.lineWidth = 1.5;
ctx.stroke();
// 半长轴 a(标注线,从椭圆中心到右顶点)
var aY = cy + 28;
ctx.beginPath();
ctx.moveTo(cx + f, aY);
ctx.lineTo(cx + f + A, aY);
ctx.strokeStyle = '#c9d1d9';
ctx.lineWidth = 1.5;
ctx.stroke();
// 箭头
ctx.beginPath();
ctx.moveTo(cx - f + 6, aY - 4);
ctx.lineTo(cx - f, aY);
ctx.lineTo(cx - f + 6, aY + 4);
ctx.strokeStyle = '#c9d1d9';
ctx.lineWidth = 1.5;
ctx.stroke();
ctx.beginPath();
ctx.moveTo(cx - f + A - 6, aY - 4);
ctx.lineTo(cx - f + A, aY);
ctx.lineTo(cx - f + A - 6, aY + 4);
ctx.strokeStyle = '#c9d1d9';
ctx.lineWidth = 1.5;
ctx.stroke();
ctx.fillStyle = '#c9d1d9';
ctx.font = '13px "Segoe UI", Arial, sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.fillText('a = ' + A.toFixed(0), cx + f + A/2, aY + 6);
// 偏心距 c = ea(从左焦点到椭圆中心)
var cY = cy - b - 20;
ctx.beginPath();
ctx.moveTo(cx, cY);
ctx.lineTo(cx + f, cY);
ctx.strokeStyle = '#d29922';
ctx.lineWidth = 1.5;
ctx.stroke();
ctx.beginPath();
ctx.moveTo(cx - f + 5, cY - 4);
ctx.lineTo(cx - f, cY);
ctx.lineTo(cx - f + 5, cY + 4);
ctx.strokeStyle = '#d29922';
ctx.lineWidth = 1.5;
ctx.stroke();
ctx.beginPath();
ctx.moveTo(cx - 5, cY - 4);
ctx.lineTo(cx, cY);
ctx.lineTo(cx - 5, cY + 4);
ctx.strokeStyle = '#d29922';
ctx.lineWidth = 1.5;
ctx.stroke();
ctx.fillStyle = '#d29922';
ctx.font = '13px "Segoe UI", Arial, sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
ctx.fillText('c = ea = ' + f.toFixed(1), cx + f/2, cY - 4);
// 半短轴 b 标注(从椭圆中心到上顶点)
var bX = cx + f + A + 18;
ctx.beginPath();
ctx.moveTo(bX, cy);
ctx.lineTo(bX, cy - b);
ctx.strokeStyle = '#3fb950';
ctx.lineWidth = 1.5;
ctx.stroke();
ctx.beginPath();
ctx.moveTo(bX - 4, cy - 6);
ctx.lineTo(bX, cy);
ctx.lineTo(bX + 4, cy - 6);
ctx.strokeStyle = '#3fb950';
ctx.lineWidth = 1.5;
ctx.stroke();
ctx.beginPath();
ctx.moveTo(bX - 4, cy - b + 6);
ctx.lineTo(bX, cy - b);
ctx.lineTo(bX + 4, cy - b + 6);
ctx.strokeStyle = '#3fb950';
ctx.lineWidth = 1.5;
ctx.stroke();
ctx.fillStyle = '#3fb950';
ctx.font = '13px "Segoe UI", Arial, sans-serif';
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
ctx.fillText('b = ' + b.toFixed(1), bX + 8, cy - b/2);
// 太阳(焦点)
ctx.beginPath();
ctx.arc(cx, cy, 7, 0, Math.PI * 2);
ctx.fillStyle = '#d29922';
ctx.fill();
ctx.strokeStyle = '#f0883e';
ctx.lineWidth = 1;
ctx.stroke();
ctx.fillStyle = '#f0f6fc';
ctx.font = '13px "Segoe UI", Arial, sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
ctx.fillText('太阳(左焦点)', cx, cy - 12);
// 近日点(左顶点:距左焦点最近)
var periX = cx + f - A;
ctx.fillStyle = '#58a6ff';
ctx.beginPath();
ctx.arc(periX, cy, 4, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#8b949e';
ctx.font = '12px "Segoe UI", Arial, sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.fillText('近日点 r = a(1-e) = ' + (A * (1 - e)).toFixed(1), periX, cy + 12);
// 远日点标注(右顶点:距左焦点最远)
var apX = cx + f + A;
ctx.fillStyle = '#58a6ff';
ctx.beginPath();
ctx.arc(apX, cy, 4, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#8b949e';
ctx.font = '12px "Segoe UI", Arial, sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.fillText('远日点 r = a(1+e) = ' + (A * (1 + e)).toFixed(1), apX, cy + 12);
// 地球(轨道上的点)
var angle = 0.4;
var ex = cx + f + A * Math.cos(angle);
var ey = cy - b * Math.sin(angle);
ctx.beginPath();
ctx.arc(ex, ey, 5, 0, Math.PI * 2);
ctx.fillStyle = '#58a6ff';
ctx.fill();
ctx.strokeStyle = '#1f6feb';
ctx.lineWidth = 1;
ctx.stroke();
ctx.fillStyle = '#58a6ff';
ctx.font = '12px "Segoe UI", Arial, sans-serif';
ctx.textAlign = 'left';
ctx.textBaseline = 'bottom';
ctx.fillText('地球', ex + 8, ey);
// 速度矢量
var vlen = 32;
var vx = -A * Math.sin(angle) * 0.7;
var vy = b * Math.cos(angle) * 0.7;
var vl = Math.sqrt(vx*vx + vy*vy);
if (vl > 0) {
vx = vx / vl * vlen;
vy = vy / vl * vlen;
}
ctx.beginPath();
ctx.moveTo(ex, ey);
ctx.lineTo(ex + vx, ey + vy);
ctx.strokeStyle = '#3fb950';
ctx.lineWidth = 2;
ctx.stroke();
ctx.beginPath();
var tipX = ex + vx, tipY = ey + vy;
var a1 = 0.3;
ctx.moveTo(tipX, tipY);
ctx.lineTo(tipX - vx * a1 + vy * a1 * 0.5, tipY - vy * a1 - vx * a1 * 0.5);
ctx.lineTo(tipX - vx * a1 - vy * a1 * 0.5, tipY - vy * a1 + vx * a1 * 0.5);
ctx.closePath();
ctx.fillStyle = '#3fb950';
ctx.fill();
// 信息面板
var roundE = Math.round(e * 10000) / 10000;
infoDiv.innerHTML =
'<span><span class="dot" style="background:#58a6ff"></span>椭圆轨道 e = ' + roundE.toFixed(4) + '</span>' +
'<span><span class="dot" style="background:#d29922"></span>偏心距 c = ' + f.toFixed(1) + '</span>' +
'<span><span class="dot" style="background:#3fb950"></span>b/a = ' + (b / A).toFixed(4) + '</span>' +
'<span>近日点 ' + (A * (1 - e)).toFixed(1) + ' / 远日点 ' + (A * (1 + e)).toFixed(1) + '</span>';
}
function update() {
var val = parseInt(slider.value);
var e = val / 1000;
eDisplay.textContent = e.toFixed(4);
draw(e);
}
slider.addEventListener('input', update);
update();
})();
</script>
</body>
</html>