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.
- mccoygroup-Psience-0.0.0/LICENSE.txt +9 -0
- mccoygroup-Psience-0.0.0/PKG-INFO +35 -0
- mccoygroup-Psience-0.0.0/Psience/AnalyticModels/AnalyticJacobianDotCalculator.py +511 -0
- mccoygroup-Psience-0.0.0/Psience/AnalyticModels/AnalyticModelConstructors.py +236 -0
- mccoygroup-Psience-0.0.0/Psience/AnalyticModels/Helpers.py +208 -0
- mccoygroup-Psience-0.0.0/Psience/AnalyticModels/__init__.py +7 -0
- mccoygroup-Psience-0.0.0/Psience/BasisReps/Bases.py +527 -0
- mccoygroup-Psience-0.0.0/Psience/BasisReps/HarmonicOscillator.py +646 -0
- mccoygroup-Psience-0.0.0/Psience/BasisReps/Operators.py +1238 -0
- mccoygroup-Psience-0.0.0/Psience/BasisReps/Representations.py +1079 -0
- mccoygroup-Psience-0.0.0/Psience/BasisReps/StateIndexers.py +140 -0
- mccoygroup-Psience-0.0.0/Psience/BasisReps/StateSpaces.py +4372 -0
- mccoygroup-Psience-0.0.0/Psience/BasisReps/Wavefunctions.py +236 -0
- mccoygroup-Psience-0.0.0/Psience/BasisReps/__init__.py +19 -0
- mccoygroup-Psience-0.0.0/Psience/DVR/BaseDVR.py +390 -0
- mccoygroup-Psience-0.0.0/Psience/DVR/ColbertMiller.py +349 -0
- mccoygroup-Psience-0.0.0/Psience/DVR/DirectProduct.py +283 -0
- mccoygroup-Psience-0.0.0/Psience/DVR/Wavefunctions.py +186 -0
- mccoygroup-Psience-0.0.0/Psience/DVR/__init__.py +17 -0
- mccoygroup-Psience-0.0.0/Psience/Data/KEData.py +22 -0
- mccoygroup-Psience-0.0.0/Psience/Data/PsiDatasets/KEData.py +174 -0
- mccoygroup-Psience-0.0.0/Psience/Data/PsiDatasets/__init__.py +0 -0
- mccoygroup-Psience-0.0.0/Psience/Data/Surfaces.py +268 -0
- mccoygroup-Psience-0.0.0/Psience/Data/__init__.py +14 -0
- mccoygroup-Psience-0.0.0/Psience/Molecools/CoordinateSystems.py +616 -0
- mccoygroup-Psience-0.0.0/Psience/Molecools/Molecule.py +860 -0
- mccoygroup-Psience-0.0.0/Psience/Molecools/MoleculeInterface.py +180 -0
- mccoygroup-Psience-0.0.0/Psience/Molecools/Properties.py +1932 -0
- mccoygroup-Psience-0.0.0/Psience/Molecools/Transformations.py +33 -0
- mccoygroup-Psience-0.0.0/Psience/Molecools/Vibrations.py +577 -0
- mccoygroup-Psience-0.0.0/Psience/Molecools/__init__.py +16 -0
- mccoygroup-Psience-0.0.0/Psience/Spectra/BaseSpectrum.py +349 -0
- mccoygroup-Psience-0.0.0/Psience/Spectra/__init__.py +7 -0
- mccoygroup-Psience-0.0.0/Psience/VPT2/Analyzer.py +893 -0
- mccoygroup-Psience-0.0.0/Psience/VPT2/Common.py +30 -0
- mccoygroup-Psience-0.0.0/Psience/VPT2/Corrections.py +702 -0
- mccoygroup-Psience-0.0.0/Psience/VPT2/DegeneracySpecs.py +531 -0
- mccoygroup-Psience-0.0.0/Psience/VPT2/Hamiltonian.py +1127 -0
- mccoygroup-Psience-0.0.0/Psience/VPT2/Runner.py +1009 -0
- mccoygroup-Psience-0.0.0/Psience/VPT2/Solver.py +2296 -0
- mccoygroup-Psience-0.0.0/Psience/VPT2/StateFilters.py +451 -0
- mccoygroup-Psience-0.0.0/Psience/VPT2/Terms.py +2297 -0
- mccoygroup-Psience-0.0.0/Psience/VPT2/Wavefunctions.py +1333 -0
- mccoygroup-Psience-0.0.0/Psience/VPT2/__init__.py +31 -0
- mccoygroup-Psience-0.0.0/Psience/Wavefun/Wavefunctions.py +135 -0
- mccoygroup-Psience-0.0.0/Psience/Wavefun/__init__.py +9 -0
- mccoygroup-Psience-0.0.0/Psience/__init__.py +28 -0
- mccoygroup-Psience-0.0.0/README.md +25 -0
- mccoygroup-Psience-0.0.0/mccoygroup_Psience.egg-info/PKG-INFO +35 -0
- mccoygroup-Psience-0.0.0/mccoygroup_Psience.egg-info/SOURCES.txt +53 -0
- mccoygroup-Psience-0.0.0/mccoygroup_Psience.egg-info/dependency_links.txt +1 -0
- mccoygroup-Psience-0.0.0/mccoygroup_Psience.egg-info/requires.txt +1 -0
- mccoygroup-Psience-0.0.0/mccoygroup_Psience.egg-info/top_level.txt +1 -0
- mccoygroup-Psience-0.0.0/setup.cfg +4 -0
- 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)
|