In this chapter, we describe fundamental elements for calculating and plotting functions in spherical coordinates. For the working examples, we focus on the so-called spherical harmonics polynomials, encountered on diverse physical problems, as is the case with electron orbitals derived from Schrödinger’s differential equation. These polynomials, represented as Yl, m, are characterized by two positive integers, l and m, with |m| ≤ l. The polynomials are functions of the zenithal and azimuthal coordinates, as described in the following sections.
Figure 12-1 shows a screenshot of the program running on an Android cell phone with a plot of ‖Re(Y3, 2)‖2.
To begin, we describe the spherical-coordinate system in the following section.
12.1 Overview of Spherical Coordinates
Figure 12-2 depicts a typical three-dimensional spherical-coordinate system.
In Figure 12-2, the parameters θ and ϕ represent the zenithal or polar and azimuthal angular coordinates, respectively. To generate points over a complete sphere, we need 0 < θ < π and 0 < ϕ < 2π. The r parameter is referred to as the radial distance.
From Figure 12-2, the coordinates of a point can be written as follows:
(12.1)
(12.2)
(12.3)
Equations (12.1)-(12.3) will be used to transform a three-dimensional differential equation from Cartesian coordinates into spherical coordinates.
In the following section, we present our working example, which consists of solving the well-known Schrödinger’s differential equation expressed in spherical coordinates.
12.2 Spherical Differential Equation Working Example
Consider the three-dimensional Schrödinger’s differential equation in Cartesian coordinates:
(12.4)
In Equation (12.4), the ψ(x, y, z) function will represent, in principle, all the possible spatial positions that a charged particle, with charge q and mass M, can take under the influence of a potential distribution function equal to . The term ℏ is the well-known Plank’s constant. The parameter ϵ0 represents vacuum permittivity.
By using Equations (12.1)-(12.3), we can express Equation (12.4) in spherical coordinates, as follows:
(12.5)
Analogous to Equation (12.4), ψ(r, θ, ϕ) in Equation (12.5) gives valid spatial positions expressed in spherical coordinates for a particle with energy E, charge q, and mass M in a hydrogen-like atom with attractive potential equal to q/r.
Equation (12.5) can be solved using the method of separation of variables, proposing a solution of the product of three functions, as follows:
(12.6)
After substituting Equation (12.6) into Equation (12.5), we see that the differential equations that correspond to the functions R(r) and T(θ) are not common ones, and therefore, they must be solved by suitable methods referred to as series expansion methods. Under these conditions, the overall solution corresponds to the product of three differential equations, as follows.
Solutions for R(r) are spherical Bessel functions.
Solutions for T(θ) are associated Legendre polynomials, typically written as Pl, m(cos(θ)).
Solutions for F(ϕ) are exponentials in the form exp(imϕ). The parameter i represents the imaginary unit.
For our example, we will focus on the case r equal to a constant. Therefore, ψ(r, θ, ϕ) becomes a function of only θ and ϕ. This condition will permit us to focus on the orbitals that the electrons follow in hydrogen-like atoms. These orbitals correspond to spherical-harmonics functions, defined as follows:
(12.7)
In Equation (12.7), the square root is a normalization factor.
The atomic orbitals correspond to probabilistic spatial distributions, which analytically correspond to the square of the absolute real value of the harmonics functions, expressed as follows:
(12.8)
To plot Equation (12.8) for a set of different l, m values, we need to calculate Pl, m(θ, ϕ). In the following section, we focus on this subject. For illustrative purposes, the normalization factor will not be considered.
12.3 The Associated Legendre Polynomials
It can be analytically demonstrated that the associated Legendre polynomials can be generated using the following equation:
(12.9)
In Equation (12.9), we used the change of variable, x = cos (θ). The l! expression represents the factorial of the integer l, calculated by the following expression, l!= l(l − 1)(l − 2)⋯(1).
In this case, we will use SymPy to symbolically calculate derivatives and factorials. The code in Listing 12-1 allows us to generate some Legendre polynomials.
import sympy as sp
x=sp.symbols("x");
def Plm(l,m ):
N=( 1/(2**l) )*(1 /sp.factorial(l) )
return N * ( (1-x**2)**(m/2) )
* sp.diff( (x**2-1)**l, x,m+l );
L_Num=3; M_Num=2; #Legendre l,m orders
R=sp.lambdify( x, Plm(L_Num,M_Num) );
Listing 12-1
Generating Legendre polynomials
In this code, x=sp.symbols("x") declares the variable x as symbolic, as described in Chapter 11. As a consequence, as Plm(l,m) function uses this variable for symbolic calculations, the function itself is, in turn a symbolic function.
The R=sp.lambdify( x, Plm(L_Num,M_Num) ) directive assigns to l, the value L_Num and m, the value M_NUM, respectively, 3 and 2 in this example. After executing sp.lambdify, we obtain the numerical function R(x).
At this point, it may be interesting to inspect the analytical expression of some associated Legendre polynomials. Listing 12-2 converts the equations given in the previous code to LaTeX. This expression can be visualized in a text editor.
import sympy as sp
x=sp.symbols("x");
#Associated Legendre l, m orders
L_Num=3; M_Num=3; # l, m orders
def Plm(l,m ):
N=( 1/(2**l) )*(1 /sp.factorial(l) );
return N * ( (1-x**2)**(m/2) )
* sp.diff( (x**2-1)**l, x,m+l );
R= Plm(L_Num,M_Num);
R1=sp.simplify(R);
T=sp.latex(R1); # Convert expression to LaTeX
# Replace symbols by spaces to allow printing the equation.
A=T.replace("\"," ");
# A can be printed in a text editor
Listing 12-2
Code for Visualizing Symbolic Equations with LaTeX
In this code, the following directive:
T=sp.latex(R1),
converts the symbolic expression R into a LaTeX expression T. However, to print the corresponding formulas in a text editor, we remove the symbol \ by using the following code:
A=T.replace("\"," ").
Listing 12-3 gives some associated Legendre polynomials obtained with the code in Listing 12-2. When required, we have replaced rational exponents with their equivalent fractions.
P0, 0(x) = 1;
Listing 12-3
List of Some Associated Legendre Polynomials
As indicated, x = cos (θ). Therefore, we will use NumPy to create a one-dimensional array for θ with entries in the range 0 < θ < π, to substitute x with its corresponding cosine values. Similarly, we will create a second array for ϕ, with values between 0 and 2 π. The corresponding code is shown in Listing 12-4.
import numpy as np
N=40;
Pi=np.pi;
Theta=np.zeros(N+1);
Phi=np.zeros(N+1);
for n in range(0,N+1):
Theta[n]=n/N*Pi;
Phi[n]=n/N*2*Pi;
Listing 12-4
Creating and Filling the Angular Axes
In this code, we generate the arrays Theta and Phi, each with 41 entries. The first array holds numbers ranging from 0 to π in equally spaced steps. The second array stores numbers from 0 to 2π. The first entry, called Theta[0], is equal to 0, while the last entry, Theta[40], is equal to π. In turn, Phi[0] is equal to 0 and Phi[40] is 2π.
To calculate the values of the associated Legendre polynomials, we use the following directive within a for loop, with n ranging from 0 to N:
R( np.cos(Theta[n]) )
The complete code needed to print the corresponding values is shown in Listing 12-5.
import numpy as np
import sympy as sp
x=sp.symbols("x");
def Plm(l,m ):
N=( 1/(2**l) )*(1 /sp.factorial(l) )
return N * ( (1-x**2)**(m/2) )
* sp.diff( (x**2-1)**l, x,m+l );
L_Num=3; M_Num=2; #Legendre l,m orders
R=sp.lambdify( x, Plm(L_Num,M_Num) );
N=40;
Pi=np.pi;
Theta=np.zeros(N+1); Phi=np.zeros(N+1);
for n in range(0,N+1):
Theta[n]=n/N*Pi;
Phi[n]=n/N*2*Pi;
for n in range(0,N+1):
print( R(np.cos(Theta[n])) );
Listing 12-5
Code for Printing Numerical Values of the Symbolic Function
At this point, we are ready to calculate the values corresponding to Equation (12.9). However, we may wonder if SymPy has a built-in function for the associated Legendre polynomials. The answer is yes. Therefore, instead of the code listed previously, we could use this:
Plm=sp.assoc_legendre(L_Num,M_Num, x);
R=sp.lambdify(x,Plm);
We can verify that both methods give the same results by comparing some values.
We are now in the position of plotting some spherical harmonics functions. In the following section, we describe our method for plotting the orbitals given in Equation (12.8).
12.4 Plotting Functions in Spherical Coordinates
The program generates the associated Legendre polynomials using the code shown in Listing 12-1, which creates the numerical function R(x) to provide the associated Legendre polynomials, Pl, m(x).
To create the 3D perspective, as in previous chapters, we use the required perspective parameters with the following values:
D=4000;
VX=600; VY=120; VZ=0;
Factor0 =(D-VZ) / (D/2-VZ);
The Factor0 variable is used to shift the plot to the center of the screen.
To calculate the spherical harmonics as functions of θ and ϕ, we use the code in Listing 12-6.
N=40;
Pi=np.pi;
Theta=np.zeros(N+1); Phi=np.zeros(N+1);
for n in range(0,N+1):
Theta[n]=n/N*Pi;
Phi[n]=n/N*2*Pi;
Listing 12-6
Creating the Angular Arrays
The code creates two arrays, each with 41 entries for the angular variables with 0 < θ < π and 0 < ϕ < 2π.
Now, we proceed to calculate the orbitals given by Equation (12.8) with the code in Listing 12-7.
import numpy as np
import sympy as sp
x=sp.symbols("x");
def Plm(l,m ):
N=( 1/(2**l) )*(1 /sp.factorial(l) )
return N * ( (1-x**2)**(m/2) )
* sp.diff( (x**2-1)**l, x,m+l );
L_Num=3; M_Num=2; #Legendre l,m orders
R=sp.lambdify( x, Plm(L_Num,M_Num) );
N=40;
Pi=np.pi;
Theta=np.zeros(N+1); Phi=np.zeros(N+1);
for n in range(0,N+1):
Theta[n]=n/N*Pi;
Phi[n]=n/N*2*Pi;
F=np.zeros( (N+1,N+1) );
for n in range(0,N+1):
for m in range(0,N+1):
F[n][m]=np.abs( R(np.cos(Theta[n]))
*np.cos(M_Num*Phi[m]) )**2;
print(F);
Listing 12-7
Calculating Orbitals
In this code, the F=np.zeros( (N+1,N+1) ) directive creates the variable F, representing a two-dimensional (N+1)*(N+1) array to store the absolute square real values of Yl. m(θ, ϕ).
To plot F, we create the mesh arrays with appropriate dimensions using the following code.
x1=np.zeros( (N+1,N+1) );
y=np.zeros( (N+1,N+1) );
z1=np.zeros( (N+1,N+1) );
Now, we will use Equations (12.1)-(12.3) to create the plot. In these equations, the radial distance r is considered constant.
We have to assign the appropriate role to each variable, as described in previous chapters. The vertical height corresponds to the y-axis, the horizontal length corresponds to the x-axis, and the depth to the z-axis. The code in Listing 12-8 assigns the corresponding parameters appropriately.
x1=np.zeros( (N+1,N+1) );
y=np.zeros( (N+1,N+1) );
z1=np.zeros( (N+1,N+1) );
L=2.5;
for n in range(0,N+1):
for m in range(0,N+1):
x1[n][m]=L*F[n][m]*np.sin(Theta[n])
*np.cos(Phi[m]);
z1[n][m]=L*F [n][m]*np.sin(Theta[n])
*np.sin(Phi[m])+D/2;
y[n][m]=L*F[n][m]*np.cos(Theta[n]);
Listing 12-8
Filling Mesh and Vertical Coordinates
In this code, we included a scale factor L for enlarging or shrinking the plot as required. Finally, GraphFunction(B) and RotateFunction(B, Sense) are in charge of plotting and rotating the function, similarly to the previous chapters.
The code for main.py and File.kv is shown in Listings 12-9 and 12-10, respectively.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.graphics import Line, Color
from kivy.clock import Clock
import os
import numpy as np
import sympy as sp
from kivy.lang import Builder
Builder.load_file(
os.path.join(os.path.dirname(os.path.abspath(
__file__)), 'File.kv')
);
#Avoid Form1 of being resizable
from kivy.config import Config
Config.set("graphics","resizable", False)
Config.set('graphics', 'width', '480');
Config.set('graphics', 'height', '680');
x=sp.symbols("x");
def Plm(l,m ):
N=( 1/(2**l) )*(1 /sp.factorial(l) )
return N * ( (1-x**2)**(m/2) )
* sp.diff( (x**2-1)**l, x,m+l );
L_Num=3; M_Num=2; #Legendre l,m orders
R=sp.lambdify( x, Plm(L_Num,M_Num) );
D=4000;
VX=600; VY=1200; VZ=0;
Factor0 =(D-VZ) / (D/2-VZ);
N=40;
Pi=np.pi;
Theta=np.zeros(N+1); Phi=np.zeros(N+1);
for n in range(0,N+1):
Theta[n]=n/N*Pi;
Phi[n]=n/N*2*Pi;
M_Num=2; N_Num=3; #Legendre m,n orders
F=np.zeros( (N+1,N+1) );
for n in range(0,N+1):
for m in range(0,N+1):
F[n,m]=np.abs( R(np.cos(Theta[n]))
*np.cos(M_Num*Phi[m]) )**2;
x1=np.zeros( (N+1,N+1) );
y=np.zeros( (N+1,N+1) );
z1=np.zeros( (N+1,N+1) );
L=2.4;
for n in range(0,N+1):
for m in range(0,N+1):
x1[n][m]=L*F[n][m]*np.sin(Theta[n])
*np.cos(Phi[m]);
z1[n][m]=L*F [n][m]*np.sin(Theta[n])
*np.sin(Phi[m])+D/2;
y[n][m]=L*F[n][m]*np.cos(Theta[n]);
#Array to store list of points
PointList=np.zeros( (N+1,2) );
def GraphFunction(B):
global x1,y, z1, N, D, VX, VY, VZ;
B.ids.Screen1.canvas.clear(); #Clear the screen
#Choose color to draw
B.ids.Screen1.canvas.add( Color(1,0,0) );
for n in range (0,N+1): #Draw horizontal lines
for m in range (0,N+1):
Factor=(D-VZ)/(D-z1[n][m]-VZ);
xA=XC+Factor*(x1[n][m]-VX)+Factor0*VX;
yA=YC+Factor*(y[n][m]-VY)+Factor0*VY;
PointList[m]=xA,yA;
B.ids.Screen1.canvas.add( Line(points=
PointList.tolist(), width=1.3));
for n in range (0,N+1): #Drawing vertical lines
for m in range (0,N+1, 1):
Factor=(D-VZ)/(D-z1[m][n]-VZ);
xA=XC+Factor*(x1[m][n]-VX)+Factor0*VX;
yA=YC+Factor*(y[m][n]-VY)+Factor0*VY;
PointList[m]=xA,yA;
B.ids.Screen1.canvas.add( Line(points=
PointList.tolist(), width=1.3));
def RotateFunction(B, Sense):
global x1, y, z1, D, N;
if Sense==-1:
Theta=np.pi/180*(-4.0);
else:
Theta=np.pi/180*(4.0);
Cos_Theta=np.cos(Theta)
Sin_Theta=np.sin(Theta);
X0=0; Y0=0; Z0=D/2 # Center of rotation
for n in range(0,N+1):
for m in range(0,N+1):
if (B.ids.Button3.state=="down" or
B.ids.Button4.state=="down"):
yP=(y[n][m]-Y0)*Cos_Theta
+ (x1[n][m]-X0)*Sin_Theta + Y0;
xP=-(y[n][m]-Y0)*Sin_Theta
+(x1[n][m]-X0)*Cos_Theta + X0;
y[n][m]=yP;
x1[n][m]=xP;
if (B.ids.Button5.state=="down" or
B.ids.Button6.state=="down"):
yP=(y[n][m]-Y0)*Cos_Theta
+ (z1[n][m]-Z0)*Sin_Theta + Y0;
zP=-(y[n][m]-Y0)*Sin_Theta
+(z1[n][m]-Z0)*Cos_Theta + Z0;
y[n][m]=yP;
z1[n][m]=zP;
if (B.ids.Button7.state=="down" or
B.ids.Button8.state=="down"):
xP=(x1[n][m]-X0)*Cos_Theta
+ (z1[n][m]-Z0)*Sin_Theta + X0;
zP=-(x1[n][m]-X0)*Sin_Theta
+(z1[n][m]-Z0)*Cos_Theta + Z0;
x1[n][m]=xP;
z1[n][m]=zP;
class Form1(FloatLayout):
def __init__(Handle, **kwargs):
super(Form1, Handle).__init__(**kwargs);
Event1=Clock.schedule_once(Handle.Initialize);
def Initialize(B, *args):
global W,H, XC,YC;
W,H=B.ids.Screen1.size;
XI,YI=B.ids.Screen1.pos
XC=XI+int (W/2);
YC=YI+int(H/2);
def Button1_Click(B):
GraphFunction(B);
def Button2_Click(B):
B.ids.Screen1.canvas.clear();
def Button3_Click(B):
RotateFunction(B,1),
GraphFunction(B);
def Button4_Click(B):
RotateFunction(B,-1),
GraphFunction(B);
def Button5_Click(B):
RotateFunction(B,-1),
GraphFunction(B);
def Button6_Click(B):
RotateFunction(B,1),
GraphFunction(B);
def Button7_Click(B):
RotateFunction(B,-1),
GraphFunction(B);
def Button8_Click(B):
RotateFunction(B,1),
GraphFunction(B);
# This is the Start Up code.
class StartUp (App):
def build (BU):
BU.title="Form1"
return Form1();
if __name__ =="__main__":
StartUp().run();
Listing 12-9
Code for the main.py File
#:set W 440
#:set H 440
<Form1>:
id : Form1
StencilView:
id: Screen1
size_hint: None,None
pos_hint: {"x":0.04, "y":0.34}
size: W,H
canvas.before:
Color:
rgba: 0.9, 0.9, 0, 1
RoundedRectangle:
pos: self.pos
size: self.size
Button:
id: Button1
on_press: Form1.Button1_Click()
text: "Button1"
size_hint: None,None
pos_hint: {"x": 0.2, "y":0.03}
size: 100,30
Button:
id: Button2
on_press: Form1.Button2_Click()
text: "Button2"
size_hint: None,None
pos_hint: {"x": 0.63, "y":0.03}
size: 100,30
Button:
id: Button3
on_press: Form1.Button3_Click()
text: "Button3"
size_hint: None,None
pos_hint: {"x": 0.05, "y":0.12}
size: 100,30
always_release: True
Button:
id: Button4
on_press: Form1.Button4_Click()
text: "Button4"
size_hint: None,None
pos_hint: {"x": 0.73, "y":0.12}
size: 100,30
Button:
id: Button5
on_press: Form1.Button5_Click()
text: "Button5"
size_hint: None,None
pos_hint: {"x": 0.05, "y":0.20}
size: 100,30
Button:
id: Button6
on_press: Form1.Button6_Click()
text: "Button6"
size_hint: None,None
pos_hint: {"x": 0.73, "y":0.20}
size: 100,30
Button:
id: Button7
on_press: Form1.Button7_Click()
text: "Button7"
size_hint: None,None
pos_hint: {"x": 0.05, "y":0.28}
size: 100,30
Button:
id: Button8
on_press: Form1.Button8_Click()
text: "Button8"
size_hint: None,None
pos_hint: {"x": 0.73, "y":0.28}
size: 100,30
Listing 12-10
Code for the file.kv File
Using this code, we generated the plots of |Pl, m(cos(θ)) cos (mϕ)|2 shown in Figure 12-3.
12.5 Summary
In this chapter, we introduced elements for plotting functions in spherical coordinates. We introduced analytical concepts on three-dimensional spherical coordinates and illustrated the programming methods with working examples of spherical harmonics polynomials.