firecode 1.0.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.
- firecode/TEST_NOTEBOOK.ipynb +3940 -0
- firecode/__init__.py +0 -0
- firecode/__main__.py +118 -0
- firecode/_gaussian.py +97 -0
- firecode/algebra.py +405 -0
- firecode/ase_manipulations.py +879 -0
- firecode/atropisomer_module.py +516 -0
- firecode/automep.py +130 -0
- firecode/calculators/__init__.py +29 -0
- firecode/calculators/_gaussian.py +98 -0
- firecode/calculators/_mopac.py +242 -0
- firecode/calculators/_openbabel.py +154 -0
- firecode/calculators/_orca.py +129 -0
- firecode/calculators/_xtb.py +786 -0
- firecode/concurrent_test.py +119 -0
- firecode/embedder.py +2590 -0
- firecode/embedder_options.py +577 -0
- firecode/embeds.py +881 -0
- firecode/errors.py +65 -0
- firecode/graph_manipulations.py +333 -0
- firecode/hypermolecule_class.py +364 -0
- firecode/mep_relaxer.py +199 -0
- firecode/modify_settings.py +186 -0
- firecode/mprof.py +65 -0
- firecode/multiembed.py +148 -0
- firecode/nci.py +186 -0
- firecode/numba_functions.py +260 -0
- firecode/operators.py +776 -0
- firecode/optimization_methods.py +609 -0
- firecode/parameters.py +84 -0
- firecode/pka.py +275 -0
- firecode/profiler.py +17 -0
- firecode/pruning.py +421 -0
- firecode/pt.py +32 -0
- firecode/quotes.json +6651 -0
- firecode/quotes.py +9 -0
- firecode/reactive_atoms_classes.py +666 -0
- firecode/references.py +11 -0
- firecode/rmsd.py +74 -0
- firecode/settings.py +75 -0
- firecode/solvents.py +126 -0
- firecode/tests/C2F2H4.xyz +10 -0
- firecode/tests/C2H4.xyz +8 -0
- firecode/tests/CH3Cl.xyz +7 -0
- firecode/tests/HCOOH.xyz +7 -0
- firecode/tests/HCOOOH.xyz +8 -0
- firecode/tests/chelotropic.txt +3 -0
- firecode/tests/cyclical.txt +3 -0
- firecode/tests/dihedral.txt +2 -0
- firecode/tests/string.txt +3 -0
- firecode/tests/trimolecular.txt +9 -0
- firecode/tests.py +151 -0
- firecode/torsion_module.py +1035 -0
- firecode/utils.py +541 -0
- firecode-1.0.0.dist-info/LICENSE +165 -0
- firecode-1.0.0.dist-info/METADATA +321 -0
- firecode-1.0.0.dist-info/RECORD +59 -0
- firecode-1.0.0.dist-info/WHEEL +5 -0
- firecode-1.0.0.dist-info/top_level.txt +1 -0
firecode/__init__.py
ADDED
|
File without changes
|
firecode/__main__.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
'''
|
|
3
|
+
|
|
4
|
+
FIRECODE: Filtering Refiner and Embedder for Conformationally Dense Ensembles
|
|
5
|
+
Copyright (C) 2021-2024 Nicolò Tampellini
|
|
6
|
+
|
|
7
|
+
This program is free software: you can redistribute it and/or modify
|
|
8
|
+
it under the terms of the GNU General Public License as published by
|
|
9
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
(at your option) any later version.
|
|
11
|
+
|
|
12
|
+
This program is distributed in the hope that it will be useful,
|
|
13
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
GNU General Public License for more details.
|
|
16
|
+
|
|
17
|
+
https://github.com/ntampellini/firecode
|
|
18
|
+
|
|
19
|
+
Nicolo' Tampellini - nicolo.tampellini@yale.edu
|
|
20
|
+
|
|
21
|
+
'''
|
|
22
|
+
import argparse
|
|
23
|
+
import os
|
|
24
|
+
import sys
|
|
25
|
+
|
|
26
|
+
__version__ = '1.0.0'
|
|
27
|
+
|
|
28
|
+
if __name__ == '__main__':
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
usage = '''\n\n 🔥 python -m firecode [-h] [-s] [-t] inputfile [-n NAME]
|
|
32
|
+
|
|
33
|
+
positional arguments:
|
|
34
|
+
inputfile Input filename, can be any text file.
|
|
35
|
+
|
|
36
|
+
optional arguments:
|
|
37
|
+
-h, --help Show this help message and exit.
|
|
38
|
+
-s, --setup Guided setup of the calculation settings.
|
|
39
|
+
-t, --test Perform some tests to check the software setup.
|
|
40
|
+
-n, --name NAME Specify a custom name for the run.
|
|
41
|
+
-cl,--command_line Read instructions from the command line instead of from an input file.
|
|
42
|
+
-c, --cite Print citation links.
|
|
43
|
+
-p, --profile Profile the run through cProfiler.
|
|
44
|
+
-b, --benchmark FILE Benchmark the geometry optimization of FILE (.xyz) to get the optimal number of procs per job.
|
|
45
|
+
--procs Number of processors to be used by each optimization job.
|
|
46
|
+
'''
|
|
47
|
+
|
|
48
|
+
parser = argparse.ArgumentParser(usage=usage)
|
|
49
|
+
parser.add_argument("-s", "--setup", help="Guided setup of the calculation settings.", action="store_true")
|
|
50
|
+
parser.add_argument("-t", "--test", help="Perform some tests to check the software setup.", action="store_true")
|
|
51
|
+
parser.add_argument("-cl", "--command_line", help="Read instructions from the command line instead of from an input file.", action="store")
|
|
52
|
+
parser.add_argument("inputfile", help="Input filename, can be any text file.", action='store', nargs='?', default=None)
|
|
53
|
+
parser.add_argument("-n", "--name", help="Specify a custom name for the run.", action='store', required=False)
|
|
54
|
+
parser.add_argument("-c", "--cite", help="Print the appropriate document links for citation purposes.", action='store_true', required=False)
|
|
55
|
+
parser.add_argument("-p", "--profile", help="Profile the run through cProfiler.", action='store_true', required=False)
|
|
56
|
+
parser.add_argument("-b", "--benchmark", help=("Benchmark the geometry optimization of FILE to get the optimal number " +
|
|
57
|
+
"of procs per job."), action='store', required=False, default=False)
|
|
58
|
+
# parser.add_argument("-r", "--restart", help="Restarts previous run from an embedder.pickle object.", action='store', required=False, default=False)
|
|
59
|
+
parser.add_argument("--procs", help="Number of processors to be used by each optimization job.", action='store', required=False, default=None)
|
|
60
|
+
|
|
61
|
+
args = parser.parse_args()
|
|
62
|
+
|
|
63
|
+
if (not (args.test or args.setup or args.command_line or args.benchmark)) and args.inputfile is None:
|
|
64
|
+
parser.error("One of the following arguments are required: inputfile, -t, -s, -b.\n")
|
|
65
|
+
|
|
66
|
+
if args.benchmark:
|
|
67
|
+
from firecode.concurrent_test import run_concurrent_test
|
|
68
|
+
run_concurrent_test(args.benchmark)
|
|
69
|
+
sys.exit()
|
|
70
|
+
|
|
71
|
+
if args.setup:
|
|
72
|
+
from firecode.modify_settings import run_setup
|
|
73
|
+
run_setup()
|
|
74
|
+
sys.exit()
|
|
75
|
+
|
|
76
|
+
if args.cite:
|
|
77
|
+
print('No citation link is available for FIRECODE yet. You can link to the code on https://www.github.com/ntampellini/firecode')
|
|
78
|
+
sys.exit()
|
|
79
|
+
|
|
80
|
+
if args.test:
|
|
81
|
+
from firecode.tests import run_tests
|
|
82
|
+
run_tests()
|
|
83
|
+
sys.exit()
|
|
84
|
+
|
|
85
|
+
if args.command_line:
|
|
86
|
+
|
|
87
|
+
filename = 'input_firecode.txt'
|
|
88
|
+
with open(filename, 'w') as f:
|
|
89
|
+
f.write(args.command_line)
|
|
90
|
+
|
|
91
|
+
args.inputfile = filename
|
|
92
|
+
|
|
93
|
+
filename = os.path.realpath(args.inputfile)
|
|
94
|
+
|
|
95
|
+
from firecode.embedder import Embedder
|
|
96
|
+
|
|
97
|
+
if args.profile:
|
|
98
|
+
from firecode.profiler import profiled_wrapper
|
|
99
|
+
profiled_wrapper(filename, args.name)
|
|
100
|
+
sys.exit()
|
|
101
|
+
|
|
102
|
+
# if args.restart:
|
|
103
|
+
# import pickle
|
|
104
|
+
# with open(args.restart, 'rb') as _f:
|
|
105
|
+
# embedder = pickle.load(_f)
|
|
106
|
+
# initialize embedder from pickle file
|
|
107
|
+
|
|
108
|
+
# embedder.run()
|
|
109
|
+
# run the program
|
|
110
|
+
|
|
111
|
+
# import faulthandler
|
|
112
|
+
# faulthandler.enable()
|
|
113
|
+
|
|
114
|
+
embedder = Embedder(filename, stamp=args.name, procs=args.procs)
|
|
115
|
+
# initialize embedder from input file
|
|
116
|
+
|
|
117
|
+
embedder.run()
|
|
118
|
+
# run the program
|
firecode/_gaussian.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
'''
|
|
3
|
+
FIRECODE: Filtering Refiner and Embedder for Conformationally Dense Ensembles
|
|
4
|
+
Copyright (C) 2021-2024 Nicolò Tampellini
|
|
5
|
+
|
|
6
|
+
SPDX-License-Identifier: LGPL-3.0-or-later
|
|
7
|
+
|
|
8
|
+
This program is free software: you can redistribute it and/or modify
|
|
9
|
+
it under the terms of the GNU Lesser General Public License as published by
|
|
10
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
11
|
+
(at your option) any later version.
|
|
12
|
+
|
|
13
|
+
This program is distributed in the hope that it will be useful,
|
|
14
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
GNU Lesser General Public License for more details.
|
|
17
|
+
|
|
18
|
+
You should have received a copy of the GNU Lesser General Public License
|
|
19
|
+
along with this program. If not, see
|
|
20
|
+
https://www.gnu.org/licenses/lgpl-3.0.en.html#license-text.
|
|
21
|
+
|
|
22
|
+
'''
|
|
23
|
+
import sys
|
|
24
|
+
from subprocess import DEVNULL, STDOUT, check_call
|
|
25
|
+
|
|
26
|
+
from firecode.settings import COMMANDS, MEM_GB
|
|
27
|
+
from firecode.solvents import get_solvent_line
|
|
28
|
+
from firecode.utils import clean_directory, pt, read_xyz
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def gaussian_opt(coords, atomnos, constrained_indices=None, method='PM6', procs=1, solvent=None, title='temp', read_output=True, **kwargs):
|
|
32
|
+
'''
|
|
33
|
+
This function writes a Gaussian .inp file, runs it with the subprocess
|
|
34
|
+
module and reads its output.
|
|
35
|
+
|
|
36
|
+
:params coords: array of shape (n,3) with cartesian coordinates for atoms.
|
|
37
|
+
:params atomnos: array of atomic numbers for atoms.
|
|
38
|
+
:params constrained_indices: array of shape (n,2), with the indices
|
|
39
|
+
of atomic pairs to be constrained.
|
|
40
|
+
:params method: string, specifiyng the first line of keywords for the MOPAC input file.
|
|
41
|
+
:params title: string, used as a file name and job title for the mopac input file.
|
|
42
|
+
:params read_output: Whether to read the output file and return anything.
|
|
43
|
+
'''
|
|
44
|
+
|
|
45
|
+
s = ''
|
|
46
|
+
|
|
47
|
+
if MEM_GB is not None:
|
|
48
|
+
if MEM_GB < 1:
|
|
49
|
+
s += f'%mem={int(1000*MEM_GB)}MB\n'
|
|
50
|
+
else:
|
|
51
|
+
s += f'%mem={MEM_GB}GB\n'
|
|
52
|
+
|
|
53
|
+
if procs > 1:
|
|
54
|
+
s += f'%nprocshared={procs}\n'
|
|
55
|
+
|
|
56
|
+
s = '# opt ' if constrained_indices is not None else '# opt=modredundant '
|
|
57
|
+
s += method
|
|
58
|
+
|
|
59
|
+
if solvent is not None:
|
|
60
|
+
s += ' ' + get_solvent_line(solvent, 'GAUSSIAN', method)
|
|
61
|
+
|
|
62
|
+
s += '\n\nGaussian input generated by FIRECODE\n\n0 1\n'
|
|
63
|
+
|
|
64
|
+
for i, atom in enumerate(coords):
|
|
65
|
+
s += '%s % .6f % .6f % .6f\n' % (pt[atomnos[i]].symbol, atom[0], atom[1], atom[2])
|
|
66
|
+
|
|
67
|
+
s += '\n'
|
|
68
|
+
|
|
69
|
+
if constrained_indices is not None:
|
|
70
|
+
|
|
71
|
+
for a, b in constrained_indices:
|
|
72
|
+
s += 'B %s %s F\n' % (a+1, b+1) # Gaussian numbering starts at 1
|
|
73
|
+
|
|
74
|
+
s = ''.join(s)
|
|
75
|
+
with open(f'{title}.com', 'w') as f:
|
|
76
|
+
f.write(s)
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
check_call(f'{COMMANDS["GAUSSIAN"]} {title}.com'.split(), stdout=DEVNULL, stderr=STDOUT)
|
|
80
|
+
|
|
81
|
+
except KeyboardInterrupt:
|
|
82
|
+
print('KeyboardInterrupt requested by user. Quitting.')
|
|
83
|
+
sys.exit()
|
|
84
|
+
|
|
85
|
+
if read_output:
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
data = read_xyz(f'{title}.out')
|
|
89
|
+
opt_coords = data.atomcoords[0]
|
|
90
|
+
energy = data.scfenergies[-1] * 23.060548867 # eV to kcal/mol
|
|
91
|
+
|
|
92
|
+
clean_directory((f'{title}.com',))
|
|
93
|
+
|
|
94
|
+
return opt_coords, energy, True
|
|
95
|
+
|
|
96
|
+
except FileNotFoundError:
|
|
97
|
+
return None, None, False
|
firecode/algebra.py
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
'''
|
|
2
|
+
|
|
3
|
+
FIRECODE: Filtering Refiner and Embedder for Conformationally Dense Ensembles
|
|
4
|
+
Copyright (C) 2021-2024 Nicolò Tampellini
|
|
5
|
+
|
|
6
|
+
This program is free software: you can redistribute it and/or modify
|
|
7
|
+
it under the terms of the GNU General Public License as published by
|
|
8
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
(at your option) any later version.
|
|
10
|
+
|
|
11
|
+
This program is distributed in the hope that it will be useful,
|
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
GNU General Public License for more details.
|
|
15
|
+
|
|
16
|
+
'''
|
|
17
|
+
from math import sqrt
|
|
18
|
+
|
|
19
|
+
import numba as nb
|
|
20
|
+
import numpy as np
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@nb.njit
|
|
24
|
+
def dihedral(p):
|
|
25
|
+
'''
|
|
26
|
+
Returns dihedral angle in degrees from 4 3D vecs
|
|
27
|
+
Praxeolitic formula: 1 sqrt, 1 cross product
|
|
28
|
+
|
|
29
|
+
'''
|
|
30
|
+
p0 = p[0]
|
|
31
|
+
p1 = p[1]
|
|
32
|
+
p2 = p[2]
|
|
33
|
+
p3 = p[3]
|
|
34
|
+
|
|
35
|
+
b0 = -1.0*(p1 - p0)
|
|
36
|
+
b1 = p2 - p1
|
|
37
|
+
b2 = p3 - p2
|
|
38
|
+
|
|
39
|
+
# normalize b1 so that it does not influence magnitude of vector
|
|
40
|
+
# rejections that come next
|
|
41
|
+
b1 /= norm_of(b1)
|
|
42
|
+
|
|
43
|
+
# vector rejections
|
|
44
|
+
# v = projection of b0 onto plane perpendicular to b1
|
|
45
|
+
# = b0 minus component that aligns with b1
|
|
46
|
+
# w = projection of b2 onto plane perpendicular to b1
|
|
47
|
+
# = b2 minus component that aligns with b1
|
|
48
|
+
v = b0 - np.dot(b0, b1)*b1
|
|
49
|
+
w = b2 - np.dot(b2, b1)*b1
|
|
50
|
+
|
|
51
|
+
# angle between v and w in a plane is the torsion angle
|
|
52
|
+
# v and w may not be normalized but that's fine since tan is y/x
|
|
53
|
+
x = np.dot(v, w)
|
|
54
|
+
y = np.dot(np.cross(b1, v), w)
|
|
55
|
+
|
|
56
|
+
return np.degrees(np.arctan2(y, x))
|
|
57
|
+
|
|
58
|
+
@nb.njit
|
|
59
|
+
def vec_angle(v1, v2):
|
|
60
|
+
v1_u = norm(v1)
|
|
61
|
+
v2_u = norm(v2)
|
|
62
|
+
return np.arccos(clip(np.dot(v1_u, v2_u), -1.0, 1.0))*180/np.pi
|
|
63
|
+
|
|
64
|
+
@nb.njit
|
|
65
|
+
def clip(n, lower, higher):
|
|
66
|
+
'''
|
|
67
|
+
jittable version of np.clip for single values
|
|
68
|
+
'''
|
|
69
|
+
if n > higher:
|
|
70
|
+
return higher
|
|
71
|
+
elif n < lower:
|
|
72
|
+
return lower
|
|
73
|
+
else:
|
|
74
|
+
return n
|
|
75
|
+
|
|
76
|
+
@nb.njit
|
|
77
|
+
def point_angle(p1, p2, p3):
|
|
78
|
+
return np.arccos(np.clip(norm(p1 - p2) @ norm(p3 - p2), -1.0, 1.0))*180/np.pi
|
|
79
|
+
|
|
80
|
+
@nb.njit
|
|
81
|
+
def norm(vec):
|
|
82
|
+
'''
|
|
83
|
+
Returns the normalized vector.
|
|
84
|
+
Reasonably faster than Numpy version.
|
|
85
|
+
Only for 3D vectors.
|
|
86
|
+
'''
|
|
87
|
+
return vec / sqrt((vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2]))
|
|
88
|
+
|
|
89
|
+
@nb.njit
|
|
90
|
+
def norm_of(vec):
|
|
91
|
+
'''
|
|
92
|
+
Returns the norm of the vector.
|
|
93
|
+
Faster than Numpy version, but
|
|
94
|
+
only compatible with 3D vectors.
|
|
95
|
+
'''
|
|
96
|
+
return sqrt((vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2]))
|
|
97
|
+
|
|
98
|
+
@nb.njit(fastmath=True)
|
|
99
|
+
def all_dists(A, B):
|
|
100
|
+
assert A.shape[1]==B.shape[1]
|
|
101
|
+
C=np.empty((A.shape[0],B.shape[0]),A.dtype)
|
|
102
|
+
I_BLK=32
|
|
103
|
+
J_BLK=32
|
|
104
|
+
|
|
105
|
+
#workaround to get the right datatype for acc
|
|
106
|
+
init_val_arr=np.zeros(1,A.dtype)
|
|
107
|
+
init_val=init_val_arr[0]
|
|
108
|
+
|
|
109
|
+
#Blocking and partial unrolling
|
|
110
|
+
#Beneficial if the second dimension is large -> computationally bound problem
|
|
111
|
+
#
|
|
112
|
+
for ii in nb.prange(A.shape[0]//I_BLK):
|
|
113
|
+
for jj in range(B.shape[0]//J_BLK):
|
|
114
|
+
for i in range(I_BLK//4):
|
|
115
|
+
for j in range(J_BLK//2):
|
|
116
|
+
acc_0=init_val
|
|
117
|
+
acc_1=init_val
|
|
118
|
+
acc_2=init_val
|
|
119
|
+
acc_3=init_val
|
|
120
|
+
acc_4=init_val
|
|
121
|
+
acc_5=init_val
|
|
122
|
+
acc_6=init_val
|
|
123
|
+
acc_7=init_val
|
|
124
|
+
for k in range(A.shape[1]):
|
|
125
|
+
acc_0+=(A[ii*I_BLK+i*4+0,k] - B[jj*J_BLK+j*2+0,k])**2
|
|
126
|
+
acc_1+=(A[ii*I_BLK+i*4+0,k] - B[jj*J_BLK+j*2+1,k])**2
|
|
127
|
+
acc_2+=(A[ii*I_BLK+i*4+1,k] - B[jj*J_BLK+j*2+0,k])**2
|
|
128
|
+
acc_3+=(A[ii*I_BLK+i*4+1,k] - B[jj*J_BLK+j*2+1,k])**2
|
|
129
|
+
acc_4+=(A[ii*I_BLK+i*4+2,k] - B[jj*J_BLK+j*2+0,k])**2
|
|
130
|
+
acc_5+=(A[ii*I_BLK+i*4+2,k] - B[jj*J_BLK+j*2+1,k])**2
|
|
131
|
+
acc_6+=(A[ii*I_BLK+i*4+3,k] - B[jj*J_BLK+j*2+0,k])**2
|
|
132
|
+
acc_7+=(A[ii*I_BLK+i*4+3,k] - B[jj*J_BLK+j*2+1,k])**2
|
|
133
|
+
C[ii*I_BLK+i*4+0,jj*J_BLK+j*2+0]=np.sqrt(acc_0)
|
|
134
|
+
C[ii*I_BLK+i*4+0,jj*J_BLK+j*2+1]=np.sqrt(acc_1)
|
|
135
|
+
C[ii*I_BLK+i*4+1,jj*J_BLK+j*2+0]=np.sqrt(acc_2)
|
|
136
|
+
C[ii*I_BLK+i*4+1,jj*J_BLK+j*2+1]=np.sqrt(acc_3)
|
|
137
|
+
C[ii*I_BLK+i*4+2,jj*J_BLK+j*2+0]=np.sqrt(acc_4)
|
|
138
|
+
C[ii*I_BLK+i*4+2,jj*J_BLK+j*2+1]=np.sqrt(acc_5)
|
|
139
|
+
C[ii*I_BLK+i*4+3,jj*J_BLK+j*2+0]=np.sqrt(acc_6)
|
|
140
|
+
C[ii*I_BLK+i*4+3,jj*J_BLK+j*2+1]=np.sqrt(acc_7)
|
|
141
|
+
#Remainder j
|
|
142
|
+
for i in range(I_BLK):
|
|
143
|
+
for j in range((B.shape[0]//J_BLK)*J_BLK,B.shape[0]):
|
|
144
|
+
acc_0=init_val
|
|
145
|
+
for k in range(A.shape[1]):
|
|
146
|
+
acc_0+=(A[ii*I_BLK+i,k] - B[j,k])**2
|
|
147
|
+
C[ii*I_BLK+i,j]=np.sqrt(acc_0)
|
|
148
|
+
|
|
149
|
+
#Remainder i
|
|
150
|
+
for i in range((A.shape[0]//I_BLK)*I_BLK,A.shape[0]):
|
|
151
|
+
for j in range(B.shape[0]):
|
|
152
|
+
acc_0=init_val
|
|
153
|
+
for k in range(A.shape[1]):
|
|
154
|
+
acc_0+=(A[i,k] - B[j,k])**2
|
|
155
|
+
C[i,j]=np.sqrt(acc_0)
|
|
156
|
+
|
|
157
|
+
return C
|
|
158
|
+
|
|
159
|
+
@nb.njit
|
|
160
|
+
def kronecker_delta(i, j) -> int:
|
|
161
|
+
if i == j:
|
|
162
|
+
return 1
|
|
163
|
+
return 0
|
|
164
|
+
|
|
165
|
+
@nb.njit
|
|
166
|
+
def get_inertia_moments(coords, masses):
|
|
167
|
+
'''
|
|
168
|
+
Returns the diagonal of the diagonalized inertia tensor, that is
|
|
169
|
+
a shape (3,) array with the moments of inertia along the main axes.
|
|
170
|
+
(I_x, I_y and largest I_z last)
|
|
171
|
+
'''
|
|
172
|
+
|
|
173
|
+
coords -= center_of_mass(coords, masses)
|
|
174
|
+
inertia_moment_matrix = np.array([[0.,0.,0.],
|
|
175
|
+
[0.,0.,0.],
|
|
176
|
+
[0.,0.,0.]])
|
|
177
|
+
|
|
178
|
+
for i in range(3):
|
|
179
|
+
for j in range(3):
|
|
180
|
+
k = kronecker_delta(i,j)
|
|
181
|
+
inertia_moment_matrix[i][j] = sum([masses[n]*((norm_of(coords[n])**2)*k - coords[n][i]*coords[n][j])
|
|
182
|
+
for n, _ in enumerate(coords)])
|
|
183
|
+
|
|
184
|
+
inertia_moment_matrix = diagonalize(inertia_moment_matrix)
|
|
185
|
+
|
|
186
|
+
return np.diag(inertia_moment_matrix)
|
|
187
|
+
|
|
188
|
+
@nb.njit
|
|
189
|
+
def diagonalize(A):
|
|
190
|
+
eigenvalues_of_A, eigenvectors_of_A = np.linalg.eig(A)
|
|
191
|
+
B = eigenvectors_of_A[:,np.abs(eigenvalues_of_A).argsort()]
|
|
192
|
+
diagonal_matrix= np.dot(np.linalg.inv(B), np.dot(A, B))
|
|
193
|
+
return diagonal_matrix
|
|
194
|
+
|
|
195
|
+
@nb.njit
|
|
196
|
+
def center_of_mass(coords, masses):
|
|
197
|
+
'''
|
|
198
|
+
Returns the center of mass for the atomic system.
|
|
199
|
+
'''
|
|
200
|
+
total_mass = sum([masses[i] for i in range(len(coords))])
|
|
201
|
+
w = np.array([0.,0.,0.])
|
|
202
|
+
for i in range(len(coords)):
|
|
203
|
+
w += coords[i]*masses[i]
|
|
204
|
+
return w / total_mass
|
|
205
|
+
|
|
206
|
+
@nb.njit
|
|
207
|
+
def internal_mean(arr):
|
|
208
|
+
'''
|
|
209
|
+
same as np.mean(arr, axis=1), but jitted
|
|
210
|
+
since numba does not support kwargs in np.mean
|
|
211
|
+
'''
|
|
212
|
+
assert len(arr.shape) == 3
|
|
213
|
+
|
|
214
|
+
out = np.zeros((arr.shape[0], arr.shape[2]), dtype=arr.dtype)
|
|
215
|
+
dim = arr.shape[1]
|
|
216
|
+
for i, vecs in enumerate(arr):
|
|
217
|
+
for v in vecs:
|
|
218
|
+
out[i] += v
|
|
219
|
+
return out / dim
|
|
220
|
+
|
|
221
|
+
@nb.njit
|
|
222
|
+
def vec_mean(arr):
|
|
223
|
+
'''
|
|
224
|
+
same as np.mean(arr, axis=0), but jitted
|
|
225
|
+
since numba does not support kwargs in np.mean
|
|
226
|
+
'''
|
|
227
|
+
assert len(arr.shape) == 2
|
|
228
|
+
|
|
229
|
+
out = np.zeros(arr.shape[1], dtype=arr.dtype)
|
|
230
|
+
dim = arr.shape[0]
|
|
231
|
+
|
|
232
|
+
for v in arr:
|
|
233
|
+
out += v
|
|
234
|
+
|
|
235
|
+
arr /= dim
|
|
236
|
+
|
|
237
|
+
return out
|
|
238
|
+
|
|
239
|
+
@nb.njit
|
|
240
|
+
def align_vec_pair(ref, tgt):
|
|
241
|
+
'''
|
|
242
|
+
ref, tgt: iterables of two 3D vectors each
|
|
243
|
+
|
|
244
|
+
return: rotation matrix that when applied to tgt,
|
|
245
|
+
optimally aligns it to ref
|
|
246
|
+
'''
|
|
247
|
+
|
|
248
|
+
B = np.zeros((3,3))
|
|
249
|
+
for i in range(3):
|
|
250
|
+
for k in range(3):
|
|
251
|
+
tot = 0
|
|
252
|
+
for j in range(2):
|
|
253
|
+
tot += ref[j][i]*tgt[j][k]
|
|
254
|
+
B[i,k] = tot
|
|
255
|
+
|
|
256
|
+
u, s, vh = np.linalg.svd(B)
|
|
257
|
+
|
|
258
|
+
# Correct improper rotation if necessary (as in Kabsch algorithm)
|
|
259
|
+
if np.linalg.det(u @ vh) < 0:
|
|
260
|
+
s[-1] = -s[-1]
|
|
261
|
+
u[:, -1] = -u[:, -1]
|
|
262
|
+
|
|
263
|
+
return np.ascontiguousarray(np.dot(u, vh))
|
|
264
|
+
|
|
265
|
+
@nb.njit
|
|
266
|
+
def quaternion_to_rotation_matrix(Q):
|
|
267
|
+
"""
|
|
268
|
+
Covert a quaternion into a full three-dimensional rotation matrix.
|
|
269
|
+
|
|
270
|
+
Input
|
|
271
|
+
:param Q: A 4 element array representing the quaternion (q0,q1,q2,q3)
|
|
272
|
+
|
|
273
|
+
Output
|
|
274
|
+
:return: A 3x3 element matrix representing the full 3D rotation matrix.
|
|
275
|
+
This rotation matrix converts a point in the local reference
|
|
276
|
+
frame to a point in the global reference frame.
|
|
277
|
+
"""
|
|
278
|
+
# Extract the values from Q (adjusting for scalar last in input)
|
|
279
|
+
q0 = Q[3]
|
|
280
|
+
q1 = Q[0]
|
|
281
|
+
q2 = Q[1]
|
|
282
|
+
q3 = Q[2]
|
|
283
|
+
|
|
284
|
+
# First row of the rotation matrix
|
|
285
|
+
r00 = 2 * (q0 * q0 + q1 * q1) - 1
|
|
286
|
+
r01 = 2 * (q1 * q2 - q0 * q3)
|
|
287
|
+
r02 = 2 * (q1 * q3 + q0 * q2)
|
|
288
|
+
|
|
289
|
+
# Second row of the rotation matrix
|
|
290
|
+
r10 = 2 * (q1 * q2 + q0 * q3)
|
|
291
|
+
r11 = 2 * (q0 * q0 + q2 * q2) - 1
|
|
292
|
+
r12 = 2 * (q2 * q3 - q0 * q1)
|
|
293
|
+
|
|
294
|
+
# Third row of the rotation matrix
|
|
295
|
+
r20 = 2 * (q1 * q3 - q0 * q2)
|
|
296
|
+
r21 = 2 * (q2 * q3 + q0 * q1)
|
|
297
|
+
r22 = 2 * (q0 * q0 + q3 * q3) - 1
|
|
298
|
+
|
|
299
|
+
# 3x3 rotation matrix
|
|
300
|
+
rot_matrix = np.array([[r00, r01, r02],
|
|
301
|
+
[r10, r11, r12],
|
|
302
|
+
[r20, r21, r22]])
|
|
303
|
+
|
|
304
|
+
return np.ascontiguousarray(rot_matrix)
|
|
305
|
+
|
|
306
|
+
@nb.njit
|
|
307
|
+
def rot_mat_from_pointer(pointer, angle):
|
|
308
|
+
'''
|
|
309
|
+
Returns the rotation matrix that rotates a system around the given pointer
|
|
310
|
+
of angle degrees. The algorithm is based on scipy quaternions.
|
|
311
|
+
:params pointer: a 3D vector
|
|
312
|
+
:params angle: an int/float, in degrees
|
|
313
|
+
:return rotation_matrix: matrix that applied to a point, rotates it along the pointer
|
|
314
|
+
'''
|
|
315
|
+
assert pointer.shape[0] == 3
|
|
316
|
+
|
|
317
|
+
pointer = norm(pointer)
|
|
318
|
+
angle *= np.pi/180
|
|
319
|
+
quat = np.array([np.sin(angle/2)*pointer[0],
|
|
320
|
+
np.sin(angle/2)*pointer[1],
|
|
321
|
+
np.sin(angle/2)*pointer[2],
|
|
322
|
+
np.cos(angle/2)])
|
|
323
|
+
# normalized quaternion, scalar last (i j k w)
|
|
324
|
+
|
|
325
|
+
return quaternion_to_rotation_matrix(quat)
|
|
326
|
+
|
|
327
|
+
@nb.njit(nb.int32[:,:](nb.int32[:]))
|
|
328
|
+
def cart_prod_idx(sizes: np.ndarray):
|
|
329
|
+
"""Generates ids tuples for a cartesian product"""
|
|
330
|
+
assert len(sizes) >= 2
|
|
331
|
+
tuples_count = np.prod(sizes)
|
|
332
|
+
tuples = np.zeros((tuples_count, len(sizes)), dtype=np.int32)
|
|
333
|
+
tuple_idx = 0
|
|
334
|
+
# stores the current combination
|
|
335
|
+
current_tuple = np.zeros(len(sizes))
|
|
336
|
+
while tuple_idx < tuples_count:
|
|
337
|
+
tuples[tuple_idx] = current_tuple
|
|
338
|
+
current_tuple[0] += 1
|
|
339
|
+
# using a condition here instead of including this in the inner loop
|
|
340
|
+
# to gain a bit of speed: this is going to be tested each iteration,
|
|
341
|
+
# and starting a loop to have it end right away is a bit silly
|
|
342
|
+
if current_tuple[0] == sizes[0]:
|
|
343
|
+
# the reset to 0 and subsequent increment amount to carrying
|
|
344
|
+
# the number to the higher "power"
|
|
345
|
+
current_tuple[0] = 0
|
|
346
|
+
current_tuple[1] += 1
|
|
347
|
+
for i in range(1, len(sizes) - 1):
|
|
348
|
+
if current_tuple[i] == sizes[i]:
|
|
349
|
+
# same as before, but in a loop, since this is going
|
|
350
|
+
# to get run less often
|
|
351
|
+
current_tuple[i + 1] += 1
|
|
352
|
+
current_tuple[i] = 0
|
|
353
|
+
else:
|
|
354
|
+
break
|
|
355
|
+
tuple_idx += 1
|
|
356
|
+
return tuples
|
|
357
|
+
|
|
358
|
+
@nb.njit
|
|
359
|
+
def vector_cartesian_product(x, y):
|
|
360
|
+
'''
|
|
361
|
+
Cartesian product, but with vectors instead of indices
|
|
362
|
+
'''
|
|
363
|
+
indices = cart_prod_idx(np.asarray((x.shape[0], y.shape[0]), dtype=np.int32))
|
|
364
|
+
dim = x.shape[-1] if len(x.shape) > 1 else 1
|
|
365
|
+
new_arr = np.zeros((*indices.shape, dim), dtype=x.dtype)
|
|
366
|
+
for i, (x_, y_) in enumerate(indices):
|
|
367
|
+
new_arr[i][0] = x[x_]
|
|
368
|
+
new_arr[i][1] = y[y_]
|
|
369
|
+
return np.ascontiguousarray(new_arr)
|
|
370
|
+
|
|
371
|
+
@nb.njit
|
|
372
|
+
def transform_coords(coords, rot, pos):
|
|
373
|
+
'''
|
|
374
|
+
Returns the rotated and tranlated
|
|
375
|
+
coordinates. Slightly faster than
|
|
376
|
+
Numpy, uses memory-contiguous arrays.
|
|
377
|
+
'''
|
|
378
|
+
t = np.transpose(coords)
|
|
379
|
+
m = rot @ t
|
|
380
|
+
f = np.transpose(m)
|
|
381
|
+
return f + pos
|
|
382
|
+
|
|
383
|
+
@nb.njit
|
|
384
|
+
def get_alignment_matrix(p, q):
|
|
385
|
+
'''
|
|
386
|
+
Returns the rotation matrix that aligns
|
|
387
|
+
vectors q to p (Kabsch algorithm)
|
|
388
|
+
Assumes centered vector sets (mean is origin)
|
|
389
|
+
'''
|
|
390
|
+
|
|
391
|
+
# calculate the covariance matrix
|
|
392
|
+
cov_mat = np.ascontiguousarray(p.T) @ q
|
|
393
|
+
# cov_mat = np.transpose(p) * q
|
|
394
|
+
|
|
395
|
+
# Compute the SVD
|
|
396
|
+
v, _, w = np.linalg.svd(cov_mat)
|
|
397
|
+
d = (np.linalg.det(v) * np.linalg.det(w)) < 0.0
|
|
398
|
+
|
|
399
|
+
if d:
|
|
400
|
+
v[:, -1] = -v[:, -1]
|
|
401
|
+
|
|
402
|
+
# Create Rotation matrix u
|
|
403
|
+
rot_mat = np.dot(v, w)
|
|
404
|
+
|
|
405
|
+
return rot_mat
|