Difference between revisions of "Mod:Hunt Research Group/plot potenital energy surface"

From wiki
Jump to navigation Jump to search
 
Line 2: Line 2:
 
[[File:emim_PES_ethyl_rot.png|600px|thumb|right|Example PES produced using plot_PES.py]]
 
[[File:emim_PES_ethyl_rot.png|600px|thumb|right|Example PES produced using plot_PES.py]]
  
=Extracting Scan Data From GaussView=
+
UPDATED 15/08/25
Currently this script requires you first extract the data from your scans from GaussView.
+
 
For each calculation making up your overall scan:
+
This script plots a 1D potential energy surface directly from a .log file
* In GaussView open your .log file
 
* Go to Results then Scan to open the Gaussian formatted PES
 
* Right click on the plot and select Save Data...
 
* Save your data as a .txt file
 
  
 
=Running plot_PES.py=
 
=Running plot_PES.py=
This script is broken into 2 files. The first is a customisation file that you can edit to change the title, x axis label, plot colour, and save location of the produced figure. You can find a list of colours and their names here: [https://matplotlib.org/stable/gallery/color/named_colors.html colours]
+
This script is broken into 2 files. The first is a customisation file that you can edit to change the title, x axis label, plot colour, and save location of the produced figure. You can find a list of colours and their names here: [https://matplotlib.org/stable/gallery/color/named_colors.html link]
  
 
* Copy the below code into a script called "PES_custom.py"
 
* Copy the below code into a script called "PES_custom.py"
 
<pre>
 
<pre>
 
x_label = "Dihedral Angle (degrees)"
 
x_label = "Dihedral Angle (degrees)"
plot_title = "Potenital Energy Scan over Rotation of Imidazolium Ethyl Group"
+
plot_title = "Rotation of Imidazolium Ethyl Group"
 
plot_colour = "black"
 
plot_colour = "black"
 
centre_on_zero = True
 
centre_on_zero = True
Line 26: Line 22:
  
 
* Copy the below code into a script called "plot_PES.py"
 
* Copy the below code into a script called "plot_PES.py"
* To execute type: python plot_PES.py scan_type input_filename_1 input_filename_2
+
* To execute type: python plot_PES.py starting_coordinate input_filename_1 input_filename_2
 +
* where you list all of the files that make up your full surface
 
* Depending on your customisation file, this will either display a figure with your PES plot or save the plot under the file name you gave in PES_custom.py
 
* Depending on your customisation file, this will either display a figure with your PES plot or save the plot under the file name you gave in PES_custom.py
 +
 +
You can find an example .log file here: [https://sagacioushours.org.uk/wiki/index.php/File:Emim_PES_full.log link] (starting dihedral angle = 105 degrees)
  
 
IMPORTANT NOTES:
 
IMPORTANT NOTES:
* Replace scan_type with the type of scan that you did (e.g bond_angle, bond_length, dihedral_angle)
+
* Starting coordinate is the initial bond length/angle/dihedral angle you started the scan from
* Your input files .txt files you saved from Gaussian
+
* Your input files are your .log files from your Gaussian scan
 
* You can list as many input files as you need, just please omit the file extensions
 
* You can list as many input files as you need, just please omit the file extensions
* You can choose whether or not to save your figure as a .png automatically by setting save_fig = True in PES_custom.py
+
* You can choose whether or not to save your figure as a .png automatically by setting 'save_fig = True' in PES_custom.py
 
* If you do not automatically save your figure, the script will only stop running when you close the figure
 
* If you do not automatically save your figure, the script will only stop running when you close the figure
 
* Any issues flick me an email at laura.doyle@vuw.ac.nz :)
 
* Any issues flick me an email at laura.doyle@vuw.ac.nz :)
  
 
<pre>
 
<pre>
 +
# import required modules and libraries
 
import sys
 
import sys
 
import os
 
import os
Line 45: Line 45:
 
import PES_custom as pes
 
import PES_custom as pes
  
 +
def center_angles(angle):
 +
    # Adjust scan angles to be between -180 and 180 degrees
 +
    centered = ((angle + 180) % 360) - 180
 +
    return centered
 +
 +
# process the input from the user
 
dir = os.getcwd()
 
dir = os.getcwd()
 
supported_scans = ['bond_length', 'bond_angle', 'dihedral_angle']
 
  
 
if len(sys.argv) < 3:
 
if len(sys.argv) < 3:
     print("To run the script please type: python PES_plotter.py scan_type input_file_name_1 input_file_name_2",  
+
    # checks to see that the user has supplied enough arguments when calling the script
         " \( and so on for as many .txt files as you have)")
+
     print("To run the script please type: python PES_plotter.py starting_coordinate input_file_name_1 input_file_name_2",  
 +
         " \( and so on for as many .log files as you have)")
 
     sys.exit()
 
     sys.exit()
 
      
 
      
else:
+
data_files = []
     if str(sys.argv[1]) not in supported_scans:
+
for i in range(len(sys.argv)-2):
        print("WARNING: This scan type may not be supported, please double check results.",
+
     data_files.append(str(sys.argv[i+2])+'.log')
              " Supported scan types are:", *supported_scans, sep="\n")
+
 
 +
 
 +
# setting up
 +
PES_data = pd.DataFrame(columns=['Coordinates','Energy'])
 +
starting_coord = float(sys.argv[1])
 +
supported_scans = {'B':'bond_length', 'A':'bond_angle', 'D':'dihedral_angle'}
 +
angle_scans = ["A", "D"]
 +
input_nextline = False
  
    data_files = []
 
    for i in range(len(sys.argv)-2):
 
        data_files.append(str(sys.argv[i+2])+'.txt')
 
   
 
scan_type = sys.argv[1]
 
  
 +
# defining search strings
 +
input_str = "The following ModRedundant input section has been read:"
 +
step_no_str = "Step number"
 +
energy_str = "SCF Done:"
  
def center_angles(angle):
 
    # Adjust scan angles to be between -180 and 180 degrees
 
    centered = ((angle + 180) % 360) - 180
 
    return centered
 
  
# Grab the data from the scan data files and combine into a single dataframe
 
PES_data = pd.DataFrame()
 
 
for data_file in data_files:
 
for data_file in data_files:
     df = pd.read_csv(data_file,sep='\s+',skiprows=4,names=["Angle", "Energy"])
+
     print("Gathering data from", data_file)
    PES_data = pd.concat([PES_data, df], ignore_index=True)
+
    file = open(data_file,'r')
 +
    line = file.readline()
 +
    energys = np.array([])
 +
    iterations = np.array([], dtype=int)
 +
    step_numbers = np.array([], dtype=int)
 +
   
 +
    # Steps through each line in the file, checking if any search strings appear
 +
    while line:
 +
        if input_nextline:
 +
            # Gathers baseline information about the PES
 +
            temp = line.rstrip().split()
 +
            scan_type = temp[0]
 +
            no_steps = int(float(temp[-2]))
 +
            no_structures = no_steps + 1
 +
            step_size = float(temp[-1])
 +
            input_nextline = False
 +
           
 +
       
 +
            if scan_type not in supported_scans:
 +
                print("WARNING: This scan type may not be supported, please double check results.",
 +
                      " Supported scan types are:", *supported_scans, sep="\n")
 +
       
 +
        if input_str in line:
 +
            input_nextline = True
 +
           
 +
        if step_no_str in line:
 +
            # Identifies which scan and optimisation step we are up to
 +
            temp = line.rstrip().split()
 +
            iterations = np.append(iterations,int(float(temp[2])))
 +
            step_numbers = np.append(step_numbers,int(float(temp[-4])))
 +
           
 +
        if energy_str in line:
 +
            # Records the energy of current step
 +
            temp = line.rstrip().split()
 +
            energys = np.append(energys,float(temp[4]))
 +
       
 +
        # Moves to the next line
 +
        line = file.readline()
 +
   
 +
    print(no_structures, "structures identified, recording optimised structure energies")
 
      
 
      
 +
    # Selects the final structure for each scan step and records its energy and x-coordinate
 +
    for i in range(no_structures):
 +
        coord = starting_coord - i * step_size
 +
        mask = step_numbers != i + 1
 +
        step_iterations = iterations.copy()
 +
        step_iterations[mask] = 0
 +
        energy_index = list(step_iterations).index(np.max(step_iterations))
 +
        eng = energys[energy_index]
 +
        df = pd.DataFrame([[coord, eng]], columns=['Coordinates','Energy'])
 +
        PES_data = pd.concat([PES_data, df], ignore_index=True)
 +
    print("Structure energies identified")
 +
       
 +
       
 
# If True, adjusts the scan angles to be centered about 0     
 
# If True, adjusts the scan angles to be centered about 0     
if pes.centre_on_zero and "angle" in scan_type: PES_data["Angle"] = PES_data["Angle"].apply(center_angles)
+
if pes.centre_on_zero and scan_type in angle_scans:
 +
    print("Adjusting angles to range between -180 degrees and 180 degrees")
 +
    PES_data["Coordinates"] = PES_data["Coordinates"].apply(center_angles)
 +
   
  
 
# Sorts the data by scanning variable
 
# Sorts the data by scanning variable
sorted_PES_data = PES_data.sort_values(by="Angle")
+
sorted_PES_data = PES_data.sort_values(by="Coordinates")
  
 
# Converts Energy from a.u. to kJ/mol and zeroes the energy to the minimum
 
# Converts Energy from a.u. to kJ/mol and zeroes the energy to the minimum
 +
print("Converting energy from a.u. to ΔkJ/mol")
 
energies_kj = sorted_PES_data["Energy"] * 2625
 
energies_kj = sorted_PES_data["Energy"] * 2625
 
zeroed_energies_kj = energies_kj - np.min(energies_kj)
 
zeroed_energies_kj = energies_kj - np.min(energies_kj)
Line 89: Line 150:
  
 
# Plots the potential energy surface
 
# Plots the potential energy surface
 +
print("Plotting PES...")
 
ax = plt.axes()     
 
ax = plt.axes()     
ax.plot(sorted_PES_data["Angle"], zeroed_energies_kj, c=pes.plot_colour, marker="o", linewidth=0.75)
+
ax.plot(sorted_PES_data["Coordinates"], zeroed_energies_kj, c=pes.plot_colour, marker="o", linewidth=0.75)
 
ax.set_xlabel(pes.x_label)
 
ax.set_xlabel(pes.x_label)
ax.set_ylabel("Relative Energy (kJ/mol)")
+
ax.set_ylabel("Energy (kJ/mol)")
 
ax.set_title(pes.plot_title)
 
ax.set_title(pes.plot_title)
  
 +
# Saves the PES as an image if user has specified, otherwise plots the figure.
 
if pes.save_fig:
 
if pes.save_fig:
 +
    print("Plotted PES saved as", pes.fig_filename)
 
     plt.savefig(pes.fig_filename)
 
     plt.savefig(pes.fig_filename)
 
else: plt.show()
 
else: plt.show()

Latest revision as of 03:52, 15 August 2025

Example PES produced using plot_PES.py

UPDATED 15/08/25

This script plots a 1D potential energy surface directly from a .log file

Running plot_PES.py

This script is broken into 2 files. The first is a customisation file that you can edit to change the title, x axis label, plot colour, and save location of the produced figure. You can find a list of colours and their names here: link

  • Copy the below code into a script called "PES_custom.py"
x_label = "Dihedral Angle (degrees)"
plot_title = "Rotation of Imidazolium Ethyl Group"
plot_colour = "black"
centre_on_zero = True
save_fig = False
fig_filename = "test_save.png"

The second file is the main script you will call. This needs to be in the same place as the customisation file you just created.

  • Copy the below code into a script called "plot_PES.py"
  • To execute type: python plot_PES.py starting_coordinate input_filename_1 input_filename_2
  • where you list all of the files that make up your full surface
  • Depending on your customisation file, this will either display a figure with your PES plot or save the plot under the file name you gave in PES_custom.py

You can find an example .log file here: link (starting dihedral angle = 105 degrees)

IMPORTANT NOTES:

  • Starting coordinate is the initial bond length/angle/dihedral angle you started the scan from
  • Your input files are your .log files from your Gaussian scan
  • You can list as many input files as you need, just please omit the file extensions
  • You can choose whether or not to save your figure as a .png automatically by setting 'save_fig = True' in PES_custom.py
  • If you do not automatically save your figure, the script will only stop running when you close the figure
  • Any issues flick me an email at laura.doyle@vuw.ac.nz :)
