Cyanine dye molecules are often modelled as one-dimension particles in a box. To understand why, start by thinking classically. You learn in organic chemistry that electrons can more “freely” along alternating double bonds. If this is true, then you can imagine that the electrons can more from one Nitrogen to the other, almost without resistance. On the other hand, there are sp3-hybridized functional groups attached to the Nitrogen atom, so once the electron gets to Nitrogen atom, it has to turn around and go back whence it came. A very, very, very simple model would be to imagine that the electron is totally free between the Nitrogen atoms, and totally forbidden from going much beyond the Nitrogen atoms. This suggests modeling these systems a potential energy function like: where is the length of the box. A reasonable approximate formula for is
What is the interpretation of the wavefunction? The Born postulate indicates that the squared magnitude of the wavefunction is proportional to the probability of observing the system at that location. E.g., if is the wavefunction for an electron as a function of , then is the probability of observing an electron at the point . This is called the Born Postulate.
The nice thing about this “particle in a box” model is that it is easy to solve the time-independent Schrödinger equation in this case. Because there is no chance that the particle could ever “escape” an infinite box like this (such an electron would have infinite potential energy!), must equal zero outside the box. Therefore the wavefunction can only be nonzero inside the box. In addition, the wavefunction should be zero at the edges of the box, because otherwise the wavefunction will not be continuous. So we should have a wavefunction like
How do we find the wavefunction for the particle-in-a-box or, for that matter, any other system? The wavefunction can be determined by solving the time-independent (when the potential is time-independent) or time-dependent (when the potential is time-dependent) Schrödinger equation.
To find the wavefunctions for a system, one solves the Schrödinger equation. For a particle of mass in a one-dimensional box, the (time-independent) Schrödinger equation is: where We already deduced that except when the electron is inside the box (), so we only need to consider the Schrödinger equation inside the box: There are systematic ways to solve this equation, but let's solve it by inspection. That is, we need to know:
Question: What function(s), when differentiated twice, are proportional to themselves?\
This suggests that the eigenfunctions of the 1-dimensional particle-in-a-box must be some linear combination of sine and cosine functions, We know that the wavefunction must be zero at the edges of the box, and . These are called the boundary conditions for the problem. Examining the first boundary condition, indicates that . The second boundary condition requires us to recall that whenever is an integer multiple of . So where . The wavefunction for the particle in a box is thus,
As seen in the previous section, if a wavefunction solves the Schrödinger equation, any constant multiple of the wavefunction also solves the Schrödinger equation, Owing to the Born postulate, the complex square of the wavefunction can be interpreted as probability. Since the probability of a particle being at some point in space is one, we can define the normalization constant, , for the wavefunction through the requirement that: In the case of a particle in a box, this is: To evaluate this integral, it is useful to remember some trigonometric identities. (You can learn more about how I remember trigonometric identities here.) The specific identity we need here is :
So Note that this does not completely determine . For example, any of the following normalization constants are allowed, In general, any square root of unity can be used, where is any real number. The arbitrariness of the phase of the wavefunction is an important feature. Because the wavefunction can be imaginary (e.g., if you choose ), it is obvious that the wavefunction is not an observable property of a system. The wavefunction is only a mathematical tool for quantum mechanics; it is not a physical object.
Summarizing, the (normalized) wavefunction for a particle with mass confined to a one-dimensional box with length can be written as:
Note that in this case, the normalization condition is the same for all ; that is an unusual property of the particle-in-a-box wavefunction.
While this normalization convention is used 99% of the time, there are some cases where it is more convenient to make a different choice for the amplitude of the wavefunctions. I say this to remind you that normalization the wavefunction is something we do for convenience; it is not required by physics!
One advantage of using Jupyter is that we can easily check our (symbolic) mathematics. Let's confirm that the wavefunction is normalized by evaluating,
# Execute this code block to import required objects.
# Note: The numpy library from autograd is imported, which behaves the same as
# importing numpy directly. However, to make automatic differentiation work,
# you should NOT import numpy directly by `import numpy as np`.
import autograd.numpy as np
from autograd import elementwise_grad as egrad
# import numpy as np
from scipy.integrate import trapezoid, quad
from scipy import constants
import ipywidgets as widgets
import matplotlib.pyplot as plt
# set the size of the plot
# plt.rcParams['figure.figsize'] = [10, 5]
#The next few lines just set up the sliders for setting parameters.
#Principle quantum number slider:
n = widgets.IntSlider(
value=1,
min=1,
max=10,
step=1,
description='n (quant. #):',
disabled=False,
continuous_update=True,
orientation='horizontal',
readout=True,
readout_format='d')
#Box length slider:
a = widgets.FloatSlider(
value=1,
min=.01,
max=10.0,
step=0.01,
description='a (length):',
disabled=False,
continuous_update=True,
orientation='horizontal',
readout=True,
readout_format='.2f',
)
# Define a function for the wavefunction
def compute_wavefunction(x, n, a):
"""Compute 1-dimensional particle-in-a-box wave-function value(s).
Parameters
----------
x: float or np.ndarray
Position of the particle.
n: int
Quantum number value.
a: float
Length of the box.
"""
# check argument n
if not (isinstance(n, int) and n > 0):
raise ValueError("Argument n should be a positive integer.")
# check argument a
if a <= 0.0:
raise ValueError("Argument a should be positive.")
# check argument x
if not (isinstance(x, float) or hasattr(x, "__iter__")):
raise ValueError("Argument x should be a float or an array!")
# compute wave-function
value = np.sqrt(2 / a) * np.sin(n * np.pi * x / a)
# set wave-function values out of the box equal to zero
if hasattr(x, "__iter__"):
value[x > a] = 0.0
value[x < 0] = 0.0
else:
if x < 0.0 or x > a:
value = 0.0
return value
# Define a function for the wavefunction squared
def compute_probability(x, n, a):
"""Compute 1-dimensional particle-in-a-box probablity value(s).
See `compute_wavefunction` parameters.
"""
return compute_wavefunction(x, n, a)**2
#This next bit of code just prints out the normalization error
def check_normalization(a, n):
#check the computed values of the moments against the analytic formula
normalization,error = quad(compute_probability, 0, a, args=(n, a))
print("Normalization of wavefunction = ", normalization)
out = widgets.interactive_output(check_normalization, {'a': a, 'n': n})
widgets.HBox([widgets.VBox([a, n]),out])
How do we compute the energy of a particle in a box? All we need to do is substitute the eigenfunctions of the Hamiltonian, back into the Schrödinger equation to determine the eigenenergies, . That is, from we deduce Using the definition of , we can rearrange this to: Notice that only certain energies are allowed. This is a fundamental principle of quantum mechanics, and it is related to the "waviness" of particles. Certain "frequencies" are resonant, and other "frequencies" cannot be observed. The only energies that can be observed for a particle-in-a-box are the ones given by the above formula.
Naïvely, you might expect that the lowest-energy state of a particle in a box has zero energy. (The potential in the box is zero, after all, so shouldn't the lowest-energy state be the state with zero kinetic energy? And if the kinetic energy were zero and the potential energy were zero, then the total energy would be zero.)
But this doesn't happen. It turns out that you can never "stop" a quantum particle; it always has a zero-point motion, typically a resonant oscillation about the lowest-potential-energy location(s). Indeed, the more you try to confine a particle to stop it, the bigger its kinetic energy becomes. This is clear in the particle-in-a-box, which has only kinetic energy. There the (kinetic) energy increases rapidly, as , as the box becomes smaller: The residual energy in the electronic ground state is called the zero-point energy, The existence of the zero-point energy, and the fact that zero-point kinetic energy is always positive, is a general feature of quantum mechanics.
Zero-Point Energy Principle: Let be a nonnegative potential. The ground-state energy is always greater than zero.
More generally, for any potential that is bound from below, the ground-state energy of the system satisfies .
Nuance: There is a tiny mathematical footnote here; there are some for which there are no bound states. In such cases, e.g., , it is possible for .)
Because Planck's constant and the electron mass are tiny numbers, it is often useful to use atomic units when performing calculations. We'll learn more about atomic units later but, for now, we only need to know that, in atomic units, , the mass of the electron, , the charge of the electron, , and the average (mean) distance of an electron from the nucleus in the Hydrogen atom, , are all defined to be equal to 1.0 in atomic units. The unit of energy in atomic units is called the Hartree, and the ground-state (zero-point) energy of the Hydrogen atom is .
We can now define functions for the eigenenergies of the 1-dimensional particle in a box:
#The next few lines just set up the sliders for setting parameters.
#Principle quantum number slider:
n = widgets.IntSlider(
value=1,
min=1,
max=10,
step=1,
description='n (quant. #):',
disabled=False,
continuous_update=True,
orientation='horizontal',
readout=True,
readout_format='d')
#Box length slider:
a = widgets.FloatSlider(
value=1,
min=.01,
max=10.0,
step=0.01,
description='a (length):',
disabled=False,
continuous_update=True,
orientation='horizontal',
readout=True,
readout_format='.2f',
)
# Define a function for the energy of a particle in a box
# with length a and quantum number n [in atomic units!]
# The length is input in Bohr (atomic units)
def compute_energy(n, a):
"Compute 1-dimensional particle-in-a-box energy."
return n**2 * np.pi**2 / (2 * a**2)
# Define a function for the energy of an electron in a box
# with length a and quantum number n [in SI units!].
# The length is input in meters.
def compute_energy_si(n, a):
"Compute 1-dimensional particle-in-a-box energy."
return n**2 * constants.h**2 / (8 * constants.m_e* a**2)
#Define variable for atomic unit of length in meters
a0 = constants.value('atomic unit of length')
#This next bit of code just prints out the energy in atomic and SI units
def print_energy(a, n):
print(f'The energy of an electron in a box of length {a:.2f} a.u. with '
f'quantum number {n} is {compute_energy(n, a):.2f} a.u..')
print(f'The energy of an electron in a box of length {a*a0:.2e} m with '
f'quantum number {n} is {compute_energy_si(n, a*a0):.2e} Joules.')
out = widgets.interactive_output(print_energy, {'a': a, 'n': n})
widgets.VBox([widgets.VBox([a, n]),out])
While the wavefunction is not itself observable, all observable properties of a system can be determined from the wavefunction. However, just because the wavefunction encapsulates all the observable properties of a system does not mean that it contains all information about a system. In quantum mechanics, some things are not observable. Consider that for the ground () state of the particle in a box, the root-mean-square average momentum, increases as you squeeze the box. That is, the more you try to constrain the particle in space, the faster it moves. You can't "stop" the particle no matter how hard you squeeze it, so it's impossible to exactly know where the particle is located. You can only determine its average position.
The correspondence principle says that for every classical observable there is a linear, Hermitian, operator that allows computation of the quantum-mechanical observable. An operator, is linear if for any complex numbers and , and any wavefunctions and , Similarly, an operator is Hermitian if it satisfies the relation, or, equivalently, That is for a linear operator, the linear operator applied to a sum of wavefunctions is equal to the sum of the linear operators directly applied to the wavefunctions separately, and the linear operator applied to a constant times a wavefunction is the constant times the linear operator applied directly to the wavefunction. A Hermitian operator can apply forward (towards ) or backwards (towards ). This is very useful, because sometimes it is much easier to apply an operator in one direction.
We've already been introduced to the quantum-mechanical operators for the momentum, and the kinetic energy, These operators are linear because the derivative of a sum is the sum of the derivatives, and the derivative of a constant times a function is that constant times the derivative of the function. These operators are also Hermitian. For example, to show that the momentum operator is Hermitian:
Here we used the product rule for derivatives, . Using the fundamental theorem of calculus and the fact the probability of observing a particle at is zero, and therefore the wavefunctions at infinity are also zero, one knows that Therefore the above equation can be simplified to
The expectation value of the momentum of a particle-in-a-box is always zero. This is intuitive, since electrons (on average) are neither moving to the right nor to the left inside the box: if they were, then the box itself would need to be moving. Indeed, for any real wavefunction, the average momentum is always zero. This follows directly from the previous derivation with . Thus: The last line follows because the only number that is equal to its negative is zero. (That is, if and only if .) It is a subtle feature that the eigenfunctions of a real-valued Hamiltonian operator can always be chosen to be real-valued themselves, so their average momentum is clearly zero. We often denote quantum-mechanical expectation values with the shorthand,
The momentum-squared of the particle-in-a-box is easily computed from the kinetic energy,
Intuitively, since the box is symmetric about , the particle has equal probability of being in the first-half and the second-half of the box. So the average position is expected to be
We can confirm this by explicit integration,
Similarly, we expect that the expectation value of will be proportional to . We can confirm this by explicit integration, \begin{align} \langle x^2 \rangle &= \int_0^a \psi_n^*(x)\, x^2 \,\psi_n(x) dx \ &= \int_0^a \left(\sqrt{\tfrac{2}{a}} \sin\left(\tfrac{n \pi x}{a} \right)\right) x^2 \left(\sqrt{\tfrac{2}{a}}\sin\left(\tfrac{n \pi x}{a} \right)\right) dx \ &= \tfrac{2}{a} \int_0^a x^2 \sin^2\left(\tfrac{n \pi x}{a} \right) dx \ &= \tfrac{2}{a} \left[ \tfrac{x^3}{6}
We can verify these formulas by explicit integration.
#The next few lines just set up the sliders for setting parameters.
#Principle quantum number slider:
n = widgets.IntSlider(
value=1,
min=1,
max=10,
step=1,
description='n (quant. #):',
disabled=False,
continuous_update=True,
orientation='horizontal',
readout=True,
readout_format='d')
#Box length slider:
a = widgets.FloatSlider(
value=1,
min=.01,
max=10.0,
step=0.01,
description='a (length):',
disabled=False,
continuous_update=True,
orientation='horizontal',
readout=True,
readout_format='.2f',
)
#Compute <x^power>, the expectation value of x^power
def compute_moment(x, n, a, power):
"""Compute the x^power moment of the 1-dimensional particle-in-a-box.
See `compute_wavefunction` parameters.
"""
return compute_probability(x, n, a)*x**power
#This next bit of code just prints out the values.
def check_moments(a, n):
#check the computed values of the moments against the analytic formula
avg_r,error = quad(compute_moment, 0, a, args=(n, a, 1))
avg_r2,error = quad(compute_moment, 0, a, args=(n, a, 2))
print(f"<r> computed = {avg_r:.5f}")
print(f"<r> analytic = {a/2:.5f}")
print(f"<r^2> computed = {avg_r2:.5f}")
print(f"<r^2> analytic = {a**2*(1/3 - 1./(2*n**2*np.pi**2)):.5f}")
out = widgets.interactive_output(check_moments, {'a': a, 'n': n})
widgets.HBox([widgets.VBox([a, n]),out])
The previous example gives a first example of the more general Heisenberg Uncertainty Principle. One specific manifestation of the Heisenberg Uncertaity Principle is that the variance of the position, , times the variance of the momentum. is greater than . We can verify this formula for the particle in a box. The right-hand-side gets larger and larger as increases, so the largest value occurs where:
To check the energy of the particle in a box, we can compute the kinetic energy density, then integrate it over all space. That is, we define: and then the kinetic energy (which is the energy for the particle in a box) is
Note: In fact there are several different equivalent definitions of the kinetic energy density, but this is not very important in an introductory quantum chemistry course. All of the kinetic energy densities give the same total kinetic energy. However, because the kinetic energy density, , represents the kinetic energy of a particle at the point , and it is impossible to know the momentum (or the momentum-squared, ergo the kinetic energy) exactly at a given point in space according to the Heisenberg uncertainty principle), there can be no unique definition for .
#The next few lines just set up the sliders for setting parameters.
#Principle quantum number slider:
n = widgets.IntSlider(
value=1,
min=1,
max=10,
step=1,
description='n (quant. #):',
disabled=False,
continuous_update=True,
orientation='horizontal',
readout=True,
readout_format='d')
#Box length slider:
a = widgets.FloatSlider(
value=1,
min=.01,
max=10.0,
step=0.01,
description='a (length):',
disabled=False,
continuous_update=True,
orientation='horizontal',
readout=True,
readout_format='.2f',
)
#The next few lines just set up the sliders for setting parameters.
#Principle quantum number slider:
def compute_wavefunction_derivative(x, n, a, order=1):
"""Compute 1-dimensional particle-in-a-box kinetic energy density.
"""
if not (isinstance(order, int) and n > 0):
raise ValueError("Argument order is expected to be a positive integer!")
def wavefunction(x):
v = np.sqrt(2 / a) * np.sin(n * np.pi * x / a)
return v
# compute derivative
deriv = egrad(wavefunction)
for _ in range(order - 1):
deriv = egrad(deriv)
# return zero for x values out of the box
deriv = deriv(x)
# deriv[x < 0] = 0.0
# deriv[x > a] = 0.0
if hasattr(x, "__iter__"):
deriv[x > a] = 0.0
deriv[x < 0] = 0.0
else:
if x < 0.0 or x > a:
deriv = 0.0
return deriv
def compute_kinetic_energy_density(x, n, a):
"""Compute 1-dimensional particle-in-a-box kinetic energy density.
See `compute_wavefunction` parameters.
"""
# evaluate wave-function and its 2nd derivative w.r.t. x
wf = compute_wavefunction(x, n, a)
d2 = compute_wavefunction_derivative(x, n, a, order=2)
return -0.5 * wf * d2
#This next bit of code just prints out the values.
def check_energy(a, n):
#check the computed values of the moments against the analytic formula
ke,error = quad(compute_kinetic_energy_density, 0, a, args=(n, a))
energy = compute_energy(n, a)
print(f"The energy computed by integrating the k.e. density is {ke:.5f}")
print(f"The energy computed directly is {energy:.5f}")
out = widgets.interactive_output(check_energy, {'a': a, 'n': n})
widgets.VBox([widgets.VBox([a, n]),out])
In the next code block, the wavefunction, probability density, derivative, second-derivative, and kinetic energy density for the particle-in-a-box are shown. Notice that the kinetic-energy-density is proportional to the probability density, and that the first and second derivatives are not zero at the edge of the box, but the wavefunction and probability density are. It's useful to change the parameters in the below figures to build your intuition for the particle-in-a-box.
#The next few lines just set up the sliders for setting parameters.
#Principle quantum number slider:
n = widgets.IntSlider(
value=1,
min=1,
max=10,
step=1,
description='n (quant. #):',
disabled=False,
continuous_update=True,
orientation='horizontal',
readout=True,
readout_format='d')
#Box length slider:
a = widgets.FloatSlider(
value=1,
min=.01,
max=10.0,
step=0.01,
description='a (length):',
disabled=False,
continuous_update=True,
orientation='horizontal',
readout=True,
readout_format='.2f',
)
#This next bit of code makes the plots and prints out the energy
def make_plots(a, n):
#check the computed values of the moments against the analytic formula
energy = compute_energy(n, a)
print(f"The energy computed directly is {energy:.5f}")
# sample x coordinates
x = np.arange(-0.6, a + 0.6, 0.01)
# evaluate wave-function & probability
wf = compute_wavefunction(x, n, a)
pr = compute_probability(x, n, a)
# evaluate 1st & 2nd derivative of wavefunction w.r.t. x
d1 = compute_wavefunction_derivative(x, n, a, order=1)
d2 = compute_wavefunction_derivative(x, n, a, order=2)
# evaluate kinetic energy density
kin = compute_kinetic_energy_density(x, n, a)
#print("Integrate KED = ", trapezoid(kin, x))
# set the size of the plot
plt.rcParams['figure.figsize'] = [15, 10]
plt.rcParams['font.family'] = 'serif'
plt.rcParams['font.serif'] = ['Times New Roman']
plt.rcParams['mathtext.fontset'] = 'stix'
plt.rcParams['xtick.labelsize'] = 15
plt.rcParams['ytick.labelsize'] = 15
# define four subplots
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2)
fig.suptitle(f'a={a} n={n} E={compute_energy(n, a):.4f} a.u.', fontsize=35, fontweight='bold')
# plot 1
ax1.plot(x, wf, linestyle='--', label=r'$\psi(x)$', linewidth=3)
ax1.plot(x, pr, linestyle='-', label=r'$\|\psi(x)\|^2$', linewidth=3)
ax1.legend(frameon=False, fontsize=20)
ax1.set_xlabel('x coordinates', fontsize=20)
# plot 2
ax2.plot(x, d1, linewidth=3, c='k')
ax2.set_xlabel('x coordinates', fontsize=20)
ax2.set_ylabel(r'$\frac{d\psi(x)}{dx}$', fontsize=30, rotation=0, labelpad=25)
# plot 3
ax3.plot(x, d2, linewidth=3, c='g', )
ax3.set_xlabel('x coordinates', fontsize=20)
ax3.set_ylabel(r'$\frac{d^2\psi(x)}{dx^2}$', fontsize=25, rotation=0, labelpad=25)
# plot 4
ax4.plot(x, kin, linewidth=3, c='r')
ax4.set_xlabel('x coordinates', fontsize=20)
ax4.set_ylabel('Kinetic Energy Density', fontsize=16)
# adjust spacing between plots
plt.subplots_adjust(left=0.125,
bottom=0.1,
right=0.9,
top=0.9,
wspace=0.35,
hspace=0.35)
#Show Plot
plt.show()
out = widgets.interactive_output(make_plots, {'a': a, 'n': n})
widgets.VBox([widgets.VBox([a, n]),out])
My favorite sources for this material are:
There are also some excellent wikipedia articles: