mccoygroup-psience 0.0.0__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.
Files changed (55) hide show
  1. mccoygroup-Psience-0.0.0/LICENSE.txt +9 -0
  2. mccoygroup-Psience-0.0.0/PKG-INFO +35 -0
  3. mccoygroup-Psience-0.0.0/Psience/AnalyticModels/AnalyticJacobianDotCalculator.py +511 -0
  4. mccoygroup-Psience-0.0.0/Psience/AnalyticModels/AnalyticModelConstructors.py +236 -0
  5. mccoygroup-Psience-0.0.0/Psience/AnalyticModels/Helpers.py +208 -0
  6. mccoygroup-Psience-0.0.0/Psience/AnalyticModels/__init__.py +7 -0
  7. mccoygroup-Psience-0.0.0/Psience/BasisReps/Bases.py +527 -0
  8. mccoygroup-Psience-0.0.0/Psience/BasisReps/HarmonicOscillator.py +646 -0
  9. mccoygroup-Psience-0.0.0/Psience/BasisReps/Operators.py +1238 -0
  10. mccoygroup-Psience-0.0.0/Psience/BasisReps/Representations.py +1079 -0
  11. mccoygroup-Psience-0.0.0/Psience/BasisReps/StateIndexers.py +140 -0
  12. mccoygroup-Psience-0.0.0/Psience/BasisReps/StateSpaces.py +4372 -0
  13. mccoygroup-Psience-0.0.0/Psience/BasisReps/Wavefunctions.py +236 -0
  14. mccoygroup-Psience-0.0.0/Psience/BasisReps/__init__.py +19 -0
  15. mccoygroup-Psience-0.0.0/Psience/DVR/BaseDVR.py +390 -0
  16. mccoygroup-Psience-0.0.0/Psience/DVR/ColbertMiller.py +349 -0
  17. mccoygroup-Psience-0.0.0/Psience/DVR/DirectProduct.py +283 -0
  18. mccoygroup-Psience-0.0.0/Psience/DVR/Wavefunctions.py +186 -0
  19. mccoygroup-Psience-0.0.0/Psience/DVR/__init__.py +17 -0
  20. mccoygroup-Psience-0.0.0/Psience/Data/KEData.py +22 -0
  21. mccoygroup-Psience-0.0.0/Psience/Data/PsiDatasets/KEData.py +174 -0
  22. mccoygroup-Psience-0.0.0/Psience/Data/PsiDatasets/__init__.py +0 -0
  23. mccoygroup-Psience-0.0.0/Psience/Data/Surfaces.py +268 -0
  24. mccoygroup-Psience-0.0.0/Psience/Data/__init__.py +14 -0
  25. mccoygroup-Psience-0.0.0/Psience/Molecools/CoordinateSystems.py +616 -0
  26. mccoygroup-Psience-0.0.0/Psience/Molecools/Molecule.py +860 -0
  27. mccoygroup-Psience-0.0.0/Psience/Molecools/MoleculeInterface.py +180 -0
  28. mccoygroup-Psience-0.0.0/Psience/Molecools/Properties.py +1932 -0
  29. mccoygroup-Psience-0.0.0/Psience/Molecools/Transformations.py +33 -0
  30. mccoygroup-Psience-0.0.0/Psience/Molecools/Vibrations.py +577 -0
  31. mccoygroup-Psience-0.0.0/Psience/Molecools/__init__.py +16 -0
  32. mccoygroup-Psience-0.0.0/Psience/Spectra/BaseSpectrum.py +349 -0
  33. mccoygroup-Psience-0.0.0/Psience/Spectra/__init__.py +7 -0
  34. mccoygroup-Psience-0.0.0/Psience/VPT2/Analyzer.py +893 -0
  35. mccoygroup-Psience-0.0.0/Psience/VPT2/Common.py +30 -0
  36. mccoygroup-Psience-0.0.0/Psience/VPT2/Corrections.py +702 -0
  37. mccoygroup-Psience-0.0.0/Psience/VPT2/DegeneracySpecs.py +531 -0
  38. mccoygroup-Psience-0.0.0/Psience/VPT2/Hamiltonian.py +1127 -0
  39. mccoygroup-Psience-0.0.0/Psience/VPT2/Runner.py +1009 -0
  40. mccoygroup-Psience-0.0.0/Psience/VPT2/Solver.py +2296 -0
  41. mccoygroup-Psience-0.0.0/Psience/VPT2/StateFilters.py +451 -0
  42. mccoygroup-Psience-0.0.0/Psience/VPT2/Terms.py +2297 -0
  43. mccoygroup-Psience-0.0.0/Psience/VPT2/Wavefunctions.py +1333 -0
  44. mccoygroup-Psience-0.0.0/Psience/VPT2/__init__.py +31 -0
  45. mccoygroup-Psience-0.0.0/Psience/Wavefun/Wavefunctions.py +135 -0
  46. mccoygroup-Psience-0.0.0/Psience/Wavefun/__init__.py +9 -0
  47. mccoygroup-Psience-0.0.0/Psience/__init__.py +28 -0
  48. mccoygroup-Psience-0.0.0/README.md +25 -0
  49. mccoygroup-Psience-0.0.0/mccoygroup_Psience.egg-info/PKG-INFO +35 -0
  50. mccoygroup-Psience-0.0.0/mccoygroup_Psience.egg-info/SOURCES.txt +53 -0
  51. mccoygroup-Psience-0.0.0/mccoygroup_Psience.egg-info/dependency_links.txt +1 -0
  52. mccoygroup-Psience-0.0.0/mccoygroup_Psience.egg-info/requires.txt +1 -0
  53. mccoygroup-Psience-0.0.0/mccoygroup_Psience.egg-info/top_level.txt +1 -0
  54. mccoygroup-Psience-0.0.0/setup.cfg +4 -0
  55. mccoygroup-Psience-0.0.0/setup.py +46 -0
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Mark Boyer, Rachel Huchmala
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,35 @@
1
+ Metadata-Version: 2.1
2
+ Name: mccoygroup-Psience
3
+ Version: 0.0.0
4
+ Summary: UNKNOWN
5
+ Home-page: UNKNOWN
6
+ Author: Mark Boyer
7
+ Author-email: b3m2a1@uw.edu
8
+ License: MIT
9
+ Description: # Psience
10
+
11
+ Psience is a set of core scientific packages written by the McCoy group for the McCoy group to handle interesting scientific problems, like DVR, managing potential and dipole surfaces, VPT2, normal mode analysis, etc.
12
+
13
+ We're working on [documenting the package](https://mccoygroup.github.io/Psience), but writing good documentation takes more time than writing good code.
14
+
15
+ ### Installation & Requirements
16
+
17
+ `Psience` is written in pure python and we've worked to try to avoid any major dependencies outside of what comes in `Anaconda` and our `McUtils` package.
18
+
19
+ The easiest way to install is via `pip`, as
20
+
21
+ ```lang-shell
22
+ pip install mccoygroup-psience
23
+ ```
24
+
25
+ ### Contributing
26
+
27
+ If you'd like to help out with this, we'd love contributions.
28
+ The easiest way to get started with it is to try it out.
29
+ When you find bugs, please [report them](https://github.com/McCoyGroup/Psience/issues/new?title=Bug%20Found:&labels=bug).
30
+ If there are things you'd like added [let us know](https://github.com/McCoyGroup/Psience/issues/new?title=Feature%20Request:&labels=enhancement), and we'll try to help you get the context you need to add them yourself.
31
+ One of the biggest places where people can help out, though, is in improving the quality of the documentation.
32
+ As you try things out, add them as examples, either to the [main page](https://mccoygroup.github.io/References/Documentation/Psience.html#examples) or to a [child page](https://mccoygroup.github.io/References/Documentation/Psience/Molecools/Molecule/Molecule.html#examples).
33
+ You can also edit the docstrings in the code to add context, explanation, argument types, return types, etc.
34
+ Platform: UNKNOWN
35
+ Description-Content-Type: text/markdown
@@ -0,0 +1,511 @@
1
+ """
2
+ Temporary storage for an attempt to get G matrix elements purely symbolically
3
+ but which lacked sufficient information about the coordinates involved
4
+ """
5
+
6
+ import numpy as np
7
+ from .Helpers import sym
8
+
9
+ class OrientationVector:
10
+ def __init__(self, x, y, z, basis):
11
+ self.vec = [x, y, z]
12
+ self.basis = basis
13
+ def __neg__(self):
14
+ return type(self)(*(-a for a in self.vec), self.basis)
15
+ def dot(self, other:'OrientationVector'):
16
+ if other.basis != self.basis:
17
+ raise ValueError("bases not the same: {} vs. {}".format(self.basis, other.basis))
18
+ else:
19
+ return sum(x*y for x,y in zip(self.vec, other.vec))
20
+ @classmethod
21
+ def rotation_matrix(cls, angle, axis:'OrientationVector'):
22
+ # a = list(axis.vec)
23
+ # if a == [1, 0, 0] or a == [-1, 0, 0]:
24
+ # mat = [
25
+ # OrientationVector(sym.cos(angle), -sym.sin(angle), 0, axis.basis),
26
+ # OrientationVector(sym.sin(angle), sym.cos(angle), 0, axis.basis),
27
+ # OrientationVector( 0, 0, 1, axis.basis)
28
+ # ]
29
+ # elif i == 2:
30
+ # mat = [
31
+ # OrientationVector(1, 0, 0, axis.basis),
32
+ # OrientationVector(0, sym.cos(angle), -sym.sin(angle), axis.basis),
33
+ # OrientationVector(0, sym.sin(angle), sym.cos(angle), axis.basis)
34
+ # ]
35
+ # if v == -axis:
36
+ # mat = [-a for a in mat]
37
+ # elif a == [1, 0, 0] or a == [-1, 0, 0]:
38
+ # mat = [
39
+ # OrientationVector(sym.cos(angle), 0, -sym.sin(angle), axis.basis),
40
+ # OrientationVector( 0, 1, 0, axis.basis),
41
+ # OrientationVector(sym.sin(angle), 0, sym.cos(angle), axis.basis)
42
+ # ]
43
+ # else:
44
+ # raise Exception("...")
45
+ a, b, c = axis.vec
46
+ cos = sym.cos(angle)
47
+ ncos = 1-cos
48
+ sin = sym.sin(angle)
49
+ # Base formula: vXv*(1-cos(a) + e3.a * sin(a)
50
+ mat = RotationMatrix(
51
+ OrientationVector(cos + (a**2)*ncos, a*b*ncos - c*sin, a*c*ncos + b*sin, axis.basis),
52
+ OrientationVector( a*b*ncos + c*sin, cos + (b**2)*ncos, b*c*ncos - a*sin, axis.basis),
53
+ OrientationVector( a*c*ncos - b*sin, b*c*ncos + a*sin, cos + (c**2)*ncos, axis.basis)
54
+ )
55
+ return mat
56
+ def __repr__(self):
57
+ return "{}({}, {})".format(type(self).__name__, self.vec, self.basis)
58
+ class RotationMatrix:
59
+ def __init__(self, v1:OrientationVector, v2:OrientationVector, v3:OrientationVector):
60
+ self.vectors = [v1, v2, v3]
61
+ def dot(self, v):
62
+ return OrientationVector(*(r.dot(v) for r in self.vectors), self.vectors[0].basis)
63
+ def __neg__(self):
64
+ return type(self)(*(-x for x in self.vectors))
65
+ class BondVector:
66
+ def __init__(self, i, j, norm=1, embedding=None):
67
+ if i == j:
68
+ raise ValueError("bond from {} to {} invalid".format(i, j))
69
+ self.i = i
70
+ self.j = j
71
+ self.norm = norm
72
+ self.embedding = embedding
73
+
74
+ @property
75
+ def direction(self):
76
+ return type(self)(self.i, self.j, norm=1, embedding=self.embedding)
77
+ def __repr__(self):
78
+ if self.norm == 1:
79
+ return '{}({}->{})'.format(type(self).__name__, self.i, self.j)
80
+ else:
81
+ return '{} {}({}->{})'.format(self.norm, type(self).__name__, self.i, self.j)
82
+ def __neg__(self):
83
+ return type(self)(self.j, self.i, norm=self.norm, embedding=self.embedding)
84
+ def __mul__(self, other):
85
+ return type(self)(self.i, self.j, norm=self.norm*other, embedding=self.embedding)
86
+ def __rmul__(self, other):
87
+ return self.__mul__(other)
88
+ def __add__(self, other):
89
+ return BondVectorSum(self, other)
90
+ def __eq__(self, other):
91
+ return self.dot(other) == 1
92
+ def angle_cos(self, other, require_int=False):
93
+ if isinstance(other, BondNormal):
94
+ return other.angle_cos(self, require_int=require_int)
95
+ elif isinstance(other, BondVectorSum):
96
+ return other.distribute(lambda x:self.angle_cos(x, require_int=require_int))
97
+ else:
98
+ match_table = (
99
+ self.i == other.i,
100
+ self.i == other.j,
101
+ self.j == other.i,
102
+ self.j == other.j
103
+ )
104
+ match_sum = np.sum(match_table)
105
+ if match_sum == 2: # two shared points
106
+ if match_table[0]:
107
+ cos = 1
108
+ else:
109
+ cos = -1
110
+ elif match_sum == 0:
111
+ embedding = self.embedding
112
+ if embedding is None:
113
+ embedding = other.embedding
114
+ if embedding is not None: # TODO: I don't need this for match_sum == 1...
115
+ my_embedding = self.polar_components(embedding, self)
116
+ other_embedding = other.polar_components(embedding, other)
117
+ # print("=" * 50, embedding)
118
+ # print(self, my_embedding)
119
+ # print(other, other_embedding)
120
+ cos = my_embedding.dot(other_embedding).expand()
121
+ # print(cos)
122
+ else:
123
+ if require_int:
124
+ return 2 + np.ravel_multi_index(match_table, (2, 2, 2, 2)) # binary encoding
125
+ # we'll build a composite binary integer so we can keep a single return type
126
+ else:
127
+ if match_table[0]: # shared first atom
128
+ i = self.j
129
+ j = self.i
130
+ k = other.j
131
+ sign = 1
132
+ elif match_table[1]: # shared first and second
133
+ i = self.j
134
+ j = self.i
135
+ k = other.i
136
+ sign = -1
137
+ elif match_table[2]: # shared second and first
138
+ i = self.i
139
+ j = self.j
140
+ k = other.j
141
+ sign = -1
142
+ else: # shared second atom
143
+ i = self.i
144
+ j = self.j
145
+ k = other.i
146
+ sign = 1
147
+ cos = sign * sym.cos(InternalJacobianDisplacements.symbolic_a(i, j, k))
148
+ return cos
149
+ def dot(self, other:'BondVector'):
150
+ if isinstance(other, BondVectorSum):
151
+ return other.distribute(
152
+ lambda x:self.angle_cos(x) * self.norm * x.norm
153
+ ).simplify()
154
+ else:
155
+ acos = self.angle_cos(other)
156
+ return acos * self.norm * other.norm
157
+ def cross(self, other):
158
+ if isinstance(other, BondVectorSum):
159
+ return other.distribute(
160
+ lambda x:self.cross(x)
161
+ )
162
+ else:
163
+ sangle = sym.sin(sym.acos(self.angle_cos(other))).trigsimp().subs(sym.Abs, sym.Id)
164
+ return BondNormal(self, other,
165
+ norm=self.norm * other.norm * sangle,
166
+ embedding=self.embedding if self.embedding is not None else other.embedding
167
+ )
168
+
169
+ @classmethod
170
+ def polar_components(self, embedding_atoms, vector):
171
+ # returns the components and the basis for the embdedding
172
+ # of atom l with respect to reference atoms i, j, and k
173
+ i, j, k = embedding_atoms
174
+ # embedding = [
175
+ # BondVector(k, j),
176
+ # BondVector(j, k).cross(BondVector(i, j)),
177
+ # BondVector(j, k).cross(BondVector(i, j)).cross(BondVector(j, k))
178
+ # ]
179
+ basis = [
180
+ BondVector(k, j),
181
+ BondNormal(BondVector(k, j),
182
+ BondNormal(BondVector(k, j), BondVector(j, i))
183
+ ),
184
+ BondNormal(BondVector(k, j), BondVector(j, i))
185
+ ]
186
+ kk = vector.i
187
+ l = vector.j
188
+ # print("?", (kk, l), embedding_atoms)
189
+ sign = 1
190
+ # given that our x is k->j we have two cases:
191
+ # 1. all stuff inside i,j,k (simple rotations)
192
+ # 2. i->l, j->l, or k->l (rotation + out of plane transform)
193
+ if ( # simple canonicalizations to cut down on the number of cases
194
+ l == k or
195
+ kk == i or
196
+ kk not in (i, j, k)
197
+ ): # flip the vector and multiply all components by negative one
198
+ kk, l = l, kk
199
+ sign = -1
200
+ matches = [l in (i, j, k), kk in (i, j, k)]
201
+ match_sum = np.sum(matches)
202
+
203
+ if match_sum == 2: # simple rotation of main
204
+ if kk == k: # vector is k->i
205
+ if l == i:
206
+ angle = InternalJacobianDisplacements.symbolic_a(j, k, i)
207
+ else: # k->j
208
+ angle = None
209
+ elif l == k:
210
+ raise NotImplementedError("...?")
211
+ else: # l == i and k == j
212
+ angle = InternalJacobianDisplacements.symbolic_a(i, j, k) - sym.pi
213
+ if angle is None:
214
+ components = OrientationVector(1, 0, 0, basis)
215
+ else:
216
+ rmat = OrientationVector.rotation_matrix(angle, OrientationVector(0, 0, 1, basis))
217
+ v = OrientationVector(1, 0, 0, basis)
218
+ components = rmat.dot(v)
219
+ elif match_sum == 0:
220
+ raise NotImplementedError("this shouldn't happen...")
221
+ else: # rotation in plane and rotation out of plane
222
+ if kk == i:
223
+ chob = InternalJacobianDisplacements.symbolic_a(i, j, k)
224
+ angle = -InternalJacobianDisplacements.symbolic_a(j, i, l)
225
+ # eij = -eji = -R(a[ijk]).ekj
226
+ polar = InternalJacobianDisplacements.symbolic_t(k, j, i, l)
227
+ elif kk == k:
228
+ chob = None
229
+ angle = InternalJacobianDisplacements.symbolic_a(j, k, l)
230
+ polar = InternalJacobianDisplacements.symbolic_t(i, j, k, l)
231
+ elif kk == j: # this feels wrong...
232
+ chob = None
233
+ angle = InternalJacobianDisplacements.symbolic_a(k, j, l)
234
+ polar = InternalJacobianDisplacements.symbolic_t(k, i, j, l)
235
+ else:
236
+ raise NotImplementedError("case: {}->{} in {}".format(kk, l, embedding_atoms))
237
+
238
+ ax = OrientationVector(1, 0, 0, basis)
239
+ # if chob is not None:
240
+ # cr = -OrientationVector.rotation_matrix(chob, OrientationVector(0, 0, 1, basis))
241
+ # ax = cr.dot(ax)
242
+ v = ax
243
+ amat = OrientationVector.rotation_matrix(angle, OrientationVector(0, 0, 1, basis))
244
+ v = amat.dot(v)
245
+ tmat = OrientationVector.rotation_matrix(polar, ax)
246
+ v = tmat.dot(v)
247
+ if chob is not None:
248
+ cr = OrientationVector.rotation_matrix(chob, OrientationVector(0, 0, 1, basis))
249
+ v = cr.dot(v)
250
+ components = v
251
+ if sign < 0:
252
+ components = -components
253
+ # print(components)
254
+ return components
255
+ class BondNormal:
256
+ def __init__(self, a, b, norm=1, embedding=None):
257
+ self.a = a
258
+ self.b = b
259
+ if abs(self.a.angle_cos(self.b, require_int=True)) == 1:
260
+ raise NotImplementedError('cross product of vector with itself ill defined')
261
+ self.norm = norm
262
+ if embedding is None:
263
+ embedding = a.embedding
264
+ if embedding is None:
265
+ embedding = b.embedding
266
+ if embedding is None:
267
+ if isinstance(a, BondVector):
268
+ i = a.i
269
+ j = a.j
270
+ k = b.i if b.i not in (i, j) else b.j
271
+ embedding = (i, j, k)
272
+ self.embedding = embedding
273
+ @property
274
+ def direction(self):
275
+ return type(self)(self.b, self.a, norm=1, embedding=self.embedding)
276
+ def __repr__(self):
277
+ if self.norm == 1:
278
+ return '{}({}x{})'.format(type(self).__name__, self.a, self.b)
279
+ else:
280
+ return '{} {}({}x{})'.format(self.norm, type(self).__name__, self.a, self.b)
281
+ def __neg__(self):
282
+ return type(self)(self.b, self.a, norm=self.norm, embedding=self.embedding)
283
+ def __rmul__(self, other):
284
+ return self.__mul__(other)
285
+ def __mul__(self, other):
286
+ return type(self)(self.a, self.b, norm=self.norm*other, embedding=self.embedding)
287
+ def __add__(self, other):
288
+ return BondVectorSum(self, other)
289
+ def __sub__(self, other):
290
+ return BondVectorSum(self, -other)
291
+ def __eq__(self, other):
292
+ return self.dot(other) == 1
293
+ @classmethod
294
+ def polar_components(cls, embedding, self):
295
+ a = self.a.polar_components(embedding, self.a)
296
+ a1, a2, a3 = a.vec
297
+ b = self.b.polar_components(embedding, self.b)
298
+ b1, b2, b3 = b.vec
299
+ sangle = sym.sin(sym.acos(self.a.angle_cos(self.b))).trigsimp().subs(sym.Abs, sym.Id)
300
+ return OrientationVector( # gotta normalize...
301
+ (a2*b3 - b2*a3)/sangle,
302
+ (b1*a3 - a1*b3)/sangle,
303
+ (a1*b2 - b1*a2)/sangle,
304
+ a.basis
305
+ )
306
+ def angle_cos(self, other, require_int=True):
307
+ if isinstance(other, BondVector):
308
+ alignment_1 = self.a.angle_cos(other, require_int=True)
309
+ alignment_2 = self.b.angle_cos(other, require_int=True)
310
+ if abs(alignment_1) == 1 or abs(alignment_2) == 1: # cross product is perpendicular to contained vector
311
+ cos = 0
312
+ else:
313
+ embedding = self.embedding
314
+ if embedding is None:
315
+ embedding = other.embedding
316
+ if embedding is not None:
317
+ # print("="*50)
318
+ # print(self.direction)
319
+ # print(other.direction)
320
+ my_embedding = self.polar_components(embedding, self)
321
+ # print("!", my_embedding)
322
+ other_embedding = other.polar_components(embedding, other)
323
+ # print("+", other_embedding.vec)
324
+ cos = my_embedding.dot(other_embedding)
325
+ # print(">", cos)
326
+ else:
327
+ raise NotImplementedError("{}".format(self))
328
+ # raise NotImplementedError('angle between vector and vector cross not symbolically useful ({} and {})'.format(
329
+ # self,
330
+ # other
331
+ # ))
332
+ else:
333
+
334
+ alignments = [
335
+ self.a.angle_cos(other.a, require_int=True),
336
+ self.a.angle_cos(other.b, require_int=True),
337
+ self.b.angle_cos(other.a, require_int=True),
338
+ self.b.angle_cos(other.b, require_int=True)
339
+ ]
340
+ matches = [
341
+ abs(x) == 1 for x in alignments
342
+ ]
343
+ match_sum = sum(matches)
344
+ if match_sum == 0:
345
+ raise NotImplementedError('angle between vector cross over different atoms not symbolically useful')
346
+ elif match_sum == 2:
347
+ # just need to determine the sign of alignment...
348
+ if matches[0]: # same vectors basically, but we still need to account for signs
349
+ cos = alignments[0] * alignments[3]
350
+ else: # flipped ordering so we pick up a negative sign
351
+ cos = -alignments[1] * alignments[2]
352
+ else:
353
+ # only one of the vectors matched, which means either we have some kind of dihedral angle
354
+ # or we have zero, which we can check by checking to total cross product alignment
355
+ if matches[0] or matches[2]:
356
+ subalignment = self.angle_cos(other.b, require_int=True)
357
+ else:
358
+ subalignment = self.angle_cos(other.a, require_int=True)
359
+
360
+ if abs(subalignment) == 1: # gotta be perpendicular...
361
+ cos = 0
362
+ else: # need to pull out components to return the correct dihedral...
363
+ if matches[0]: # shared first bond, making these atoms j and k
364
+ i = self.b.unshared_atom(self.a)
365
+ j = self.b.shared_atom(self.a)
366
+ k = self.a.unshared_atom(self.b)
367
+ l = other.b.unshared_atom(other.a)
368
+ elif matches[1]: # shared first and second
369
+ i = self.b.unshared_atom(self.a)
370
+ j = self.b.shared_atom(self.a)
371
+ k = self.a.unshared_atom(self.b)
372
+ l = other.a.unshared_atom(other.b)
373
+ elif matches[2]: # shared second and first
374
+ i = self.a.unshared_atom(self.b)
375
+ j = self.a.shared_atom(self.b)
376
+ k = self.b.unshared_atom(self.a)
377
+ l = other.b.unshared_atom(other.a)
378
+ else: # shared second atom
379
+ i = self.a.unshared_atom(self.b)
380
+ j = self.a.shared_atom(self.b)
381
+ k = self.b.unshared_atom(self.a)
382
+ l = other.a.unshared_atom(other.b)
383
+ cos = sym.cos(InternalJacobianDisplacements.symbolic_t(i, j, k, l))
384
+
385
+ return cos
386
+ def cross(self, other):
387
+ if isinstance(other, BondVectorSum):
388
+ return other.distribute(
389
+ lambda x: self.cross(x)
390
+ )
391
+ else:
392
+ sangle = sym.sin(sym.acos(self.angle_cos(other))).trigsimp().subs(sym.Abs, sym.Id)
393
+ return BondNormal(self, other,
394
+ norm=self.norm * other.norm * sangle,
395
+ embedding=self.embedding if self.embedding is not None else other.embedding
396
+ )
397
+ def dot(self, other):
398
+ return self.angle_cos(other) * self.norm * other.norm
399
+ class BondVectorSum:
400
+ def __init__(self, *terms):
401
+ self.terms = terms
402
+ def __add__(self, other):
403
+ if isinstance(other, BondVectorSum):
404
+ return type(self)(*self.terms, *other.terms)
405
+ else:
406
+ return type(self)(*self.terms, other)
407
+ def __rmul__(self, other):
408
+ return self.distribute(lambda a:other*a)
409
+ def __mul__(self, other):
410
+ return self.distribute(lambda a:other*a)
411
+ def __repr__(self):
412
+ return "{}({})".format(type(self).__name__, self.terms)
413
+ def simplify(self):
414
+ return sum(self.terms)
415
+ def distribute(self, f):
416
+ return type(self)(*(f(a) for a in self.terms))
417
+ def dot(self, other):
418
+ return self.distribute(lambda a:a.dot(other))
419
+
420
+ class InternalJacobianDisplacements:
421
+
422
+ @staticmethod
423
+ def symbolic_m(i):
424
+ return sym.Symbol('m[{i}]'.format(i=i), positive=True)
425
+
426
+ @staticmethod
427
+ def symbolic_r(i, j):
428
+ if j < i:
429
+ i, j = j, i
430
+ return sym.Symbol('r[{i},{j}]'.format(i=i, j=j), positive=True)
431
+
432
+ @staticmethod
433
+ def symbolic_a(i, j, k):
434
+ if k < i:
435
+ i, k = k, i
436
+ return sym.Symbol('a[{i},{j},{k}]'.format(i=i, j=j, k=k), real=True)
437
+
438
+ @staticmethod
439
+ def symbolic_t(i, j, k, l):
440
+ if l < i:
441
+ i, j, k, l = l, k, j, i
442
+ return sym.Symbol('t[{i},{j},{k},{l}]'.format(i=i, j=j, k=k, l=l), real=True)
443
+
444
+ @staticmethod
445
+ def symbolic_y(i, j, k, l):
446
+ return sym.Symbol('y[{i},{j},{k},{l}]'.format(i=i, j=j, k=k, l=l), real=True)
447
+
448
+ @classmethod
449
+ def lam(cls, i, j, k):
450
+ r12 = cls.symbolic_r(i, j)
451
+ r23 = cls.symbolic_r(j, k)
452
+ a = cls.symbolic_a(i, j, k)
453
+ return 1 / sym.sin(a) * (1 / r12 - sym.cos(a) / r23)
454
+
455
+ @classmethod
456
+ def dr(cls, i, j):
457
+ return [
458
+ BondVector(j, i),
459
+ BondVector(i, j)
460
+ ]
461
+
462
+ @classmethod
463
+ def da(cls, i, j, k):
464
+ e12 = BondVector(i, j, embedding=(i, j, k))
465
+ r12 = cls.symbolic_r(i, j)
466
+ e23 = BondVector(j, k, embedding=(i, j, k))
467
+ r23 = cls.symbolic_r(j, k)
468
+ a = cls.symbolic_a(i, j, k)
469
+ l321 = cls.lam(k, j, i)
470
+ l123 = cls.lam(i, j, k)
471
+ return [
472
+ 1 / (r12 * sym.sin(a)) * (sym.cos(a) * e12 + e23),
473
+ -l321 * e12 + l123 * e23,
474
+ 1 / (r23 * sym.sin(a)) * (e12 + sym.cos(a) * e23)
475
+ ]
476
+
477
+ @classmethod
478
+ def dt(cls, i, j, k, l):
479
+ e12 = BondVector(i, j, embedding=(i, j, k))
480
+ r12 = cls.symbolic_r(i, j)
481
+ e23 = BondVector(j, k, embedding=(i, j, k))
482
+ r23 = cls.symbolic_r(j, k)
483
+ e34 = BondVector(k, l, embedding=(i, j, k))
484
+ r34 = cls.symbolic_r(k, l)
485
+ a1 = cls.symbolic_a(i, j, k)
486
+ a2 = cls.symbolic_a(j, k, l)
487
+ l123 = cls.lam(i, j, k)
488
+ l432 = cls.lam(l, k, j)
489
+ x123 = BondNormal(e12, e23)
490
+ x432 = BondNormal(e34, e23)
491
+ return [
492
+ -1 / (r12 * sym.sin(a1)) * x123,
493
+ l123 * x123 + sym.cot(a2) / r23 * x432,
494
+ l432 * x432 + sym.cot(a1) / r23 * x123,
495
+ -1 / (r34 * sym.sin(a2)) * x432
496
+ ]
497
+
498
+ @classmethod
499
+ def dy(cls, i, j, k, l):
500
+ raise NotImplementedError("oops")
501
+
502
+ @classmethod
503
+ def displacement_vectors(cls, inds):
504
+ if len(inds) == 2:
505
+ return cls.dr(*inds)
506
+ elif len(inds) == 3:
507
+ return cls.da(*inds)
508
+ elif len(inds) == 4:
509
+ return cls.dt(*inds)
510
+ else:
511
+ return cls.dy(*inds)