From 785d8888872ebb254827152d5698c948fecaa802 Mon Sep 17 00:00:00 2001 From: Stephen Yu <135070653+sgaofen@users.noreply.github.com> Date: Fri, 15 May 2026 15:27:12 -0700 Subject: [PATCH 1/3] add viz/index.html: interactive TDSE particle-in-a-box demo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Single-file HTML visualization aligned with the Fortran solver: analytic eigenstate expansion on (-1, 1), Wigner phase space matching wigner.f90, bilingual (EN/中文) walkthrough. Co-Authored-By: Claude Opus 4.7 (1M context) --- viz/index.html | 1102 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1102 insertions(+) create mode 100644 viz/index.html diff --git a/viz/index.html b/viz/index.html new file mode 100644 index 0000000..c334476 --- /dev/null +++ b/viz/index.html @@ -0,0 +1,1102 @@ + + + + + +Particle in a Box · TDSE Interactive Demo + + + +
+ +
+
+ + +
+ +

+ Particle in a Box + 盒中粒子 +

+

+ A Gaussian wavepacket inside an infinite square well, evolved by the time-dependent Schrödinger equation using the same eigenstate-expansion method as the tdse Fortran solver. It bounces off the walls, interferes with its own reflection, and eventually scrambles into noise. + 一个高斯波包关在无限深势阱里,用跟 tdse Fortran 求解器同一套本征态展开方法演化。它会撞墙反弹、和反射波互相干涉,最终被打散成一片"噪声"。 +

+

+ TDSE · particle in a box on x ∈ (−1, 1) · eigenstate expansion · Wigner phase space + 含时薛定谔方程 · 一维势阱 x ∈ (−1, 1) · 本征态展开 · Wigner 相空间 +

+
+ +
+
+

+ Position space位置空间 + |ψ(x,t)|2 +

+
+ + |ψ|² · probability density + |ψ|² · 概率密度 + + Re(ψ) + Im(ψ) + + walls硬墙 + +
+ +

+ The wavefunction vanishes at x = ±1: the walls are infinitely steep. Inside the box ψ propagates as if free; when it hits a wall the reflected wave superposes on the incident one and weaves an interference pattern. + 波函数在 x = ±1 处必须等于 0:势阱壁是无穷陡的。盒子内部 ψ 像自由粒子那样演化;撞到墙后,反射波叠加在入射波上,织出干涉图案。 +

+
+
+

+ Phase space · Wigner function相空间 · Wigner 函数 + W(x,p,t) +

+
+ negative + + positive +
+ +

+ A quasi-probability density on the (x, p) plane, computed point-for-point with the kernel from wigner.f90. Unlike a classical probability, W can go negative — those blue patches are the literal fingerprints of quantum interference. + (x, p) 平面上的准概率密度,逐点使用 wigner.f90 的核函数计算。和经典概率密度不同,W 可以取负值——那些蓝色区域就是量子干涉的直接指纹。 +

+
+
+ +
+
⟨x⟩0.00
+
Δx0.00
+
⟨p⟩0.00
+
Δp0.00
+
⟨E⟩0.00
+
‖ψ‖1.000
+
+ +
+
+ + + t = 0.000 + +
+
+ + + +
+
+ +
+

What you're looking at

+

看动画时,你在看什么

+ +

A quantum particle isn't a point but a complex-valued wavefunction ψ(x,t). Its modulus-squared |ψ(x,t)|² is the probability density of finding it at position x at time t. Here that particle is trapped between two infinitely tall walls at x = ±1—the canonical "particle in a box."

+

量子粒子不是一个点,而是一个复值的 波函数 ψ(x,t)。模平方 |ψ(x,t)|² 告诉你在时刻 t 的位置 x 上能找到它的概率密度。这里这个粒子被两堵无限高的墙关在 x = ±1 之间——也就是教科书上的"盒中粒子"。

+ +

I · The eigenstates of the box

+

一、势阱的本征态

+

For an empty box on (−1, 1), the Hamiltonian Ĥ = −½ ∂²/∂x² with Dirichlet boundary conditions has a complete set of energy eigenstates indexed by an integer n = 1, 2, 3, …

+

(−1, 1) 上的空盒子里,哈密顿量 Ĥ = −½ ∂²/∂x² 在 Dirichlet 边界条件下有一组完备的能量本征态,用整数 n = 1, 2, 3, … 编号:

+
φn(x) = sin(nπ(x+1)/2),    En = n²π² / 8
+

