ben-chem-tools 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from .geometries import xyz_atom
|
|
2
|
+
from .geometries import xyz_molecule
|
|
3
|
+
|
|
4
|
+
from .input_creation import g16_input
|
|
5
|
+
from .input_creation import stationary_point_count
|
|
6
|
+
from .input_creation import get_n_geometry
|
|
7
|
+
from .input_creation import rerun_from_last_geom
|
|
8
|
+
from .input_creation import quick_rerun_optimizations
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import periodictable as pt
|
|
3
|
+
|
|
4
|
+
##################################################
|
|
5
|
+
# This file holds objects for geometries like
|
|
6
|
+
# atoms and molecules.
|
|
7
|
+
##################################################
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class xyz_atom:
|
|
11
|
+
|
|
12
|
+
def __init__(self,atom_label:str,x_coord:float,y_coord:float,z_coord:float):
|
|
13
|
+
|
|
14
|
+
self.atom_label = str(atom_label).split()[0]
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
self.x_coord = float(x_coord)
|
|
18
|
+
except ValueError:
|
|
19
|
+
raise ValueError("x_coord must be a numeric value.")
|
|
20
|
+
try:
|
|
21
|
+
self.y_coord = float(x_coord)
|
|
22
|
+
except ValueError:
|
|
23
|
+
raise ValueError("y_coord must be a numeric value.")
|
|
24
|
+
try:
|
|
25
|
+
self.z_coord = float(x_coord)
|
|
26
|
+
except ValueError:
|
|
27
|
+
raise ValueError("z_coord must be a numeric value.")
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def from_string(cls,atom_string:str):
|
|
31
|
+
atom_string = str(atom_string).strip().split()
|
|
32
|
+
if len(atom_string) !=4:
|
|
33
|
+
raise ValueError(f"Supplied string splits in to {len(atom_string)} items. Method requires 4.")
|
|
34
|
+
return cls(*atom_string)
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def from_cclib_vals(cls,atom_num:int,coords:list[float]):
|
|
38
|
+
try:
|
|
39
|
+
atom_label = pt.elements[int(atom_num)]
|
|
40
|
+
except ValueError:
|
|
41
|
+
atom_label = atom_num
|
|
42
|
+
except KeyError:
|
|
43
|
+
KeyError(f"atom number {atom_num} not found")
|
|
44
|
+
if len(coords) != 3:
|
|
45
|
+
raise ValueError(f"coords is {len(coords)} long. It must have a length of 3.")
|
|
46
|
+
return cls(atom_label,coords[0],coords[1],coords[2])
|
|
47
|
+
|
|
48
|
+
def as_list(self):
|
|
49
|
+
return [self.atom_label,[self.x_coord,self.y_coord,self.z_coord]]
|
|
50
|
+
|
|
51
|
+
def apply_transormation(self,transformation_matrix,inplace=False):
|
|
52
|
+
|
|
53
|
+
transformation_matrix = np.array(transformation_matrix)
|
|
54
|
+
transformation_matrix = transformation_matrix.reshape((3,3))
|
|
55
|
+
position_vector = np.array([self.x_coord,self.y_coord,self.z_coord])
|
|
56
|
+
position_vector = position_vector @ transformation_matrix
|
|
57
|
+
|
|
58
|
+
if inplace:
|
|
59
|
+
self.x_coord = float(position_vector[0])
|
|
60
|
+
self.y_coord = float(position_vector[1])
|
|
61
|
+
self.z_coord = float(position_vector[2])
|
|
62
|
+
return position_vector
|
|
63
|
+
|
|
64
|
+
def __str__(self):
|
|
65
|
+
return f"{self.atom_label} {self.x_coord:.6f} {self.y_coord:.6f} {self.z_coord:.6f}"
|
|
66
|
+
|
|
67
|
+
def __repr__(self):
|
|
68
|
+
return f"{self.atom_label} {self.x_coord:.6f} {self.y_coord:.6f} {self.z_coord:.6f}"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class xyz_molecule:
|
|
74
|
+
|
|
75
|
+
def __init__(self,atom_list:list[xyz_atom]):
|
|
76
|
+
self.atom_list:list[xyz_atom] = atom_list
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def from_cclib_vals(cls,atom_nums,atom_coords):
|
|
80
|
+
acc = []
|
|
81
|
+
for atom_num,atom_coord in zip(atom_nums,atom_coords):
|
|
82
|
+
acc.append(xyz_atom.from_cclib_vals(atom_num,atom_coords))
|
|
83
|
+
return cls(acc)
|
|
84
|
+
|
|
85
|
+
@classmethod
|
|
86
|
+
def from_string(cls,molecule_string:str):
|
|
87
|
+
molecule_string = molecule_string.strip()
|
|
88
|
+
acc = []
|
|
89
|
+
for atom_string in molecule_string.split("\n"):
|
|
90
|
+
acc.append(xyz_atom.from_string(atom_string))
|
|
91
|
+
return cls(acc)
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
def from_xyz_file(cls,file_path:str):
|
|
95
|
+
with open(file_path,"r") as file:
|
|
96
|
+
file_lines = file.readlines()
|
|
97
|
+
num_atoms = int(file_lines[0].strip())
|
|
98
|
+
acc = []
|
|
99
|
+
for line in file_lines[2:2+num_atoms]:
|
|
100
|
+
acc.append(xyz_atom.from_string(line))
|
|
101
|
+
return cls(acc)
|
|
102
|
+
|
|
103
|
+
def add_atom(self,new_atom):
|
|
104
|
+
self.atom_list.append(new_atom)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def as_list(self):
|
|
109
|
+
return [atom.as_list() for atom in self.atom_list]
|
|
110
|
+
|
|
111
|
+
def write_xyz_file(self,file_name,comment_line=""):
|
|
112
|
+
with open(file_name,"w") as outfile:
|
|
113
|
+
outfile.write(f"{len(self.atom_list)}\n")
|
|
114
|
+
outfile.write(f"{comment_line}\n")
|
|
115
|
+
for atom in self.atom_list:
|
|
116
|
+
outfile.write(f"{str(atom)}\n")
|
|
117
|
+
|
|
118
|
+
def apply_transformation(self,transformation_matrix,inplace=False):
|
|
119
|
+
transformed_coords = []
|
|
120
|
+
for atom in self.atom_list:
|
|
121
|
+
transformed_coords.append(atom.apply_transormation(transformation_matrix,inplace))
|
|
122
|
+
return transformed_coords
|
|
123
|
+
|
|
124
|
+
def __str__(self):
|
|
125
|
+
acc = ""
|
|
126
|
+
for atom in self.atom_list:
|
|
127
|
+
acc += f"{str(atom)}\n"
|
|
128
|
+
acc = acc.strip()
|
|
129
|
+
return acc
|
|
130
|
+
|
|
131
|
+
def __repr__(self):
|
|
132
|
+
acc = ""
|
|
133
|
+
for atom in self.atom_list:
|
|
134
|
+
acc += f"{str(atom)}\n"
|
|
135
|
+
acc = acc.strip()
|
|
136
|
+
return acc
|
|
137
|
+
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
from glob import glob
|
|
2
|
+
import cclib
|
|
3
|
+
import periodictable as pt
|
|
4
|
+
from geometries import xyz_molecule, xyz_atom
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class g16_input:
|
|
9
|
+
""" a base level g16 input file
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, input_line:str, geometry:xyz_molecule, file_name:str,charge:int,spin_mult:int, nproc = 36,mem = 5):
|
|
13
|
+
self.checkpoint = file_name[:-3] + "chk"
|
|
14
|
+
self.file_name = file_name
|
|
15
|
+
self.geometry = geometry
|
|
16
|
+
self.mem = mem
|
|
17
|
+
self.nproc = nproc
|
|
18
|
+
self.input_line = input_line.lower()
|
|
19
|
+
self.extra = ""
|
|
20
|
+
self.title_card = "Title Card"
|
|
21
|
+
self.charge = charge
|
|
22
|
+
self.spin_mult = spin_mult
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def write_file(self):
|
|
26
|
+
"""writes a file associated with the g16 input instance
|
|
27
|
+
"""
|
|
28
|
+
out_string = f"""%chk={self.checkpoint}
|
|
29
|
+
%nproc={self.nproc}
|
|
30
|
+
%mem={self.mem}GB
|
|
31
|
+
#p {self.input_line}
|
|
32
|
+
|
|
33
|
+
{self.title_card}
|
|
34
|
+
|
|
35
|
+
{self.charge} {self.spin_mult}
|
|
36
|
+
{str(self.geometry)}
|
|
37
|
+
|
|
38
|
+
{self.extra}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
"""
|
|
42
|
+
with open(self.file_name,"w") as file:
|
|
43
|
+
file.write(out_string)
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def from_file(cls,file_name):
|
|
47
|
+
collect_input = False
|
|
48
|
+
input_line = ""
|
|
49
|
+
nproc = 36
|
|
50
|
+
mem = 5
|
|
51
|
+
charge_collected = False
|
|
52
|
+
charge = 0
|
|
53
|
+
spin_mult = 1
|
|
54
|
+
Title_Card = False
|
|
55
|
+
blank_counter = 0
|
|
56
|
+
geometry = xyz_molecule([])
|
|
57
|
+
|
|
58
|
+
with open(file_name,"r") as file:
|
|
59
|
+
for line in file:
|
|
60
|
+
true_line = line.strip(" \n")
|
|
61
|
+
|
|
62
|
+
if "%mem" in true_line:
|
|
63
|
+
mem = int(true_line.split("=")[1][:-2])
|
|
64
|
+
|
|
65
|
+
if "%nproc" in true_line:
|
|
66
|
+
nproc = int(true_line.split("=")[1])
|
|
67
|
+
|
|
68
|
+
if collect_input == True and blank_counter == 0:
|
|
69
|
+
input_line = input_line + " " + line.strip(" \n")
|
|
70
|
+
|
|
71
|
+
if "#p" in true_line:
|
|
72
|
+
collect_input = True
|
|
73
|
+
input_line = input_line + line.strip("\n")
|
|
74
|
+
|
|
75
|
+
if blank_counter == 2 and charge_collected and true_line != "":
|
|
76
|
+
geometry.add_atom(xyz_atom.from_string(true_line))
|
|
77
|
+
|
|
78
|
+
if blank_counter == 2 and charge_collected == False:
|
|
79
|
+
true_line = line.strip(" \n\t")
|
|
80
|
+
charge_mult = true_line.split()
|
|
81
|
+
charge = charge_mult[0]
|
|
82
|
+
spin_mult = charge_mult[1]
|
|
83
|
+
charge_collected = True
|
|
84
|
+
|
|
85
|
+
if true_line.strip("\t") == "":
|
|
86
|
+
blank_counter = blank_counter + 1
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
return cls(input_line,geometry,file_name,charge,spin_mult,nproc = nproc,mem = mem)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def stationary_point_count(output_file_pattern:str="*.log"):
|
|
94
|
+
"""Evaluates files for number of stationary points
|
|
95
|
+
Parameters
|
|
96
|
+
----------
|
|
97
|
+
output_file_pattern (str): a file pattern to match and evaluate stationary points.
|
|
98
|
+
|
|
99
|
+
Returns
|
|
100
|
+
-------
|
|
101
|
+
list[tuple[str,int]]: a list of tuples containing the names of the log files and the number of stationary points"""
|
|
102
|
+
|
|
103
|
+
results ={}
|
|
104
|
+
|
|
105
|
+
for output_file in glob(output_file_pattern):
|
|
106
|
+
results[output_file] = 0
|
|
107
|
+
with open(output_file) as current_file:
|
|
108
|
+
for line in current_file:
|
|
109
|
+
results[output_file] += "Stationary point" in line
|
|
110
|
+
|
|
111
|
+
results = [(key,results[key]) for key in results.keys()]
|
|
112
|
+
if len(results.keys()) == 0:
|
|
113
|
+
raise FileExistsError(f"No files found to match pattern '{output_file_pattern}'.")
|
|
114
|
+
results = sorted(results,key = lambda x: x[1])
|
|
115
|
+
return results
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def get_n_geometry(log_file_name:str,n_geometry:int = -1):
|
|
119
|
+
"""Grabs the nth geometry (zero indexed) from a log file. Defaults to the last geometry
|
|
120
|
+
Parameters
|
|
121
|
+
----------
|
|
122
|
+
log_file_name (str): the path to a log file
|
|
123
|
+
|
|
124
|
+
n_geometry (int): the index of the desired geometry
|
|
125
|
+
|
|
126
|
+
Returns
|
|
127
|
+
-------
|
|
128
|
+
xyz_molecule"""
|
|
129
|
+
|
|
130
|
+
outfile = cclib.io.ccread(log_file_name)
|
|
131
|
+
atom_nums = outfile.atomnos
|
|
132
|
+
correct_geom = outfile.geometries[n_geometry]
|
|
133
|
+
return xyz_molecule.from_cclib_vals(atom_nums,correct_geom)
|
|
134
|
+
|
|
135
|
+
def rerun_from_last_geom(file_to_rerun):
|
|
136
|
+
"""reruns a job from the last geometry found in the log file.
|
|
137
|
+
Parameters
|
|
138
|
+
----------
|
|
139
|
+
file_to_rerun (str): the path to a log file
|
|
140
|
+
|
|
141
|
+
Returns
|
|
142
|
+
-------
|
|
143
|
+
None: reformats and resubmits g16 input"""
|
|
144
|
+
new_geometry = get_n_geometry(file_to_rerun)
|
|
145
|
+
old_input = g16_input.from_file(file_to_rerun[:-3]+"gjf")
|
|
146
|
+
old_input.geometry = new_geometry
|
|
147
|
+
old_input.write_file()
|
|
148
|
+
print(f"Rerunning {file_to_rerun[:-4]}")
|
|
149
|
+
os.system(f"sbatch {file_to_rerun[:-3]+"s"}")
|
|
150
|
+
|
|
151
|
+
def quick_rerun_optimizations(log_file_pattern:str="*.log"):
|
|
152
|
+
"""Reruns any files with only one stationary point, and notifies about files with 0 stationary points
|
|
153
|
+
Parameters
|
|
154
|
+
----------
|
|
155
|
+
|
|
156
|
+
log_file_pattern (str): a pattern for the path of files to rerun.
|
|
157
|
+
|
|
158
|
+
Returns
|
|
159
|
+
-------
|
|
160
|
+
None: reruns files with only 1 stationary point and prints advice for files with 0."""
|
|
161
|
+
statp_eval = stationary_point_count(log_file_pattern)
|
|
162
|
+
|
|
163
|
+
for file,statp_count in statp_eval:
|
|
164
|
+
if statp_count == 1:
|
|
165
|
+
rerun_from_last_geom(file)
|
|
166
|
+
elif statp_count == 0:
|
|
167
|
+
print(f"{file} requires manual check, not Stationary point found")
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: ben-chem-tools
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: common useful tools for computational chemistry
|
|
5
|
+
Author: Ben
|
|
6
|
+
Author-email: Ben <bennyp2494@gmail.com>
|
|
7
|
+
Requires-Dist: cclib>=1.8.1
|
|
8
|
+
Requires-Dist: matplotlib>=3.9.4
|
|
9
|
+
Requires-Dist: numpy>=2.0.2
|
|
10
|
+
Requires-Dist: pandas>=2.3.3
|
|
11
|
+
Requires-Dist: periodictable>=2.0.2
|
|
12
|
+
Requires-Dist: scipy>=1.13.1
|
|
13
|
+
Requires-Dist: seaborn>=0.13.2
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
ben_chem_tools/__init__.py,sha256=WxmVwpqbKRK5Jskrg2h7bRw5eWxB-Kk11uU4Rt8T8uY,306
|
|
2
|
+
ben_chem_tools/geometries.py,sha256=_JHTUmUrjnw1gaUu4Yvbfw-S2hlTop3-_CNNJAGvUpo,4695
|
|
3
|
+
ben_chem_tools/input_creation.py,sha256=udpR2qlmKLe4EnAnswbj5QfZyPssgqtcBb6o24fNo-I,5327
|
|
4
|
+
ben_chem_tools-0.1.0.dist-info/WHEEL,sha256=ZyFSCYkV2BrxH6-HRVRg3R9Fo7MALzer9KiPYqNxSbo,79
|
|
5
|
+
ben_chem_tools-0.1.0.dist-info/entry_points.txt,sha256=EdNGlhX6tyQEG45tvftVxZWsuL91D4dBhsZlf4JGlNY,56
|
|
6
|
+
ben_chem_tools-0.1.0.dist-info/METADATA,sha256=nMizn1F49XpXzoxhXirgg1_f5_EA9J62ejBwL5sFm-o,446
|
|
7
|
+
ben_chem_tools-0.1.0.dist-info/RECORD,,
|