Visualizing Quantum Mechanics with Python-CRC Press (2024) (1)
Visualizing Quantum Mechanics with Python-CRC Press (2024) (1)
Visualizing Quantum Mechanics with Python-CRC Press (2024) (1)
Quantum Mechanics can be an abstract and complex subject. Students often complain of confu-
sion, struggle, and frustration as they try to master the topic. The goal of this book is to reduce
the complexity and clarify the abstractions with concrete visual examples driven by simple py-
thon programs. It is assumed that the reader is concurrently taking a course in quantum me-
chanics, or self-studying quantum mechanics, but is looking for supplementary material to help
with understanding and visualizing how quantum mechanics works.
The focus of this book is writing python programs to visualize the underlying behavior of the
mathematical theory. The background needed to understand quantum mechanics is differen-
tial equations, linear algebra and modern physics. We need a strong foundation in differential
equations and linear algebra because the behavior of quantum systems is governed by equations
that are written in terms of these concepts. Modern physics includes concepts such as special
relativity and quantum phenomena like the photoelectric effect and energy quantization that
the theory of quantum mechanics seeks to explain. This book is also not an introduction to the
python programming language, or to numpy, or even to VPython. However its programming
examples start simply and grow more complex as the chapters progress, so deep expertise in any
of these is not a pre-requisite.
Key features:
• Provides an accessible and practical guide to the abstractions in quantum mechanics with
concrete visual examples driven by simple python programs.
• Contains few derivations, equations, and proofs.
• For complete beginners of python programming, appendix B serves as a very brief introduc-
tion to the main concepts needed to understand the code in this book.
Dr. Steve Spicklemire is Associate Professor of Physics at the University of Indianapolis, USA.
He has been teaching physics at the University of Indianapolis for more than 30 years. From the
implementation of “flipped” physics class to the modernization of scientific computing and labo-
ratory instrumentation courses, he has brought the strengths of his background in physics, engi-
neering and computer science into the classroom. Dr. Spicklemire also does IT and engineering
consulting. He is an active participant in several national research initiatives relating to improv-
ing physics education. These range from improving materials to help students prepare for class,
to supporting students success through standards based grading. He is an active developer of the
VPython and WebVPython projects and a contributor to the Matter and Interactions textbook.
Taylor & Francis
Taylor & Francis Group
http://taylorandfrancis.com
Visualizing Quantum
Mechanics with Python
Steve Spicklemire
First edition published 2024
by CRC Press
2385 NW Executive Center Drive, Suite 320, Boca Raton FL 33431
Reasonable efforts have been made to publish reliable data and information, but the author and publisher cannot assume responsibility for
the validity of all materials or the consequences of their use. The authors and publishers have attempted to trace the copyright holders of
all material reproduced in this publication and apologize to copyright holders if permission to publish in this form has not been obtained.
If any copyright material has not been acknowledged please write and let us know so we may rectify in any future reprint.
Except as permitted under U.S. Copyright Law, no part of this book may be reprinted, reproduced, transmitted, or utilized in any form by any
electronic, mechanical, or other means, now known or hereafter invented, including photocopying, microfilming, and recording, or in any
information storage or retrieval system, without written permission from the publishers.
For permission to photocopy or use material electronically from this work, access www.copyright.com or contact the Copyright Clearance
Center, Inc. (CCC), 222 Rosewood Drive, Danvers, MA 01923, 978-750-8400. For works that are not available on CCC please contact
[email protected]
Trademark notice: Product or corporate names may be trademarks or registered trademarks and are used only for identification and
explanation without intent to infringe.
DOI: 10.1201/9781003437703
Acknowledgment vii
Chapter 1 ■ Introduction 1
v
vi ■ Contents
Appendix A 47
Appendix B 48
Index 53
Acknowledgment
I would like to thank my colleagues Ruth Chabay, Bruce Sherwood, and Aaron Titus for
their unwavering support and encouragement and for creating WebVPython and the Matter
and Interactions textbook which were the greatest influences on this project. I’d like to thank
my editors, Danny Kielty and Ashraf Reza for their (nearly) inexhaustible patience, and
helpful feedback. I’d like to thank my loving wife, Tawn Spicklemire, for always supporting
me in my various crazy endeavors.
vii
Taylor & Francis
Taylor & Francis Group
http://taylorandfrancis.com
CHAPTER 1
Introduction
z = a + ib (1.1)
You can think of a complex number z as having two components, a a real part and b an
imaginary part. These two parts behave a little bit like coordinates in a two-dimensional
cartesian space called the complex plane. This can be visualized as a spatial coordinate
system as illustrated in Fig. 1.1.
1.2 PHASORS
Another way to visualize a complex number is as shown in Fig. 1.2 as a phasor. A phasor
has a magnitude R and a phase θ.
DOI: 10.1201/9781003437703-1 1
2 Visualizing Quantum Mechanics with Python
a = R cos θ (1.4)
b = R sin θ (1.5)
1.3 EXAMPLES
1.3.1 Example: Convert a Number from Rectangular to Exponential Form
Suppose you’re given a complex number: z = 3 + 4i. What is its exponential form?
>>> z = 3 + 4j
>>> R = abs(z)
>>> print(R)
5.0
Note that you don’t have to compute the square root of the sum of squares manually
in python, you can just use the abs function to get the “absolute value” or magnitude of
the complex number. Similarly, to get the angle we can use the math.atan2 function to
compute the angle. The np.arctan2 function takes the y, and x components (respectively)
of a vector and returns the angle the vector makes with the +x axis. Of course it also works
for phasors.
4 ■ Visualizing Quantum Mechanics with Python
import numpy as np
theta = np.arctan2(z.imag, z.real)
print(np.degrees(theta))
53.13010235415598
√ √
a = R cos θ = 2 cos(π/6) = 2 3/2 = 3 (1.9)
b = R sin θ = 2 sin(π/6) = 2(1/2) = 1 (1.10)
R = 2
theta = np.pi/6
z = R*np.exp(1j*theta)
print(z)
(1.7320508075688774+0.9999999999999999j)
phasors in 2D and then use the remaining dimension to picture how the function depends
on time.
Another common case is a function created by multiplying a function of position by a
function of time, like so:
E = hf (1.14)
−34 2
where h is the Planck constant: 6.62607004 × 10 m kg/s. In this book, we’ll often use
angular frequency ω (phase angle per unit time), rather than linear frequency f (complete
cycles per unit time) as a measure of frequency, so it will be convenient to introduce ℏ =
h/(2π) so that the relationship can also be written as:
h 2π
E = hf = = ℏω (1.15)
2π T
As we study quantum mechanics we’re going to apply this relationship not just to
photons, but to many quantum behaviors. Exactly how this happens will become clear as
we go, but keep it in mind as we continue our study of quantum mechanics.
Another is the product of ℏ and c (the speed of light). This can be expressed in handy
units for practical calculations as:
p2 (ℏk)2 (ℏπ)2 ℏ2 π 2
E = KE = = = 2
= (1.22)
2m 2m 2mL 2mL2
That’s it! But wait, what’s the value? You can naturally just plug in numbers and com-
pute E, but wouldn’t it be nice to estimate quickly without having to get out a calculator?
8 Visualizing Quantum Mechanics with Python
Sure it would! But how? Easy, use the known value of c and the known value of the mass-
energy of the electron from Table 1.1 to substitute approximate values. Just multiply m by
c2 and by c:
p2 (k)2 (π)2 2 π 2
E = KE = = = = (1.23)
2m 2m 2mL2 2mL2
(c)2 π 2 (197eV.nm)2 π 2
= = (1.24)
2(mc2 )L2 2(0.511 × 106 eV)L2
(200eV.nm)2 π
2
4 × 104 eV
≈
0.511 × 10 eV) (3 2 ≈ ≈ 0.04eV (1.25)
(
2 6 nm) 106
We got a bit lucky with the numerical coincidence that 3 nm ≈ π and 2 × 0.511 ≈ 1 but
the point is that you can use these numbers to make quick estimates without having to pull
out the computer or the calculator. By the way, the more exact answer is 0.0416 eV so it’s
within about 5% of our estimate. Not bad for the back of an envelope!
import vpython as vp
which produces a nice 3D arrow similar to the one displayed in Fig. 1.4.
How can we use this to create a 3D representation of a phasor? The most natural way
would be to have a function that accepts an arrow and a complex number. It would modify
the arrow object to represent the complex number as a phasor, something like this:
Why use the y and z directions? As we’ll learn in Chapter 2 we’re going to be using
the x direction to represent real physical space, and the phasors we’ll be drawing will be
representing complex numbers that vary with position (x) and time. The real and imaginary
parts of those complex numbers will be represented by phasors that point into the y-z plane.
Now we can use this function, along with a phasor from Example 1.2 to represent that
phasor as a 3D object. Note that we’ll be using VPython cylinder objects to represent the
coordinate axes.
import vpython as vp
import numpy as np
vp.scene.background = vp.color.white
vp.cylinder(axis=2*vp.vec(1,0,0), radius=0.05)
vp.cylinder(axis=2*vp.vec(0,1,0), radius=0.05)
vp.cylinder(axis=1*vp.vec(0,0,1), radius=0.05)
R = 2
theta = np.pi/6
z = R*np.exp(1j*theta)
setArrowFromComplexNumber(myArrow, z)
10 ■ Visualizing Quantum Mechanics with Python
We’ll see in Chapter 2 how this approach can be extended to represent arbitrary 1D
complex wavefunctions using 3D phasors.
1.10.1 Exercise
Rerun the program with different values of R and θ. Also, print out the actual value of z.
What happens when θ = π/2? Does that make sense? How about θ = π? Is that consistent
with what you see in the 3-D view?
CHAPTER 2
Visualizing 1D Quantum
Wavefunctions
NE OF the most basic ideas of quantum mechanics is that the condition of a system
O is represented mathematically by a wavefunction. This is a complex valued mathe-
matical function of that tells us everything we can know about the system. In order to
make predictions we apply mathematical operations to the wavefunction that tell us the
probability of various outcomes that we can compare to experimental results. The wave-
function is a complex valued function. In other words when you evaluate it for a particular
set of inputs (e.g., position, time, momentum, energy, etc.) the output is a complex number.
The magnitude of the complex number gives information about the relative likelihood of a
particular outcome. That seems pretty odd I’m sure. The best way to get comfortable with
the notion is to play around with it. Let’s consider an example.
Ψ(x, t) = A (2.1)
This seems super simple. However, if the particle has energy we know the Einstein
relation requires that it have a frequency proportional to the energy, so we need to add that
as a time-dependent phase:
DOI: 10.1201/9781003437703-2 11
12 ■ Visualizing Quantum Mechanics with Python
t(sec)
1.5
1.0
B C
0.5
A
0.0
x(csec)
0.0 0.5 1.0 1.5 2.0 2.5
t
t′
1.5
1.0
x′
1.0
C 1.5
0.5
0.5
B 1.0
0.5
0.0 x
A
0.0 0.5 1.0 1.5 2.0 2.5
Figure 2.3 The same space-time diagram of the wavefunction of a particle stationary in
the unprimed frame of reference with events A, B, and C. Superposed on this frame is the
primed frame of a moving observer. In the primed frame, an observer would see a different
phase behavior and the three events would occur in a different order.
14 ■ Visualizing Quantum Mechanics with Python
t′
t
1.5
1.0 1.0
B
0.5 0.5
C
0.0 A
x′
0.0 0.5 1.0 1.5 2.0 2.5
0.5
1.0
1.5
x
Figure 2.4 The same space-time diagram of the wavefunction of a particle stationary in
the unprimed frame of reference with events A, B, and C. This version is drawn with the
primed axes in the vertical and horizontal orientation. It’s more clearly illustrated here that
in the primed frame, an observer would see a different phase behavior and the three events
would occur in a different order.
differently from the observer in the unprimed frame where the particle is at rest. First,
notice that the events A, B, and C now occur in a different sequence. Lines of constant
t′ are not horizontal as in the unprimed frame, but they slope up and to the right. In the
primed frame the events B and C are no longer simultaneous. The primed observer will
experience events A and C as simultaneous events first, and then, more than 0.5 sec. Later,
event B will occur. Notice also that the dashed and dotted lines in Fig. 2.3 are not parallel
to the x′ axis. What is the consequence of this? It means that the phase of the wavefunction
will vary in space at any moment in time for the observer in the primed frame. How can we
visualize this more clearly? What if we transform the space-time diagram so that the primed
observer’s axes are vertical and horizontal? This is illustrated in Fig. 2.4. In this view it’s
clear that the events A and C are now simultaneous and event B now occurs at a different
place (to the left of the origin) and at a much later time (in fact it occurs after 0.5 sec, even
later than it did in the unprimed frame). Of course the goal here is not to learn relativity
theory, but to understand how the wavefunction of a particle can be viewed from a moving
frame of reference. If you’re interested in learning more about space-time diagrams, see the
recommended background readings cited in the Introduction. The good news is that we’ll
not be needing a lot of relativity, but it’s worthwhile to understand that the connection be-
tween the wavelength of a particle with well-known momentum and it’s motion is actually a
relativistic effect. How’s that? Let’s look at the 3D view of the wavefunction from the point
Visualizing 1D Quantum Wavefunctions 15
of view of this observer in the primed frame. The 3D view of the wavefunction is shown in
Fig. 2.5. What do we see? Now the wavefunction which, at any moment in time, used to
be a bland line of phasors all pointing in the same direction now is a rich helical spirally
thing with a clear period in both space and time. It’s got a wavelength. Out of the simple
case of a stationary particle, just looking at it from the point of view of a moving observer
we get that the wavefunction has a wavelength directly related to the observer’s motion.
Of course, the observer in the primed frame doesn’t think they’re moving, but really, from
their point of view, it’s the particle that’s moving. So we see that from this argument we
can conclude that a moving particle with a well-known momentum has a wavefunction like
that shown in Fig. 2.2.
While the full mathematical derivation of the result is beyond the scope of this book the
final result is actually quite simple. It’s just the de Broglie result: Eq. 1.17. The wavelength
of a wavefunction is related to the momentum of the particle by the de Broglie formula.
Also, it’s useful to point out that you can infer from this that there is a simple mathematical
operation that can effectively extract the momentum and the energy from the wavefunction
itself. This is a key insight for interpreting the Schrödinger Wave Equation (SWE). You’ll
note that in a frame of reference where the particle is moving we end up with a wavefunction
Eq. 2.3.
import vpython as vp
import numpy as np
vp.scene.background = vp.color.white
Next we’ll need to create a collection of arrows to represent the value of the wavefunction
at different positions in space as 3D objects. To manage a collection of arrows, we’ll use
the python list object. We’ll also use a numpy array to evaluate the wavefunction itself. If
you’re not familiar with the concept of a python list, or the features of the numpy library,
please consult Appendix B for a short introduction.
Now we have a collection of arrows in the arrows list. Any time we need to update
the wavefunction, we can simply loop over the arrows and update each one to represent
the value of the wavefunction at the corresponding value of x. For example, let’s define a
Visualizing 1D Quantum Wavefunctions 17
python function psi(x) to evaluate the un-normalized wavefunction for the case when the
momentum and energy are both well known. As we’ve seen this is a plane-wave solution
ψ(x) = Aei(kx−ωt) . What should we do to compute the normalization constant A? For now,
let’s leave this since it just scales the wavefunction but doesn’t affect the shape. We’ll see
how this shows up, and makes more sense, in more realistic situations later. For the moment,
we’ll just use a default value of 1. As long as we don’t need to answer any questions about
the probability of finding the particle in some range of x values, this value is arbitrary
anyway. Let’s write a python function that evaluates this function:
T = 0.5
omega = 2*np.pi/T
k = 0 # particle is stationary, so k=0
A = 1
So now the plan is clear. If we need to display the wavefunction at any particular time,
we can simply call this function to first evaluate the wavefunction, and then loop over the
arrows and update their direction and magnitude using the setArrowFromComplexNumber
function. We’ll be using this same plan in virtually every program we write. This is a core
concept of visualizing quantum wavefunctions with VPython.
t = 0
psiArray = psi(x, t) # evaluate the wavefunction
for i in range(N):
setArrowFromComplexNumber(arrows[i], psiArray[i]) # update the arrows
When you run this code you’ll see a visualization of the wavefunction at t = 0 that looks
like Fig. 2.6. You can use the QR code to see the program in 3D.
How can we turn on the time? Easy. Just iterate this process with ever increasing values
of t. See the code below. Note that this code will animate the time evolution for 5 sec, with
a time step of 0.1 sec per step.
t = 0
dt = 0.02
for i in range(N):
setArrowFromComplexNumber(arrows[i], psiArray[i]) # update the arrows
Note that we used a new VPython function here: rate. The rate function is useful
for animations that need to modify 3D object properties over time. Without rate all these
modifications would be made a very high speeds and would be impossible to view by humans.
We use rate to specify a maximum frame rate for animation. In this case we want to animate
at 10 frames per second which will produce a real time animation since the time-step size
is 0.1 sec. This is a little chunky, so you may be happier with a time-step of 0.05 or 0.02,
but realize that then you’ll be viewing the scene in slow-motion. If you wanted higher
time resolution, you could dial down the time-step even more and increase the frame rate
accordingly. When you run this code you’ll see all the phasors pointing in the y-z plane in
the same direction, but rotating at a constant speed around the x axis. This is exactly what
you’d expect.
If you increase k to 1 you’ll see the effect of a non-zero momentum on the wavefunction
as shown in Fig. 2.7.
These wavefunctions are sometimes called energy eigenfunctions because they are solutions
to an eigenvalue problem looking for functions that correspond to systems with well-defined
energy. These wavefunctions will always have very simple time evolution that consists of
multiplying the spatial part of the wavefunction ψ(x), by a time-dependent phase, just like
Eq. 2.2. In a 3D visualization, like Fig. 2.2 this just means the phasors will all rotate at a
uniform rate as a group around the x axis.
CHAPTER 3
Visualizing 1D Bound
Systems
ANY INTERESTING systems are “bound” in the sense that the particle is con-
M strained to a finite region by forces that prevent it from escaping. It’s easy to visualize
this in one dimension by graphing the potential energy as a function of position. In a bound
system, the potential energy has at least one minimum somewhere and rises to a maximum
value, possibly infinity, on both the right and left. A typical case is illustrated in Fig. 3.1.
The potential energy (solid) is low near the original, and rises to the left and right. The
sum of kinetic and potential energy is also plotted here (dashed) which is often referred to
as the “energy”. Note that if you look far enough to the right or left the potential energy
(solid) is greater than the energy (dashed). This is the hallmark of a bound system! If this
were a classical system the particle would be bound between the turning points (−7.5 and
+7.5 on this graph) since at these points the kinetic energy (the difference between the blue
line and the red line) would approach zero. This is where the classical particle would slow
down, momentarily stop, and then return to the region of lower potential energy.
In this chapter, we’ll consider three examples of bound systems: the Infinite Square
Well, the Simple Harmonic Oscillator, and the Finite Square Well. Each of these has some
unique properties and behaviors, but they also have many similarities. Then, at the end,
we’ll touch on the connection between bound systems and scattering. Let’s start with the
simplest bound system, the Infinite Square Well (ISW).
20 DOI: 10.1201/9781003437703-3
Visualizing 1D Bound Systems 21
Figure 3.1 Typical potential energy function (solid) with energy (dotted) somewhere
between minimum and maximum values of the potential energy.
3.1.1 Continuity
Why does the solution need to go to zero? It turns out that to be physically plausible,
all quantum wavefunctions need to be finite and continuous functions of position. Since
the potential is infinite outside, you’ll recall that we used the free particle wavefunction to
infer the meaning of the momentum (−i∂/∂x) and energy (i∂/∂t) operators. Applying
this interpretation to the current example, the kinetic energy (KE) would be proportional
to the second derivative of the wavefunction: −2 (∂ 2 Ψ(x, t)/∂x2 )/(2m) as described in
Eq. 2.4. This has implications for the continuity of the wavefunction and its derivatives.
In this example, the potential energy goes to infinity outside the boundaries of the well.
This means the KE can become infinite where the PE is infinite so long as it can be done
in a way that keeps the sum KE+PE finite. However, for the KE to be defined it means
the wavefunction has to be continuous, otherwise the first derivative would have an infinite
value, and the second derivative (the KE) wouldn’t even be defined. So, we can add multiple
solutions together to produce a solution that works for the ISW, but it still needs to satisfy
the requirement that the wavefunction is continuous. Since the wavefunction needs to be
zero outside the boundaries of the ISW, it also needs to be zero at the boundaries of
the well.
22 ■ Visualizing Quantum Mechanics with Python
Since the coordinate system for the ISW goes from 0 to L rather than −L/2 to +L/2 we
need to move the scene.center to point to the middle of the well. To visualize the ground
state you can set k = π/L and change the psi function to match:
Figure 3.3 Arrows representing the ground state wavefunction of the ISW.
When you run this program you should see arrows that look like this rotating, in phase,
about the x axis with a definite frequency (ω). At the end you should see something like
Fig. 3.3.
Note that the length of the arrows does not change over time. Since the probability of
finding the particle at any location on the x axis is proportional to the squared length of
each arrow, it means the probability of finding the particle at any location is not changing
with time, even though the phase of the arrows is clearly changing. In this sense these states
of fixed ω are sometimes called stationary states.
psiArray = psiArray/np.sqrt((abs(psiArray)**2).sum())
Note that exponentiation in python uses the ‘**’ operator rather than ‘ˆ’. The idea is to
scale psiArray by a factor such that when you take the sum of the squares of the absolute
values of the psiArray elements you’ll get 1. Note that when you do this you may need to
scale the arrows in the visualization so that they’re still easy to see. One easy way to do
this is to find the max arrow length and the scale all the arrows so that they’re some nice
fraction of the scale L. You can see an example of this in the code below. Also, check with
QR code for Fig. 3.4.
Then we’ll need to adjust the arrows and timestep to make the interaction between the
two states clearer:
t = 0
dt = 0.001
maxpsi = max(abs(psi(x, 0)))
Also in the loop we’ll need to rescale the arrows to make the interaction easier to see:
for i in range(N):
setArrowFromComplexNumber(arrows[i], L*psiArray[i]/(2*maxpsi))
After these changes, when you run this program you should see arrows undulating in a
way that the amplitudes seem to “slosh” back and forth from left to right as the superpo-
sition phase changes over time.
There are a number of features one can notice about this superposition. Since the energy
eigenstates have energies that go like n2 , you can see that the n = 2 state has four times
the energy, and therefore four times the frequency of the ground state (n = 1). It’s also
interesting to graph the expectation value of the position as a function of time. How do we do
that? Easy! Since the wave function is normalized already we can simply use the probability
Visualizing 1D Bound Systems ■ 25
that the particle’s position is associated with each arrow: |ψ(x)|2 = abs(psi(x,t)**2)
multiplied by the position, and then summed over all the arrows. This is easy with numpy.
We can just update the main loop like so:
for i in range(N):
setArrowFromComplexNumber(arrows[i], L*psiArray[i]/(2*maxpsi))
The gr plotting/graphing object is created at the beginning of the program like so:
You can see the full program if you navigate to the QR code from Fig. 3.4.
Figure 3.5 Compare ISW (left) and SHO (right) eigenstate wavefunctions.
the n = 0 ISW wavefunction would be zero everywhere. The lowest energy available to the
ISW is n = 1. In contrast the SHO eigenstate wavefunctions are all products of Gaussians
multiplied by nth order polynomials (the Hermite polynomials). Since there is nothing
pathological about a zeroth order polynomial (i.e., a constant) times a Gaussian, it means
that the lowest energy eigenstate of the SHO is n = 0. The qualitative similarity between
the SHO and ISW eigenstate wavefunctions can be clearly seen in Fig. 3.5. There are a few
important features that should be emphasized:
• The general shape of each ISW wavefunction matches very nearly the corresponding
SHO wavefunction except that the n values are “off by one”. This is the consequence
of the ground state being most conveniently defined as n = 1 for the ISW but n = 0
for the SHO.
• The ISW wavefunctions are strictly constrained by the limits of the well. The SHO
wavefunctions “spread out” as n increases.
• The energy of the ISW eigenstates increases as n2 , while the SHO energies are linearly
related to n (En = ω(n + 1/2)).
NHs=20
hs=np.zeros((NHs,NA),np.double) # the hermite polynomials, an NHs x
NA array
coefs=np.zeros(NHs,np.double) # the coherent state coefficients, an
NHs x 1 array
psis=np.zeros((NHs,NA), np.double) # the stationary states, an NHs x NA
array
#
# Compute the first NHs Hermite Polynomials,
# use recurrence relation to get the rest of the Hn(x)
#
# (see e.g., http://en.wikipedia.org/wiki/Hermite_polynomials#Recursion
_relation)
#
for n in range(1,NHs-1):
hs[n+1]=2*x*hs[n] - 2*n*hs[n-1]
Once the Hermite polynomials are available, calculating the wavefunctions as products
of Gaussian and Hermite polynomials is a matter of keeping track of the normalizing scale
factor for each state, along with the Gaussian and Hermite polynomial factors. These can
be kept in a list of energy eigenfunctions, called ‘psis[]‘, for convenience.
#
# Get the stationary states using the hs array and compute the
# normalization factor in a loop to avoid overflow
#
normFactor = 1.0/pi**0.25
psis[0]=np.exp(-x**2/2)
for i in range(1,NHs):
normFactor = normFactor/sqrt(2.0*i)
psis[i]=normFactor*hs[i]*np.exp(-x**2/2)
#
# Now do the sum to compute the initial wavefunction
#
Once the stationary states are computed, one can easily construct arbitrary superposi-
tions of these eigenstates, like this one:
28 ■ Visualizing Quantum Mechanics with Python
psi=np.zeros(len(x),complex)
for m in range(len(coefs)):
psi += coefs[m]*psis[m]
#
# Normalize!
#
psi=psi/sqrt((abs(psi)**2).sum())
#
# build the arrows. Scale them on the screen by a factor
# of 3 so they look nice.
#
alist = []
for i in range(NA):
alist.append(vp.arrow(pos=vp.vec(x[i],0,0), color=vp.color.red))
SetArrowFromCN(3*psi[i],alist[i])
Finally, in the same way we’ve done this before, we can advance the time and plot the
expectation value of the position, just as we did for the ISW. The output is illustrated in
Fig. 3.6.
Figure 3.6Arrows representing the time evolution of an equal superposition state of the
SHO. The QR code will lead you to a live version of the program.
Visualizing 1D Bound Systems ■ 29
while t<4*pi:
rate(1.0/dt)
psi=np.zeros(len(x),complex) # start with an empty
wf array
for m in range(len(coefs)): # for each basis
function
psi += coefs[m]*psis[m]*np.exp(-1j*(0.5+m)*t) # add each with time
dependence
psi=psi/np.sqrt((abs(psi)**2).sum()) # normalize
for i in range(NA):
SetArrowFromCN(3*psi[i], alist[i]) # update arrows, scale
up so they’re easier
to see
Note the similarity to the code to compute the time evolution of the ISW states. This
particular superposition state evolves in a way roughly similar to the ISW as shown in
Fig. 3.6.
3.2.2.1 Exercise
What is the frequency of oscillation of the expectation value of the position when you include
only two neighboring eigenstates? Change the code to verify this. What happens if you only
include even eigenstates? Or odd eigenstates? Do you still see oscillations? Compare the
magnitude of the oscillation to that when there are neighboring (e.g., n = 0, and n = 1)
eigenstates in the superposition. What’s happening? Explain.
result is easy to understand and remember. The depth of the well is V0 and the “half-width”
of the well is a. For any given bound eigenstate energy the energy must be between −V0
and 0. The wavefunctions will look like the ISW inside the well, where the energy is greater
than the potential energy, but outside the well, where the potential energy is greater than
the energy, the wavefunction will be a real exponential. The wavenumber inside the well, k
is related to E − (−V0 ) as shown in Fig. 3.7. However, the wavenumber outside the well, κ,
will depend only on the value of the energy E. In order to ensure the wavefunction and its
first derivative are continuous one must identify and satisfy one of these two constraints:
depending on whether the wavefunction is symmetric (Eq. 3.2) or antisymmetric (Eq. 3.3).
These constraints can be solved graphically by finding the values of ka that simultaneously
solve the left- and right-hand side of Eq. 3.2 and Eq. 3.3. as shown in Fig. 3.8.
Visualizing these states is similar to the ISW and SHO cases, except that the wavefunc-
tions are made up of piecewise continuous sin(), cos(), and exponential functions. First,
Visualizing 1D Bound Systems ■ 31
using the values of z1 and z2 from Fig. 3.8, we can compute values for k1 and k2 for the
interior of the well, as well as κ1 and κ2 in the exterior. These can then be applied to com-
pute the wavefunction solutions inside and outside the well in addition to the frequencies
of the two eigenstates, ω1 and ω2 .
#
# numerical solutions for z when z0 = 2.1*pi, you should find a better z0
# and find solutions for that choice.
#
z1 = 1.35
z2 = 2.67
k1 = k0*z1/z0
k2 = k0*z2/z0
kap1 = np.sqrt(k0**2 - k1**2)
kap2 = np.sqrt(k0**2 - k2**2)
E1 = -(hbar*kap1)**2/(2.0*m)
E2 = -(hbar*kap2)**2/(2.0*m)
w1 = E1/hbar
w2 = E2/hbar
wn=[w1,w2]
T = 2*np.pi/(w2-w1) # the approximate period of oscillation
t = 0.0
dt = T/200.0 # a small fraction of a period
To actually calculate the wavefunction we need to splice the exterior and interior
functions together, matching the value of the wavefunction at the boundary. This is most
easily accomplished using the numpy function called piecewise.
psis = np.zeros((2,NA),np.double)
def f1(x):
return np.cos(k1*x)
def f2(x):
return np.sin(k2*x)
def f3(x):
return f1(a)*np.exp(-abs(kap1*x))/np.exp(-abs(kap1*a))
def f4(x):
return np.sign(x)*f2(a)*np.exp(-abs(kap2*x))/np.exp(-abs(kap2*a))
32 ■ Visualizing Quantum Mechanics with Python
Once the eigenstates are calculated, one can create superpositions and graphs of the
position expectation value precisely as was done for the ISW and SHO.
# Equal parts 1 and 2
c1 = 1.0/np.sqrt(2)
c2 = 1.0/np.sqrt(2)
alist = []
for i in range(NA):
ar = vp.arrow(pos=vp.vec(x[i],0,0), axis=vp.vec(0,a,0),
shaftwidth=0.02*a, color=vp.color.red)
alist.append(ar)
SetArrowFromCN(arrowScale*psi[i],alist[i])
#
# all the arrows are made, and the basis functions and coefficients are set.
# Create a loop that produces the corresponding time evolution.
#
t = t+dt
psi = np.zeros(NA, complex)
Visualizing 1D Bound Systems ■ 33
for i in range(len(cn)):
psi = psi + cn[i]*psis[i]*np.exp(-1j*wn[i]*t) # a phasor product for
each eigenstate
for i in range(NA):
SetArrowFromCN(arrowScale*psi[i],alist[i])
xexp=(x*abs(psi*psi)).sum()
g1.plot(pos=(t,xexp))
Again, the superposition states have qualitatively similar behavior as shown in Fig. 3.9.
Figure 3.10 Comparing wave packets of different widths in position and momentum space.
(a) k0 = 2π rad/m, and a = 5 m in position space, (b) k0 = 2π rad/m, and a = 5 m
in momentum space, (c) k0 = 2π rad/m, and a = 0.5 m in position space, and (d) k0 =
2π rad/m, and a = 0.5 m in momentum space.
choice but to broaden over time. Note that the wave packet must be constructed of momen-
tum components with different wavelengths and speeds. As a result, some components will
propagate more slowly, and others will propagate more quickly.
the wavefunction component, we need to understand this technicality and use the correct
k values. The code below demonstrates one way to do this.
# this next step computes the correct value of n for the FFT
n = np.piecewise(n, [n<=NA/2, n>NA/2], [lambda n:n, lambda n:n - NA])
t = 0.0
dt = 0.01
kMin = 2*pi/L # resolution of the k array
k0 = 20*kMin # pick a value of k0
a = 1 # width of the wave packet in units of L
arrowScale = sqrt(NA*L*2*a)/10.0 # rescale arrows so they’re easier to see
Note that the last step in all this preparation is to actually compute the FFT and store
the result in the phi0 array. Each element of this array is the amplitude of one component of
the free particle wavefunction. To get the wavefunction at a later time we need to multiply
each element of the array by a phase factor that depends on the energy of that component.
This is done in the function doStep below. This function also computes the current width
of the wave packet, and graphs it, or it computes the current expectation value of the
wave packet’s position, and graphs that depending on the value of the boolean variable
plotSigma.
def doStep(plotSigma=False):
"""
For the current value of "t" compute the wavefunction.
"""
psi=np.fft.ifft(phi0*np.exp(-1j*(omega)*t))
for i in range(NA):
SetArrowFromCN(arrowScale*psi[i], alist[i]) # set the arrows
36 ■ Visualizing Quantum Mechanics with Python
xexp = (x*abs(psi)**2).sum()
xxexp = (x*x*abs(psi)**2).sum()
sig=sqrt(xxexp-xexp**2)
if plotSigma:
gr.plot(pos=(t,sig))
else:
gr.plot(pos=(t,xexp))
The 3D visualization of the wave packet is done in the same way as the ISW, SHO, and
FSW examples. The only difference is that the wavefunction is computed using the inverse
FFT instead of the Hermite polynomials, or the sine and cosine functions. The full program
is available at the QR code in Fig. 3.11.
Figure 3.11 The view of the wave packet as it evolves from a relatively compact packet at
t = 0 to a much broader packet at t = 3. The graph shows the value of σ as a function of
time.
phi=np.fft.fft(psi)
psi=np.fft.ifft(phi*np.exp(-1j*(omega)*dt)) # update free particle state
psi=psi*np.exp(-1j*V*dt)
CHAPTER 4
Visualizing Higher
Dimensions
NCE WE leave the realm of either one dimensional or single particle systems, visu-
O alizing quantum wavefunctions becomes more challenging. Suppose, for example, we
choose to model a single particle confined to a two-dimensional surface? This is analogous
to the 1D systems we encountered in Chapter 3 except that the particle can now move
in more than one dimension. The wavefunction then has to depend on both x and y. The
trouble is that when we attempt to visualize such a wavefunction we can no longer rep-
resent the complex value as a 2D phasor, since after using the x and y direction for the
position in space, we only have the z direction left for visualization! Since a phase has two
independent parts, either (real, imaginary) or (magnitude, phase). We need some way to
visualize these two components distributed over some area. One approach is to use color to
represent phase, and height in the z direction to represent magnitude. Another would be to
place small objects at a distribution of spatial points and use the properties (e.g., color, size,
orientation) to represent various aspects of the wavefunction. In this chapter we’ll explore
a combination of these approaches and develop some insights from the results.
38 DOI: 10.1201/9781003437703-4
Visualizing Higher Dimensions ■ 39
The next issue is how to represent the wavefunction over a 2D domain. In this case we’ll
use a grid of cylinders. The code for this is given below. We’ll also create a “boundary”
to represent the edges of the domain. This is done by creating a set of cylinders that are
placed along the edges of the domain.
x, y = np.meshgrid(np.linspace(0,a,NA),np.linspace(0,a,NA))
#
#
# draw the boundaries of the ISW in 2D
boundary_radius = a/100
vp.cylinder(pos=vp.vec(-a/2,-a/2,0), axis=vp.vec(a,0,0),
color=vp.color.blue, radius=boundary_radius)
vp.cylinder(pos=vp.vec(-a/2,-a/2,0), axis=vp.vec(0,a,0),
color=vp.color.blue, radius=boundary_radius)
vp.cylinder(pos=vp.vec(a/2,-a/2,0), axis=vp.vec(0,a,0),
color=vp.color.blue, radius=boundary_radius)
vp.cylinder(pos=vp.vec(-a/2,a/2,0), axis=vp.vec(a,0,0),
color=vp.color.blue, radius=boundary_radius)
#
# build vp.cylinders.... in 2-D space, store them in a set of nested lists
#
alist = []
for i in range(NA):
sublist = []
alist.append(sublist)
for j in range(NA):
r = r0 + vector(x[i,j], y[i,j], 0)
sublist.append(vp.cylinder(pos=r, axis=(0,0,1), color=vp.color.red))
40 ■ Visualizing Quantum Mechanics with Python
Note that the np.meshgrid function is a great way to generate x and y coordinates over
a 2D grid. The code above uses the same approach as the code in Chapter 3 to generate a
1D array of arrows. The main difference here is that the cylinder positions are now specified
in two dimensions.
Next we need to compute the Fourier coefficients, and frequencies for the eigenstates in
the superposition.
#
# compute the eigenstates and store them in a dictionary ’eigenstates’
#
for nx in NX:
for ny in NY:
psinxmy = np.sin(nx*np.pi*x/a)*np.sin(ny*np.pi*y/a)
# compute the
n,m energy
eigenstate
psinxmy = psinxmy/np.sqrt((abs(psinxmy)**2).sum()) # normalize it.
eigenstates[(nx,ny)] = psinxmy
psi0 = np.zeros((NA,NA),complex)
psi0[:NA2,:NA2] = 1.0
psi0 = psi0/np.sqrt((abs(psi0)**2).sum()) # get psi at t=0, normalized
Next we need to create the 3D scene and set up the initial wavefunction.
#
# build up psi via fourier series
#
for i in range(NA):
for j in range(NA):
SetCylinderFromCN(cylinderScale*psi[i,j],alist[i][j])
Visualizing Higher Dimensions ■ 41
Figure 4.1 Initial wavefunction representation, left. Later time, right, for the 2D ISW.
while True:
rate(20.0/dt)
if RSdict[’runStop’]:
t += dt
psi = np.zeros((NA,NA), complex) # initialize psi
#
# Here’s where you put in your code to compute the
# wavefunction psi, at later times
#
for nmPair in eigenstates.keys():
psi += coefs[nmPair]*eigenstates[nmPair]*np.exp(1j*
omegas[nmPair]*t)
for i in range(NA):
for j in range(NA):
SetCylinderFromCN(cylinderScale*psi[i,j],alist[i][j])
#
# Spherical Harmonics
#
Y={}
Y[(0,0)] = lambda t,p: sqrt(1.0/(4.0*pi))
Y[(1,0)] = lambda t,p: sqrt(3.0/(4.0*pi))*cos(t)
Y[(1,1)] = lambda t,p: -sqrt(3.0/(8.0*pi))*sin(t)*exp(+1j*p)
Y[(1,-1)] = lambda t,p: sqrt(3.0/(8.0*pi))*sin(t)*exp(-1j*p)
Y[(2,0)] = lambda t,p: sqrt(5.0/(16.0*pi))*(2*cos(t)**2-1.0)
Y[(2,1)] = lambda t,p: -sqrt(15.0/(8.0*pi))*sin(t)*cos(t)*exp(+1j*p)
Y[(2,-1)] = lambda t,p: sqrt(15.0/(8.0*pi))*sin(t)*cos(t)*exp(-1j*p)
Y[(2,2)] = lambda t,p: (15.0/(32.0*pi))*sin(t)**2*exp(+2j*p)
Y[(2,-2)] = lambda t,p: (15.0/(32.0*pi))*sin(t)**2*exp(-2j*p)
Y[(3,0)] = lambda t,p: sqrt(7.0/(16*pi))*(5*cos(t)**3-3*cos(t))
Y[(3,1)] = lambda t,p: -sqrt(21.0/(64*pi))*sin(t)*(5*cos(t)**2-1)*exp(1j*p)
Y[(3,-1)] = lambda t,p: sqrt(21.0/(64*pi))*sin(t)*(5*cos(t)**2-1)*exp(-1j*p)
Y[(3,2)] = lambda t,p: sqrt(105.0/(32*pi))*sin(t)**2*cos(t)*exp(2j*p)
Y[(3,-2)] = lambda t,p: sqrt(105.0/(32*pi))*sin(t)**2*cos(t)*exp(-2j*p)
Y[(3,3)] = lambda t,p: -sqrt(35.0/(64*pi))*sin(t)**3*exp(3j*p)
Y[(3,-3)] = lambda t,p: sqrt(35.0/(64*pi))*sin(t)**3*exp(-3j*p)
Note that this approach creates a simple data structure of functions that can be used
to pull the correct spherical harmonic from the collection based on the values of l and m as
a python tuple.
4.4 SPIN
The spin angular momentum is a quantum number that is associated with the electron’s
intrinsic angular momentum. The spin angular momentum is quantized to the values s =
0, 1/2, 1, 3/2, . . . and the z component of the spin angular momentum is quantized to the
values sz = −s, −s + 1, . . . , s − 1, s.
p The physical values of the spin angular momentum are
given by the relationships S = ℏ s(s + 1) and Sz = sz ℏ, respectively. The spin angular
momentum is a half-integer quantum number, so the spin angular momentum can only take
on the values s = 0, 1/2, 1, 3/2, . . ..
Visualizing Higher Dimensions ■ 43
class funcComp:
for n in [1,2,3,4]:
for l in range(n):
for m in range(-l, l+1):
wfs[(n,l,m)] = funcComp(n,l,m)
44 ■ Visualizing Quantum Mechanics with Python
The idea here is that instances of the class funcComp are callable objects that can be
used to construct a wavefunction for a particular state. The class funcComp is initialized
with the quantum numbers n, l, and m and then the call method is used to construct the
wavefunction. The call method takes the arguments r, θ, and ϕ and returns the value of
the wavefunction at that point. After the class funcComp is defined, we can construct a
dictionary of wavefunctions that can be used to pull the correct wavefunction from the
collection based on the values of n, l, and m as a python tuple (n,l,m) as needed.
An associated class HState can be used to create a callable wavefunction with variables
r, θ, and ϕ.
class HState:
Now, how to visualize all this? You could imagine a 3D grid that displays the value of
the wavefunction, similar to what we used in 1D and 2D. Unfortunately, this doesn’t extend
well to 3D. The number of needed grid points goes up like N 3 where N is the number of
grid points in 1D. This quickly becomes impractical.
An alternative is to use the “monte-carlo walker” strategy below that follows walkers
around in 3D space and creates or destroys walkers based on the relative probability density
associated with the wavefunction at that point. The “size” of the walkers is proportional to
the relative probability density at the location of the walker. The details of the algorithm
are beyond the scope of this text, but basically each walker is represented by a sphere in 3D.
The sphere is colored according to the complex phase of the wavefunction at each location
at a particular moment in time. On each time-step all the walkers are given a random
“bump” in space. If the probability associated with their new location increases, the update
is always kept. If the probability goes down, the new position is kept with a probability
equal to the ratio of the new probability divided by the probability of their last location.
This is a famous algorithm that’s known to produce a distribution of positions, in the long
run, proportional to the underlying probability density of the wavefunction.
Walkers=random.normal(size=(3,n0))*3+(minX+maxX)/2.0
if AXES:
c1 = cylinder(pos=vec(minX,0,0), axis=vec((maxX-minX),0,0),
radius=0.005*(maxX-minX), color=color.red)
c2 = cylinder(pos=vec(0,1.3*minX,0), axis=vec(0,(1.3*(maxX-minX)),0),
radius=0.005*(maxX-minX), color=color.green)
c3 = cylinder(pos=vec(0,0,1.3*minX), axis=vec(0,0,(1.3*(maxX-minX))),
radius=0.005*(maxX-minX), color=color.blue)
spheres = []
for w in Walkers.T:
spheres.append(sphere(pos=vec(*w), radius = 0.05, color=color.red,
opacity=opacity))
Visualizing Higher Dimensions ■ 45
def compPsi(Walkers):
x = Walkers[0]
y = Walkers[1]
z = Walkers[2]
return psi
psi = psi/(abs(psi)**2).sum()
for i in range(len(psi)):
spheres[i].pos = vec(*Walkers.T[i])
SetSphereFromCN(scaleFactor*psi[i],spheres[i],maxSize=0.1*diffX,
minSize=0.001*diffX)
for i in range(n):
newWalkers = Walkers + random.normal(size=(3,n0))*ds
# bump walkers a bit...
newPsiSq = abs(compPsi(newWalkers))**2
probRatio = newPsiSq/oldPsiSq
maybeIndexes = flatnonzero(probRatio<1.0)
testRatios = probRatio.take(maybeIndexes)
r = random.random(len(testRatios))
keep = r<testRatios
oldIndexesIndexes = flatnonzero(1ˆkeep)
failRatio = len(oldIndexesIndexes)*1.0/len(testRatios)
if len(oldIndexesIndexes):
useOldIndexes = maybeIndexes[oldIndexesIndexes]
newWalkers[:,useOldIndexes] = Walkers[:,useOldIndexes]
# update with rejected Walkers
Walkers = newWalkers
oldPsiSq = abs(compPsi(Walkers))**2
return Walkers
46 ■ Visualizing Quantum Mechanics with Python
This results in a visualization shown in Fig. 4.2. The grayscale value is determined by the
wavefunction phase. The density and size of the spheres is determined by the wavefunction
magnitude at the location in space.
4.5.1 Exercise
How does the distribution of walkers change as you adjust the quantum numbers of the
energy eigenstate? Pay close attention to the phase as a function of angle around the z axis.
How should changing l values change the behavior of this phase? Explain.
Appendix A
INSTALLING VPYTHON
You can install VPython on your computer if you’d prefer that to using WebVPython.
Note: The instructions provided here are current as of the publication of this
book, but for the most up-to-date instructions, please visit the VPython website at
http://vpython.org.
The current recommendation from the VPython website is to install the Anaconda
Python distribution.
The vpython module currently works with Python versions 3.8, 3.9, and 3.10.
or
or
or
When running from a terminal, if the program does not end with a loop containing a
rate() statement, you need to add “while True: rate(30)” to the end of the program. This
is not necessary when launching from environments such as Jupyter notebook, IDLE, or
Spyder.
DOI: 10.1201/9781003437703-A 47
Appendix B
PYTHON CONCEPTS
If you’re not very familiar with Python or Numpy this appendix could help you along with
an introduction to the basic concepts.
a = 3
assigns the object “3” to the label “a”. In the future, references to “a” will result in the
object “3” being used. If you reassign “a” to a different value, it will forget the old value,
and begin to refer to the new value.
You’ll also see comments sprinkled in the code to help the learner to understand what’s
going on. In python a comment is any text preceded by a “pound sign”: “#”.
So for example we could have:
This is functionally equivalent to the statement “a=3”, but with commentary that python
ignores.
myList = []
Once created you can add things to a list by using the “append” method:
myList.append(1)
myList.append(2)
This will then result in a list with things in it. The command:
print(myList)
[1, 2]
Once can retrieve an item from a list by indexing into the list with an integer specifying
the desired item (starting with the index ‘0’) like so:
48 DOI: 10.1201/9781003437703-B
Appendix B ■ 49
print(myList[0])
print(myList[1])
resulting in:
1
2
A dictionary is also a collection, but rather than indexing using the position of the item,
you use a “key” that was originally used to store the item in the dictionary.
Let’s create an empty dictionary:
myDict = {}
Note the only difference between an empty dictionary and an empty list is that we use
“{” and “}” rather than “[’ and ‘]”. Now let’s store two values in the dictionary:
myDict[’a’] = 7
myDict[’b’] = 12
print(myDict)
Note that both the keys (a and b) and the values (7 and 12) are printed. To retrieve a
value from a dictionary, just “index” it with the corresponding key:
print(myDict[’a’])
CONDITIONALS
A conditional is a section of code that only executes when a specific condition is true, or
false. For example suppose we want to increase x by 1 when t is less than five (t<5) but
decrease x by 1 when t is greater than or equal to five (t>=5). We could do this in python
using a conditional like so:
if t<5:
x = x + 1
else:
x = x - 1
Note the placement of the colon “:”, and the indention. Python uses indention to specify
the scope of a series of expressions. All the code that’s indented has the same scope, and
executes in sequence. Any code that’s not indented is “outside the scope” of the “if” or
the “else” clause. You can chain conditions using boolean operators like and and or. For
example, one could also write:
50 ■ Visualizing Quantum Mechanics with Python
if t<5 or t>10:
x = x + 1
y = y - 2
else:
x = x - 1
y = y + 2
This would increment x by 1 and decrement y by 2 anytime t was either less than five
or more than ten. Otherwise it would decrement x by 1 and increment y by 2.
LOOPS
There are two types of loops frequently used in python. The while loop and the for loop.
The while loop depends on a condition and continues so long as the condition remains
true. The for loop iterates over the elements of a list-like object (in our case, that generally
means arrays and lists). Here are examples of each:
t=0
while t<5:
t = t + 1
print("t=",t)
print("done.")
which produces:
t= 1
t= 2
t= 3
t= 4
t= 5
done.
for t in [1,2,3,4,5]:
print("t=",t)
print("done.")
t= 1
t= 2
t= 3
t= 4
t= 5
done.
Numpy
Numpy is a library intended to improve support for numerical calculation. We’ll be using
it primarily because it simplifies mathematical operations on arrays of numbers. One way
to create a numpy array is to use the “array” method that takes a python list and converts
it into a numpy array. Here’s an example:
Appendix B ■ 51
import numpy as np
x = np.array([1,2,3])
print(x)
x = np.array([1,2,3])
print(x)
[1 2 3]
It looks just like a list! However, it behaves differently. Compare these two behaviors:
xList = [1,2,3]
yList = [4,5,6]
xArray = np.array(xList)
yArray = np.array(yList)
print("xList*2=", xList*2)
print("xArray*2=", xArray*2)
print("xList + yList=",xList + yList)
print("xArray + yArray=", xArray + yArray)
xList*2= [1, 2, 3, 1, 2, 3]
xArray*2= [2 4 6]
xList + yList= [1, 2, 3, 4, 5, 6]
xArray + yArray= [5 7 9]
Note that multiplying a list by 2 gives you a longer list. Multiplying a numpy array by
2 produces an array where each element of the array is multiplied by 2. This is much more
useful behavior for us! When you add two lists together you get a list which is the first list
and then the second list combined. When you add two numpy arrays together the result is
an array where each element is the sum of the corresponding elements of the two arrays.
This also works with other mathematical operations, subtraction, multiplication, division,
exponentiation, and so on. You can also use the functions in the numpy library that accept
numpy arrays are arguments. For example:
print(np.exp(xArray))
print(np.sin(xArray))
print(np.sqrt(xArray))
produces:
Also, there are some convenience functions in the numpy library that help to create
number arrays, like np.linspace, ones, and zeros:
np.linspace(0,10,11)
np.ones(10)
np.zeros(10)
52 ■ Visualizing Quantum Mechanics with Python
That produce:
array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.])
array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
As you can see, np.linspace produces an array that spans the range from one value to
another with a specified number of elements, while np.ones and np.zeros produce numpy
arrays with either ones or zeros, respectively, with a specified number of elements. Note
that one can also use a numpy array as a “list like” object in a for loop like so:
for t in np,array([1,2,3,4,5]):
print("t=",t)
print("done.")
Python Functions
We spend much time in quantum mechanics discussing wavefunctions which are, of course,
mathematical functions. It’s useful to use functions, defined in python, to mimic the math-
ematical functions that arise in quantum theory. To define a python function you use the
def keyword. For example we can create a function that takes two arguments, and returns
the first plus twice the second, like so:
Then when we call the function with two arguments, we get the desired result:
print(myFunction(1,2))
produces:
5
Index
53
54 ■ Index
functions, 52
indention, 49
library, 50
list, 16, 48
variable, 48
quantized, 5, 22, 29
quantum mechanics, 5
real part, 1
recursion relationships, 26
scale, 23
Schrödinger Wave Equation, 15
second derivative, 21
simple harmonic oscillator, 25
similarities to ISW, 25
simultaneous, 12
space-time, 12
diagram, 12
spatial frequency, 34
spherical harmonics, 41
spherical symmetry, 41
spin angular momentum, 42
stationary states, 22
superposition, 21
SWE, 15
turning points, 20
two-dimensional surface, 38
visualization, 23
VPython, 1, 8, 47
arrow, 8
cylinder, 9
vpython
cylinder, 38
vpython:rate, 18
wave properties, 6
wavefunction, 4, 11
representation, 16
wavelength, 5
wavenumber, 6