These are standing waves that vanish at both walls. The energies grow as —higher modes oscillate faster and faster.

+

这些是在两堵墙上都为零的驻波。能量随 增长——高阶模式的振荡频率越来越快。

+ +

II · The initial wavepacket as a sum of standing waves

+

二、初始波包 = 驻波的叠加

+

We start from a Gaussian, the same shape the Fortran's tdse.f90 writes onto the grid:

+

我们从一个高斯出发——形状跟 Fortran 的 tdse.f90 写到网格上的那个一致:

+
ψ(x, 0) ∝ exp[ −α (x − x0)2 + i p0 (x − x0) ]
+

Three knobs: x₀ is the center, p₀ is the average momentum, α sets how sharp the packet is. Project this Gaussian onto every φₙ:

+

三个旋钮:x₀ 是中心位置,p₀ 是平均动量,α 决定波包多窄。然后把这个高斯投影到每一个 φₙ 上:

+
cn = ∫−11 φn(x) ψ(x, 0) dx
+

The Fortran computes these (numerically, via finite differences); this page computes them with the same trapezoidal rule but on the exact analytic eigenstates—so the spectrum agrees with the Fortran to leading order in 1/n_grid.

+

Fortran 用有限差分数值算 cₙ;这一页用同样的梯形积分,但作用在精确的解析本征态上——所以两套频谱在 1/n_grid 的领头阶上一致。

+ +

III · Each mode rotates on its own clock

+

三、每个模式按自己的节奏旋转

+

Because each φₙ is an energy eigenstate, the Schrödinger equation reduces to a phase rotation per mode:

+

每个 φₙ 都是哈密顿量本征态,所以薛定谔方程就退化成每个模式独立的相位旋转:

+
ψ(x, t) = Σn cn φn(x) e−i En t
+

This is exactly what propagate_convert.f90 does in the Fortran: transform to the sine basis, multiply by phases exp(−i Eₙ t), transform back. Every frame on this page is one evaluation of this sum, on a 256-point grid, with the first 100 modes. No time-stepping, no accumulated error.

+

这正是 Fortran 里 propagate_convert.f90 干的事:变换到 sine 基底,乘上相位 exp(−i Eₙ t),再变换回来。本页每一帧都是这个求和的一次求值——256 个网格点,前 100 个模式。没有时间推进、没有累积误差。

+ +

IV · Wigner phase space

+

四、Wigner 相空间

+

The right panel projects the wavefunction onto a (position, momentum) plane via the Wigner quasi-distribution:

+

右图通过 Wigner 准分布把波函数投到 (位置, 动量) 平面上:

+
W(x, p, t) = (1/π) ∫ ψ*(x+ξ, t) ψ(x−ξ, t) e2 i p ξ
+

Same formula as wigner.f90; this page computes it on every frame with an extended momentum range so the peak stays in view. The crucial feature: W can be negative. Classical probabilities can't—so any blue patch on the right is a quantum-only effect, the signature of two pieces of ψ interfering with each other.

+

公式跟 wigner.f90 一致;本页在更宽的动量范围内逐帧计算,让峰始终在视野内。关键性质:W 可以取负值。经典概率不行——所以右图里任何一块蓝色都是量子专属现象,是两段 ψ 之间在干涉。

+ +

V · What you'll see

+

五、你会看到什么

+

Bouncing. The Wigner blob slides toward whichever wall p₀ points at, with classical round-trip time T_cl ≈ 4/|p₀|.

+

反弹。Wigner 斑点朝 p₀ 指向的那堵墙滑过去,经典往返周期约为 T_cl ≈ 4/|p₀|

+

Reflection flip. When the packet hits a wall, the Wigner blob splits and the momentum sign flips—you'll literally see it jump from +p₀ to −p₀ on the vertical axis.

+

反弹翻转。波包撞墙的瞬间,Wigner 斑点会"裂"开,动量符号翻转——你会直观看到它从纵轴的 +p₀ 跳到 −p₀

+

Interference fringes. Near a wall, the incoming and reflected pieces overlap. The position panel develops vertical stripes with spatial period π / |p₀| (the de Broglie wavelength), and the phase-space picture sprouts a band of blue between the two streams—the W-function admitting that interference is happening.

+

干涉条纹。墙附近入射和反射波同时存在,位置面板会出现竖条纹,空间周期约为 π / |p₀|(德布罗意波长);相空间则会在两股之间长出一条蓝带——W 函数直接写明了"这里有干涉"。

+

Scrambling. Different modes accumulate phases at different rates (∝ n²). After enough bounces the wavefunction looks like noise. It isn't random—it's a coherent superposition with too many components for the eye to parse.

+

"打乱"。不同模式按不同的速率累积相位(∝ n²)。反弹几次后波函数看起来像噪声——其实并不是随机,只是叠加的成分太多、肉眼分不清而已。

+

Revivals. Because every Eₙ is an integer multiple of π²/8, all phases re-align at T_rev = 16/π ≈ 5.09. Even partial revivals at T_rev / q are pretty—several little copies of the packet appear at once.

+

复活。因为所有 Eₙ 都是 π²/8 的整数倍,所有相位会在 T_rev = 16/π ≈ 5.09 重新对齐。在分数时刻 T_rev/q 还有漂亮的"部分复活"——同时出现几个小副本。

+

Energy and norm are conserved. ⟨E⟩ = Σ |cₙ|² Eₙ and ‖ψ‖² both stay constant in the readout below.

+

能量与归一化都守恒。下方读数里 ⟨E⟩ = Σ |cₙ|² Eₙ‖ψ‖² 都不会变。

+ +

VI · How this aligns with the Fortran code

+

六、跟 Fortran 求解器对得上吗

+

Same domain x ∈ (−1, 1), same boundary condition ψ = 0 at the walls, same initial Gaussian formula (matching tdse.f90:50–55), same eigenstate-expansion method (matching propagate_convert.f90), same Wigner kernel (matching wigner.f90). The one deliberate difference: this page uses the exact analytic eigenfunctions sin(nπ(x+1)/2) while the Fortran uses the finite-difference eigenvectors of its kinetic matrix—the two agree as n_grid → ∞, and for the low modes that dominate the dynamics on screen they're visually indistinguishable. Run the Fortran with the same (x₀, p₀, α) and any of its psi_⟨step⟩ output files will match the curve here at t = step × τ.

+

同一区域 x ∈ (−1, 1)、同一边界条件(墙上 ψ = 0)、同一初始高斯公式(对应 tdse.f90:50–55)、同一种本征态展开(对应 propagate_convert.f90)、同一 Wigner 核(对应 wigner.f90)。唯一刻意的差别:本页用解析本征函数 sin(nπ(x+1)/2),Fortran 用其动能矩阵的有限差分本征矢——两者在 n_grid → ∞ 时一致,对屏幕上主导的低阶模式肉眼看不出差别。用同样的 (x₀, p₀, α) 跑 Fortran,它任何 psi_⟨step⟩ 文件都能对上本页 t = step × τ 时刻的曲线。

+ +

VII · Implementation notes

+

七、给教授的版本说明

+

Single self-contained HTML file. No dependencies, no build step. Open viz/index.html in any browser. The position panel is SVG (right-click any frame → save as a vector graphic for slides). The phase-space panel is Canvas. Animation speed, time horizon, and grid resolution are constants at the top of the <script> block.

+

单文件、零依赖、不需要构建。任何浏览器里打开 viz/index.html 即可。位置面板是 SVG(右键单帧 → 另存为矢量图直接进 slides)。相空间面板是 Canvas。动画速度、时间范围、网格分辨率在 <script> 块顶部的常量里可调。

+
+ +
+ TDSE viz · particle in a box · analytic eigenstate expansion · Wigner phase space · matches the Fortran solver pointwise + TDSE viz · 盒中粒子 · 解析本征态展开 · Wigner 相空间 · 与 Fortran 求解器逐点一致 +
+ +
+ + + + From 80452e089009f31c58399574d6426517461fe329 Mon Sep 17 00:00:00 2001 From: sgaofen Date: Fri, 22 May 2026 17:43:19 -0700 Subject: [PATCH 2/3] viz: default position panel to |psi|^2; make Re/Im opt-in Re(psi) and Im(psi) are components of the complex wavefunction, not directly observable. They oscillate at the de Broglie wavelength and drown out |psi|^2 structure (e.g. wall-reflection interference fringes) when p0 is large. Default the position panel to |psi|^2 only; add legend-checkbox toggles to overlay Re/Im on demand. Co-Authored-By: Claude Opus 4.7 (1M context) --- viz/index.html | 49 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/viz/index.html b/viz/index.html index c334476..1b63369 100644 --- a/viz/index.html +++ b/viz/index.html @@ -303,6 +303,23 @@ margin-right: 5px; border-radius: 1px; } + .legend label.toggle { + display: inline-flex; + align-items: center; + gap: 5px; + cursor: pointer; + user-select: none; + padding: 2px 6px; + border-radius: 3px; + transition: background 0.15s; + } + .legend label.toggle:hover { background: var(--rule-soft); } + .legend label.toggle input[type=checkbox] { + margin: 0; + accent-color: var(--ink-soft); + } + .legend label.toggle:not(.on) { opacity: 0.55; text-decoration: line-through; } + .legend label.toggle:not(.on) .sw { opacity: 0.35; } @@ -339,8 +356,14 @@

