import numpy as np
import matplotlib.pyplot as plt
from scipy.linalg import eigh_tridiagonal
from scipy.sparse import diags
from scipy.sparse.linalg import eigsh, norm, onenormest
import sympy as sp
from IPython.display import display
Using https://www.latex4technics.com/ for latex typesetting
We start with the Schrödinger's equation:
$$ \left [ \frac{- \hbar^2}{2m} \frac{\partial}{\partial x^2} + V(x,t) \right ] \Psi (x,t) = E \, \Psi (x,t) $$
where in he case of the infinite square well:
$$ \psi(0) = \psi(L) = 0 $$
This can be represented as a eigen equation:
$$ \hat{H} \Psi = E \Psi $$
Where $ \hat{H} = \left [ \frac{- \hbar^2}{2m} \frac{\partial}{\partial x^2} + V(x,t) \right ] $
To make x dimensionless we substitute $ \overline{x} = x / L $ to get
$$ -\frac{1}{2}\frac{d^2\psi}{\overline{x}^2} + \frac{m L^2 V(\overline{x})}{\hbar^2} \psi = \frac{m L^2 E}{ \hbar^2 } \psi $$
# Set up the symbolic variables
x = sp.symbols('x')
n = sp.symbols('n', integer=True, positive=True)
m, hbar, xbar, E, L = sp.symbols(r'm hbar \bar{x} E, L', real=True, positive=True)
Psi = sp.Function('psi')
V = sp.Function('V')
We will set up for a zero potential for the middle part. So from Schrödinger's equation with no potential we have
$$ \frac{- \hbar^2}{2m} \frac{\partial}{\partial x^2} \Psi (x,t) = E \, \Psi (x,t) $$
Ok we want to write x in dimensionless terms so we write $ \bar{x} = x/ L $ then using the second derivative chain rule we can find $ d^2 \psi / dx^2 $ in terms of $ \bar{x} $. To do this we use the second derivative chain rule.
$$ \frac{d^2 \psi}{d x^2} = \frac{d^2\psi}{d\bar{x}^2} \left ( \frac{d\bar{x}}{dx} \right )^2 + \frac{d\psi}{d\bar{x}} \frac{d^2 \bar{x}}{d x^2} $$
Now because $ \displaystyle \frac{d^2 \bar{x}}{d x^2} = 0 $ we can exclude the right hand term. Therefore
$$ \frac{d^2 \psi}{d x^2} = \frac{d^2\psi}{d\bar{x}^2} \left ( \frac{1}{L} \right )^2 $$
Thus our new Schrödinger equation becomes
$$ \frac{- \hbar^2}{2m L^2} \frac{\partial}{\partial \bar{x}^2} \psi (L \bar{x}) = E \, \psi (L \bar{x}) $$
Now define a new $ \displaystyle \psi $ so that $ \psi_{new} (\bar{x}) = \psi_{orig} (L \bar{x}) $ Then we know that the solutions will be the same and related back to the original with just a scaling of the x argument. (ChatGPT claims this is known as Equivalence of differential equations under diffeomorphism).
$$ \frac{\partial}{\partial \bar{x}^2} \psi_{new} (\bar{x}) = -\frac{2 m L^2 E}{\hbar^2} \, \psi_{new} (\bar{x}) $$
eq = sp.Eq(sp.Derivative(Psi(x), x, x), -(2*m*L**2)* E*Psi(x)/hbar**2)
eq
dsoln = sp.dsolve(eq, Psi(x))
dsoln
expr = dsoln.rhs.args[0]
expr
constants = sorted(
[s for s in dsoln.free_symbols if s.name.startswith("C")],
key=lambda s: s.name
)
C1, C2 = constants
constants
[C1, C2]
# Rather than using sp.symbols and sp.Wild it is possible to match the sin expression directly without
# regard to what is outside it. The argument of what you pass to the atoms() method determines which
# types are returned
sin_part = next(iter(expr.atoms(sp.sin)))
arg = sin_part.args[0]
k = arg.coeff(x)
k
soln = dsoln
soln
This implies that $ C_2 = 0 $ as it must be zero at the boundaries. It must also be zero at the other boundary where x = L. We also ignore the C1 that is used for now
# Because it must be true that ψ = 0 when x = 0 then we can can conclude that it
# must also be true that C2 = 0
soln.rhs.subs(x,0)
# From that we know we need only deal with the sine part
sin_part
k = sin_part.args[0].subs(x, L)
k
Now to determine the normalisation factor
integral = sp.integrate(sin_part**2,(x,0,L))
normalisation = integral.subs(sp.sin(k), 0)
normalisation
psifn = sp.sqrt(1/normalisation)*sin_part
psifn
Now we revert back to $ \psi_{old} (x) = \psi_{new} (L x) $
C = sp.Wild("C")
k = sp.Wild("k")
# match x*L , not just x
match = psifn.match(C * sp.sin(k*x*L))
amplitude = match[C]
frequency = match[k]
display(amplitude)
frequency_factor = match[k]
display(frequency_factor)
# Now calculate our original old ψ
psi_old = amplitude*sp.sin(frequency_factor * x)
psi_old
expr = sp.Eq(frequency*L , sp.pi*n)
expr
energy = sp.solve(expr, E)[0]
energy
So $ \displaystyle E_n = \frac{\pi^{2} \hbar^{2} n^{2}}{2 L^{2} m} $
Now the units of $ \hbar $ are $ M L^2 T^{-1} $ or equivalently $ J \cdot s $
Rearranging $ \displaystyle \frac{m L^2 E_n}{\hbar^2} = \frac{\pi^2 n^2}{2} $
$$ \begin{bmatrix}\frac{1}{\Delta x^2}+mL^2V_1/\hbar^2 & -\frac{1}{2 \Delta x^2} & 0 & 0...\\ -\frac{1}{2 \Delta x^2} & \frac{1}{\Delta x^2}+mL^2V_2/\hbar^2 & -\frac{1}{2 \Delta x^2} & 0... \\ ...& ... & ... & -\frac{1}{2 \Delta x^2}\\...0 & 0 & -\frac{1}{2 \Delta x^2} & \frac{1}{\Delta x^2}+mL^2V_{N-1}/\hbar^2 \\ \end{bmatrix} \begin{bmatrix} \psi_1 \\ \psi_2 \\ ... \\ \psi_{N-1} \end{bmatrix} = \frac{mL^2 E}{\hbar^2} \begin{bmatrix} \psi_1 \\ \psi_2 \\ ... \\ \psi_{N-1} \end{bmatrix} $$
$$ \psi_0 = \psi_N = 0$$
$$ \hbar = 1.054571817...×10^{−34} J⋅s $$ However $ \hbar $ is not needed in our evaluation as our potential is either zero or infinity and dividing it by $ \hbar^2 $ is not expected to make any difference on the matrix side but would in the eigenvalue side.
from scipy.constants import hbar as hbar0
Define what $N$ and $dx$ is
# The grid spacing turned out to be tricky and getting it wrong multiplies the
# eigenvalues by a erroneous factor. I have made the dx formula more explicit so
# its not left to guesswork.
N = 1000
start = 0
end = 1
dx = (end - start)/N
x = np.linspace(start, end, N)
Define potential $mL^2 V$
# This is currently not used as it is more efficient to make the well as wide as
# the matrix.
def mL2V(x):
# The square well is zero only between 0 and 1
return np.where(((x<0) | (x>1)), 1e8, 0)
d = 1/dx**2 * np.ones(N) #+ mL2V(x) # main diagnonal
e = -1/(2*dx**2) * np.ones(len(d)-1) # off diagonal
offsets=[0,-1, 1]
diagonals = [d, e, e]
sparse_matrix = diags(diagonals, offsets, format='csr')
# 1-norm of the matrix
norm_A = norm(sparse_matrix, ord=1)
# Estimated 1-norm of the inverse matrix
norm_inv_A = onenormest(sparse_matrix)
# Condition number estimation
cond_num_est = norm_A * norm_inv_A
print("Estimated Condition Number (1-norm):", cond_num_est)
Estimated Condition Number (1-norm): 4000000000000.0
#w, v = eigh_tridiagonal(d, e)
w,v = eigsh(sparse_matrix, k=10, which='SM')
fig = plt.figure(figsize=(10,5))
ax = fig.gca()
ax.set_xlim(-0.5, 1.5)
ax.set_ylim(-0.5, 5)
plt.plot(x, mL2V(x), lw=3)
# Add a vertical line at x = 2, from y = 0 to y = 12
plt.vlines(x=0, ymin=0, ymax=12, linestyle='-', lw=3)
plt.vlines(x=1, ymin=0, ymax=12, linestyle='-', lw=3)
plt.title('Infinite Potential Well', fontsize=25)
plt.ylabel('$V/V_0$', fontsize=25)
plt.xlabel('$x/L$', fontsize=25)
plt.grid()
#plt.savefig('v3p1.png', dpi=200)
print (len(x), len(v.T[0]))
1000 1000
fig = plt.figure(figsize=(10,5))
ax = fig.gca()
ax.set_xlim(-0.25, 1.25)
ax.set_ylim(-0.05, 0.05)
line1, = plt.plot(x, v.T[0])
clr = line1.get_color()
plt.hlines(y=0, xmin=-.25, xmax=0, color=clr, linestyle='-', linewidth=4)
plt.hlines(y=0, xmin=1, xmax=1.25, color=clr, linestyle='-', linewidth=4)
plt.vlines(x=0, ymin=0, ymax=12, linestyle='-', lw=4)
plt.vlines(x=1, ymin=0, ymax=12, linestyle='-', lw=4)
plt.plot(x, v.T[0], label='1st eigenstate')
plt.plot(x, v.T[1], label='2nd eigenstate')
plt.plot(x, v.T[2], label='3rd eigenstate')
plt.title('Eigenstates', fontsize=30)
plt.ylabel(r'$ \psi $', fontsize=25)
plt.xlabel('x/L', fontsize=25)
plt.legend()
plt.grid()
#plt.savefig('v3p2.png', dpi=200)
Now $ \displaystyle \frac{m L^2 E_n}{\hbar^2} = \frac{\pi^2 n^2}{2} $ so we should be able to check the eigenvalues against $ \displaystyle \frac{\pi^2 n^2}{2} $
plt.bar(np.arange(0, 10, 1), w[0:10])
plt.ylabel(r"$ \frac{mL²E}{\hbar^2} $", fontsize=20)
plt.grid()
from scipy.constants import pi as pi0
errors = []
for i in range(10):
predicted = pi0**2*(i+1)**2/2
numerical = w[i]
error = abs(numerical - predicted)/predicted
errors.append(error)
print (f"n={i+1} numerical = {w[i]:.2f} predicted = {pi0**2*(i+1)**2/2:.2f} rel.error = {error:.4f} " )
n=1 numerical = 4.92 predicted = 4.93 rel.error = 0.0020 n=2 numerical = 19.70 predicted = 19.74 rel.error = 0.0020 n=3 numerical = 44.32 predicted = 44.41 rel.error = 0.0020 n=4 numerical = 78.80 predicted = 78.96 rel.error = 0.0020 n=5 numerical = 123.12 predicted = 123.37 rel.error = 0.0020 n=6 numerical = 177.29 predicted = 177.65 rel.error = 0.0020 n=7 numerical = 241.31 predicted = 241.81 rel.error = 0.0020 n=8 numerical = 315.18 predicted = 315.83 rel.error = 0.0020 n=9 numerical = 398.89 predicted = 399.72 rel.error = 0.0021 n=10 numerical = 492.45 predicted = 493.48 rel.error = 0.0021