├── DIAS_analysis.py ├── DIAS_inputparser.py ├── DIAS_reduce_reorder.py ├── LICENSE ├── README.md ├── autoDIAS.py ├── input_example.inp ├── old └── test_autofrag.py ├── plotting_examples ├── 2D_print.py └── IRC_print.py └── requirements.txt /DIAS_analysis.py: -------------------------------------------------------------------------------- 1 | radian2degree= 57.2958 2 | hartree2kcal = 627.509 3 | 4 | import numpy as np 5 | import os, time 6 | 7 | def getSCF(file): 8 | SCF = getmultiSCF(file) 9 | 10 | return SCF 11 | 12 | def getmultiSCF(g09out): 13 | out = open(g09out, "r") 14 | SCFenergy = 0.00 15 | out.seek(0) #returns to top of .log file 16 | out = reversed(list(out)) # reverse list to read from back to find last SCF optimization 17 | for line in out: 18 | if "SCF Done" in line: # Gaussian 19 | SCFenergy = line.split()[4] 20 | break 21 | if "Energy=" in line: # Gaussian Force Field 22 | SCFenergy = line.split()[3] 23 | break 24 | elif "FINAL SINGLE POINT ENERGY" in line: # Orca 25 | SCFenergy = line.split()[4] 26 | break 27 | elif "Total energy in the final basis set" in line: # Qchem 28 | SCFenergy = line.split()[8] 29 | break 30 | elif "Total DFT energy" in line: # NWChem, but only for DFT 31 | SCFenergy = line.split()[4] 32 | break 33 | #check if it is indeed a float 34 | try: 35 | SCFenergy = float(SCFenergy) 36 | pass 37 | except ValueError: 38 | SCFenergy = 0.00 39 | return SCFenergy 40 | 41 | def getDistance(A, B): 42 | distance = np.sqrt( (A[0]-B[0])**2 + (A[1]-B[1])**2 + (A[2]-B[2])**2) 43 | return distance 44 | 45 | # get angle between three atoms A, B and C, takes numpy arrays for coordinates 46 | def getAngle(A, B, C): 47 | angle = 0.00 48 | #get unit vectors of vector BA and BC 49 | BA_u = (A - B) / np.linalg.norm(A-B) 50 | BC_u = (C - B) / np.linalg.norm(C-B) 51 | #calculate angle 52 | angle = np.arccos(np.clip(np.dot(BA_u, BC_u), -1, 1)) 53 | angle = angle * radian2degree 54 | return angle 55 | 56 | def getDihedral(p0, p1, p2, p3): 57 | b0 = -1.0*(p1 - p0) 58 | b1 = p2 - p1 59 | b2 = p3 - p2 60 | b1 /= np.linalg.norm(b1) 61 | v = b0 - np.dot(b0, b1)*b1 62 | w = b2 - np.dot(b2, b1)*b1 63 | x = np.dot(v, w) 64 | y = np.dot(np.cross(b1, v), w) 65 | return np.degrees(np.arctan2(y, x)) 66 | 67 | def create_analysis_file(settings): 68 | 69 | analysis_file = open(settings.name+'_DIAS.txt', 'w') 70 | analysis_file.write('{0:<15}'.format('Step')) 71 | 72 | try: 73 | for x in range(0, len(settings.geo_dist)): 74 | analysis_file.write('{0:<18}'.format('Distance between')) 75 | except NameError: 76 | pass 77 | 78 | try: 79 | for x in range(0, len(settings.geo_ang)): 80 | analysis_file.write('{0:<18}'.format('Angle between')) 81 | except NameError: 82 | pass 83 | 84 | try: 85 | for x in range(0, len(settings.geo_dih)): 86 | analysis_file.write('{0:<18}'.format('Dihedral between')) 87 | except NameError: 88 | pass 89 | 90 | 91 | analysis_file.write('{0:<15}'.format('Total energy')) 92 | analysis_file.write('{0:<15}'.format('E(Int)')) 93 | analysis_file.write('{0:<15}'.format('E(Dist)')) 94 | analysis_file.write('{0:<15}'.format('E(Dist)')) 95 | analysis_file.write('{0:<15}'.format('E(Dist)')) 96 | analysis_file.write('{0:<20}'.format('E(SCF)')) 97 | analysis_file.write('{0:<20}'.format('E(SCF)')) 98 | analysis_file.write('{0:<20}'.format('E(SCF)')) 99 | analysis_file.write('\n') 100 | 101 | 102 | analysis_file.write('{0:<15}'.format('')) 103 | try: 104 | for x in range(0, len(settings.geo_dist)): 105 | analysis_file.write('{0:<18}'.format(str(settings.geo_dist[x][0])+'-'+str(settings.geo_dist[x][1]))) 106 | except NameError: 107 | pass 108 | try: 109 | for x in range(0, len(settings.geo_ang)): 110 | analysis_file.write('{0:<18}'.format(settings.geo_ang[x][0]+'-'+settings.geo_ang[x][1]+'-'+ settings.geo_ang[x][2])) 111 | except NameError: 112 | pass 113 | try: 114 | for x in range(0, len(settings.geo_dih)): 115 | analysis_file.write('{0:<18}'.format(settings.geo_dih[x][0]+'-'+settings.geo_dih[x][1]+'-'+ settings.geo_dih[x][2]+'-'+ settings.geo_dih[x][3])) 116 | except NameError: 117 | pass 118 | 119 | analysis_file.write('{0:<15}'.format('')) 120 | analysis_file.write('{0:<15}'.format('')) 121 | analysis_file.write('{0:<15}'.format('Total')) 122 | analysis_file.write('{0:<15}'.format(settings.frag1name)) 123 | analysis_file.write('{0:<15}'.format(settings.frag2name)) 124 | analysis_file.write('{0:<20}'.format('complex')) 125 | analysis_file.write('{0:<20}'.format(settings.frag1name)) 126 | analysis_file.write('{0:<20}'.format(settings.frag2name)) 127 | analysis_file.write('\n') 128 | 129 | analysis_file.write('{0:<15}'.format('')) 130 | try: 131 | for x in range(0, len(settings.geo_dist)): 132 | analysis_file.write('{0:<18}'.format('angstrom')) 133 | except NameError: 134 | pass 135 | try: 136 | for x in range(0, len(settings.geo_ang)): 137 | analysis_file.write('{0:<18}'.format('degree')) 138 | except NameError: 139 | pass 140 | try: 141 | for x in range(0, len(settings.geo_dih)): 142 | analysis_file.write('{0:<18}'.format('degree')) 143 | except NameError: 144 | pass 145 | 146 | analysis_file.write('{0:<15}'.format('kcal/mol')) 147 | analysis_file.write('{0:<15}'.format('kcal/mol')) 148 | analysis_file.write('{0:<15}'.format('kcal/mol')) 149 | analysis_file.write('{0:<15}'.format('kcal/mol')) 150 | analysis_file.write('{0:<15}'.format('kcal/mol')) 151 | analysis_file.write('{0:<20}'.format('hartree')) 152 | analysis_file.write('{0:<20}'.format('hartree')) 153 | analysis_file.write('{0:<20}'.format('hartree')) 154 | analysis_file.write('\n') 155 | try: 156 | for x in range(0, len(settings.geo_dist)): 157 | analysis_file.write('{0:-<18}'.format('')) 158 | except NameError: 159 | pass 160 | try: 161 | for x in range(0, len(settings.geo_ang)): 162 | analysis_file.write('{0:-<18}'.format('')) 163 | except NameError: 164 | pass 165 | try: 166 | for x in range(0, len(settings.geo_dih)): 167 | analysis_file.write('{0:-<18}'.format('')) 168 | except NameError: 169 | pass 170 | analysis_file.write('{0:-<150}'.format('')) 171 | analysis_file.write('\n') 172 | analysis_file.close() 173 | return 174 | 175 | def analysis(settings, structures, x): 176 | analysis_file = open(settings.name+'_DIAS.txt', 'a') 177 | irc_SCF = getSCF(settings.name+'_output/complex_{0:04d}.'.format(x+1)+settings.output_file_extension) 178 | fragment1_SCF = getSCF(settings.name+'_output/'+settings.frag1name+'_{0:04d}.'.format(x+1)+settings.output_file_extension) 179 | fragment2_SCF = getSCF(settings.name+'_output/'+settings.frag2name+'_{0:04d}.'.format(x+1)+settings.output_file_extension) 180 | 181 | # calculate distortion energiesdef analysis_only(structures,settings): 182 | fragment1_dist = fragment1_SCF - settings.frag1energy 183 | fragment2_dist = fragment2_SCF - settings.frag2energy 184 | # calculate interaction energy 185 | interaction_energy = irc_SCF - (fragment1_SCF + fragment2_SCF) 186 | 187 | # extract geometric values 188 | distance_values = [] 189 | for y in range(0, len(settings.geo_dist)): 190 | distance_values = distance_values + [getDistance(structures.xyz[x][int(settings.geo_dist[y][0])-1],structures.xyz[x][int(settings.geo_dist[y][1])-1])] 191 | 192 | angle_values = [] 193 | for y in range(0, len(settings.geo_ang)): 194 | angle_values = angle_values + [getAngle(structures.xyz[x][int(settings.geo_ang[y][0])-1],structures.xyz[x][int(settings.geo_ang[y][1])-1],structures.xyz[x][int(settings.geo_ang[y][2])-1])] # -1 since array begins with 0 195 | dihedral_values = [] 196 | for y in range(0, len(settings.geo_dih)): 197 | dihedral_values = dihedral_values + [getDihedral(structures.xyz[x][int(settings.geo_dih[y][0])-1],structures.xyz[x][int(settings.geo_dih[y][1])-1],structures.xyz[x][int(settings.geo_dih[y][2])-1],structures.xyz[x][int(settings.geo_dih[y][3])-1])] # -1 since array begins with 0 198 | 199 | # print to file 200 | analysis_file.write('{0:<15}'.format('{0:04d}'.format(x+1))) 201 | 202 | for element in distance_values: 203 | analysis_file.write('{0:<18}'.format('{0:.5f}'.format(float(element)))) 204 | for element in angle_values: 205 | analysis_file.write('{0:<18}'.format('{0:.3f}'.format(float(element)))) 206 | for element in dihedral_values: 207 | analysis_file.write('{0:<18}'.format('{0:.3f}'.format(float(element)))) 208 | 209 | analysis_file.write('{0:<15}'.format('{0:.5f}'.format(float((irc_SCF-settings.frag1energy-settings.frag2energy)*hartree2kcal)))) 210 | analysis_file.write('{0:<15}'.format('{0:.5f}'.format(float(interaction_energy*hartree2kcal)))) 211 | analysis_file.write('{0:<15}'.format('{0:.5f}'.format(float((fragment1_dist+fragment2_dist)*hartree2kcal)))) 212 | analysis_file.write('{0:<15}'.format('{0:.5f}'.format(float(fragment1_dist*hartree2kcal)))) 213 | analysis_file.write('{0:<15}'.format('{0:.5f}'.format(float(fragment2_dist*hartree2kcal)))) 214 | analysis_file.write('{0:<20}'.format('{0:.9f}'.format(float(irc_SCF)))) 215 | analysis_file.write('{0:<20}'.format('{0:.9f}'.format(float(fragment1_SCF)))) 216 | analysis_file.write('{0:<20}'.format('{0:.9f}'.format(float(fragment2_SCF)))) 217 | analysis_file.write('\n') 218 | analysis_file.close() 219 | 220 | 221 | def analysis_only(structures, settings): 222 | starttime= time.time() 223 | create_analysis_file(settings) 224 | for i in range(0, len(structures.xyz)): 225 | analysis(settings, structures, i) 226 | os.remove(settings.logfile) 227 | endtime=time.time() 228 | totaltime=str(endtime-starttime) 229 | seconds=totaltime.split('.')[0] 230 | milliseconds=float('0.'+totaltime.split('.')[1])*1000 231 | print('Analysis of {0} structures done in {1} seconds and {2:.0f} ms'.format(len(structures.xyz),seconds, float(milliseconds))) 232 | return -------------------------------------------------------------------------------- /DIAS_inputparser.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import sys 3 | import re 4 | 5 | def line_prepender(filename, line): 6 | with open(filename, 'r+') as f: 7 | content = f.read() 8 | f.seek(0, 0) 9 | f.write(line.rstrip('\r\n') + '\n' + content) 10 | 11 | class structures: 12 | pass 13 | 14 | periodic_table = ["","H","He","Li","Be","B","C","N","O","F","Ne","Na","Mg","Al","Si","P","S","Cl","Ar","K","Ca","Sc","Ti","V","Cr","Mn","Fe","Co","Ni","Cu","Zn","Ga","Ge","As","Se","Br","Kr","Rb","Sr","Y","Zr", 15 | "Nb","Mo","Tc","Ru","Rh","Pd","Ag","Cd","In","Sn","Sb","Te","I","Xe","Cs","Ba","La","Ce","Pr","Nd","Pm","Sm","Eu","Gd","Tb","Dy","Ho","Er","Tm","Yb","Lu","Hf","Ta","W","Re","Os","Ir","Pt","Au","Hg","Tl", 16 | "Pb","Bi","Po","At","Rn","Fr","Ra","Ac","Th","Pa","U","Np","Pu","Am","Cm","Bk","Cf","Es","Fm","Md","No","Lr","Rf","Db","Sg","Bh","Hs","Mt","Ds","Rg","Uub","Uut","Uuq","Uup","Uuh","Uus","Uuo"] 17 | 18 | #Covalent radii taken from DOI: 10.1039/b801115j 19 | #Everything beyond Cm was set to 1.80 20 | covalent_radii = [0.00,0.32,0.28,1.28,0.96,0.84,0.76,0.71,0.66,0.57,0.58,1.66,1.41,1.21,1.11,1.07,1.05,1.02,1.06,2.03,1.76,1.70,1.60,1.53,1.39,1.61,1.52,1.50,1.24,1.32,1.22,1.22,1.20,1.19,1.20,1.20,1.16,2.20,1.95,1.90,1.75, 21 | 1.64,1.54,1.47,1.46,1.42,1.39,1.45,1.44,1.42,1.39,1.39,1.38,1.39,1.40,2.44,2.15,2.07,2.04,2.03,2.01,1.99,1.98,1.98,1.96,1.94,1.92,1.92,1.89,1.90,1.87,1.87,1.75,1.70,1.62,1.51,1.44,1.41,1.36,1.36,1.32,1.45, 22 | 1.46,1.48,1.40,1.50,1.50,2.60,2.21,2.15,206,2.00,1.96,1.90,1.87,180,169,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80] 23 | 24 | #takes xyz coordinates as numpy array and returns distance 25 | def getDistance(atom1, atom2): 26 | distance = 0.00 27 | distance = np.sqrt((atom1[0]-atom2[0])**2+(atom1[1]-atom2[1])**2+(atom1[2]-atom2[2])**2) 28 | return distance 29 | 30 | # checks if "s" is an int, returns true or false 31 | def isInt(s): 32 | try: 33 | int(s) 34 | return True 35 | except ValueError: 36 | return False 37 | 38 | def molecular_formula(atoms): 39 | alphabetic_periodic_table = sorted(copy.deepcopy(periodic_table)) 40 | formula = "" 41 | count = [] 42 | for element in alphabetic_periodic_table: 43 | count = count + [atoms.count(element)] 44 | if count[alphabetic_periodic_table.index("C")] > 1: 45 | formula = formula + 'C' + str(count[alphabetic_periodic_table.index("C")]) 46 | count[alphabetic_periodic_table.index("C")] = 0 47 | elif count[alphabetic_periodic_table.index("C")] > 0: 48 | formula = formula + 'C' 49 | count[alphabetic_periodic_table.index("C")] = 0 50 | if count[alphabetic_periodic_table.index("H")] > 1: 51 | formula = formula + 'H' + str(count[alphabetic_periodic_table.index("H")]) 52 | count[alphabetic_periodic_table.index("H")] = 0 53 | elif count[alphabetic_periodic_table.index("H")] > 0: 54 | formula = formula + 'H' 55 | count[alphabetic_periodic_table.index("H")] = 0 56 | for x in range(0, len(alphabetic_periodic_table)): 57 | if count[x] == 1: 58 | formula = formula + alphabetic_periodic_table[x] 59 | elif count[x] > 1: 60 | formula = formula + alphabetic_periodic_table[x] + str(count[x]) 61 | return formula 62 | 63 | def isBond(distance, atomtype1, atomtype2, covalent_radii): 64 | bond = True 65 | '''find bond threshold based on covalent radii 66 | taken from 10.1039/b801115j 67 | For everything beyond Cm 1.80 A was used. 68 | get radii from saved numpy array 69 | use atomtypes as numbers 70 | use factor to be sure to catch bonds. Bigger factor means more bonds are detected 71 | ''' 72 | atomtype1 = int(periodic_table.index(atomtype1)) 73 | atomtype2 = int(periodic_table.index(atomtype2)) 74 | bond_threshold = 1.25 * (covalent_radii[atomtype1]+covalent_radii[atomtype2]) 75 | if distance > bond_threshold: 76 | bond = False 77 | return bond 78 | 79 | def getFragments(structure, atoms): 80 | #get connectivity for each irc structure 81 | adjacency_matrix = [] 82 | # iterate over every atom 83 | for i in range(0, len(structure)): 84 | distances = [] 85 | #get the distance to every other atom and itself 86 | for j in range(0, len(structure)): 87 | distances = distances + [getDistance(structure[i], structure[j])] 88 | adjacency_matrix = adjacency_matrix + [distances] 89 | 90 | #convert distance to 1 for bond, 0 for no bond 91 | for i in range(0, len(adjacency_matrix)): 92 | for j in range(0, len(adjacency_matrix[i])): 93 | if isBond(adjacency_matrix[i][j], atoms[i], atoms[j], covalent_radii): 94 | adjacency_matrix[i][j] = 1 95 | else: 96 | adjacency_matrix[i][j] = 0 97 | # now make a list of fragments 98 | #first convert each line to a list of the atoms in the structure which show a bond 99 | for i in range(0, len(adjacency_matrix)): 100 | adjacency_matrix[i] = [j+1 for j,x in enumerate(adjacency_matrix[i]) if x == 1] 101 | #make empty fragments list 102 | fragments = [adjacency_matrix[0]] 103 | #now iterate over all the elements in adjacency_matrix 104 | for i in range (1, len(adjacency_matrix)): 105 | #now iterate over all the fragments in fragments and check if elements are the same 106 | for j in range(0, len(fragments)): 107 | #check if elements are already present in this list... 108 | if len(set(adjacency_matrix[i]) & set(fragments[j])) > 0: 109 | #....if they are add the lists together and eliminate duplicates... 110 | fragments[j] = list(set(fragments[j]+adjacency_matrix[i])) 111 | break 112 | else: 113 | #....add this list as new list in fragments 114 | fragments = fragments + [adjacency_matrix[i]] 115 | #now we got all of our fragments but some might belong to the same structure, so we need to test if we need to combine them, then combine them, 116 | #then check again and so on until nothing else changes. iterative process. Take length of fragments for that since it converged if amount of fragments doesn't changes 117 | n_frag_old=len(fragments) 118 | #set j to some value that cannot be the length of fragments 119 | n_frag_new=-5 120 | while n_frag_old != n_frag_new: 121 | #set i to current value of len(fragments) 122 | n_frag_old=len(fragments) 123 | new_fragments = [sorted(fragments[0])] 124 | #combine fragments like before 125 | for i in range(0, len(fragments)): 126 | for j in range(0, len(new_fragments)): 127 | if len(set(fragments[i]) & set(new_fragments[j])) > 0: 128 | new_fragments[j] = sorted(list(set(new_fragments[j]+fragments[i]))) 129 | break 130 | else: 131 | new_fragments = new_fragments + [fragments[i]] 132 | fragments=new_fragments 133 | n_frag_new=len(fragments) 134 | return fragments 135 | 136 | def getConnectivity(structure): 137 | connectivity_matrix = np.zeros((len(structure),len(structure))) 138 | for i in range(0,len(connectivity_matrix )): 139 | for j in range(0,len(connectivity_matrix)): 140 | distance=getDistance(structure[i],structure[j]) 141 | if isBond(distance,structures.atoms[i], structures.atoms[j],covalent_radii): 142 | connectivity_matrix[i][j] = 1 143 | else: 144 | connectivity_matrix[i][j] = 0 145 | return connectivity_matrix 146 | 147 | 148 | # finds up to two bonds that get formed 149 | def getBonds(structures): 150 | count = np.zeros((structures.xyz[0].shape[0],structures.xyz[0].shape[0])) 151 | #construct connectivity matrix step 0 152 | connectivity_old = getConnectivity(structures.xyz[0]) 153 | #now iterate over all the matrices and count changes 154 | for element in structures.xyz: 155 | connectivity = getConnectivity(element) 156 | for i in range(0, len(connectivity)): 157 | for j in range(0, len(connectivity[i])): 158 | if connectivity[i][j] != connectivity_old[i][j]: 159 | count[i][j] = count[i][j] +1 160 | connectivity_old= connectivity 161 | #clear of lower triangle of the matrix to eliminate duplicates 162 | for i in range(0,len(count)): 163 | for j in range(0, len(count)): 164 | if i >= j: 165 | count[i][j] = 0 166 | 167 | count[count == 0] = 1000 168 | #now find conenctivity for lowest number in count matrix, equals the bond that changed status the least, might not be the optimal solution 169 | freq = np.amin(count) 170 | geodist = [[int(np.where(count == freq)[0][0])+1,int(np.where(count == freq)[1][0])+1]] 171 | count[np.where(count == freq)[0][0],np.where(count == freq)[1][0]] = 1001 172 | 173 | freq = np.amin(count) 174 | geodist = geodist + [[int(np.where(count == freq)[0][0])+1,int(np.where(count == freq)[1][0])+1]] 175 | 176 | return geodist 177 | 178 | 179 | def duplicates(int_list): 180 | int_list = sorted(int_list) 181 | duplicates = False 182 | for i in range (0, len(int_list)-1): 183 | if int_list[i] == int_list[i+1]: 184 | duplicates = True 185 | else: 186 | pass 187 | return duplicates 188 | 189 | def auto_frag(structures,settings): 190 | list_of_fragments = [] 191 | for i in range(0, len(structures.xyz)): 192 | list_of_fragments = list_of_fragments + [getFragments(structures.xyz[i], structures.atoms)] 193 | n_fragments = [] 194 | for i in range(0,len(list_of_fragments)): 195 | n_fragments = n_fragments + [len(list_of_fragments[i])] 196 | #this part determines if there is at least one structure with 2 fragments and at least one with more than 2 197 | twofrag = False 198 | morefrag = False 199 | for element in n_fragments: 200 | if element == 2: 201 | twofrag = True 202 | break 203 | if element > 2: 204 | morefrag = True 205 | if twofrag: 206 | indices = [i for i, x in enumerate(n_fragments) if x == 2] 207 | counts = [list_of_fragments.count(list_of_fragments[x]) for x in indices] 208 | fragments = list_of_fragments[indices[counts.index(max(counts))]] 209 | settings.frag1atoms = np.sort(fragments[0]) 210 | settings.frag2atoms = np.sort(fragments[1]) 211 | elif morefrag: 212 | n_fragments = [x for x in n_fragments if x > 2] 213 | indices = [i for i, x in enumerate(n_fragments) if x == min(n_fragments)] 214 | counts = [list_of_fragments.count(list_of_fragments[x]) for x in indices] 215 | fragments = list_of_fragments[indices[counts.index(max(counts))]] 216 | settings.frag1atoms = np.sort(fragments[0]) 217 | # now combine all other fragments into one 218 | settings.frag2atoms = np.sort([j for i in fragments[1:] for j in i]) 219 | line_prepender(settings.logfile, "WARNING: more than two fragments detected! Check fragments!\n\n") 220 | else: 221 | line_prepender(settings.logfile, "CRITICAL ERROR: couldn't determine fragments automatically!\n\n") 222 | sys.exit("CRITICAL ERROR: couldn't determine fragments automatically!") 223 | return settings 224 | 225 | def get_single(file): 226 | structures.atoms = [] 227 | structures.xyz = [] 228 | structures.title = ['single structure'] 229 | with open(file) as input_file: 230 | file_contents = input_file.read() 231 | if "Optimization completed." in file_contents: 232 | regex = r'\ ---------------------------------------------------------------------\n((?:(?!\ ---------------------------------------------------------------------\n).)*?)\ ---------------------------------------------------------------------\n(?:(?!\ ---------------------------------------------------------------------\n).)*?Stationary\ point\ found' 233 | for match in re.finditer(regex, file_contents, re.IGNORECASE|re.DOTALL): 234 | raw_coordinates = match.group(1).strip().split("\n") 235 | break 236 | else: 237 | regex = r'\ ---------------------------------------------------------------------\n((?:(?!\ ---------------------------------------------------------------------\n).)*?)\ ---------------------------------------------------------------------\n(?:(?!\ ---------------------------------------------------------------------\n).)*?Rotational\ constants' 238 | for match in re.finditer(regex, file_contents, re.IGNORECASE|re.DOTALL): 239 | raw_coordinates = match.group(1).strip().split("\n") 240 | structures.xyz.append(np.array([x.split()[3:] for x in raw_coordinates])) 241 | structures.atoms = [x.split()[1] for x in raw_coordinates] 242 | structures.atoms = [periodic_table[int(x)] for x in structures.atoms] 243 | for i in range(len(structures.xyz)): 244 | structures.xyz[i] = structures.xyz[i].astype(float) 245 | return structures 246 | 247 | def get_scan(file): 248 | with open(file) as input_file: 249 | file_contents = input_file.read() 250 | structures.xyz = [] 251 | structures.atoms = [] 252 | 253 | # find everything except the TS structure 254 | regex = r'---------------------------------------------------------------------((?:(?!---------------------------------------------------------------------).)*?)---------------------------------------------------------------------(?:(?!---------------------------------------------------------------------).)*?Stationary\ point\ found' 255 | for match in re.finditer(regex, file_contents, re.IGNORECASE|re.DOTALL): 256 | raw_coordinates = match.group(1).strip().split("\n") 257 | structures.xyz.append(np.array([x.split()[3:] for x in raw_coordinates])) 258 | structures.atoms = [x.split()[1] for x in raw_coordinates] 259 | structures.atoms = [periodic_table[int(x)] for x in structures.atoms] 260 | 261 | for i in range(len(structures.xyz)): 262 | structures.xyz[i] = structures.xyz[i].astype(float) 263 | structures.title = [str(x) for x in range(len(structures.xyz))] 264 | return structures 265 | 266 | def get_irc(file): 267 | with open(file) as input_file: 268 | file_contents = input_file.read() 269 | structures.xyz = [] 270 | structures.atoms = [] 271 | #check if solvent was used 272 | if "scrf" in file_contents: 273 | a =0 274 | regex = r'Z\n ---------------------------------------------------------------------\n(.*?)\n ---------------------------------------------------------------------\n' 275 | for match in re.finditer(regex, file_contents, re.IGNORECASE|re.DOTALL): 276 | raw_coordinates = match.group(1).strip().split("\n") 277 | structures.xyz.append(np.array([x.split()[3:] for x in raw_coordinates])) 278 | print(a) 279 | a+=1 280 | structures.atoms = [x.split()[1] for x in raw_coordinates] 281 | structures.atoms = [periodic_table[int(x)] for x in structures.atoms] 282 | for i in range(len(structures.xyz)): 283 | structures.xyz[i] = structures.xyz[i].astype(float) 284 | structures.title = [str(x) for x in range(len(structures.xyz))] 285 | return structures 286 | # find TS structure, it's the first xyz structure available 287 | regex = r'Z\n ---------------------------------------------------------------------\n(.*?)\n ---------------------------------------------------------------------\n' 288 | for match in re.finditer(regex, file_contents, re.IGNORECASE|re.DOTALL): 289 | raw_coordinates = match.group(1).strip().split("\n") 290 | break 291 | structures.xyz.append(np.array([x.split()[3:] for x in raw_coordinates])) 292 | structures.atoms = [x.split()[1] for x in raw_coordinates] 293 | structures.atoms = [periodic_table[int(x)] for x in structures.atoms] 294 | # find everything except the TS structure for normal run 295 | regex2 = r'---------------------------------------------------------------------((?:(?!---------------------------------------------------------------------).)*?)---------------------------------------------------------------------(?:(?!---------------------------------------------------------------------).)*?Delta-x\ Convergence\ Met' 296 | for match in re.finditer(regex2, file_contents, re.IGNORECASE|re.DOTALL): 297 | raw_coordinates = match.group(1).strip().split("\n") 298 | structures.xyz.append(np.array([x.split()[3:] for x in raw_coordinates])) 299 | # find everything except the TS structure for #p 300 | regex3 = r'(?:(CURRENT).+?)(?:---------------------------------------------------------------------)(?:.*?(---------------------------------------------------------------------))(.*?)(?:(---------------------------------------------------------------------))' 301 | for match in re.finditer(regex3, file_contents, re.DOTALL): 302 | raw_coordinates = match.group(3).strip().split("\n") 303 | structures.xyz.append(np.array([x.split()[2:] for x in raw_coordinates])) 304 | for i in range(len(structures.xyz)): 305 | structures.xyz[i] = structures.xyz[i].astype(float) 306 | structures.title = [str(x) for x in range(len(structures.xyz))] 307 | return structures 308 | 309 | def check_fragments(settings,structures): 310 | for x in settings.frag1atoms: 311 | if int(x) > len(structures.atoms): 312 | line_prepender(settings.logfile, "CRITICAL ERROR: an atom number in fragment 1 is higher than the highest atom number!\n\n") 313 | sys.exit("CRITICAL ERROR: an atom number in fragment 1 is higher than the highest atom number!") 314 | for x in settings.frag2atoms: 315 | if int(x) > len(structures.atoms): 316 | line_prepender(settings.logfile, "CRITICAL ERROR: an atom number in fragment 2 is higher than the highest atom number!\n\n") 317 | sys.exit("CRITICAL ERROR: an atom number in fragment 2 is higher than the highest atom number!") 318 | if len(settings.frag1atoms) + len(settings.frag2atoms) != len(structures.atoms): 319 | line_prepender(settings.logfile, "CRITICAL ERROR: number of specified fragment atoms does not equal total number of atoms!\n\n") 320 | sys.exit("CRITICAL ERROR: number of specified fragment atoms does not equal total number of atoms!") 321 | if duplicates(settings.frag1atoms): 322 | line_prepender(settings.logfile, "CRITICAL ERROR: at least one atom was defined more than once in fragment1!\n\n") 323 | sys.exit("CRITICAL ERROR: at least one atom was defined more than once in fragment1!") 324 | if duplicates(settings.frag2atoms): 325 | line_prepender(settings.logfile, "CRITICAL ERROR: at least one atom was defined more than once in fragment2!\n\n") 326 | sys.exit("CRITICAL ERROR: at least one atom was defined more than once in fragment2!") 327 | elif len(set(settings.frag1atoms) & set(settings.frag2atoms)) > 0: 328 | line_prepender(settings.logfile, "CRITICAL ERROR: at least one atom was defined in both fragments!\n\n") 329 | sys.exit("CRITICAL ERROR: at least one atom was defined in both fragments!") 330 | return 331 | 332 | def check_geo(structures, settings): 333 | natoms = len(structures.xyz[0]) 334 | i = 0 335 | while i < len(settings.geo_dist): #check if every bond has two elements 336 | if len(settings.geo_dist[i]) != 2: 337 | del settings.geo_dist[i] 338 | line_prepender(settings.logfile, "WARNING: defined bond did not have two elements! - This input is ignored\n\n") 339 | else: 340 | i = i+1 341 | i = 0 342 | while i < len(settings.geo_dist): #check if it's inly integers 343 | if isInt(settings.geo_dist[i][0]) and isInt(settings.geo_dist[i][1]): 344 | i = i+1 345 | else: 346 | del settings.geo_dist[i] 347 | line_prepender(settings.logfile, "WARNING: defined bond did have non-integer input! - This input is ignored\n\n") 348 | if len(settings.geo_dist) == 0: 349 | settings.geo_dist.append(getBonds(structures)) 350 | line_prepender(settings.logfile, "WARNING: no correct definitions of bonds found! - automatic detection of forming bonds\n\n") 351 | i = 0 352 | while i < len(settings.geo_dist): #check if it's out of range 353 | if int(settings.geo_dist[i][0]) > natoms or int(settings.geo_dist[i][1]) > natoms or int(settings.geo_dist[i][0]) < 1 or int(settings.geo_dist[i][1]) < 1 : 354 | del settings.geo_dist[i] 355 | line_prepender(settings.logfile, "WARNING: bond definition: atom number out of range! - This input is ignored\n\n") 356 | else: 357 | i = i+1 358 | # angles 359 | i=0 360 | while i < len(settings.geo_ang): 361 | if len(settings.geo_ang[i]) != 3: 362 | del settings.geo_ang[i] 363 | line_prepender(settings.logfile, "WARNING: defined angle did not have three elements! - This input is ignored\n\n") 364 | else: 365 | i = i+1 366 | i = 0 367 | while i < len(settings.geo_ang): #check if it's inly integers 368 | if isInt(settings.geo_ang[i][0]) and isInt(settings.geo_ang[i][1]) and isInt(settings.geo_ang[i][2]): 369 | i = i+1 370 | else: 371 | del settings.geo_ang[i] 372 | line_prepender(settings.logfile, "WARNING: defined angle did have non-integer input! - This input is ignored\n\n") 373 | i=0 374 | while i < len(settings.geo_ang): #check if it's out of range 375 | if int(settings.geo_ang[i][0]) > natoms or int(settings.geo_ang[i][1]) > natoms or int(settings.geo_ang[i][2]) > natoms or int(settings.geo_ang[i][0]) < 1 or int(settings.geo_ang[i][1]) < 1 or int(settings.geo_ang[i][2]) < 1: 376 | del settings.geo_ang[i] 377 | line_prepender(settings.logfile, "WARNING: angle definition: atom number out of range! - This input is ignored\n\n") 378 | else: 379 | i = i+1 380 | # dihedral angles 381 | i=0 382 | while i < len(settings.geo_dih): 383 | if len(settings.geo_dih[i]) != 4: 384 | del settings.geo_dih[i] 385 | line_prepender(settings.logfile, "WARNING: defined dihedral angle did not have three elements! - This input is ignored\n\n") 386 | else: 387 | i = i+1 388 | i = 0 389 | while i < len(settings.geo_dih): #check if it's inly integers 390 | if isInt(settings.geo_dih[i][0]) and isInt(settings.geo_dih[i][1]) and isInt(settings.geo_dih[i][2]) and isInt(settings.geo_dih[i][3]): 391 | i = i+1 392 | else: 393 | del settings.geo_dih[i] 394 | line_prepender(settings.logfile, "WARNING: defined dihedral angle did have non-integer input! - This input is ignored\n\n") 395 | i=0 396 | while i < len(settings.geo_dih): #check if it's out of range 397 | if int(settings.geo_dih[i][0]) > natoms or int(settings.geo_dih[i][1]) > natoms or int(settings.geo_dih[i][2]) > natoms or int(settings.geo_dih[i][3]) > natoms or int(settings.geo_dih[i][0]) < 1 or int(settings.geo_dih[i][1]) < 1 or int(settings.geo_dih[i][2]) < 1 or int(settings.geo_dih[i][3]) < 1: 398 | del settings.geo_dih[i] 399 | line_prepender(settings.logfile, "WARNING: dihedral angle definition: atom number out of range! - This input is ignored\n\n") 400 | else: 401 | i = i+1 402 | return settings 403 | 404 | def structures_from_G(file, settings): 405 | file_object = open(file, 'r') 406 | input = (line for line in file_object) 407 | type = "single" 408 | for line in input: 409 | if "IRC-IRC-IRC" in line: 410 | type = "IRC" 411 | break 412 | elif "Scan" in line: 413 | type = "scan" 414 | break 415 | # Single 416 | if type == "single": 417 | structures = get_single(file) 418 | elif type == "scan": 419 | structures = get_scan(file) 420 | else: 421 | structures = get_irc(file) 422 | # Get Charge and multiplicity 423 | file_object.seek(0) 424 | input_file = (line for line in file_object) # make generator 425 | for line in input_file: 426 | if "Charge =" in line: 427 | settings.charge = line.split()[2] 428 | settings.multi = line.split()[5] 429 | break 430 | return structures, settings 431 | 432 | 433 | def structures_from_xyz(file): 434 | structures.atoms = [] 435 | structures.xyz = [] 436 | structures.title = [] 437 | file_object = open(file, 'r') 438 | input = (line for line in file_object) #make generator 439 | #search for number of atoms 440 | for line in input: 441 | if isInt(line.strip()): 442 | n_atoms=int(line) 443 | break 444 | else: #exits if no line with number of atoms was found 445 | sys.exit('Error:\t\tNo xyz coordinates found in file: ' + file) 446 | #skip one line 447 | structures.title.append(next(input).strip()) 448 | # now there should be n_atoms lines of coordinates 449 | for i in range(n_atoms): 450 | l=next(input).split() 451 | if l[0] in periodic_table: 452 | structures.atoms.append(l[0]) #get atom symbol and append to atom list 453 | else: 454 | sys.exit('Error:\t\tsomething is wrong with the first structure in file: '+file) 455 | coords=[float(x) for x in l[1:]] #convert line to list of floats 456 | coords=np.array([coords]) #create array with coords 457 | try: #try append, doesn't work if XYZ doesn't exist yet 458 | XYZ=np.concatenate((XYZ,coords), axis=0) 459 | except NameError: 460 | XYZ=coords 461 | structures.xyz.append(XYZ) #append first structure to structures list 462 | del XYZ #get rid of that for the next structure 463 | #now search for more structures 464 | for line in input: 465 | #start extracting if atom number line is found 466 | try: 467 | if int(line.strip()) == n_atoms: 468 | #read one line to skip title 469 | structures.title.append(next(input).strip()) 470 | # now there should be n_atoms lines of coordinates 471 | for i in range(n_atoms): 472 | l=next(input).split() 473 | coords=[float(x) for x in l[1:]] 474 | coords=np.array([coords]) 475 | try: #try append, doesn't work if XYZ doesn't exist yet 476 | XYZ=np.concatenate((XYZ,coords), axis=0) 477 | except NameError: 478 | XYZ=coords 479 | structures.xyz.append(XYZ) 480 | del XYZ 481 | except ValueError: 482 | pass 483 | return structures 484 | 485 | def parse_in(input_filename, analysisonly): 486 | class settings: 487 | pass 488 | settings.void = [] 489 | input_object = open(input_filename, 'r') 490 | #------------structures filename 491 | input_object.seek(0) 492 | input_file = (line for line in input_object) # make generator 493 | for line in input_file: 494 | if len(line.split()) > 1: # This is needed otherwise it produces out of range error when contents of lines too short 495 | if line.split()[0].upper()+line.split()[1].upper() == "INPUTFILE": 496 | settings.ircfile = line.split()[-1] 497 | break 498 | else: #information on input file type is missing 499 | sys.exit("No information on the structure input file could be found!") 500 | #------------GET JOBNAME 501 | input_object.seek(0) 502 | input_file = (line for line in input_object) # make generator 503 | settings.name = settings.ircfile 504 | for line in input_file: 505 | if len(line.split()) > 3: 506 | if line.split()[0].upper()+line.split()[1].upper() == "JOBNAME": 507 | try: 508 | settings.name = line.split()[3] 509 | break 510 | except IndexError: 511 | break 512 | #------------Set Logfilename 513 | if analysisonly: 514 | settings.logfile= settings.name + "_analysisonly_log.txt" 515 | f= open(settings.logfile, 'w') 516 | f.close() 517 | else: 518 | settings.logfile= settings.name + "_log.txt" 519 | f= open(settings.logfile, 'w') 520 | f.close() 521 | #------------Parse Structures 522 | #find out what kind of file we are dealing with. See if it's a gaussian file 523 | try: 524 | structure_object = open(settings.ircfile, 'r') 525 | except FileNotFoundError: 526 | line_prepender(settings.logfile, "CRITICAL ERROR: structure input file {0} not found!\n\n".format(settings.ircfile)) 527 | sys.exit("CRITICAL ERROR: structure input file {0} not found!".format(settings.ircfile)) 528 | structure_object.seek(0) 529 | structure_file = (line for line in structure_object) # make generator 530 | settings.filetype="X" 531 | for line in structure_file: 532 | if "Gaussian, Inc." in line: 533 | settings.filetype="G" 534 | break 535 | if settings.filetype == "X": 536 | try: 537 | structures = structures_from_xyz(settings.ircfile) 538 | except StopIteration: 539 | line_prepender(settings.logfile, "CRITICAL ERROR: problem reading structures from file {}!\n\n".format(settings.ircfile)) 540 | sys.exit("CRITICAL ERROR: problem reading structures from file {}!".format(settings.ircfile)) 541 | else: 542 | structures, settings = structures_from_G(settings.ircfile, settings) 543 | #------------Get Information on Fragments/determine automatically 544 | #CHARGE 545 | input_object.seek(0) 546 | input_file = (line for line in input_object) # make generator 547 | #check if they were extracted from file, if not set to 0 and then try to read 548 | try: 549 | settings.charge 550 | except AttributeError: 551 | settings.charge = 0 552 | for line in input_file: 553 | if len(line.split()) > 0: 554 | if line.split()[0].upper() == "CHARGE": 555 | try: 556 | settings.charge = int(line.split()[-1]) 557 | break 558 | except IndexError: 559 | break 560 | #MULTIPLICITY 561 | try: 562 | settings.charge 563 | except AttributeError: 564 | settings.multi = 1 565 | for line in input_file: 566 | if len(line.split()) > 0: 567 | if line.split()[0].upper() == "MULTIPLICITY": 568 | try: 569 | settings.multi = int(line.split()[-1]) 570 | break 571 | except (IndexError,ValueError) as error: 572 | break 573 | #FRAGMENT1 574 | input_object.seek(0) 575 | input_file = (line for line in input_object) # make generator 576 | settings.frag1name = "fragment1" 577 | for line in input_file: 578 | if len(line.split()) > 1: 579 | if line.split()[0].upper()+line.split()[1].upper() == "FRAGMENT1NAME": 580 | try: 581 | settings.frag1name = line.split()[-1] 582 | break 583 | except (IndexError,ValueError) as error: 584 | break 585 | input_object.seek(0) 586 | input_file = (line for line in input_object) # make generator 587 | settings.frag1charge= 0 588 | for line in input_file: 589 | if len(line.split()) > 1: 590 | if line.split()[0].upper()+line.split()[1].upper() == "FRAGMENT1CHARGE": 591 | try: 592 | settings.frag1charge = int(line.split()[-1]) 593 | break 594 | except (IndexError,ValueError) as error: 595 | break 596 | input_object.seek(0) 597 | input_file = (line for line in input_object) # make generator 598 | settings.frag1multi = 1 599 | for line in input_file: 600 | if len(line.split()) > 1: 601 | if line.split()[0].upper()+line.split()[1].upper() == "FRAGMENT1MULTIPLICITY": 602 | try: 603 | settings.frag1multi = int(line.split()[-1]) 604 | break 605 | except (IndexError,ValueError) as error: 606 | break 607 | input_object.seek(0) 608 | input_file = (line for line in input_object) # make generator 609 | settings.frag1energy = 0 610 | for line in input_file: 611 | if len(line.split()) > 1: 612 | if line.split()[0].upper()+line.split()[1].upper() == "FRAGMENT1ENERGY": 613 | try: 614 | settings.frag1energy = float(line.split()[-1]) 615 | break 616 | except (IndexError,ValueError) as error: 617 | break 618 | input_object.seek(0) 619 | input_file = (line for line in input_object) # make generator 620 | settings.frag1atoms = "auto" 621 | for line in input_file: 622 | if len(re.split("\s+|,",line)) > 2: 623 | if line.split()[0].upper()+line.split()[1].upper() == "FRAGMENT1ATOMS": 624 | try: 625 | settings.frag1atoms =re.split("\s+|,",line)[3:] 626 | break 627 | except IndexError: 628 | break 629 | settings.frag1atoms = [int(x) for x in settings.frag1atoms if isInt(x)] 630 | if len(settings.frag1atoms) == 0: 631 | settings.frag1atoms = "auto" 632 | #FRAGMENT2 633 | input_object.seek(0) 634 | input_file = (line for line in input_object) # make generator 635 | settings.frag2name = "fragment2" 636 | for line in input_file: 637 | if len(line.split()) > 1: 638 | if line.split()[0].upper()+line.split()[1].upper() == "FRAGMENT2NAME": 639 | try: 640 | settings.frag2name = line.split()[-1] 641 | break 642 | except IndexError: 643 | break 644 | input_object.seek(0) 645 | input_file = (line for line in input_object) # make generator 646 | settings.frag2charge= 0 647 | for line in input_file: 648 | if len(line.split()) > 1: 649 | if line.split()[0].upper()+line.split()[1].upper() == "FRAGMENT2CHARGE": 650 | try: 651 | settings.frag2charge = int(line.split()[-1]) 652 | break 653 | except (IndexError,ValueError) as error: 654 | break 655 | input_object.seek(0) 656 | input_file = (line for line in input_object) # make generator 657 | settings.frag2multi = 1 658 | for line in input_file: 659 | if len(line.split()) > 1: 660 | if line.split()[0].upper()+line.split()[1].upper() == "FRAGMENT2MULTIPLICITY": 661 | try: 662 | settings.frag2multi = int(line.split()[-1]) 663 | break 664 | except (IndexError,ValueError) as error: 665 | break 666 | input_object.seek(0) 667 | input_file = (line for line in input_object) # make generator 668 | settings.frag2energy = 0 669 | for line in input_file: 670 | if len(line.split()) > 1: 671 | if line.split()[0].upper()+line.split()[1].upper() == "FRAGMENT2ENERGY": 672 | try: 673 | settings.frag2energy = float(line.split()[-1]) 674 | break 675 | except (IndexError,ValueError) as error: 676 | break 677 | input_object.seek(0) 678 | input_file = (line for line in input_object) # make generator 679 | settings.frag2atoms = "auto" 680 | for line in input_file: 681 | if len(re.split("\s+|,",line)) > 2: 682 | if line.split()[0].upper()+line.split()[1].upper() == "FRAGMENT2ATOMS": 683 | try: 684 | settings.frag2atoms =re.split("\s+|,",line)[3:] 685 | break 686 | except IndexError: 687 | break 688 | settings.frag2atoms = [int(x) for x in settings.frag2atoms if isInt(x)] 689 | if len(settings.frag2atoms) == 0: 690 | settings.frag2atoms = "auto" 691 | #Determine fragment atoms if not set 692 | if settings.frag1atoms == "auto" and settings.frag2atoms == "auto": 693 | settings = auto_frag(structures,settings) 694 | elif settings.frag1atoms == "auto": 695 | settings.frag1atoms = list(range(1,len(structures.atoms)+1)) 696 | settings.frag1atoms = [item for item in settings.frag1atoms if item not in set(settings.frag2atoms)] 697 | elif settings.frag2atoms == "auto": 698 | settings.frag2atoms = list(range(1,len(structures.atoms)+1)) 699 | settings.frag2atoms = [item for item in settings.frag2atoms if item not in set(settings.frag1atoms)] 700 | check_fragments(settings,structures) #checks if atom lists are coherent 701 | #get fragment xyz 702 | structures.xyz_1 = [] 703 | settings.frag1atoms[:] = [x -1 for x in settings.frag1atoms] 704 | for x in range(0, len(structures.xyz)): 705 | structures.xyz_1 = structures.xyz_1 + [structures.xyz[x][settings.frag1atoms]] 706 | structures.xyz_2 = [] 707 | settings.frag2atoms[:] = [x -1 for x in settings.frag2atoms] 708 | for x in range(0, len(structures.xyz)): 709 | structures.xyz_2 = structures.xyz_2 + [structures.xyz[x][settings.frag2atoms]] 710 | structures.frag1atoms = [] 711 | for element in settings.frag1atoms: 712 | structures.frag1atoms = structures.frag1atoms + [structures.atoms[element]] 713 | structures.frag2atoms = [] 714 | for element in settings.frag2atoms: 715 | structures.frag2atoms = structures.frag2atoms + [structures.atoms[element]] 716 | 717 | #------------Get analysis information 718 | input_object.seek(0) 719 | input_file = (line for line in input_object) # make generator 720 | settings.analysis = True 721 | for line in input_file: 722 | if len(line.split()) > 0: 723 | if line.split()[0].upper() == "ANALYSIS": 724 | try: 725 | if line.split()[2].upper() == "YES": 726 | settings.analysis = True 727 | break 728 | elif line.split()[2].upper() == "NO": 729 | settings.analysis = False 730 | break 731 | except IndexError: 732 | break 733 | if settings.analysis: 734 | settings.geo_dist = [[]] 735 | with open(input_filename) as input_file: 736 | file_contents = input_file.read() # reset generator 737 | 738 | for match in re.finditer(r'(.*?)', file_contents, re.IGNORECASE|re.DOTALL): 739 | settings.geo_dist = match.group(1).strip().split('\n') 740 | settings.geo_dist = [element.split() for element in settings.geo_dist] 741 | settings.geo_ang = [] 742 | with open(input_filename) as input_file: 743 | file_contents = input_file.read() # reset generator 744 | for match in re.finditer(r'(.*?)', file_contents, re.IGNORECASE|re.DOTALL): 745 | settings.geo_ang = match.group(1).strip().split('\n') 746 | settings.geo_ang = [element.split() for element in settings.geo_ang] 747 | settings.geo_dih = [] 748 | with open(input_filename) as input_file: 749 | file_contents = input_file.read() # reset generator 750 | for match in re.finditer(r'(.*?)', file_contents, re.IGNORECASE|re.DOTALL): 751 | settings.geo_dih = match.group(1).strip().split('\n') 752 | settings.geo_dih = [element.split() for element in settings.geo_dih] 753 | 754 | #Automatic determination of formed, broken bonds if requested 755 | if settings.geo_dist[0] == []: 756 | settings.geo_dist = [["auto"]] 757 | for element in settings.geo_dist: 758 | if "auto" in element: 759 | auto_distances = getBonds(structures) 760 | #replace auto in that list against the new distances 761 | settings.geo_dist = [ x for x in settings.geo_dist if "auto" not in x] 762 | settings.geo_dist.append(auto_distances[0]) 763 | settings.geo_dist.append(auto_distances[1]) 764 | break 765 | #eliminate problems with empty lists 766 | if len(settings.geo_ang)>0: 767 | if settings.geo_ang[0] == []: 768 | settings.geo_ang = [] 769 | if len(settings.geo_dih)>0: 770 | if settings.geo_dih[0] == "": 771 | settings.geo_dih = [] 772 | #eliminate problems with wrong inputs 773 | settings = check_geo(structures, settings) 774 | #------------Get Further Setting 775 | #keep xyz 776 | input_object.seek(0) 777 | input_file = (line for line in input_object) # make generator 778 | settings.keepxyz = True 779 | for line in input_file: 780 | if len(line.split()) > 2: 781 | if line.split()[0].upper()+line.split()[1].upper() == "KEEPXYZ": 782 | try: #in case it's blank 783 | line.split()[3] 784 | except IndexError: 785 | settings.keepxyz = True 786 | break 787 | if line.split()[3].upper() == "YES": 788 | settings.keepxyz = True 789 | elif line.split()[3].upper() == "NO": 790 | settings.keepxyz = False 791 | else: 792 | settings.keepxyz = True 793 | break 794 | #keep input files 795 | input_object.seek(0) 796 | input_file = (line for line in input_object) # make generator 797 | settings.keepinput = True 798 | for line in input_file: 799 | if len(line.split()) > 3: 800 | if line.split()[0].upper()+line.split()[1].upper()+line.split()[2].upper() == "KEEPINPUTFILES": 801 | try: #in case it's blank 802 | line.split()[4] 803 | except IndexError: 804 | settings.keepinput = True 805 | break 806 | if line.split()[4].upper() == "YES": 807 | settings.keepinput = True 808 | elif line.split()[4].upper() == "NO": 809 | settings.keepinput = False 810 | else: 811 | settings.keepinput = True 812 | break 813 | #keep output files 814 | input_object.seek(0) 815 | input_file = (line for line in input_object) # make generator 816 | settings.keepoutput = True 817 | for line in input_file: 818 | if len(line.split()) > 3: 819 | if line.split()[0].upper()+line.split()[1].upper()+line.split()[2].upper() == "KEEPOUTPUTFILES": 820 | try: #in case it's blank 821 | line.split()[4] 822 | except IndexError: 823 | settings.keepoutput = True 824 | break 825 | if line.split()[4].upper() == "YES": 826 | settings.keepoutput = True 827 | elif line.split()[4].upper() == "NO": 828 | settings.keepoutput = False 829 | else: 830 | settings.keepoutput = True 831 | break 832 | #keep log file 833 | input_object.seek(0) 834 | input_file = (line for line in input_object) # make generator 835 | settings.keeplog = True 836 | for line in input_file: 837 | if len(line.split()) > 3: 838 | if line.split()[0].upper()+line.split()[1].upper()+line.split()[2].upper() == "KEEPLOGFILE": 839 | try: #in case it's blank 840 | line.split()[4] 841 | except IndexError: 842 | settings.keeplog = True 843 | break 844 | if line.split()[4].upper() == "YES": 845 | settings.keeplog = True 846 | elif line.split()[4].upper() == "NO": 847 | settings.keeplog = False 848 | else: 849 | settings.keeplog = True 850 | break 851 | #reorder 852 | input_object.seek(0) 853 | input_file = (line for line in input_object) # make generator 854 | settings.reorder = False 855 | for line in input_file: 856 | if len(line.split()) > 2: 857 | if line.split()[0].upper() == "REORDER": 858 | try: #in case it's blank 859 | line.split()[2] 860 | except IndexError: 861 | settings.reorder = False 862 | break 863 | if line.split()[2].upper() == "YES": 864 | settings.reorder = True 865 | elif line.split()[2].upper() == "NO": 866 | settings.reorder = False 867 | else: 868 | settings.reorder = False 869 | break 870 | #reduce structures 871 | input_object.seek(0) 872 | input_file = (line for line in input_object) # make generator 873 | settings.reduce = False 874 | for line in input_file: 875 | if len(line.split()) > 3: 876 | if line.split()[0].upper()+line.split()[1].upper() == "REDUCESTRUCTURES": 877 | try: #in case it's blank 878 | line.split()[3] 879 | except IndexError: 880 | settings.reduce = False 881 | break 882 | if line.split()[3].upper() == "YES": 883 | settings.reduce = True 884 | elif line.split()[3].upper() == "NO": 885 | settings.reduce = False 886 | else: 887 | settings.reduce = False 888 | break 889 | if settings.reduce: 890 | input_object.seek(0) 891 | input_file = (line for line in input_object) # make generator 892 | settings.reduce_tresh = 0.2 893 | for line in input_file: 894 | if len(line.split()) > 3: 895 | if line.split()[0].upper()+line.split()[1].upper() == "RMSDTRESHOLD": 896 | try: #in case it's blank 897 | line.split()[3] 898 | except IndexError: 899 | settings.reduce_tresh = 0.2 900 | break 901 | try: 902 | settings.reduce_tresh = float(line.split()[3].upper() ) 903 | except ValueError: 904 | settings.reduce_tresh = 0.2 905 | break 906 | #prepare only 907 | input_object.seek(0) 908 | input_file = (line for line in input_object) # make generator 909 | settings.prepareonly = False 910 | for line in input_file: 911 | if len(line.split()) > 3: 912 | if line.split()[0].upper()+line.split()[1].upper() == "PREPAREONLY": 913 | try: #in case it's blank 914 | line.split()[3] 915 | except IndexError: 916 | settings.prepareonly = False 917 | break 918 | if line.split()[3].upper() == "YES": 919 | settings.prepareonly = True 920 | elif line.split()[3].upper() == "NO": 921 | settings.prepareonly = False 922 | else: 923 | settings.prepareonly = False 924 | break 925 | #------------Get Settings for running the application 926 | input_object.seek(0) 927 | input_file = (line for line in input_object) # reset generator 928 | settings.input_file_extension = "com" #setting default 929 | for line in input_file: 930 | if len(line.split()) > 2: # This is needed otherwise it produces out of range error when contents of lines too short 931 | if line.split()[0].upper()+line.split()[1].upper()+line.split()[2].upper() == "INPUTFILEEXTENSION": 932 | try: #in case it's blank 933 | settings.input_file_extension = line.split()[4] 934 | break 935 | except IndexError: 936 | break 937 | input_object.seek(0) 938 | input_file = (line for line in input_object) # reset generator 939 | settings.output_file_extension = "log" #setting default 940 | for line in input_file: 941 | if len(line.split()) > 2: # This is needed otherwise it produces out of range error when contents of lines too short 942 | if line.split()[0].upper()+line.split()[1].upper()+line.split()[2].upper() == "OUTPUTFILEEXTENSION": 943 | try: #in case it's blank 944 | settings.output_file_extension = line.split()[4] 945 | break 946 | except IndexError: 947 | break 948 | #get input layout 949 | settings.inputlayout = "" 950 | with open(input_filename) as input_file: 951 | file_contents = input_file.read() # reset generator 952 | for match in re.finditer(r'(.*?)', file_contents, re.IGNORECASE|re.DOTALL): 953 | settings.inputlayout = match.group(1).strip()+'\n\n' 954 | #get run settings 955 | settings.submit_setting = "" 956 | with open(input_filename) as input_file: 957 | file_contents = input_file.read() # reset generator 958 | for match in re.finditer(r'(.*?)', file_contents, re.IGNORECASE|re.DOTALL): 959 | settings.submit_setting = match.group(1).strip() 960 | 961 | return settings, structures -------------------------------------------------------------------------------- /DIAS_reduce_reorder.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | 4 | # rotates structure, takes strcuture A and rotation R, returns rotated structure B 5 | def rotate(A, rot): 6 | B=np.dot(A, rot) 7 | return B 8 | 9 | # does partial procrustes analysis, takes structure A and reference R and returns RMSD 10 | def procrustes(A, R): 11 | rot=find_rotation(A, R) 12 | #calculates rotated structure B 13 | B=rotate(A, rot) 14 | RMSD=calc_RMSD(B,R) 15 | return RMSD 16 | 17 | #function that finds centroid from numpy array, returns numpy array with position of centroid 18 | def find_centroid(P): 19 | C=P.mean(axis=0) 20 | return C 21 | 22 | #function that calculates RMSD between two xyz structures, takes structures as (m*3) numpy array 23 | def calc_RMSD(A, B): 24 | RMSD=0.0 25 | for i in range(3): 26 | for x in range(len(A)): 27 | RMSD += (A[x,i]-B[x,i])**2 28 | return np.sqrt(RMSD/len(A)) 29 | 30 | #takes structure A and reference structure R, calculates optimal rotation (R) using SVD for alignment based on Kabsch algorithm! 31 | def find_rotation(A, R): 32 | # Computation of the covariance matrix 33 | C = np.dot(np.transpose(A), R) 34 | 35 | u, s, vh = np.linalg.svd(C) 36 | 37 | #assure right handed coordinate system) 38 | if (np.linalg.det(u) * np.linalg.det(vh)) < 0.0: 39 | s[-1] = -s[-1] 40 | u[:, -1] = -u[:, -1] 41 | 42 | #calculate rotation R 43 | rot = np.dot(u, vh) 44 | return rot 45 | 46 | def center_xyz(structure, atoms, mode): 47 | 48 | #first calculate translation vector V by calculating center and reversing the vector 49 | if mode == 'c': 50 | V=-1*find_centroid(structure) 51 | elif mode == 'm': 52 | V=-1*find_centerofmass(structure, atoms) 53 | else: 54 | sys.exit('something went wrong') 55 | 56 | #now add vector to each atom 57 | structure=structure+V 58 | return structure 59 | 60 | def reorder(structures): 61 | if len(structures.xyz) == 1: # only one structure 62 | return structures 63 | RMSD = [] 64 | for i in range(0, len(structures.xyz)-1): 65 | RMSD = RMSD + [procrustes(structures.xyz[i],structures.xyz[i+1])] 66 | if np.amax(RMSD) > 3* np.mean(RMSD): 67 | cut = RMSD.index(np.amax(RMSD)) 68 | rmsd1=procrustes(structures.xyz[0], structures.xyz[cut+1]) 69 | rmsd2=procrustes(structures.xyz[cut], structures.xyz[cut+1]) 70 | rmsd3=procrustes(structures.xyz[0], structures.xyz[-1]) 71 | rmsd4=procrustes(structures.xyz[cut], structures.xyz[-1]) 72 | 73 | if rmsd1 < rmsd2 and rmsd1 < rmsd3 and rmsd1 < rmsd4: 74 | structures.xyz = structures.xyz[cut::-1]+structures.xyz[cut+1:] 75 | structures.xyz_1 = structures.xyz_1[cut::-1]+structures.xyz_1[cut+1:] 76 | structures.xyz_2 = structures.xyz_2[cut::-1]+structures.xyz_2[cut+1:] 77 | structures.title = structures.title[cut::-1]+structures.title[cut+1:] 78 | elif rmsd2 < rmsd1 and rmsd2 < rmsd3 and rmsd2 < rmsd4: 79 | pass 80 | elif rmsd3 < rmsd1 and rmsd3 < rmsd2 and rmsd3 < rmsd4: 81 | structures.xyz = structures.xyz[cut::-1]+structures.xyz[:cut:-1] 82 | structures.xyz_1 = structures.xyz_1[cut::-1]+structures.xyz_1[:cut:-1] 83 | structures.xyz_2 = structures.xyz_2[cut::-1]+structures.xyz_2[:cut:-1] 84 | structures.title = structures.title[cut::-1]+structures.title[:cut:-1] 85 | elif rmsd4 < rmsd1 and rmsd4 < rmsd2 and rmsd4 < rmsd3: 86 | structures.xyz = structures.xyz[:cut+1]+structures.xyz[:cut:-1] 87 | structures.xyz_1 = structures.xyz_1[:cut+1]+structures.xyz_1[:cut:-1] 88 | structures.xyz_2 = structures.xyz_2[:cut+1]+structures.xyz_2[:cut:-1] 89 | structures.title = structures.title[:cut+1]+structures.title[:cut:-1] 90 | else: 91 | pass 92 | return structures 93 | 94 | def reduce(structures, settings): 95 | if len(structures.xyz) == 1: # only one structure 96 | return structures 97 | i=0 98 | count = 0 99 | while i < len(structures.xyz)-1: 100 | if procrustes(structures.xyz[i], structures.xyz[i+1]) >= settings.reduce_tresh: 101 | i = i+1 102 | else: 103 | count = count +1 104 | del structures.xyz[i+1] 105 | del structures.title[i+1] 106 | del structures.xyz_1[i+1] 107 | del structures.xyz_2[i+1] 108 | log = open(settings.logfile, 'a') 109 | log.write("Reduced structures by {0} ({1:.0f}%)!\n".format(count, float(count)/(len(structures.xyz)+count)*100)) 110 | log.close() 111 | return structures -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Dennis Svatunek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # autoDIAS 2 | 3 | ## a python tool for an automated Distortion/Interaction Activation Strain Analysis 4 | 5 | The Distortion/Interaction Activation Strain (DIAS) analysis is a powerful tool for the investigation of energy barriers. However, setup and data analysis of such a calculation can be cumbersome and requires lengthy intervention of the user. We present autoDIAS, a python tool for the automated setup, performance, and data extraction of the DIAS analysis, including automated detection of fragments and relevant geometric parameters. 6 | 7 | -------------------------------------------------------------------------------- /autoDIAS.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | banner=""" 4 | ***************************************************************************************** 5 | autoDIAS 6 | Dennis Svatunek 7 | UCLA 8 | d.svatunek@chem.ucla.com 9 | @clickchemist 10 | 11 | Download the newest version from: 12 | https://github.com/dsvatunek/autoDIAS 13 | 14 | Please cite. DOI: 10.1002/jcc.26023 15 | ***************************************************************************************** 16 | 17 | Python wrapper for several computational chemistry software packages to automatically 18 | perform a Distortion/Interaction Activation Strain analysis calculations. 19 | 20 | ***************************************************************************************** 21 | """ 22 | 23 | from DIAS_inputparser import * 24 | from DIAS_analysis import * 25 | from DIAS_reduce_reorder import * 26 | import argparse, sys, time, subprocess, os, datetime, copy 27 | 28 | __version__= '1.0.0' 29 | __author__= 'Dennis Svatunek' 30 | __email__ = "d.svatunek@chem.ucla.edu" 31 | 32 | #Python2 compatibility 33 | try: 34 | range = xrange 35 | except NameError: 36 | pass 37 | 38 | def save_xyz(A, atoms, title, filename): 39 | file = open(filename + '.xyz', 'w') 40 | file.write(str(len(atoms))+ "\n"+ title + "\n") 41 | for i in range(len(A)): 42 | file.write("{0:2s} {1:15.12f} {2:15.12f} {3:15.12f}\n".format(atoms[i], A[i, 0], A[i, 1], A[i, 2])) 43 | file.close() 44 | return 45 | 46 | def save_input(A, atoms, title, filename, jobname, g09structure, charge, multiplicity): 47 | file = open(filename, 'w') 48 | #make a string with coordinates 49 | coordinates = "" 50 | for i in range(len(A)): 51 | coordinates = coordinates +("{0:2s} {1:15.12f} {2:15.12f} {3:15.12f}\n".format(atoms[i], A[i, 0], A[i, 1], A[i, 2])) 52 | #use G09 input string ans substitute the correct things 53 | g09structure = g09structure.replace("$multiplicity", str(multiplicity)) 54 | g09structure = g09structure.replace("$charge", str(charge)) 55 | g09structure = g09structure.replace("$filename", jobname) 56 | g09structure = g09structure.replace("$coordinates", coordinates.strip()) 57 | file.write(g09structure) 58 | file.close() 59 | return 60 | 61 | def molecular_formula(atoms): 62 | alphabetic_periodic_table = sorted(copy.deepcopy(periodic_table)) 63 | formula = "" 64 | count = [] 65 | for element in alphabetic_periodic_table: 66 | count = count + [atoms.count(element)] 67 | 68 | if count[alphabetic_periodic_table.index("C")] > 1: 69 | formula = formula + 'C' + str(count[alphabetic_periodic_table.index("C")]) 70 | count[alphabetic_periodic_table.index("C")] = 0 71 | elif count[alphabetic_periodic_table.index("C")] > 0: 72 | formula = formula + 'C' 73 | count[alphabetic_periodic_table.index("C")] = 0 74 | 75 | if count[alphabetic_periodic_table.index("H")] > 1: 76 | formula = formula + 'H' + str(count[alphabetic_periodic_table.index("H")]) 77 | count[alphabetic_periodic_table.index("H")] = 0 78 | elif count[alphabetic_periodic_table.index("H")] > 0: 79 | formula = formula + 'H' 80 | count[alphabetic_periodic_table.index("H")] = 0 81 | 82 | for x in range(0, len(alphabetic_periodic_table)): 83 | if count[x] == 1: 84 | formula = formula + alphabetic_periodic_table[x] 85 | elif count[x] > 1: 86 | formula = formula + alphabetic_periodic_table[x] + str(count[x]) 87 | return formula 88 | 89 | def main(): 90 | 91 | parser = argparse.ArgumentParser(usage='%(prog)s inp_file') 92 | parser.add_argument('inp_file', metavar='Input file', type=str, help='Name of the input file') 93 | parser.add_argument("-a", "--analysis", action='store_true', help='Analysis only', default=False) 94 | if len(sys.argv) == 1: 95 | parser.print_help() 96 | sys.exit(1) 97 | args = parser.parse_args() 98 | 99 | #---------------------------------------------------------------------------------------- 100 | overallstarttime=time.time() 101 | #-------------------------------------Parse Settings and Inputs---------------------- 102 | starttime=time.time() # start timing for input parsing 103 | settings, structures = parse_in(args.inp_file,args.analysis) 104 | #analysis only run analysis only and then stop 105 | if args.analysis: 106 | endtime=time.time() 107 | totaltime=str(endtime-starttime) 108 | seconds=totaltime.split('.')[0] 109 | milliseconds=float('0.'+totaltime.split('.')[1])*1000 110 | print('Processed input and parsed structures in {0} seconds and {1:.0f} ms'.format(seconds, float(milliseconds))) 111 | if settings.reorder: 112 | structures = reorder(structures) 113 | if settings.reduce: 114 | structures = reduce(structures, settings) 115 | analysis_only(structures,settings) 116 | return 117 | 118 | endtime=time.time() 119 | totaltime=str(endtime-starttime) 120 | seconds=totaltime.split('.')[0] 121 | milliseconds=float('0.'+totaltime.split('.')[1])*1000 122 | 123 | log = open(settings.logfile, 'a') 124 | log.write(banner) 125 | try: #python2.6 126 | hostname = subprocess.check_output(['hostname']).decode('utf-8') 127 | username = subprocess.check_output(['whoami']).decode('utf-8') 128 | except AttributeError: 129 | hostname= "" 130 | username= "" 131 | log.write(str(username).strip()+'@'+str(hostname).strip()+'\n') 132 | log.write(time.ctime()+'\n') 133 | log.write('-------------------------------------------------------\n') 134 | log.write('Input file: '+args.inp_file) 135 | log.write('\nProcessed input and parsed structures in {0} seconds and {1:.0f} ms\n'.format(seconds, float(milliseconds))) 136 | log.write('-------------------------------------------------------\n') 137 | log.write('Determined settings and variables: \n\n') 138 | log.write('{0:50}{1}\n'.format("Job name",settings.name)) 139 | log.write('{0:50}{1}\n'.format("Structure input",settings.ircfile)) 140 | log.write('{0:50}{1}\n'.format("Structure input filetype",settings.filetype)) 141 | if settings.analysis: 142 | log.write('{0:50}{1}\n'.format("\nAnalysis will be performed","YES")) 143 | if len(settings.geo_dist) > 0: 144 | log.write(' {0:50}\n'.format("The following bond distances will be examined:\n")) 145 | for i in range(len(settings.geo_dist)): 146 | log.write(' {0:15}atom 1: {1}\n {2:15}atom 2: {3}\n\n'.format("Bond "+str(i+1),settings.geo_dist[i][0],"",settings.geo_dist[i][1])) 147 | if len(settings.geo_ang) > 0: 148 | log.write(' {0:50}\n'.format("The following angles will be examined:\n")) 149 | for i in range(len(settings.geo_ang)): 150 | log.write(' {0:15}atom 1: {1}\n {2:15}atom 2: {3}\n {2:15}atom 3: {4}\n\n'.format("Angle "+str(i+1),settings.geo_ang[i][0],"",settings.geo_ang[i][1],settings.geo_ang[i][2])) 151 | if len(settings.geo_dih) > 0: 152 | log.write(' {0:50}\n'.format("The following dihedral angles will be examined:\n")) 153 | for i in range(len(settings.geo_dih)): 154 | log.write(' {0:15}atom 1: {1}\n {2:15}atom 2: {3}\n {2:15}atom 3: {4}\n {2:15}atom 4: {5}\n\n'.format("Dihedral "+str(i+1),settings.geo_dih[i][0],"",settings.geo_dih[i][1],settings.geo_dih[i][2],settings.geo_dih[i][3])) 155 | log.write('{0:50}{1}\n'.format("Keep xyz files","YES" if settings.keepxyz else "NO")) 156 | log.write('{0:50}{1}\n'.format("Keep input files","YES" if settings.keepinput else "NO")) 157 | log.write('{0:50}{1}\n'.format("Keep output files","YES" if settings.keepoutput else "NO")) 158 | log.write('{0:50}{1}\n'.format("Keep this log file","YES" if settings.keeplog else "NO")) 159 | log.write('{0:50}{1}\n'.format("Reorder structures","YES" if settings.reorder else "NO")) 160 | log.write('{0:50}{1}\n'.format("Reduce structures","YES" if settings.reduce else "NO")) 161 | if settings.reduce: 162 | log.write('{0:50}{1}\n'.format("Threshold for reducing structures",settings.reduce_tresh)) 163 | log.write('{0:50}{1}\n'.format("Prepare input and exit","YES" if settings.prepareonly else "NO")) 164 | # structure fragment details 165 | formula = molecular_formula(structures.atoms) 166 | fragment1_formula = molecular_formula(structures.frag1atoms) 167 | fragment2_formula = molecular_formula(structures.frag2atoms) 168 | 169 | log.write('\nStructure information\n\n') 170 | log.write('Input structure has {0} atoms with a molecular formula of:{1:>15}\n\n'.format(len(structures.atoms), formula)) 171 | log.write('{0} has {1} atoms with a molecular formula of:{2:>15}\n\n'.format(settings.frag1name, len(structures.frag1atoms), fragment1_formula)) 172 | log.write('{0} has {1} atoms with a molecular formula of:{2:>15}\n\n'.format(settings.frag2name, len(structures.frag2atoms), fragment2_formula)) 173 | log.write('Complex has a charge and multiplicity of: {0} {1}\n'.format(settings.charge, settings.multi)) 174 | log.write('{0} has a charge and multiplicity of: {1} {2}\n'.format(settings.frag1name, settings.frag1charge, settings.frag1multi)) 175 | log.write('{0} has a charge and multiplicity of: {1} {2}\n'.format(settings.frag2name, settings.frag2charge, settings.frag2multi)) 176 | log.write('{0} has an energy of: {1}\n'.format(settings.frag1name, settings.frag1energy)) 177 | log.write('{0} has an energy of: {1}\n'.format(settings.frag2name, settings.frag2energy)) 178 | log.write('\n') 179 | 180 | log.write('-------------------------------------------------------\n') 181 | log.close() 182 | 183 | #-------------------------------------Reorder and reduce---------------------------- 184 | if settings.reorder: 185 | structures = reorder(structures) 186 | if settings.reduce: 187 | structures = reduce(structures, settings) 188 | #-------------------------------------Produce input--------------------------------- 189 | starttime=time.time() 190 | 191 | if settings.keepxyz: 192 | if not os.path.exists(settings.name+'_xyz'): 193 | os.makedirs(settings.name+'_xyz') 194 | for x in range(0, len(structures.xyz)): 195 | save_xyz(structures.xyz[x], structures.atoms, structures.title[x], settings.name+'_xyz/complex_{0:04d}'.format(x+1)) 196 | save_xyz(structures.xyz_1[x], structures.frag1atoms,settings.frag1name+'_{0:04d}'.format(x+1), settings.name+'_xyz/'+settings.frag1name+'_{0:04d}'.format(x+1)) 197 | save_xyz(structures.xyz_2[x], structures.frag2atoms,settings.frag2name+'_{0:04d}'.format(x+1), settings.name+'_xyz/'+settings.frag2name+'_{0:04d}'.format(x+1)) 198 | os.system("cat {0}_xyz/complex*.xyz > {0}_xyz/complete.xyz".format(settings.name)) 199 | if not os.path.exists(settings.name+'_input'): 200 | os.makedirs(settings.name+'_input') 201 | if not os.path.exists(settings.name+'_output'): 202 | os.makedirs(settings.name+'_output') 203 | 204 | for x in range (0, len(structures.xyz)): 205 | save_input(structures.xyz[x], structures.atoms, structures.title[x], settings.name+'_input/complex_{0:04d}.'.format(x+1)+settings.input_file_extension, settings.name+'_output/complex_{0:04d}'.format(x+1), settings.inputlayout, settings.charge, settings.multi) 206 | save_input(structures.xyz_1[x], structures.frag1atoms, settings.frag1name+'_{0:04d}'.format(x+1), settings.name+'_input/'+settings.frag1name+'_{0:04d}.'.format(x+1)+settings.input_file_extension, settings.name+'_output/'+settings.frag1name+'_{0:04d}'.format(x+1), settings.inputlayout, settings.frag1charge, settings.frag1multi) 207 | save_input(structures.xyz_2[x], structures.frag2atoms, settings.frag2name+'_{0:04d}'.format(x+1), settings.name+'_input/'+settings.frag2name+'_{0:04d}.'.format(x+1)+settings.input_file_extension, settings.name+'_output/'+settings.frag2name+'_{0:04d}'.format(x+1), settings.inputlayout, settings.frag2charge, settings.frag2multi) 208 | 209 | endtime=time.time() 210 | totaltime=str(endtime-starttime) 211 | seconds=totaltime.split('.')[0] 212 | milliseconds=float('0.'+totaltime.split('.')[1])*1000 213 | log = open(settings.logfile, 'a') 214 | log.write('Produced input files for {0} structures in {1} seconds and {2:.0f} ms\n'.format(len(structures.xyz),seconds, float(milliseconds))) 215 | log.write('-------------------------------------------------------\n') 216 | log.close() 217 | 218 | #-------------------------------------Run jobs----------------------------------------- 219 | if settings.analysis and not settings.prepareonly: 220 | create_analysis_file(settings) 221 | log = open(settings.logfile, 'a') 222 | 223 | if settings.prepareonly: 224 | log.write('No calculations requested!\n') 225 | else: 226 | for x in range (0, len(structures.xyz)): 227 | #produce string with correct bash command 228 | command = settings.submit_setting.replace("$input", settings.name+'_input/complex_{0:04d}.'.format(x+1)+settings.input_file_extension) 229 | command = command.replace("$output", settings.name+'_output/complex_{0:04d}.'.format(x+1)+settings.output_file_extension) 230 | log.write(time.ctime()+'{0:<30}'.format('\tStarting {0:>15}'.format('complex_{0:04d}'.format(x+1)))) 231 | log.flush() 232 | starttime=time.time() 233 | os.system(command) 234 | endtime=time.time() 235 | totaltime=endtime-starttime 236 | log.write('\t finished after {0:0>15}\n'.format(str(datetime.timedelta(seconds=totaltime)))) 237 | log.flush() 238 | 239 | command = settings.submit_setting.replace("$input", settings.name+'_input/'+settings.frag1name+'_{0:04d}.'.format(x+1)+settings.input_file_extension) 240 | command = command.replace("$output", settings.name+'_output/'+settings.frag1name+'_{0:04d}.'.format(x+1)+settings.output_file_extension) 241 | log.write(time.ctime()+'{0:<30}'.format('\tStarting {0:>15}'.format(settings.frag1name+'_{0:04d}'.format(x+1)))) 242 | log.flush() 243 | starttime=time.time() 244 | os.system(command) 245 | endtime=time.time() 246 | totaltime=endtime-starttime 247 | log.write('\t finished after {0:0>15}\n'.format(str(datetime.timedelta(seconds=totaltime)))) 248 | log.flush() 249 | 250 | command = settings.submit_setting.replace("$input", settings.name+'_input/'+settings.frag2name+'_{0:04d}.'.format(x+1)+settings.input_file_extension) 251 | command = command.replace("$output", settings.name+'_output/'+settings.frag2name+'_{0:04d}.'.format(x+1)+settings.output_file_extension) 252 | log.write(time.ctime()+'{0:<30}'.format('\tStarting {0:>15}'.format(settings.frag2name+'_{0:04d}'.format(x+1)))) 253 | log.flush() 254 | starttime=time.time() 255 | os.system(command) 256 | endtime=time.time() 257 | totaltime=endtime-starttime 258 | log.write('\t finished after {0:0>15}\n'.format(str(datetime.timedelta(seconds=totaltime)))) 259 | log.flush() 260 | if settings.analysis: 261 | analysis(settings, structures, x) 262 | 263 | totaltime=time.time()-overallstarttime 264 | log.write('-------------------------------------------------------\n') 265 | log.write(time.ctime()+' Finished after {0:0>15}\n'.format(str(datetime.timedelta(seconds=totaltime)))) 266 | log.write('-------------------------------------------------------\n') 267 | log.close() 268 | #-------------------------------------Clean up----------------------------------------- 269 | if settings.keepinput == False: 270 | shutil.rmtree("input/", ignore_errors=True) 271 | if settings.keepoutput == False: 272 | shutil.rmtree("output/", ignore_errors=True) 273 | if settings.keeplog == False: 274 | os.remove(settings.logfile) 275 | 276 | if __name__ == "__main__": 277 | main() 278 | 279 | -------------------------------------------------------------------------------- /input_example.inp: -------------------------------------------------------------------------------- 1 | #-------------------------------------------------- 2 | Example input file 3 | #-------------------------------------------------- 4 | 5 | # relative or absolute path to file with structures. Can be xyz file or G09 output from IRC, Scan, single point, frequency or geometry opt calculation 6 | 7 | Input file = test.xyz 8 | 9 | # Define a job name. Default is the input file name without file extension 10 | 11 | Job name = test 12 | 13 | #-------------------------------------------------- 14 | Information on Structures 15 | #-------------------------------------------------- 16 | 17 | # Charge and multiplicity of the complex. Needs to be set for xyz files. Optional for G09 input 18 | 19 | charge = 0 20 | multiplicity = 1 21 | 22 | # Fragment information 23 | # fragment name, default is fragment1 or fragment2. 24 | Fragment1 name = Fragment1 25 | # fragment charge and multiplicity have to be provided. Default is charge 0 and multiplicity of 1 26 | Fragment1 charge = 0 27 | Fragment1 multiplicity = 1 28 | # list of the atoms in this fragment, provide a comma or space separated list, leave empty for automatic determination. If atom lists for both fragments are empty, then fragment 1 will contain the fragment containing atom 1 29 | Fragment1 atoms = 30 | # Electronic energy of this fragment, required for calculation of relative energy of the complex and distortion energies. Defaults to 0. 31 | Fragment1 energy = -243.485731 32 | 33 | Fragment2 name = Fragment2 34 | Fragment2 charge = 0 35 | Fragment2 multiplicity = 1 36 | Fragment2 atoms = 37 | Fragment2 energy = -464.752500 38 | 39 | #-------------------------------------------------- 40 | Information for Analysis 41 | #-------------------------------------------------- 42 | 43 | # Analysis can be turned on (YES) or off (NO), default is YES 44 | Analysis = YES 45 | 46 | # Distances can be extracted. Use atom numbers to define distances. Use one line per atom pair 47 | # write "auto" in line to attempt finding a total of two bonds that get formed or broken. 48 | 49 | 1 2 50 | auto 51 | 52 | 53 | # Angles can be extracted. Use atom numbers to define angles. Use one line per angle 54 | 55 | 1 2 3 56 | 57 | 58 | # Dihedral angles can be extracted. Use atom numbers to define angles. Use one line per angle 59 | 60 | 1 2 3 4 61 | 62 | 63 | #-------------------------------------------------- 64 | Further Settings 65 | #-------------------------------------------------- 66 | 67 | # If set to YES autoDIA produces XYZ files of all complex and fragment structures and saves them in a folder called _xyz 68 | Keep xyz = YES 69 | # If set to YES autoDIA does not delete input files of all complex and fragment structures in folder called _input 70 | Keep input files = YES 71 | # If set to YES autoDIA does not delete output files of all complex and fragment structures in folder called _output 72 | Keep output files = YES 73 | # If set to YES autoDIA does not delete the log file called _log.txt after successful termination 74 | Keep log file = YES 75 | 76 | #Set if structures should be reordered 77 | reorder = NO 78 | 79 | #Eliminate very similar structures based on a certain RMSD treshold 80 | Reduce structures = NO 81 | RMSD treshold = 0.2 82 | 83 | #Does only prepare input files but does not run them, does not do analysis (use -a flag for analysis only) 84 | Prepare only = NO 85 | 86 | #-------------------------------------------------- 87 | Input File Structure 88 | #-------------------------------------------------- 89 | 90 | # Input file structure for the chosen driver 91 | # provide the settings for single point calculations 92 | # use $filename and $coordinates $charge and $multiplicity in places where they are required 93 | # Keep in mind the correct number of blank lines 94 | 95 | 96 | %mem=32GB 97 | %nprocshared=16 98 | # B3LYP/6-311+g(d,p) empiricaldispersion=gd3 99 | 100 | Title 101 | 102 | $charge $multiplicity 103 | $coordinates 104 | 105 | 106 | 107 | 108 | #-------------------------------------------------- 109 | Input File Structure 110 | #-------------------------------------------------- 111 | # provide the correct command to run the desired Software (e.g. Gaussian) on your system 112 | # use $input and if needed $output for input and output file name 113 | 114 | 115 | g09 $input $output 116 | 117 | 118 | # Option to set the input file extension, some software might require a specific one. Defaults to com 119 | input file extension = com 120 | # Option to set the output file extension, some software might require a specific one. Defaults to log 121 | output file extension = log 122 | -------------------------------------------------------------------------------- /old/test_autofrag.py: -------------------------------------------------------------------------------- 1 | #fragments test 2 | class settings: 3 | pass 4 | class structures: 5 | pass 6 | 7 | import numpy as np 8 | 9 | 10 | periodic_table = ["","H","He","Li","Be","B","C","N","O","F","Ne","Na","Mg","Al","Si","P","S","Cl","Ar","K","Ca","Sc","Ti","V","Cr","Mn","Fe","Co","Ni","Cu","Zn","Ga","Ge","As","Se","Br","Kr","Rb","Sr","Y","Zr", 11 | "Nb","Mo","Tc","Ru","Rh","Pd","Ag","Cd","In","Sn","Sb","Te","I","Xe","Cs","Ba","La","Ce","Pr","Nd","Pm","Sm","Eu","Gd","Tb","Dy","Ho","Er","Tm","Yb","Lu","Hf","Ta","W","Re","Os","Ir","Pt","Au","Hg","Tl", 12 | "Pb","Bi","Po","At","Rn","Fr","Ra","Ac","Th","Pa","U","Np","Pu","Am","Cm","Bk","Cf","Es","Fm","Md","No","Lr","Rf","Db","Sg","Bh","Hs","Mt","Ds","Rg","Uub","Uut","Uuq","Uup","Uuh","Uus","Uuo"] 13 | 14 | #Covalent radii taken from DOI: 10.1039/b801115j 15 | #Everything beyond Cm was set to 1.80 16 | covalent_radii = [0.00,0.32,0.28,1.28,0.96,0.84,0.76,0.71,0.66,0.57,0.58,1.66,1.41,1.21,1.11,1.07,1.05,1.02,1.06,2.03,1.76,1.70,1.60,1.53,1.39,1.61,1.52,1.50,1.24,1.32,1.22,1.22,1.20,1.19,1.20,1.20,1.16,2.20,1.95,1.90,1.75, 17 | 1.64,1.54,1.47,1.46,1.42,1.39,1.45,1.44,1.42,1.39,1.39,1.38,1.39,1.40,2.44,2.15,2.07,2.04,2.03,2.01,1.99,1.98,1.98,1.96,1.94,1.92,1.92,1.89,1.90,1.87,1.87,1.75,1.70,1.62,1.51,1.44,1.41,1.36,1.36,1.32,1.45, 18 | 1.46,1.48,1.40,1.50,1.50,2.60,2.21,2.15,206,2.00,1.96,1.90,1.87,180,169,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80,1.80] 19 | 20 | def isInt(s): 21 | try: 22 | int(s) 23 | return True 24 | except ValueError: 25 | return False 26 | #takes xyz coordinates as numpy array and returns distance 27 | def getDistance(atom1, atom2): 28 | distance = 0.00 29 | distance = np.sqrt((atom1[0]-atom2[0])**2+(atom1[1]-atom2[1])**2+(atom1[2]-atom2[2])**2) 30 | return distance 31 | 32 | def structures_from_xyz(file): 33 | structures.atoms = [] 34 | structures.xyz = [] 35 | structures.title = [] 36 | file_object = open(file, 'r') 37 | input = (line for line in file_object) #make generator 38 | #search for number of atoms 39 | for line in input: 40 | if isInt(line.strip()): 41 | n_atoms=int(line) 42 | break 43 | else: #exits if no line with number of atoms was found 44 | sys.exit('Error:\t\tNo xyz coordinates found in file: ' + file) 45 | #skip one line 46 | structures.title.append(next(input).strip()) 47 | # now there should be n_atoms lines of coordinates 48 | for i in range(n_atoms): 49 | l=next(input).split() 50 | if l[0] in periodic_table: 51 | structures.atoms.append(l[0]) #get atom symbol and append to atom list 52 | else: 53 | sys.exit('Error:\t\tsomething is wrong with the first structure in file: '+file) 54 | coords=[float(x) for x in l[1:]] #convert line to list of floats 55 | coords=np.array([coords]) #create array with coords 56 | try: #try append, doesn't work if XYZ doesn't exist yet 57 | XYZ=np.concatenate((XYZ,coords), axis=0) 58 | except NameError: 59 | XYZ=coords 60 | structures.xyz.append(XYZ) #append first structure to structures list 61 | del XYZ #get rid of that for the next structure 62 | #now search for more structures 63 | for line in input: 64 | #start extracting if atom number line is found 65 | try: 66 | if int(line.strip()) == n_atoms: 67 | #read one line to skip title 68 | structures.title.append(next(input).strip()) 69 | # now there should be n_atoms lines of coordinates 70 | for i in range(n_atoms): 71 | l=next(input).split() 72 | coords=[float(x) for x in l[1:]] 73 | coords=np.array([coords]) 74 | try: #try append, doesn't work if XYZ doesn't exist yet 75 | XYZ=np.concatenate((XYZ,coords), axis=0) 76 | except NameError: 77 | XYZ=coords 78 | structures.xyz.append(XYZ) 79 | del XYZ 80 | except ValueError: 81 | pass 82 | return structures 83 | 84 | def isBond(distance, atomtype1, atomtype2, covalent_radii): 85 | bond = True 86 | '''find bond threshold based on covalent radii 87 | taken from 10.1039/b801115j 88 | For everything beyond Cm 1.80 A was used. 89 | get radii from saved numpy array 90 | use atomtypes as numbers 91 | use factor to be sure to catch bonds. Bigger factor means more bonds are detected 92 | ''' 93 | atomtype1 = int(periodic_table.index(atomtype1)) 94 | atomtype2 = int(periodic_table.index(atomtype2)) 95 | bond_threshold = 1.25 * (covalent_radii[atomtype1]+covalent_radii[atomtype2]) 96 | if distance > bond_threshold: 97 | bond = False 98 | return bond 99 | 100 | def getFragments(structure, atoms): 101 | #get connectivity for each irc structure 102 | adjacency_matrix = [] 103 | # iterate over every atom 104 | for i in range(0, len(structure)): 105 | distances = [] 106 | #get the distance to every other atom and itself 107 | for j in range(0, len(structure)): 108 | distances = distances + [getDistance(structure[i], structure[j])] 109 | adjacency_matrix = adjacency_matrix + [distances] 110 | 111 | #convert distance to 1 for bond, 0 for no bond 112 | for i in range(0, len(adjacency_matrix)): 113 | for j in range(0, len(adjacency_matrix[i])): 114 | if isBond(adjacency_matrix[i][j], atoms[i], atoms[j], covalent_radii): 115 | adjacency_matrix[i][j] = 1 116 | else: 117 | adjacency_matrix[i][j] = 0 118 | # now make a list of fragments 119 | #first convert each line to a list of the atoms in the structure which show a bond 120 | for i in range(0, len(adjacency_matrix)): 121 | adjacency_matrix[i] = [j+1 for j,x in enumerate(adjacency_matrix[i]) if x == 1] 122 | #make empty fragments list 123 | fragments = [adjacency_matrix[0]] 124 | #now iterate over all the elements in adjacency_matrix 125 | for i in range (1, len(adjacency_matrix)): 126 | #now iterate over all the fragments in fragments and check if elements are the same 127 | for j in range(0, len(fragments)): 128 | #check if elements are already present in this list... 129 | if len(set(adjacency_matrix[i]) & set(fragments[j])) > 0: 130 | #....if they are add the lists together and eliminate duplicates... 131 | fragments[j] = list(set(fragments[j]+adjacency_matrix[i])) 132 | break 133 | else: 134 | #....add this list as new list in fragments 135 | fragments = fragments + [adjacency_matrix[i]] 136 | #now we got all of our fragments but some might belong to the same structure, so we need to test if we need to combine them, then combine them, 137 | #then check again and so on until nothing else changes, iterative process. Take length of fragments for that since it converged if amount of fragments doesn't changes 138 | n_frag_old=len(fragments) 139 | #set j to some value that cannot be the length of fragments 140 | n_frag_new=-5 141 | while n_frag_old != n_frag_new: 142 | #set i to current value of len(fragments) 143 | n_frag_old=len(fragments) 144 | new_fragments = [sorted(fragments[0])] 145 | #combine fragments like before 146 | for i in range(0, len(fragments)): 147 | for j in range(0, len(new_fragments)): 148 | if len(set(fragments[i]) & set(new_fragments[j])) > 0: 149 | new_fragments[j] = sorted(list(set(new_fragments[j]+fragments[i]))) 150 | break 151 | else: 152 | new_fragments = new_fragments + [fragments[i]] 153 | fragments=new_fragments 154 | n_frag_new=len(fragments) 155 | return fragments 156 | 157 | def auto_frag(structures,settings): 158 | list_of_fragments = [] 159 | for i in range(0, len(structures.xyz)): 160 | list_of_fragments = list_of_fragments + [getFragments(structures.xyz[i], structures.atoms)] 161 | n_fragments = [] 162 | print(list_of_fragments) 163 | for i in range(0,len(list_of_fragments)): 164 | n_fragments = n_fragments + [len(list_of_fragments[i])] 165 | #the next steps only work in cases where two fragments were found 166 | try: 167 | indices = [i for i, x in enumerate(n_fragments) if x == 2] 168 | counts = [list_of_fragments.count(list_of_fragments[x]) for x in indices] 169 | fragments = list_of_fragments[indices[counts.index(max(counts))]] 170 | settings.frag1atoms = np.sort(fragments[0]) 171 | settings.frag2atoms = np.sort(fragments[1]) 172 | except: 173 | pass 174 | return settings 175 | 176 | settings.ircfile = "test.xyz" 177 | structures = structures_from_xyz(settings.ircfile) 178 | settings = auto_frag(structures, settings) 179 | print(settings.frag1atoms) 180 | print(settings.frag2atoms) -------------------------------------------------------------------------------- /plotting_examples/2D_print.py: -------------------------------------------------------------------------------- 1 | #example of plotting a complete DIA along a 2D surface 2 | import numpy as np 3 | from matplotlib import pyplot as plt 4 | import matplotlib.mlab as ml 5 | 6 | #import data from autoDIA output file 7 | a = np.genfromtxt('Scan2D_DIA.txt',skip_header=4) 8 | 9 | #create plot and subplots and layout 10 | f, axes = plt.subplots(2, 3,figsize=(5.25, 5),dpi=300,gridspec_kw = {'width_ratios':[5,5, 0.5]}) 11 | f.delaxes(axes[0,1]) 12 | f.delaxes(axes[0,2]) 13 | axes[0,0].set_aspect(1, adjustable='box') 14 | axes[1,0].set_aspect(1, adjustable='box') 15 | axes[1,1].set_aspect(1, adjustable='box') 16 | axes[1,2].set_aspect(10, adjustable='box') 17 | f.subplots_adjust(wspace=0, hspace=0) 18 | 19 | """The second column (keep in mind that Numpy numbering starts with 0) represents the first bond distance, 20 | the third column contains information about the second bond distance, the 6th column contains the total energy 21 | """ 22 | x = a[:,1] 23 | y = a[:,2] 24 | z = a[:,5] 25 | 26 | #getting the limits for the axis 27 | xmin = np.amin(x) 28 | xmax = np.amax(x) 29 | ymin = np.amin(y) 30 | ymax = np.amax(y) 31 | 32 | #Manually select lower and upper energy limits 33 | Emin=-180 34 | Emax=150 35 | 36 | #create array for contour lines which should be plottet. Spacing 10 kcal/mol 37 | b = np.linspace(Emin,Emax,(Emax-Emin)//10) 38 | 39 | #create the griddata with z values needed for contour and pcolormesh plotting 40 | xi = np.linspace(xmin, xmax, len(x)) 41 | yi = np.linspace(ymin, ymax, len(y)) 42 | zi = ml.griddata(x, y, z, xi, yi, interp='linear') 43 | 44 | #plotting contour lines and colormesh for the total energy subplot 45 | axes[0,0].contour(xi, yi, zi, b, linewidths = 0.5, colors = 'k',linestyles='solid') 46 | z1_plot = axes[0,0].pcolormesh(xi, yi, zi,vmin=Emin,vmax=Emax, cmap = plt.get_cmap('rainbow')) 47 | 48 | #setting axes 49 | axes[0,0].set_title('Electronic energy') 50 | axes[0,0].set_ylabel('Bond distance 1') 51 | axes[0,0].set_yticks([1.5,2.0,2.5,3.0]) 52 | 53 | #plotting colorbar 54 | cb = plt.colorbar(z1_plot,cax=axes[1,2], ticks=[-150,-100, -50, 0, 50, 100, 150]) 55 | cb.ax.set_yticklabels([-150,-100, -50, 0, 50, 100, 150],ha='right') 56 | cb.ax.yaxis.set_tick_params(pad=25) 57 | axes[0,0].set_xticklabels([]) 58 | 59 | #column 7 contains information about the interaction energy 60 | z = a[:,6] 61 | 62 | #create the griddata with z values needed for contour and pcolormesh plotting 63 | zi = ml.griddata(x, y, z, xi, yi, interp='linear') 64 | 65 | #plotting contour lines and colormesh for the interaction subplot 66 | axes[1,0].contour(xi, yi, zi, b, linewidths = 0.5, colors = 'k',linestyles='solid') 67 | axes[1,0].pcolormesh(xi, yi, zi,vmin=Emin,vmax=Emax, cmap = plt.get_cmap('rainbow')) 68 | 69 | #setting axes 70 | axes[1,0].set_title('Interaction energy') 71 | axes[1,0].set_ylabel('Bond distance 1') 72 | axes[1,0].set_xlabel('Bond distance 2') 73 | axes[1,0].set_yticks([1.5,2.0,2.5,3.0]) 74 | 75 | #column 8 contains information about the distortion energy 76 | z = a[:,7] 77 | 78 | #create the griddata with z values needed for contour and pcolormesh plotting 79 | zi = ml.griddata(x, y, z, xi, yi, interp='linear') 80 | 81 | #plotting contour lines and colormesh for the distortion subplot 82 | axes[1,1].contour(xi, yi, zi, b, linewidths = 0.5, colors = 'k',linestyles='solid') 83 | axes[1,1].pcolormesh(xi, yi, zi,vmin=Emin,vmax=Emax, cmap = plt.get_cmap('rainbow')) 84 | 85 | #setting axes 86 | axes[1,1].set_title('Distortion energy') 87 | axes[1,1].set_xlabel('Bond distance 2') 88 | axes[1,1].set_yticklabels([]) 89 | 90 | #layout and saving the figure 91 | f.tight_layout() 92 | plt.savefig('2D.png') 93 | -------------------------------------------------------------------------------- /plotting_examples/IRC_print.py: -------------------------------------------------------------------------------- 1 | #example of plotting a complete DIA along an IRC as shown in Figure 6 2 | import numpy as np 3 | from matplotlib import pyplot as plt 4 | import matplotlib.patches as mpatches 5 | 6 | #create plot 7 | f= plt.figure(figsize=(2.5, 2.5),dpi=300) 8 | ax1 = f.add_subplot(1,1,1) 9 | 10 | #read data from autoDIA output 11 | a = np.genfromtxt('IRC1_DIA.txt',skip_header=4) 12 | 13 | #set axis 14 | ax1.set_ylabel('Energy') 15 | ax1.set_xlabel('Forming bond distance') 16 | ax1.axis([np.amax(a[:,2])+0.1, np.amin(a[:,2])-0.1, np.amin(a[:,6])-20, np.amax(a[:,7])+20]) 17 | ax1.axhline(y=0, color='grey', linestyle='--',linewidth = 0.5) 18 | 19 | #the second geometric parameter (column 3 - keep in mind that Numpy counting starts at 0) representing a bond length is used as X-Value 20 | x = a[:,2] 21 | #In this case column 7 contains information about interaction energy 22 | y = a[:,6] 23 | ax1.plot(x,y,'-',color='g',linewidth = 1) 24 | 25 | #In this case column 8 contains information about total distortion energy 26 | y = a[:,7] 27 | ax1.plot(x,y,'-',color='r',linewidth = 1) 28 | 29 | #In this case column 9 contains information about the acrolein distortion energy 30 | y = a[:,8] 31 | ax1.plot(x,y,'-',color='lightcoral',linewidth = 0.5) 32 | 33 | #In this case column 10 contains information about the butadiene distortion energy 34 | y = a[:,9] 35 | ax1.plot(x,y,'-',color='orange',linewidth = 0.5) 36 | 37 | #In this case column 6 contains information about the total energy. Plottet last to be on top. 38 | y = a[:,5] 39 | ax1.plot(x,y,'-',color='k',linewidth = 1) 40 | 41 | #creating the legend 42 | black_patch = mpatches.Patch(color='k', label='Total energy') 43 | red_patch = mpatches.Patch(color='r', label='Distortion energy') 44 | green_patch = mpatches.Patch(color='g', label='Interaction energy') 45 | coral_patch = mpatches.Patch(color='lightcoral', label='Acrolein distortion energy') 46 | orange_patch = mpatches.Patch(color='orange', label='Butadiene distortion energy') 47 | plt.legend(handles=[black_patch,red_patch,coral_patch,orange_patch, green_patch],prop={'size': 5},loc=3,frameon=False) 48 | 49 | f.tight_layout() 50 | 51 | f.savefig('IRC.png') 52 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | matplotlib 3 | --------------------------------------------------------------------------------