|ψ|² · probability density |ψ|² · 概率密度 - Re(ψ) - Im(ψ) + + walls硬墙 @@ -805,8 +828,8 @@

七、给教授的版本说明

svg.appendChild(xlab); const fillPath = makeSvgEl('path', { id: 'psi2-fill', fill: '#2d5d57', 'fill-opacity': 0.18, stroke: 'none' }); - const rePath = makeSvgEl('path', { id: 're-line', fill: 'none', stroke: '#2f5d9e', 'stroke-width': 1.0, 'stroke-opacity': 0.7 }); - const imPath = makeSvgEl('path', { id: 'im-line', fill: 'none', stroke: '#c4793a', 'stroke-width': 1.0, 'stroke-opacity': 0.7 }); + const rePath = makeSvgEl('path', { id: 're-line', fill: 'none', stroke: '#2f5d9e', 'stroke-width': 1.0, 'stroke-opacity': 0.7, display: 'none' }); + const imPath = makeSvgEl('path', { id: 'im-line', fill: 'none', stroke: '#c4793a', 'stroke-width': 1.0, 'stroke-opacity': 0.7, display: 'none' }); const psi2Path = makeSvgEl('path', { id: 'psi2-line', fill: 'none', stroke: '#2d5d57', 'stroke-width': 1.8 }); svg.appendChild(fillPath); svg.appendChild(rePath); @@ -1038,6 +1061,23 @@

七、给教授的版本说明

syncParams(); } +function bindReImToggles() { + const pairs = [ + ['tgl-re', 're-line', 'tgl-re-wrap'], + ['tgl-im', 'im-line', 'tgl-im-wrap'] + ]; + for (const [cbId, pathId, wrapId] of pairs) { + const cb = document.getElementById(cbId); + const wrap = document.getElementById(wrapId); + const apply = () => { + document.getElementById(pathId).style.display = cb.checked ? '' : 'none'; + wrap.classList.toggle('on', cb.checked); + }; + cb.addEventListener('change', apply); + apply(); + } +} + function bindLang() { const btnEn = document.getElementById('btn-en'); const btnZh = document.getElementById('btn-zh'); @@ -1096,6 +1136,7 @@

七、给教授的版本说明

paintColorbar(); bindLang(); bindControls(); +bindReImToggles(); requestAnimationFrame(loop); From 475a8daeca6a4617c9cca49ad908d27d5417af00 Mon Sep 17 00:00:00 2001 From: sgaofen Date: Fri, 22 May 2026 17:44:29 -0700 Subject: [PATCH 3/3] mp4gen.bash: plot |psi|^2 vs x, not (Re psi, Im psi) The original `using 2:3` plotted a curve in the (Re psi, Im psi) complex plane parameterized by x, which is not a physical observable. Switch to `using 1:($2**2+$3**2)` so the animation shows the probability density |psi|^2 against position x. Also tighten the axis ranges to the box [-1, 1] and a positive y-range, and label the axes. Co-Authored-By: Claude Opus 4.7 (1M context) --- mp4gen.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mp4gen.bash b/mp4gen.bash index fef0f15..b7a5182 100755 --- a/mp4gen.bash +++ b/mp4gen.bash @@ -1,3 +1,3 @@ #!/bin/bash -gnuplot -e "set yrange [-2:2]; set xrange [-2:2]; set term png size 1600,1200; do for [i=1:1000] {; set output 'psi_'.i.'.png'; p 'psi_'.i using 2:3 w l}" +gnuplot -e "set yrange [0:1.5]; set xrange [-1:1]; set xlabel 'x'; set ylabel '|psi|^2'; set term png size 1600,1200; do for [i=1:1000] {; set output 'psi_'.i.'.png'; p 'psi_'.i using 1:(\$2**2+\$3**2) w l}" ffmpeg -f image2 -r 10.0 -i psi_%d.png -pix_fmt yuv420p -qscale 1 out.mp4