# import required modules and libraries
import sys
import os
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import PES_custom as pes

def center_angles(angle):
    # Adjust scan angles to be between -180 and 180 degrees
    centered = ((angle + 180) % 360) - 180
    return centered

# process the input from the user
dir = os.getcwd()

if len(sys.argv) < 3:
    # checks to see that the user has supplied enough arguments when calling the script
    print("To run the script please type: python PES_plotter.py starting_coordinate input_file_name_1 input_file_name_2", 
         " \( and so on for as many .log files as you have)")
    sys.exit()
    
data_files = []
for i in range(len(sys.argv)-2):
    data_files.append(str(sys.argv[i+2])+'.log')


# setting up 
PES_data = pd.DataFrame(columns=['Coordinates','Energy'])
starting_coord = float(sys.argv[1])
supported_scans = {'B':'bond_length', 'A':'bond_angle', 'D':'dihedral_angle'}
angle_scans = ["A", "D"]
input_nextline = False


# defining search strings
input_str = "The following ModRedundant input section has been read:"
step_no_str = "Step number"
energy_str = "SCF Done:"


for data_file in data_files:
    print("Gathering data from", data_file)
    file = open(data_file,'r')
    line = file.readline()
    energys = np.array([])
    iterations = np.array([], dtype=int)
    step_numbers = np.array([], dtype=int)
    
    # Steps through each line in the file, checking if any search strings appear
    while line:
        if input_nextline:
            # Gathers baseline information about the PES
             temp = line.rstrip().split()
             scan_type = temp[0]
             no_steps = int(float(temp[-2]))
             no_structures = no_steps + 1
             step_size = float(temp[-1])
             input_nextline = False
             
        
             if scan_type not in supported_scans:
                 print("WARNING: This scan type may not be supported, please double check results.",
                       " Supported scan types are:", *supported_scans, sep="\n")
        
        if input_str in line:
             input_nextline = True
             
        if step_no_str in line:
            # Identifies which scan and optimisation step we are up to
            temp = line.rstrip().split()
            iterations = np.append(iterations,int(float(temp[2])))
            step_numbers = np.append(step_numbers,int(float(temp[-4])))
            
        if energy_str in line:
            # Records the energy of current step
            temp = line.rstrip().split()
            energys = np.append(energys,float(temp[4]))
        
        # Moves to the next line
        line = file.readline()
    
    print(no_structures, "structures identified, recording optimised structure energies")
    
    # Selects the final structure for each scan step and records its energy and x-coordinate
    for i in range(no_structures):
        coord = starting_coord - i * step_size
        mask = step_numbers != i + 1
        step_iterations = iterations.copy()
        step_iterations[mask] = 0
        energy_index = list(step_iterations).index(np.max(step_iterations))
        eng = energys[energy_index]
        df = pd.DataFrame([[coord, eng]], columns=['Coordinates','Energy'])
        PES_data = pd.concat([PES_data, df], ignore_index=True)
    print("Structure energies identified")
        
        
# If True, adjusts the scan angles to be centered about 0    
if pes.centre_on_zero and scan_type in angle_scans: 
    print("Adjusting angles to range between -180 degrees and 180 degrees")
    PES_data["Coordinates"] = PES_data["Coordinates"].apply(center_angles)
    

# Sorts the data by scanning variable
sorted_PES_data = PES_data.sort_values(by="Coordinates")

# Converts Energy from a.u. to kJ/mol and zeroes the energy to the minimum
print("Converting energy from a.u. to ΔkJ/mol")
energies_kj = sorted_PES_data["Energy"] * 2625
zeroed_energies_kj = energies_kj - np.min(energies_kj)


# Plots the potential energy surface
print("Plotting PES...")
ax = plt.axes()    
ax.plot(sorted_PES_data["Coordinates"], zeroed_energies_kj, c=pes.plot_colour, marker="o", linewidth=0.75)
ax.set_xlabel(pes.x_label)
ax.set_ylabel("Energy (kJ/mol)")
ax.set_title(pes.plot_title)

# Saves the PES as an image if user has specified, otherwise plots the figure.
if pes.save_fig:
    print("Plotted PES saved as", pes.fig_filename)
    plt.savefig(pes.fig_filename)
else: plt.show()


sys.exit()