webmo 1.3.3__tar.gz → 1.4.2__tar.gz

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.
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: webmo
3
- Version: 1.3.3
3
+ Version: 1.4.2
4
4
  Summary: A Python-based interface to WebMO
5
5
  Home-page: https://github.com/jrschmidt2/webmo-python
6
- Download-URL: https://github.com/jrschmidt2/webmo-python/archive/refs/tags/1.3.3.tar.gz
6
+ Download-URL: https://github.com/jrschmidt2/webmo-python/archive/refs/tags/1.4.2.tar.gz
7
7
  Author: J.R. Schmidt
8
8
  Author-email: schmidt@webmo.net
9
9
  License: MIT
@@ -1,2 +1,2 @@
1
1
  from .webmo_rest import WebMOREST
2
- __version__ = "1.3.3"
2
+ __version__ = "1.4.2"
@@ -50,6 +50,8 @@ class JupyterGUI(_WebMOGUIBase):
50
50
  #fail silently, since this should never happen in Jupyter
51
51
  pass #print('ipywidgets not found; disabling support for JupyterGUI')
52
52
 
53
+ import time
54
+
53
55
  def __init__(self, template, query_vars, additional_vars=None):
54
56
  """Constructor for JupyterGUI
55
57
 
@@ -66,6 +68,7 @@ class JupyterGUI(_WebMOGUIBase):
66
68
 
67
69
  _WebMOGUIBase.__init__(self, template, query_vars, additional_vars)
68
70
  self._widgets = []
71
+ self._display_time = -1
69
72
 
70
73
  for var in query_vars:
71
74
  if template['variables'][var]['type'] == 'checkbox':
@@ -93,11 +96,20 @@ class JupyterGUI(_WebMOGUIBase):
93
96
 
94
97
  def display(self):
95
98
 
99
+ self._display_time = self.time.time()
100
+
96
101
  for widget in self._widgets:
97
102
  display(widget)
98
103
 
99
104
  def get_variables(self):
100
105
 
106
+ MIN_INPUT_TIME = 1.0
107
+ # sanity check to make sure user has had time to input something
108
+ # fail this call during re-execution of entire notebook at once
109
+ elapsed_time = self.time.time() - self._display_time
110
+ if self._display_time == -1 or elapsed_time < MIN_INPUT_TIME:
111
+ raise RuntimeError('Must provide JupyterGUI input before calling get_variables')
112
+
101
113
  # add in additional user-specified variables
102
114
  if (self._additional_vars != None):
103
115
  variables = {**self._additional_vars}
@@ -330,8 +330,10 @@ def voigt_line(center, intensity, width=10, q=0.5, start=0, stop=4000, step=1):
330
330
  Returns:
331
331
  (x,y): a tuple of the x and y numpy arrays of the specified line
332
332
  """
333
- if (not 0 < q < 1):
334
- q = 0.5
333
+ if (q <= 0):
334
+ return(gauss_line(center, intensity, width, start, stop, step))
335
+ elif (q >= 1):
336
+ return(lorentz_line(center,intensity,width,start,stop,step))
335
337
 
336
338
  x = np.arange(start=start, stop=stop, step=step)
337
339
  vfunc = np.vectorize(_arb_voigt(center, intensity, q, width))
@@ -345,7 +347,7 @@ def _arb_gaussian(center, intensity, width=10):
345
347
  from math import sqrt, pi, exp, log
346
348
 
347
349
  sigma = (width / (2 * sqrt(2 * log(2))))
348
- l = lambda x: intensity * (1/sigma*sqrt(2*pi)) * exp(-((x-center)**2/(2*sigma**2)))
350
+ l = lambda x: intensity * (1/(sigma*sqrt(2*pi))) * exp(-((x-center)**2/(2*sigma**2)))
349
351
 
350
352
  return l
351
353
 
@@ -403,7 +405,7 @@ def lorentz_line(center, intensity, width=10, start=0, stop=4000, step=1):
403
405
  vfunc = np.vectorize(_arb_lorentz(center, intensity, width))
404
406
  return((x,vfunc(x)))
405
407
 
406
- def construct_spectrum(points, intensities, lineshape="gauss", width=10, start=0, stop=4000, step=1):
408
+ def construct_spectrum(points, intensities, lineshape="gauss", frac_loren=0.5, width=10, start=0, stop=4000, step=1):
407
409
  """Construct an entire spectrum plot of data
408
410
 
409
411
  This function returns a pair of numpy arrays that make a spectrum; an x axis
@@ -415,6 +417,7 @@ def construct_spectrum(points, intensities, lineshape="gauss", width=10, start=0
415
417
  intensities([float]): a list of corresponding peak intensities - the order must
416
418
  match `points`
417
419
  lineshape(string,optional): the desired lineshape. The options are "gauss", "lorentz", and "voigt"
420
+ frac_loren(float,optional): the fraction lorentzian of the voigt lineshape, if specified
418
421
  width(float,optional): the full width half max of each peak
419
422
  start(float,optional): the starting point of the returned array
420
423
  stop(float,optional): the ending point of the returned array
@@ -428,10 +431,10 @@ def construct_spectrum(points, intensities, lineshape="gauss", width=10, start=0
428
431
  linefuncs = {
429
432
  "gauss": gauss_line,
430
433
  "lorentz": lorentz_line,
431
- "voigt": voigt_line
434
+ "voigt": lambda x, y, **kwargs: voigt_line(x,y,q=frac_loren, **kwargs)
432
435
  }
433
436
  if (lineshape not in linefuncs.keys()):
434
- linefunc = gauss_line
437
+ raise AssertionError("invalid lineshape specified")
435
438
  else:
436
439
  linefunc = linefuncs[lineshape]
437
440
 
@@ -0,0 +1,287 @@
1
+ import numpy as np
2
+
3
+ def xyz_from_name(name):
4
+ """Obtains an XYZ-formatted geometry for the molecule name via PubChem lookup.
5
+
6
+ Arguments:
7
+ name(str): The name of the molecule.
8
+
9
+ Returns:
10
+ A string containing the molecule's XYZ-formatted geometry.
11
+ """
12
+ import pubchempy as pcp
13
+
14
+ results = pcp.get_compounds(name, 'name', record_type='3d')
15
+ if (len(results) == 0):
16
+ raise ValueError("Specified molecule could not be found in PubChem")
17
+
18
+ record = results[0]
19
+ geom = ""
20
+ for atom in record.atoms:
21
+ element = atom.element
22
+ x = float(atom.x)
23
+ y = float(atom.y)
24
+ z = float(atom.z)
25
+ geom += f"{element}\t{x:f}\t{y:f}\t{z:f}\n"
26
+
27
+ return geom
28
+
29
+ def get_energy(results, include_units=False):
30
+ """Returns the molecular energy from the job results. This should correspond to the ground state energy
31
+ at molecule at the requested level of theory.
32
+
33
+ Requires WebMO 25 or higher.
34
+
35
+ Arguments:
36
+ dict(results): A dictionary of job results, obtained from get_job_results.
37
+ bool(include_units): Whether to include the units with the returned energy (e.g. 1.5 vs. "1.5 Hartree")
38
+
39
+ Returns:
40
+ The molecular energy.
41
+ """
42
+ properties = results['properties']
43
+
44
+ if not 'method_energy_name' in properties.keys():
45
+ raise RuntimeError('Cannot locate "method_energy_name" in results')
46
+ method_energy_name = properties['method_energy_name'].lower().replace(' ', '_') + '_energy'
47
+
48
+ found = False
49
+ #also check related key names, since Gaussian prepends some things occasionally
50
+ for key in [method_energy_name, 'r' + method_energy_name, 'u' + method_energy_name, 'ro' + method_energy_name]:
51
+ if key in properties:
52
+ energy = properties[key]['value']
53
+ units = properties[key]['units']
54
+ found = True
55
+
56
+ if not found:
57
+ raise RuntimeError('Could not locate "' + method_energy_name + '" in results')
58
+
59
+ if include_units:
60
+ energy = f"{energy} {units}"
61
+
62
+ return energy
63
+
64
+ def get_energies(results, include_units=False):
65
+ """Returns a dictionary of available energies from the job results.
66
+
67
+ Arguments:
68
+ dict(results): A dictionary of job results, obtained from get_job_results.
69
+ bool(include_units): Whether to include the units with the returned energy (e.g. 1.5 vs. "1.5 Hartree")
70
+
71
+ Returns:
72
+ A dictionary of available energies.
73
+ """
74
+ properties = results['properties']
75
+ energies = {}
76
+
77
+ for key in properties.keys():
78
+ index = key.find('_energy')
79
+
80
+ if key.endswith('_energy'):
81
+ index = key.find('_energy')
82
+ energy_type = key[:index]
83
+
84
+ if include_units:
85
+ energies[energy_type] = f"{properties[key]['value']} {properties[key]['units']}"
86
+ else:
87
+ energies[energy_type] = properties[key]['value']
88
+
89
+ return energies
90
+
91
+ def get_geometry(results):
92
+ """Returns the XYZ-formatted final geometry from the job results.
93
+
94
+ Arguments:
95
+ dict(results): A dictionary of job results, obtained from get_job_results.
96
+
97
+ Returns:
98
+ The XYZ-formatted final geometry as a string.
99
+ """
100
+
101
+
102
+ geom = ""
103
+ index = 0
104
+ for element in results['symbols']:
105
+
106
+ x = results['geometry'][index]
107
+ y = results['geometry'][index + 1]
108
+ z = results['geometry'][index + 2]
109
+
110
+ geom += f"{element}\t{x:f}\t{y:f}\t{z:f}\n"
111
+ index += 3
112
+
113
+ return geom
114
+
115
+ def get_stoichiometry(results):
116
+ """Returns the stoichiometry from the job results.
117
+
118
+ Arguments:
119
+ dict(results): A dictionary of job results, obtained from get_job_results.
120
+
121
+ Returns:
122
+ The stoichiometry as a string.
123
+ """
124
+
125
+ properties = results['properties']
126
+ return properties['stoichiometry']
127
+
128
+ def get_dipole_moment(results):
129
+ """Returns the dipole moment from the job results.
130
+
131
+ Arguments:
132
+ dict(results): A dictionary of job results, obtained from get_job_results.
133
+
134
+ Returns:
135
+ The magnitude of the dipole moment.
136
+ """
137
+
138
+ from math import sqrt
139
+
140
+ properties = results['properties']
141
+ dipole = properties['dipole_moment']
142
+ mag = sqrt(dipole[0]**2 + dipole[1]**2 + dipole[2]**2)
143
+
144
+ return mag
145
+
146
+ def get_property(results, property_name):
147
+ """Returns an arbitrary calculated property from the job results.
148
+
149
+ Arguments:
150
+ dict(results): A dictionary of job results, obtained from get_job_results.
151
+ str(property_name): The name of the desired property
152
+
153
+ Returns:
154
+ The value of the calculated property (scalar or dictionary).
155
+ """
156
+
157
+ properties = results['properties']
158
+ return properties[property_name]
159
+
160
+
161
+ def get_bond_length(results, index1, index2):
162
+ """Returns the specified bond length between two atoms from the job results OR an xyz-formatted string.
163
+
164
+ Arguments:
165
+ dict(results): A dictionary of job results obtained from get_job_results OR an xyz-formatted string.
166
+ index1(int): The one-based index of the first atom of the bonded pair
167
+ index2(int): The one-based index of the second atom of the bonded pair
168
+
169
+ Returns:
170
+ The calculated bond length.
171
+ """
172
+
173
+ if not isinstance(results, dict):
174
+ geom = _xyz_to_array(results)
175
+ else:
176
+ geom = results['geometry']
177
+
178
+ #convert to 0 index
179
+ index1 -= 1
180
+ index2 -= 1
181
+
182
+ atom1 = np.array(geom[index1*3:index1*3 + 3])
183
+ atom2 = np.array(geom[index2*3:index2*3 + 3])
184
+
185
+ return np.linalg.norm(atom1 - atom2)
186
+
187
+ def get_bond_angle(results, index1, index2, index3):
188
+ """Returns the specified bond angle between three atoms from the job results OR an xyz-formatted string.
189
+
190
+ Arguments:
191
+ dict(results): A dictionary of job results obtained from get_job_results OR an xyz-formatted string.
192
+ index1(int): The one-based index of the first atom
193
+ index2(int): The one-based index of the second atom
194
+ index3(int): The one-based index of the third atom
195
+
196
+ Returns:
197
+ The calculated bond angle.
198
+ """
199
+
200
+ if not isinstance(results, dict):
201
+ geom = _xyz_to_array(results)
202
+ else:
203
+ geom = results['geometry']
204
+
205
+ #convert to 0 index
206
+ index1 -= 1
207
+ index2 -= 1
208
+ index3 -= 1
209
+
210
+ atom1 = np.array(geom[index1*3:index1*3 + 3])
211
+ atom2 = np.array(geom[index2*3:index2*3 + 3])
212
+ atom3 = np.array(geom[index3*3:index3*3 + 3])
213
+
214
+ vec1 = atom1 - atom2
215
+ vec2 = atom3 - atom2
216
+
217
+ vec1 /= np.linalg.norm(vec1)
218
+ vec2 /= np.linalg.norm(vec2)
219
+
220
+ return np.arccos(np.dot(vec1,vec2))*180./np.pi
221
+
222
+ def get_dihedral_angle(results, index1, index2, index3, index4):
223
+ """Returns the specified dihedral angle between four atoms from the job results OR an xyz-formatted string.
224
+
225
+ Arguments:
226
+ dict(results): A dictionary of job results obtained from get_job_results OR an xyz-formatted string.
227
+ index1(int): The one-based index of the first atom
228
+ index2(int): The one-based index of the second atom
229
+ index3(int): The one-based index of the third atom
230
+ index4(int): The one-based index of the fourth atom
231
+
232
+ Returns:
233
+ The calculated dihedral angle.
234
+ """
235
+
236
+ if not isinstance(results, dict):
237
+ geom = _xyz_to_array(results)
238
+ else:
239
+ geom = results['geometry']
240
+
241
+ #convert to 0 index
242
+ index1 -= 1
243
+ index2 -= 1
244
+ index3 -= 1
245
+ index4 -= 1
246
+
247
+ atom1 = np.array(geom[index1*3:index1*3 + 3])
248
+ atom2 = np.array(geom[index2*3:index2*3 + 3])
249
+ atom3 = np.array(geom[index3*3:index3*3 + 3])
250
+ atom4 = np.array(geom[index4*3:index4*3 + 3])
251
+
252
+ b1 = atom1 - atom2
253
+ b2 = atom2 - atom3
254
+ b3 = atom3 - atom4
255
+
256
+ b1 /= np.linalg.norm(b1)
257
+ b2 /= np.linalg.norm(b2)
258
+ b3 /= np.linalg.norm(b3)
259
+
260
+ n1 = np.cross(b1, b2)
261
+ n2 = np.cross(b2, b3)
262
+ m = np.cross(n1, b2)
263
+
264
+ x = np.dot(n1, n2)
265
+ y = np.dot(m, n2)
266
+
267
+ return np.arctan2(y, x)*180./np.pi
268
+
269
+ def _xyz_to_array(xyz):
270
+ xyz = xyz.strip()
271
+
272
+ try:
273
+ #throw exception if NOT a number, indicating that our XYZ file has no header
274
+ tokens = xyz.split()
275
+ int(tokens[0])
276
+ #this XYZ formatted string is proper, containing a header that must be removed
277
+ xyz = xyz.split("\n",2)[2]
278
+ except ValueError:
279
+ pass
280
+
281
+ #process the actual geometry of the XYZ file, converting into an array
282
+ geom = xyz.split()
283
+ #remove the element names, leaving only an array of positions
284
+ del geom[0::4]
285
+
286
+ #convert to an array of floats and return
287
+ return np.array(geom,dtype=float)
@@ -621,22 +621,10 @@ class WebMOREST:
621
621
  #
622
622
  # Helper functions
623
623
  #
624
- def wait_for_job(self, job_number, poll_frequency=5):
625
- """Waits for completion of the specified WebMO job
624
+ def wait_for_job(self, *job_numbers, poll_frequency=5):
625
+ """Waits for completion of the specified WebMO jobs(s)
626
626
 
627
- This call blocks until the specified WebMO job has finished executing (successfully or not)
628
-
629
- Arguments:
630
- job_number(int): The job number to wait for
631
- poll_frequency(int, optional): The frequency at which to check the job status (default is 5s)
632
- """
633
-
634
- self.wait_for_jobs([job_number], poll_frequency)
635
-
636
- def wait_for_jobs(self, job_numbers, poll_frequency=5):
637
- """Waits for completion of the specified list of WebMO jobs
638
-
639
- This call blocks until the specified WebMO jobs have all finished executing (successfully or not)
627
+ This call blocks until the specified WebMO job(s) have all finished executing (successfully or not)
640
628
 
641
629
  Arguments:
642
630
  job_numbers(list): A list of job numbers which will be waited upon
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: webmo
3
- Version: 1.3.3
3
+ Version: 1.4.2
4
4
  Summary: A Python-based interface to WebMO
5
5
  Home-page: https://github.com/jrschmidt2/webmo-python
6
- Download-URL: https://github.com/jrschmidt2/webmo-python/archive/refs/tags/1.3.3.tar.gz
6
+ Download-URL: https://github.com/jrschmidt2/webmo-python/archive/refs/tags/1.4.2.tar.gz
7
7
  Author: J.R. Schmidt
8
8
  Author-email: schmidt@webmo.net
9
9
  License: MIT
webmo-1.3.3/webmo/util.py DELETED
@@ -1,25 +0,0 @@
1
- def xyz_from_name(name):
2
- """Obtains an XYZ-formatted geometry for the molecule name via PubChem lookup.
3
-
4
- Arguments:
5
- name(str): The name of the molecule.
6
-
7
- Returns:
8
- A string containing the molecule's XYZ-formatted geometry.
9
- """
10
- import pubchempy as pcp
11
-
12
- results = pcp.get_compounds(name, 'name', record_type='3d')
13
- if (len(results) == 0):
14
- raise ValueError("Specified molecule could not be found in PubChem")
15
-
16
- record = results[0]
17
- geom = ""
18
- for atom in record.atoms:
19
- element = atom.element
20
- x = float(atom.x)
21
- y = float(atom.y)
22
- z = float(atom.z)
23
- geom += f"{element}\t{x:f}\t{y:f}\t{z:f}\n"
24
-
25
- return geom
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes