\pagestyle{plain} %For page numbering in PDF version.

Electron Orbital Visualization

This notebook was developed for the Quantum Chemistry graduate course (CHM 6470 - Chemical Bonding and Spectra I) at the University of Florida. It used the Hydrogen atom wavefunctions to provide visualizations of the quantum equations of the atomic orbitals. The visualization requires the use of the "mayavi2" library to display isoplots (surfaces of equal value) of the probability density functions. It is these functions that can be interpreted as describing the most probable location for finding an electron.

Usage:

To use this notebook start it with the following command:

ipython notebook

Authors:

Debugging help from:

  • Nathan Roehr Ph.D. Candidate

Acknowledgements

This project is financially supported by the National Science Foundation under grant CHE-0845450.

The information in this notebook is partially based on the information found at the following sites:

http://panda.unm.edu/Courses/Finley/P262/Hydrogen/WaveFcns.html

http://www.hasenkopf2000.net/wiki/page/3d-hydrogen-structure-python/

http://www.sethanil.com/python-for-reseach/5

Sample output using quantum number values of $n=3$, $l=2$, $m=0$

  • Number of contours used = 16
  • Opacity = 0.5

Quantum Equations

The (radial) probability function can be expressed as the product of the square of the wavefunction ($\psi$) times the square of the distance (r) from the nucleus.

$P = \psi^2 r^2$ (probability function)

$\psi$ is a wavefunction that is composed of the radial ($R_{nl}$) and the spherical harmonics ($Y_{lm}$) functions. $R_{nl}$ is dependent on the distance from the center of the atom $r$, and the principal quantum number $n$, and angular momentum quantum number $l$. $Y_{lm}$ is a function of $l$, the magnetic angular momentum $m_l$, $\phi$ (defined as the angle of the positon vector in the $\textbf{xy}$ plane), and $\theta$ (i.e., angle between $\textbf{z}$-axis and position vector) in a spherical coordinate system. For the purposes of this notebook $m_l$ will be expressed simply by the term $m$ to avoid confusion with $l$.

coords

$$\psi_{nlm}(r,\theta,\phi) = R_{nl}(r)Y_{lm}(\theta,\phi)$$

(Note: In the following derivations for $\psi$, $R$ and $Y$'s the quantum number subscripts are dropped in order to prevent confusion with multiplication terms.)

Where:

$$R(r) = \left(\frac{2\cdot z}{n\cdot a_0}\right)^{3/2} \sqrt{\frac{(n-l-1)!}{2\cdot n\cdot [(n+l)!]^3}}\cdot exp\left(\frac{-z\cdot r}{n \cdot a_0}\right) \cdot \left(\frac{2\cdot z\cdot r}{n \cdot a_0} \right)^l \cdot L^{2\cdot l +1}_{n-l-1} \left(\frac{2 \cdot z \cdot r}{n\cdot a_0} \right)$$

Where:

$z$ refers to the atomic charge of the nucleus (i.e., +1 for hydrogen)

$a_0$ refers to the Bohr radius (assume $a_0 = 1$ for unity)

and $L$ is a Laguerre polynomial:

$$\hspace{20 mm}\text{Where: } L^{2\cdot l +1}_{n-l-1}\left(\frac{2 \cdot z \cdot r}{n\cdot a_0} \right) = \sum_{i=0}^{n-l-1}{\frac{(-i)^i\cdot\left((n+l)!\right)^2 \left( \frac {2zr}{n a_0}\right)^i} {i!\cdot(n-l-1-i)!\cdot(2\cdot l+1+i)!}}$$

$$\text{ }$$

$$Y^{m}_{l}(\theta,\phi)=(-1)^{|m|}\cdot\sqrt{\frac{(2\cdot l+1)(l-|m|)!}{4\cdot\pi\cdot (l+|m|)!}}\cdot e^{i\cdot m \cdot\phi}\cdot P^{|m|}_{l}(cos(\theta))$$

Where $P_l^m$ is a Legendre polynomial.

$$\hspace{20 mm}\text{Where: }P^{|m|}_{l}(cos(\theta))=\left(1-cos^2(\theta)\right)^{\left|\frac{m}{2}\right|} \cdot \frac{\delta^{|m|} }{\delta (cos(\theta))^{|m|}}(P_l(cos(\theta)))$$

$$\hspace{20 mm}\text{Where: }P^{|m|}_{l}(cos(\theta))=sin^{\left|m\right|}(\theta) \cdot \frac{\delta^{|m|} }{\delta (cos(\theta))^{|m|}}(P_l(cos(\theta)))$$

$\hspace{20 mm}$Rodrigues’ formula provides a solution to the Legendre polynomial $P_l$.

$$\hspace{20 mm}\hspace{20 mm}\text{and where: }P_l(cos(\theta)) = \frac{1}{2^l\cdot l!} \frac{d^l}{d (cos(\theta))^l} (cos^2(\theta)-1)^l$$

(Note that the $\delta^m$ term refers refers to the derivative with order m with respect to $cos(\theta)$ and the $d^l$ term refers to the derivative with order $l$ with respect to $cos(\theta)$.)

Displaying the Orbitals

For purposes of display we are only intrested in the real portion of $Y^{m}_{l}(\theta,\phi)$.

Specifically, if $m\ne0$ then we evaluate $Y$ in with the following modifications.

For the real part:

$$Y^{m}_{l}(\theta,\phi)=\left|\sqrt{\frac{(2\cdot l+1)(l-|m|)!}{4\cdot\pi\cdot (l+|m|)!}}\cdot \left(\sqrt{2} \cdot \Re\left\{e^{i\cdot m \cdot\phi}\right\}\right)\cdot P^{|m|}_{l}(cos(\theta))\right|$$ (If $m>0$)

(Note that the $(-1)^{|m|}$ goes away when only using the real or imaginary portions but you have to take the absolute value.)

(Note that $\Re\left\{e^{i\cdot m \cdot\phi}\right\}$ denotes the real part of $e^{i\cdot m \cdot\phi}$)

or, for the imaginary part:

$$Y^{m}_{l}(\theta,\phi)=\left|\sqrt{\frac{(2\cdot l+1)(l-|m|)!}{4\cdot\pi\cdot (l+|m|)!}}\cdot \left(\sqrt{2} \cdot \Im\left\{e^{i\cdot m \cdot\phi}\right\}\right)\cdot P^{|m|}_{l}(cos(\theta))\right|$$ (If $m<0$)

(Note that the real and imaginary portions are multiplied with a scaling factor equal to $\frac{2}{\sqrt{2}} = \sqrt{2}$)

Notes on computational alternatives:

When solving the real or imaginary portions by hand the Euler's formula is used and the results are added up.

For example, if $l=1$ and $m=1$, the real solution is:

$=\frac{1}{2}\cdot\sqrt{\frac{3}{2\cdot\pi}}\cdot sin(\theta)\cdot\left(cos(\phi)-i\cdot sin(\phi) + cos(\phi)+i\cdot sin(\phi) \right)$

$=\frac{1}{2}\cdot\sqrt{\frac{3}{2\cdot\pi}}\cdot sin(\theta)\cdot\left(cos(\phi) + cos(\phi) \right)$

$=\frac{1}{2}\cdot\sqrt{\frac{3}{2\cdot\pi}}\cdot sin(\theta)\cdot 2 \cdot cos(\phi) $

$=\sqrt{\frac{3}{2\cdot\pi}}\cdot sin(\theta)\cdot cos(\phi) $

This result needs to be scaled scaling by a factor $\frac{1}{\sqrt{2}}$.

$=\frac{1}{\sqrt{2}}\cdot\sqrt{\frac{3}{2\cdot\pi}}\cdot sin(\theta)\cdot cos(\phi) $

$=\sqrt{\frac{3}{4\cdot\pi}}\cdot sin(\theta)\cdot cos(\phi) $

Import Libraries:

In order to have mayavi.mlab display correctly, version 2.7 of Python must be used.

In [1]:
import sys #texting for the right version of Python to use with mayavi
print("Python version = {}".format(sys.version))
print("Machine readable Python version = {}".format(sys.version_info))
sys.version_info[0]
Python version = 2.7.5+ (default, Sep 19 2013, 13:48:49) 
[GCC 4.8.1]
Machine readable Python version = sys.version_info(major=2, minor=7, micro=5, releaselevel='final', serial=0)

Out[1]:
2

Additionally, this notebook uses the sympy library to symbolically develop the equations needed to calculate the probability function to the wave equation. Once derived these equations are converted to numpy versions for speed and compatibility with the mayavi library.

(Note: To install mayavi2 on Ubuntu 12.04, type "sudo apt-get install mayavi2" in the terminal window.)

In [2]:
print("The library version numbers used in this notebook are:")
if sys.version_info[0]==2:
    #this requires the use of no higher than python 2.7 
    from mayavi import __version__ as mayavi_version  
#     import scipy.special
#     import scipy.misc
    from mayavi import mlab
    print("mayavi version:  %s"%mayavi_version)
else:
    print("The use of mayavi.mlab to display the orbital requires the "+\
          "use of Python 2.7.3")
 
from IPython import __version__ as IPython_version 
from IPython.display import Latex
import numpy as np
import sympy

#"I" is sympy's imaginary number
from sympy import symbols,I,latex,pi,diff 
from sympy.utilities.lambdify import lambdastr
from sympy import factorial as fac
from sympy.functions import Abs,sqrt,exp,cos,sin
from sympy import re, im, simplify

#display the latex representation of a symbolic variable by default.
from sympy import init_printing 
init_printing(use_unicode=True)

a_0,z,r=symbols("a_0,z,r")
n,m,l=symbols("n,m,l",integer=True)
int_m=symbols("int_m",integer=True)
theta,phi = symbols("\\theta,\\phi",real=True)

#The variables will used with lambdify...
angle_theta, angle_phi, radius = symbols("angle_theta,angle_phi,radius",
                                         real=True)


print("numpy version:   %s"%np.__version__)
print("sympy version:   %s"%sympy.__version__)

print("IPython version: %s"%IPython_version)
The library version numbers used in this notebook are:
mayavi version:  4.1.0
numpy version:   1.7.1
sympy version:   0.7.4.1
IPython version: 1.2.0

(Note: Even though the SciPy library has both the Laguerre and and spherical harmonics functions in it, for purposes of clarity in the derivation and consistency in the way orbitals have been traditionally represented in Chemistry, specific solutions of these functions are developed and written here.)

Declare Functions:

Defining $P_l$ (Legendre polynomial):

$$P_l(cos(\theta)) = \frac{1}{2^l\cdot l!} \frac{d^l}{d (cos(\theta))^l} (cos^2(\theta)-1)^l$$

In [3]:
def P_l(l,theta): #valid for l greater than equal to zero
    """Legendre polynomial"""
    if l>=0:
        eq=diff((cos(theta)**2-1)**l,cos(theta),l)
    else:
        print("l must be an integer equal to 0 or greater")
        raise ValueError
    return 1/(2**l*fac(l))*eq

Validation

In [4]:
P_l(0,theta)
Out[4]:
$$1$$
In [5]:
P_l(1,theta)
Out[5]:
$$\cos{\left (\theta \right )}$$
In [6]:
P_l(2,theta)
Out[6]:
$$\frac{1}{2} \left(3 \cos^{2}{\left (\theta \right )} - 1\right)$$

Defining $P^{|m|}_{l}$ (Legendre polynomial):

$$P^{|m|}_{l}(cos(\theta))=\left(1-cos^2(\theta)\right)^{\left|\frac{m}{2}\right|} \cdot {\frac{\delta^{|m|} }{\delta (cos(\theta))^{|m|}}(P_l(cos(\theta)))}$$

Which equals...

$$P^{|m|}_{l}(cos(\theta))=sin^{\left|m\right|}(\theta) \cdot {\frac{\delta^{|m|} }{\delta (cos(\theta))^{|m|}}(P_l(cos(\theta)))}$$

In [7]:
def P_l_m(m,l,theta):
    """Legendre polynomial"""
    eq = diff(P_l(l,theta),cos(theta),Abs(m))
    result = sin(theta)**Abs(m)*eq #note 1-cos^2(theta) = sin^2(theta)
    return result

Validation

In [8]:
P_l_m(1,1,theta) #Because sqrt(1-cos^(theta)) = sin(theta)
Out[8]:
$$\sin{\left (\theta \right )}$$
In [9]:
P_l_m(-1,1,theta) 
Out[9]:
$$\sin{\left (\theta \right )}$$

Defining $Y^{m}_{l}(\theta,\phi)$ (Spherical harmonics):

$$Y^{m}_{l}(\theta,\phi)=\left|\sqrt{\frac{(2\cdot l+1)(l-|m|)!}{4\cdot\pi\cdot (l+|m|)!}}\cdot \left(\sqrt{2} \cdot \Re\left\{e^{i\cdot m \cdot\phi}\right\}\right)\cdot P^{|m|}_{l}(cos(\theta))\right|$$ (If $m>0$)

or

$$Y^{m}_{l}(\theta,\phi)=\left|\sqrt{\frac{(2\cdot l+1)(l-|m|)!}{4\cdot\pi\cdot (l+|m|)!}}\cdot \left(\sqrt{2} \cdot \Im\left\{e^{i\cdot m \cdot\phi}\right\}\right)\cdot P^{|m|}_{l}(cos(\theta))\right|$$ (If $m<0$)

In [10]:
def Y_l_m(l,m,phi,theta):
    """Spherical harmonics"""
    eq = P_l_m(m,l,theta)
    if m>0:
        pe=re(exp(I*m*phi))*sqrt(2)
    elif m<0:
        pe=im(exp(I*m*phi))*sqrt(2)
    elif m==0:
        pe=1
    return abs(sqrt(((2*l+1)*fac(l-Abs(m)))/(4*pi*fac(l+Abs(m))))*pe*eq)

Validation

In [11]:
Y_l_m(0,0,phi,theta)
Out[11]:
$$\frac{1}{2 \sqrt{\pi}}$$
In [12]:
Y_l_m(1,0,phi,theta)
Out[12]:
$$\frac{\sqrt{3}}{2 \sqrt{\pi}} \left\lvert{\cos{\left (\theta \right )}}\right\rvert$$
In [13]:
l=1
m=-1
Y_l_m(l,m,phi,theta)
Out[13]:
$$\frac{\sqrt{3}}{2 \sqrt{\pi}} \left\lvert{\sin{\left (\phi \right )} \sin{\left (\theta \right )}}\right\rvert$$
In [14]:
l=1
m=1
Y_l_m(l,m,phi,theta)
Out[14]:
$$\frac{\sqrt{3}}{2 \sqrt{\pi}} \left\lvert{\sin{\left (\theta \right )} \cos{\left (\phi \right )}}\right\rvert$$
In [15]:
print(Y_l_m(1,1,phi,theta)) #showing the computer's version of symbolic
sqrt(3)*Abs(sin(\theta)*cos(\phi))/(2*sqrt(pi))

Defining L, R, $\psi$, and the probability function P:

$$R(r) = \left(\rho \right)^{3/2} \sqrt{\frac{(n-l-1)!}{2\cdot n\cdot [(n+l)!]^3}}\cdot e^{\frac{-z}{n \cdot a_0}\cdot r} \left(\rho \right)^l \cdot L^{2\cdot l +1}_{n-l-1} \left(\rho \right)$$

$$\text{Where: }$$

$$\hspace{20 mm} L^{2\cdot l +1}_{n-l-1}\left(\rho \right) = \sum_{i=0}^{n-l-1}{\frac{(-i)^i\cdot\left((n+l)!\right)^2 \left( \rho \right)^i} {i!\cdot(n-l-1-i)!\cdot(2\cdot l+1+i)!}}$$

$$\hspace{20 mm} \text{Where: }\rho = \frac{2 \cdot z \cdot r}{n \cdot a_0}$$

In [16]:
def L(l,n,rho):
    """Laguerre polynomial"""
    _L = 0.
    for i in range((n-l-1)+1): #using a loop to do the summation 
        _L += ((-i)**i*fac(n+l)**2.*rho**i)/(fac(i)*fac(n-l-1.-i)*\
                                          fac(2.*l+1.+i))
    return _L

def R(r,n,l,z=1.,a_0=1.):
    """Radial function"""
    rho = 2.*z*r/(n*a_0)
    _L = L(l,n,rho)
    _R = (2.*z/(n*a_0))**(3./2.)*sqrt(fac(n-l-1.)/\
         (2.*n*fac(n+l)**3.))*exp(-z/(n*a_0)*r)*rho**l*_L
    return _R

$$\psi_{nlm}(r,\theta,\phi) = R_{nl}(r)Y_{lm}(\theta,\phi)$$

$$P = \psi^2 r^2$$

In [17]:
def Psi(r,n,l,m,phi,theta,z=1,a_0=1):
    """Wavefunction"""
    _Y = Y_l_m(l,m,phi,theta)
    _R = R(r,n,l)
    return _R*_Y

def P(r,n,l,m,phi,theta):
    """Returns the symbolic equation probability of the location 
    of an electron"""
    return Psi(r,n,l,m,phi,theta)**2*r**2

Orbital Plotting Functions

In [18]:
r_fun = lambda _x,_y,_z: (np.sqrt(_x**2+_y**2+_z**2))
theta_fun = lambda _x,_y,_z: (np.arccos(_z/r_fun(_x,_y,_z)))
phi_fun = lambda _x,_y,_z: (np.arctan(_y/_x)*(1+_z-_z))

def display_orbital(n,l,m_,no_of_contours = 16,Opaque=0.5):
    """Diplays a 3D view of electron orbitals"""
    #The plot density settings (don't mess with unless you are sure)
    rng = 12*n*1.5 #This determines the size of the box 
    _steps = 55j#           (it needs to be bigger with n).
    _x,_y,_z = np.ogrid[-rng:rng:_steps,-rng:rng:_steps,-rng:rng:_steps]
    
    #Plot tweaks
    color = (0,1.0,1.0) #relative RGB color (0-1.0 vs 0-255)
    mlab.figure(bgcolor=color) #set the background color of the plot
    
    
    P_tex = "" #initialize the LaTex string of the probabilities
    
    #Validate the quantum numbers
    assert(n>=1), "n must be greater or equal to 1"       #validate the value of n
    assert(0<=l<=n-1), "l must be between 0 and n-1"      #validate the value of l
    assert(-l<=max(m_)<=l), "p must be between -l and l"  #validate the value of p
    assert(-l<=min(m_)<=l), "p must be between -l and l"  #validate the value of p
    
        
    for m in m_:
        #Determine the probability equation symbolically and convert
        #it to a string
        prob = lambdastr((radius,angle_phi,angle_theta), P(radius,n,l,m,
                                                           angle_phi,
                                                           angle_theta))
        
        #record the probability equation as a LaTex string
        P_eq = simplify(P(r,n,l,m,phi,theta))
        P_tex+="$$P ="+latex(P_eq)+"$$ \n\n " 
        
        
        #print("prob before substitution = \n\n".format(prob)) #for debugging
        
        if '(nan)' in prob: #Check for errors in the equation
            print("There is a problem with the probability function.")
            raise ValueError
        
        #Convert the finctions in the probability equation from the sympy  
        #library to the numpy library to allow for the use of matrix 
        #calculations
        prob = prob.replace('sin','np.sin') #convert to numpy
        prob = prob.replace('cos','np.cos') #convert to numpy
        prob = prob.replace('Abs','np.abs') #convert to numpy
        prob = prob.replace('pi','np.pi')   #convert to numpy
        prob = prob.replace('exp','np.exp') #convert to numpy
        
        #print("prob after substitution = \n\n".format(prob)) #for debugging
        
        #convert the converted string to a callable function
        Prob = eval(prob)
        
        #generate a set of data to plot the contours of.
        w = Prob(r_fun(_x,_y,_z),phi_fun(_x,_y,_z),theta_fun(_x,_y,_z))
        
        #add the generated data to the plot
        mlab.contour3d(w,contours=no_of_contours,opacity = Opaque, transparent=True)
    
    mlab.colorbar()
    mlab.outline()
    mlab.show() #this pops up a interactive window that allows you to 
    #            rotate the view
    

    #Information used for the 2D slices below
    limits = []
    lengths = []
    for cor in (_x,_y,_z):
        limit = (np.min(cor),np.max(cor))
        limits.append(limit)
        #print(np.size(cor))
        lengths.append(np.size(cor))
        #print(limit)
    return (limits, lengths, _x, _y, _z, P_tex)

Displaying the Orbitals:

To display the orbital(s), execute the cell below .

Notes on the quantum numbers:

  • $n$ is greater than or equal to 1
  • $l$ falls within the inclusive range of zero to $n - 1$
  • $m$ is within the range of negative $l$ to positive $l$

(Example values from wikipedia)

In [19]:
#    Set the quantum numbers for the orbitals (all in one spot)
import warnings # in order to suppress divide_by_zero warnings...

print("n must be greater or equal to 1.")
n = int(raw_input("Entry the quantum number for n = "))

print("")
l_str = raw_input("Enter a value for l such that it is less than n (default l=n-1) l = ")
if l_str=="":
    l=n-1
else:
    l = int(l_str)

print("\nThe next value, m, determines which specific orbital will be displayed.")
print("Enter a value(s) for m such that -l <= m <= l.\n"+\
      "m can be a single number for a single orbital or a range for multiple.")
m_str = raw_input("Enter a number like, 2, or a range like, -2, 2.  m = ")
if "," in m_str:
    _m = eval("("+m_str+")")
    m_ = range(_m[0],_m[1]+1,1)
else:
    m_ = [int(m_str)]

print("\nElectron orbitals are traditionally displayed at a 95% confidence interval.  ")
print("The isosurface you would see is at a P value that depends on the principal ")
print("quantum number and the shape of the orbital.  They are analogous to the height ")
print("on a normal probability density function plot.  In displaying this surface ")
print("the mayavi library is picking the confidence interval value for you, however ")
print("some control can be had by setting number of isosuface contours.  ")
print
print("I have found that 16 is a good place to start, however on older computers there ")
print("may be a problem rendering more than 7.")
n_o_c = raw_input("\nEnter the number of isosurface contours to be displayed:[Default = 16] ")
if n_o_c=="":
    n_o_c = 16
else:
    n_o_c = abs(int(n_o_c))
print("\nIn order to see the various probability surfaces you can set the opacity of the ")
print("plot from 0 to 1")
opacity = raw_input("Enter the opacity of the orbital plot:[Range 0 - 1, Default = 0.5] ")
if opacity=="":
    opacity = 0.5
else:
    opacity = abs(float(opacity))
with warnings.catch_warnings():
    warnings.simplefilter("ignore")
    limits, lengths, _x, _y, _z, P_tex = display_orbital(n,l,m_,n_o_c,opacity)

txt = r"$$\textbf{The symbolic expression for the resulting probability equation is:}$$ "
Latex(txt+P_tex)
n must be greater or equal to 1.
Entry the quantum number for n = 3

Enter a value for l such that it is less than n (default l=n-1) l = 2

The next value, m, determines which specific orbital will be displayed.
Enter a value(s) for m such that -l <= m <= l.
m can be a single number for a single orbital or a range for multiple.
Enter a number like, 2, or a range like, -2, 2.  m = 0

Electron orbitals are traditionally displayed at a 95% confidence interval.  
The isosurface you would see is at a P value that depends on the principal 
quantum number and the shape of the orbital.  They are analogous to the height 
on a normal probability density function plot.  In displaying this surface 
the mayavi library is picking the confidence interval value for you, however 
some control can be had by setting number of isosuface contours.  

I have found that 16 is a good place to start, however on older computers there 
may be a problem rendering more than 7.

Enter the number of isosurface contours to be displayed:[Default = 16] 

In order to see the various probability surfaces you can set the opacity of the 
plot from 0 to 1
Enter the opacity of the orbital plot:[Range 0 - 1, Default = 0.5] 

Out[19]:
$$\textbf{The symbolic expression for the resulting probability equation is:}$$ $$P =\frac{120^{1.0} r^{6}}{\pi} 2.11688597605379 \cdot 10^{-7} \left(3 \cos^{2}{\left (\theta \right )} - 1\right)^{2} e^{- 0.666666666666667 r}$$

Probability Equations:

Because the probability equations are solved symbolically we can have the additional benefit of seeing them expressed as equations.

For example when $n=2$, with a little simplification, we get the following probability functions:

When m is entered as -1 we have: $$\hspace{50 mm} P = \frac{r^{4} e^{- r} \sin^{2}{\left (\phi \right )} \sin^{2}{\left (\theta \right )}}{32 \pi}$$

When m is entered as 0 we have: $$\hspace{50 mm} P = \frac{r^{4} e^{- r} \cos^{2}{\left (\theta \right )}}{32 \pi}$$

When m is entered as 1 we have: $$\hspace{50 mm} P = \frac{r^{4} e^{- r} \sin^{2}{\left (\theta \right )} \cos^{2}{\left (\phi \right )}}{32 \pi}$$

Execute the cell below to see the LaTex code for your probability equation.

In [20]:
# Uncomment the line below to see the LaTex probability functions
print(P_tex)
# Latex(P_tex)
$$P =\frac{120^{1.0} r^{6}}{\pi} 2.11688597605379 \cdot 10^{-7} \left(3 \cos^{2}{\left (\theta \right )} - 1\right)^{2} e^{- 0.666666666666667 r}$$ 

 

Phase Information (2D Slices)

The expression of the probability equation - specifically, taking the square of the wavefunction - leads to a loss of the phase information of the wavefunction. As visualized in the animation of a d-orbital (see below), we can examine the phase information in a 2D representation.

Wikipedia

In order to retain the phase information, the mathematical treatment needs to be different, which we elaborate on below.

Run the cell below once to define the functions needed for a 2D slice.

In [21]:
import matplotlib.pyplot as plt

def Y_l_m_signed(l,m,phi,theta):
    """Signed spherical harmonics"""
    eq = P_l_m(m,l,theta)
    if m>0:
        pe=re(exp(I*m*phi))*sqrt(2)
    elif m<0:
        pe=im(exp(I*m*phi))*sqrt(2)
    elif m==0:
        pe=1
    return sqrt(((2*l+1)*fac(l-Abs(m)))/(4*pi*fac(l+Abs(m))))*pe*eq

def Psi_signed(r,n,l,m,phi,theta,z=1,a_0=1):
    """Signed wave function """
    _Y = Y_l_m_signed(l,m,phi,theta)
    _R = R(r,n,l)
    return _R*_Y

def signed_P(r,n,l,m,phi,theta):
    """Returns the symbolic equation probability of the location 
    of an electron with the phase information included"""
    result = Psi_signed(r,n,l,m,phi,theta)**3*r**2/abs(Psi_signed(r,n,l,m,phi,theta))
    return result

def signed_prob(m):
    """Returns a callable signed probability function that can be 
    used to plot 2D slices and probability amplitude vs radius."""
    #Determine the probability equation symbolically and convert
    #it to a string 
        
    ##(THIS SHOULD BE SIGNED [+ OR -])##
        
    prob = lambdastr((radius,angle_phi,angle_theta), signed_P(radius,
                                                              n,l,m,
                                                              angle_phi,
                                                              angle_theta))
        
    #record the probability equation as a LaTex string
    P_eq = simplify(P(r,n,l,m,phi,theta))
        
    #print("prob before substitution = \n\n".format(prob)) #for debugging
        
    if '(nan)' in prob: #Check for errors in the equation
        print("There is a problem with the probability function.")
        raise ValueError
        
    #Convert the finctions in the probability equation from the sympy  
    #library to the numpy library to allow for the use of matrix 
    #calculations
    prob = prob.replace('sin','np.sin') #convert to numpy
    prob = prob.replace('cos','np.cos') #convert to numpy
    prob = prob.replace('Abs','np.abs') #convert to numpy
    prob = prob.replace('pi','np.pi')   #convert to numpy
    prob = prob.replace('exp','np.exp') #convert to numpy
        
    #print("prob after substitution = \n\n{}".format(prob)) #for debugging
        
    #convert the converted string to a callable function
    Prob = eval(prob)
    return Prob

def orbital_plane(plane = "xy"):
    """Returns information values to display the cross sestional view of the orbitals"""
    center = (np.max(length)-1)/2
    if plane not in ['xy','yz','xz']:
        er = "The plane can only be 'xy', 'yz', or 'xz'"
        raise ValueError(er)
    for m in m_:
        Prob = signed_prob(m)
               
        #generate a set of data to plot the contours of.
        w = Prob(r_fun(_x,_y,_z),phi_fun(_x,_y,_z),theta_fun(_x,_y,_z))
        #print(plane)
        if plane=="xy":
            W=w[:,:,center]
        elif plane == "yz":
            W=np.array(w[center,:,:]).transpose()
        elif plane == "xz":
            W=np.array(w[:,center,:]).transpose()
        na = W==np.nan
        W[np.isnan(W)] = 0
    return W, Prob

Display the 2D slices

The phase information of the orbital that was previously rendered in 3D will be displayed be executing the cell below.

In [22]:
from mpl_toolkits.axes_grid1 import make_axes_locatable
from matplotlib import rcParams
%matplotlib inline
rcParams.update({'font.size': 12, 'font.family': 'serif'})
length=max(lengths)
# print(length)
x0,x1,y0,y1 = (-length/2,length/2)*2
def demo():
    #Figure sizes
    fig_x = 12
    fig_y = 24
    
    fig1 = plt.figure(1,figsize=(fig_x,fig_y))
    ax=[]
    for index, plane in enumerate(['xy','yz','xz']):
        ax.append(fig1.add_subplot(3, 1, index+1))
        ax[index].set_title('%s Plane'%plane)
        ax[index].set_xlabel(plane[0])
        ax[index].set_ylabel(plane[1])
        result, Prob = orbital_plane(plane)
        im = ax[index].imshow(result,origin='lower', 
                              extent=[x0,x1,y0,y1])
        
        plt.colorbar(im)
        plt.tight_layout()

    plt.draw()
    plt.show()
    return result

with warnings.catch_warnings():
    warnings.simplefilter("ignore")
    result = demo()

Apendix: Probability Amplitude Plot - Future Work

Adding functions that plot the probability amplitude of orbitals as seen here.

Due to the inexact nature of plotting 3D contours it is useful to look at a plot of the probability amplitude in a simple plot as a function of distance from the center of the atom. However, to do this we should restrict ourselves to a single value m for clarity. We also have to pick a radial direction. This is based on the angle from the positive $x$-axis is in the $xy$ plane, $\phi$ (phi) and the angle from the positive $z$-axis, $\theta$ (theta).

The radial direction will be the direction that the plot is done through the orbital cloud. If the cloud is a s type orbital then the direction will not matter but with other orbitals the direction will make a great deal of difference in what is shown.

Appendix: (Other Examples)

Example of Single Orbital using $n=3$, $l=2$, $m=0$

Variable values used:

n=3
l=2
m = 0
no_of_contours = 16
opacity = 1

Example of Single Orbital using $n=2$, $l=1$, $m=0$

Variable values used:

n=2
l=1
m = 0
no_of_contours = 16
opacity = 1

Example of Single Orbital using $n=4$, $l=3$, $m=-2$

Variable values used:

n=4
l=3
m = -2
no_of_contours = 16
opacity = 1

Example Output using $n=8$, $l=7$, $m= -7 \text{, } 7$

Variable values used:

n=8
l=7
m = -7, 7
no_of_contours = 16
opacity = 1