Setting up a Potential
Let's refresh our memory with a look at the full Schrödinger equation:
Now that we have a particle in our simulation, we make things really interesting by giving it something to interact with. In quantum mechanics, this interaction is determined by the potential term, , in the Schrödinger equation. Indeed, it is in the interactions with changing potentials that quantum mechanics displays behavior that differs strongly from classical physics.
Being able to experiment with, and visualize, these interactions and behaviors is a powerful tool for developing a strong intuitive understanding of quantum behaviors. The wave function's interaction with a varying potential also affords us another opportunity to examine the code's correctness and stability.
In order to visualize the potential and its effect on the wave function, we need to expand the SchrodingerResults to include the potential, the energy of the incoming particle, and also the real and imaginary components of the wave function. To differentiate it from the previous version we'll call it SchrodingerRenderer.
The big change for the SchrodingerRenderer is that the
components of the wave function can be negative. Instead of ranging from 0 to PsiMax, the y-axis
now ranges from -PsiMax to PsiMax. At texture coordinate t
= 0, psi = -PsiMax,
and at t
= 1.0, psi = PsiMax. Of course, psi = 0 at t
= 0.5. We get
exactly this scale if we take the psi that pixel corresponds to as psi = 2*PsiMax*t - PsiMax
We make two additional modifications to the rendering code. We use a combination of smoothsteps
to soften the lines a bit, and when the values have changed by more than one vertical pixel we
fill in the curve with a short vertical line. We utilize the same pixelColor
method
for each of the
,
,
,
and the initial particle energy
curves.
/**
* Color pixels to represent the numerical values of a function on a grid. The function is
* confined to values in [-scale, +scale]. We also adjust the upper and lower bounds of the
* line as necessary to fill in discontinuities in the numerical values.
*
* @param {vec4} color The color for a line of the given function.
* @param {float} scale Possible values range from -scale to +scale.
* @param {float} fragY The y, vertical, fragment shader coordinate of this pixel
* in the range [0, yResolution-1] top to bottom.
* @param {Integer} yResolution The number of vertical pixels in the plot.
* @param {float} width Roughly the line width in pixels.
* @param {float} value The value of the function at the current position.
* @param {float} previousValue The previous function value.
*/
fn pixelColor(color: vec4f, scale: f32, fragY: f32, yResolution: u32,
width: f32, value: f32, previousValue: f32) -> vec4f
{
// The total height runs from -scale to +scale
let scale2 = 2.0*scale;
let adjustedPixel = -fragY + (f32(yResolution-1)/2.0);
// The function value for this pixel.
let pxValue = scale2*adjustedPixel/f32(yResolution);
// Begin fading in the color at the function value
// but adjust toward the previous value for continuity
let lowerEdge = min(value, previousValue+scale2/f32(yResolution));
// Begin fading out the color at the function value
// but adjust toward the previous value for continuity
let upperEdge = max(value, previousValue-scale2/f32(yResolution));
return color*(smoothstep(lowerEdge - scale2*width/f32(yResolution),
lowerEdge - scale2/f32(yResolution),
pxValue)
-smoothstep(upperEdge + scale2/f32(yResolution),
upperEdge + scale2*width/f32(yResolution),
pxValue));
}
This produces a readily understandable graph of the wave function as it propagates and interacts with the potential barrier.
This example shows our first unambiguously quantum behavior. At the potential barrier, the wave function splits in two, and is half reflected, and half transmitted. This represents roughly equal probabilities of detecting the particle as being reflected or as being transmitted. It does not correspond to the particle splitting into two.
We also did something a bit different in setting up the potential. Rather than write a new shader to populate the potential array, we utilize a JavaScript function. This makes it much easier for later users and content authors to modify the potential to their exact needs.
/**
* Create a potential array with v=0 except over a narrow range where it is elevated
* forming a potential barrier.
*/
function setPotential(xResolution) {
const potentialData = new Float32Array(xResolution);
const xResolution3 = xResolution/3;
for (var i = 0; i < xResolution; ++i) {
if (i < xResolution3) {
potentialData[i] = 0.0;
}
else if (i < xResolution3 + 10) {
potentialData[i] = 100.0;
}
else {
potentialData[i] = 0.0;
}
}
return potentialData;
}
