firecode 1.0.0__tar.gz → 1.1.1__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.
- {firecode-1.0.0 → firecode-1.1.1}/CHANGELOG.md +14 -0
- {firecode-1.0.0/firecode.egg-info → firecode-1.1.1}/PKG-INFO +20 -3
- {firecode-1.0.0 → firecode-1.1.1}/README.md +3 -2
- {firecode-1.0.0 → firecode-1.1.1}/docs/conf.py +1 -2
- {firecode-1.0.0 → firecode-1.1.1}/firecode/__main__.py +3 -1
- {firecode-1.0.0 → firecode-1.1.1}/firecode/algebra.py +4 -1
- {firecode-1.0.0 → firecode-1.1.1}/firecode/ase_manipulations.py +226 -109
- {firecode-1.0.0 → firecode-1.1.1}/firecode/atropisomer_module.py +0 -1
- {firecode-1.0.0 → firecode-1.1.1}/firecode/automep.py +2 -2
- {firecode-1.0.0 → firecode-1.1.1}/firecode/calculators/_xtb.py +269 -253
- firecode-1.1.1/firecode/eig_based_RMSD_test.ipynb +464 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/embedder.py +168 -31
- {firecode-1.0.0 → firecode-1.1.1}/firecode/embedder_options.py +7 -3
- {firecode-1.0.0 → firecode-1.1.1}/firecode/graph_manipulations.py +4 -4
- {firecode-1.0.0 → firecode-1.1.1}/firecode/hypermolecule_class.py +1 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/multiembed.py +1 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/operators.py +64 -14
- {firecode-1.0.0 → firecode-1.1.1}/firecode/optimization_methods.py +18 -2
- {firecode-1.0.0 → firecode-1.1.1}/firecode/reactive_atoms_classes.py +12 -9
- {firecode-1.0.0 → firecode-1.1.1}/firecode/solvents.py +1 -1
- {firecode-1.0.0 → firecode-1.1.1}/firecode/torsion_module.py +23 -18
- {firecode-1.0.0 → firecode-1.1.1}/firecode/utils.py +310 -1
- {firecode-1.0.0 → firecode-1.1.1/firecode.egg-info}/PKG-INFO +20 -3
- {firecode-1.0.0 → firecode-1.1.1}/firecode.egg-info/SOURCES.txt +1 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode.egg-info/requires.txt +5 -2
- {firecode-1.0.0 → firecode-1.1.1}/setup.py +6 -3
- {firecode-1.0.0 → firecode-1.1.1}/LICENSE +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/MANIFEST.in +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/docs/examples.rst +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/docs/index.rst +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/docs/installation.rst +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/docs/introduction.rst +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/docs/license.rst +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/docs/operators_keywords.rst +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/docs/requirements.txt +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/docs/usage.rst +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/TEST_NOTEBOOK.ipynb +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/__init__.py +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/_gaussian.py +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/calculators/__init__.py +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/calculators/_gaussian.py +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/calculators/_mopac.py +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/calculators/_openbabel.py +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/calculators/_orca.py +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/concurrent_test.py +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/embeds.py +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/errors.py +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/mep_relaxer.py +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/modify_settings.py +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/mprof.py +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/nci.py +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/numba_functions.py +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/parameters.py +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/pka.py +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/profiler.py +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/pruning.py +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/pt.py +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/quotes.json +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/quotes.py +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/references.py +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/rmsd.py +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/settings.py +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/tests/C2F2H4.xyz +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/tests/C2H4.xyz +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/tests/CH3Cl.xyz +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/tests/HCOOH.xyz +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/tests/HCOOOH.xyz +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/tests/chelotropic.txt +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/tests/cyclical.txt +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/tests/dihedral.txt +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/tests/string.txt +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/tests/trimolecular.txt +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode/tests.py +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode.egg-info/dependency_links.txt +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/firecode.egg-info/top_level.txt +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/pyproject.toml +0 -0
- {firecode-1.0.0 → firecode-1.1.1}/setup.cfg +0 -0
|
@@ -4,6 +4,20 @@
|
|
|
4
4
|
<!-- - ... mep_relax> BETA
|
|
5
5
|
- ... IMAGES kw, also implement it for neb>-->
|
|
6
6
|
|
|
7
|
+
## FIRECODE 1.1.1 🔥 (January 2025)
|
|
8
|
+
- Added pretty error traceback with rich.
|
|
9
|
+
<!-- SMARTS Constraint class -->
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## FIRECODE 1.1.0 🔥 (December 24 2024)
|
|
13
|
+
- Implemented planar angle and dihedral constraints in ensemble optimization (only intra-file, but also available in embedding runs). These are propagated to xtb, ASE-mediated calculators and PyTorch models, as well as to operators like mtd> (CREST). These constraints can be specified in new input lines that start with a blank space under the reference molecule line, with the syntax "i1 i2 i3 \[i4\] target(float)" (i.e. "0 1 2 3 90.0" for dihedrals or "0 1 2 0.0" for planar angles). For now, these constraints are enforced at every step (as capital letter constraints, but not associated to any letter).
|
|
14
|
+
- Improved comments parsing: possible to comment in any input line (with "#", following a Pyhton-style syntax).
|
|
15
|
+
- Tweaked similarity_refining maximum ensemble size thresholds to carry different pruning steps.
|
|
16
|
+
- Turned off interactive ASE GUI specification of Sp3 atoms leaving groups for orbital building.
|
|
17
|
+
|
|
18
|
+
## FIRECODE 1.0.1 🔥 (September 29 2024)
|
|
19
|
+
- Minor bugfixes and printout clean-ups.
|
|
20
|
+
|
|
7
21
|
## FIRECODE 1.0.0 🔥 (July 5 2024)
|
|
8
22
|
- Renamed project to better reflect the expanded functionality of the toolbox! 🔥
|
|
9
23
|
- New Pruner class: simpler, more homogenized implementation of iterative subset pruning, sharing the same basic architecture across different similarity evaluation functions, with cached calls to the eval similarity function.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: firecode
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.1
|
|
4
4
|
Summary: Computational chemistry general purpose ensemble optimizer and transition state builder
|
|
5
5
|
Home-page: https://www.github.com/ntampellini/firecode
|
|
6
6
|
Author: Nicolò Tampellini
|
|
@@ -23,11 +23,14 @@ Requires-Dist: networkx==2.5.1
|
|
|
23
23
|
Requires-Dist: ase==3.21.1
|
|
24
24
|
Requires-Dist: sella==2.3.2
|
|
25
25
|
Requires-Dist: scikit-learn==1.0.1
|
|
26
|
-
Requires-Dist: numba==0.
|
|
26
|
+
Requires-Dist: numba==0.58.1
|
|
27
27
|
Requires-Dist: prettytable==3.3.0
|
|
28
28
|
Requires-Dist: typing-extensions==4.8.0
|
|
29
|
-
Requires-Dist: llvmlite
|
|
29
|
+
Requires-Dist: llvmlite==0.41.1
|
|
30
30
|
Requires-Dist: importlib-metadata==7.0.1
|
|
31
|
+
Requires-Dist: psutil==5.9.6
|
|
32
|
+
Requires-Dist: setuptools==75.3.0
|
|
33
|
+
Requires-Dist: rich
|
|
31
34
|
|
|
32
35
|
## FIRECODE: Filtering Refiner and Embedder for Conformationally Dense Ensembles.
|
|
33
36
|
Ensemble optimizer. Systematic generation of multimolecular arrangements for mono/bi/trimolecular transition states. Numerous utilities for conformational exploration, selection, pruning and constrained ensemble optimization.
|
|
@@ -38,6 +41,20 @@ Ensemble optimizer. Systematic generation of multimolecular arrangements for mon
|
|
|
38
41
|
<!-- - ... mep_relax> BETA
|
|
39
42
|
- ... IMAGES kw, also implement it for neb>-->
|
|
40
43
|
|
|
44
|
+
## FIRECODE 1.1.1 🔥 (January 2025)
|
|
45
|
+
- Added pretty error traceback with rich.
|
|
46
|
+
<!-- SMARTS Constraint class -->
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## FIRECODE 1.1.0 🔥 (December 24 2024)
|
|
50
|
+
- Implemented planar angle and dihedral constraints in ensemble optimization (only intra-file, but also available in embedding runs). These are propagated to xtb, ASE-mediated calculators and PyTorch models, as well as to operators like mtd> (CREST). These constraints can be specified in new input lines that start with a blank space under the reference molecule line, with the syntax "i1 i2 i3 \[i4\] target(float)" (i.e. "0 1 2 3 90.0" for dihedrals or "0 1 2 0.0" for planar angles). For now, these constraints are enforced at every step (as capital letter constraints, but not associated to any letter).
|
|
51
|
+
- Improved comments parsing: possible to comment in any input line (with "#", following a Pyhton-style syntax).
|
|
52
|
+
- Tweaked similarity_refining maximum ensemble size thresholds to carry different pruning steps.
|
|
53
|
+
- Turned off interactive ASE GUI specification of Sp3 atoms leaving groups for orbital building.
|
|
54
|
+
|
|
55
|
+
## FIRECODE 1.0.1 🔥 (September 29 2024)
|
|
56
|
+
- Minor bugfixes and printout clean-ups.
|
|
57
|
+
|
|
41
58
|
## FIRECODE 1.0.0 🔥 (July 5 2024)
|
|
42
59
|
- Renamed project to better reflect the expanded functionality of the toolbox! 🔥
|
|
43
60
|
- New Pruner class: simpler, more homogenized implementation of iterative subset pruning, sharing the same basic architecture across different similarity evaluation functions, with cached calls to the eval similarity function.
|
|
@@ -4,16 +4,17 @@
|
|
|
4
4
|
<div align="center">
|
|
5
5
|
|
|
6
6
|
[](https://opensource.org/licenses/LGPL-3.0)
|
|
7
|
-
[](https://www.codefactor.io/repository/github/ntampellini/firecode)
|
|
8
7
|

|
|
9
8
|

|
|
10
|
-
|
|
11
9
|

|
|
10
|
+
[](https://www.codefactor.io/repository/github/ntampellini/firecode)
|
|
11
|
+
|
|
12
12
|
[](https://pypi.org/project/firecode/)
|
|
13
13
|
[](https://pypi.org/project/firecode/)
|
|
14
14
|
[](https://firecode.readthedocs.io/en/latest/?badge=latest)
|
|
15
15
|

|
|
16
16
|
[](https://github.com/charliermarsh/ruff)
|
|
17
|
+
|
|
17
18
|

|
|
18
19
|
|
|
19
20
|
</div>
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
#
|
|
13
13
|
import os
|
|
14
14
|
import sys
|
|
15
|
-
from firecode.__main__ import __version__
|
|
16
15
|
|
|
17
16
|
# sys.path.insert(0, os.path.abspath('.'))
|
|
18
17
|
sys.path.insert(0, os.path.join(os.path.abspath('..'), 'firecode'))
|
|
@@ -26,7 +25,7 @@ author = 'Nicolò Tampellini'
|
|
|
26
25
|
|
|
27
26
|
# The full version, including alpha/beta/rc tags
|
|
28
27
|
|
|
29
|
-
release =
|
|
28
|
+
release = '1.1.0'
|
|
30
29
|
# release='latest'
|
|
31
30
|
|
|
32
31
|
# -- General configuration ---------------------------------------------------
|
|
@@ -73,8 +73,11 @@ def clip(n, lower, higher):
|
|
|
73
73
|
else:
|
|
74
74
|
return n
|
|
75
75
|
|
|
76
|
-
@nb.njit
|
|
76
|
+
# @nb.njit
|
|
77
77
|
def point_angle(p1, p2, p3):
|
|
78
|
+
'''
|
|
79
|
+
Returns the planar angle between three points in space, in degrees.
|
|
80
|
+
'''
|
|
78
81
|
return np.arccos(np.clip(norm(p1 - p2) @ norm(p3 - p2), -1.0, 1.0))*180/np.pi
|
|
79
82
|
|
|
80
83
|
@nb.njit
|
|
@@ -40,14 +40,14 @@ from ase.optimize import BFGS, LBFGS
|
|
|
40
40
|
from ase.vibrations import Vibrations
|
|
41
41
|
from sella import Sella
|
|
42
42
|
|
|
43
|
-
from firecode.algebra import norm, norm_of
|
|
43
|
+
from firecode.algebra import dihedral, norm, norm_of, point_angle
|
|
44
44
|
from firecode.graph_manipulations import findPaths, graphize, neighbors
|
|
45
45
|
from firecode.rmsd import get_alignment_matrix
|
|
46
46
|
from firecode.settings import COMMANDS, MEM_GB
|
|
47
47
|
from firecode.solvents import get_solvent_line
|
|
48
48
|
from firecode.utils import (HiddenPrints, align_structures, clean_directory,
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
get_double_bonds_indices, molecule_check,
|
|
50
|
+
scramble_check, time_to_string, write_xyz)
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
class Spring:
|
|
@@ -57,7 +57,7 @@ class Spring:
|
|
|
57
57
|
Spring constant is very high to achieve tight convergence,
|
|
58
58
|
but maximum force is dampened so as not to ruin structures.
|
|
59
59
|
'''
|
|
60
|
-
def __init__(self, i1, i2, d_eq, k=
|
|
60
|
+
def __init__(self, i1, i2, d_eq, k=300):
|
|
61
61
|
self.i1, self.i2 = i1, i2
|
|
62
62
|
self.d_eq = d_eq
|
|
63
63
|
self.k = k
|
|
@@ -119,6 +119,176 @@ class HalfSpring:
|
|
|
119
119
|
def __repr__(self):
|
|
120
120
|
return f'Spring - ids:{self.i1}/{self.i2} - d_max:{self.d_max}, k:{self.k}'
|
|
121
121
|
|
|
122
|
+
class PlanarAngleSpring:
|
|
123
|
+
'''
|
|
124
|
+
ASE Custom Constraint Class
|
|
125
|
+
Adds an harmonic force among a triad of atoms.
|
|
126
|
+
Spring constant is dinamycally adjusted to achieve tight convergence,
|
|
127
|
+
but maximum force is dampened so as not to ruin structures.
|
|
128
|
+
eq_angle: in degrees
|
|
129
|
+
'''
|
|
130
|
+
def __init__(self, i1, i2, i3, eq_angle):
|
|
131
|
+
self.i1, self.i2, self.i3 = i1, i2, i3
|
|
132
|
+
self.eq_angle = eq_angle
|
|
133
|
+
|
|
134
|
+
self.k_min = 0.2
|
|
135
|
+
self.k_max = 2
|
|
136
|
+
self.theta_mid = 3
|
|
137
|
+
|
|
138
|
+
def adjust_positions(self, atoms, newpositions):
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
def adjust_forces(self, atoms, forces):
|
|
142
|
+
|
|
143
|
+
# Get positions
|
|
144
|
+
p1 = atoms.positions[self.i1]
|
|
145
|
+
p2 = atoms.positions[self.i2]
|
|
146
|
+
p3 = atoms.positions[self.i3]
|
|
147
|
+
|
|
148
|
+
# Calculate vectors
|
|
149
|
+
v21 = p1 - p2 # Vector from atom 2 to 1
|
|
150
|
+
v23 = p3 - p2 # Vector from atom 2 to 3
|
|
151
|
+
|
|
152
|
+
# Get bond lengths
|
|
153
|
+
r1 = norm_of(v21)
|
|
154
|
+
r3 = norm_of(v23)
|
|
155
|
+
|
|
156
|
+
# Calculate current angle
|
|
157
|
+
current_angle = point_angle(p1, p2, p3)
|
|
158
|
+
delta_angle = self.eq_angle - current_angle
|
|
159
|
+
|
|
160
|
+
# Calculate unit vectors
|
|
161
|
+
e21 = v21 / r1
|
|
162
|
+
e23 = v23 / r3
|
|
163
|
+
|
|
164
|
+
# Normal to the plane
|
|
165
|
+
normal = np.cross(e21, e23)
|
|
166
|
+
if norm_of(normal) > 1e-10: # Check if atoms aren't collinear
|
|
167
|
+
|
|
168
|
+
normal = norm(normal)
|
|
169
|
+
|
|
170
|
+
# Calculate perpendicular directions in the plane
|
|
171
|
+
f1_direction = np.cross(e21, normal)
|
|
172
|
+
f3_direction = np.cross(normal, e23)
|
|
173
|
+
|
|
174
|
+
# Dynamically adjust force constant: k_min for large delta_angles,
|
|
175
|
+
# up to k_max for smaller ones. Sigmoid-like function centered on self.theta_mid.
|
|
176
|
+
sigmoid = 1-(delta_angle-self.theta_mid)/(1+np.abs(delta_angle-self.theta_mid))
|
|
177
|
+
k_dynamic = (self.k_max-self.k_min)/2 * (sigmoid) + self.k_min
|
|
178
|
+
|
|
179
|
+
force_magnitude = np.clip(delta_angle * k_dynamic, -2, 2)
|
|
180
|
+
|
|
181
|
+
# Calculate forces perpendicular to bonds
|
|
182
|
+
f1 = force_magnitude * f1_direction
|
|
183
|
+
f3 = force_magnitude * f3_direction
|
|
184
|
+
|
|
185
|
+
# The central force needs to maintain both force and torque balance
|
|
186
|
+
f2 = -(f1 + f3)
|
|
187
|
+
|
|
188
|
+
forces[self.i1] += f1
|
|
189
|
+
forces[self.i2] += f2
|
|
190
|
+
forces[self.i3] += f3
|
|
191
|
+
|
|
192
|
+
def __repr__(self):
|
|
193
|
+
return f'PlanarAngleSpring - ids:{self.i1}/{self.i2}/{self.i3} - eq_angle:{self.eq_angle}'
|
|
194
|
+
|
|
195
|
+
class DihedralSpring:
|
|
196
|
+
'''
|
|
197
|
+
ASE Custom Constraint Class
|
|
198
|
+
Adds a harmonic force to control a dihedral angle between four atoms.
|
|
199
|
+
Improved handling of stiff bonds and out-of-plane rotations.
|
|
200
|
+
|
|
201
|
+
Parameters:
|
|
202
|
+
-----------
|
|
203
|
+
i1, i2, i3, i4 : int
|
|
204
|
+
Indices of the four atoms defining the dihedral angle
|
|
205
|
+
eq_angle : float
|
|
206
|
+
Target dihedral angle in degrees
|
|
207
|
+
k_min : float, optional
|
|
208
|
+
Minimum force constant (default: 0.1)
|
|
209
|
+
k_max : float, optional
|
|
210
|
+
Maximum force constant (default: 1)
|
|
211
|
+
'''
|
|
212
|
+
def __init__(self, i1, i2, i3, i4, eq_angle, k_min=0.1, k_max=1):
|
|
213
|
+
self.i1, self.i2, self.i3, self.i4 = i1, i2, i3, i4
|
|
214
|
+
self.eq_angle = eq_angle
|
|
215
|
+
|
|
216
|
+
self.k_min = k_min
|
|
217
|
+
self.k_max = k_max
|
|
218
|
+
self.theta_mid = 30.0
|
|
219
|
+
|
|
220
|
+
def adjust_positions(self, atoms, newpositions):
|
|
221
|
+
pass
|
|
222
|
+
|
|
223
|
+
def get_delta_angle(self, current, target):
|
|
224
|
+
"""
|
|
225
|
+
Calculate the smallest angle difference considering periodicity.
|
|
226
|
+
Returns the difference in degrees.
|
|
227
|
+
"""
|
|
228
|
+
diff = ((target - current + 180) % 360) - 180
|
|
229
|
+
return diff
|
|
230
|
+
|
|
231
|
+
def adjust_forces(self, atoms, forces):
|
|
232
|
+
# Get positions
|
|
233
|
+
p1 = atoms.positions[self.i1].copy()
|
|
234
|
+
p2 = atoms.positions[self.i2].copy()
|
|
235
|
+
p3 = atoms.positions[self.i3].copy()
|
|
236
|
+
p4 = atoms.positions[self.i4].copy()
|
|
237
|
+
|
|
238
|
+
# Calculate current dihedral angle
|
|
239
|
+
current_angle =dihedral((p1, p2, p3, p4))
|
|
240
|
+
|
|
241
|
+
# Calculate periodic angle difference
|
|
242
|
+
delta_angle = self.get_delta_angle(current_angle, self.eq_angle)
|
|
243
|
+
|
|
244
|
+
# Calculate central bond length (important for force scaling)
|
|
245
|
+
v23 = p3 - p2
|
|
246
|
+
bond_length = norm_of(v23)
|
|
247
|
+
|
|
248
|
+
# Dynamic force constant calculation - quadratic scaling near target
|
|
249
|
+
rel_angle = np.abs(delta_angle) / self.theta_mid
|
|
250
|
+
k_dynamic = self.k_max * np.exp(-rel_angle) + self.k_min
|
|
251
|
+
|
|
252
|
+
# Scale force by bond length (shorter bonds need more force)
|
|
253
|
+
force_scale = 1.0 / bond_length
|
|
254
|
+
|
|
255
|
+
# Calculate force magnitude with scaled clipping
|
|
256
|
+
force_magnitude = np.clip(delta_angle * k_dynamic * force_scale, -5.0, 5.0)
|
|
257
|
+
|
|
258
|
+
# Calculate force directions
|
|
259
|
+
v21 = p1 - p2
|
|
260
|
+
v34 = p4 - p3
|
|
261
|
+
|
|
262
|
+
# Calculate perpendicular components for force application
|
|
263
|
+
n1 = np.cross(v21, v23)
|
|
264
|
+
n2 = np.cross(v23, v34)
|
|
265
|
+
|
|
266
|
+
n1_norm = norm_of(n1)
|
|
267
|
+
n2_norm = norm_of(n2)
|
|
268
|
+
|
|
269
|
+
if n1_norm < 1e-6 or n2_norm < 1e-6:
|
|
270
|
+
return
|
|
271
|
+
|
|
272
|
+
f1_direction = n1 / n1_norm
|
|
273
|
+
f4_direction = n2 / n2_norm
|
|
274
|
+
|
|
275
|
+
# Apply forces with improved balance
|
|
276
|
+
f1 = force_magnitude * f1_direction
|
|
277
|
+
f4 = force_magnitude * f4_direction
|
|
278
|
+
|
|
279
|
+
# Distribute forces to maintain total force and torque
|
|
280
|
+
forces[self.i1] += f1
|
|
281
|
+
forces[self.i4] += f4
|
|
282
|
+
forces[self.i2] += -0.75 * f1
|
|
283
|
+
forces[self.i3] += -0.75 * f4
|
|
284
|
+
# Remaining -0.25 * (f1 + f4) is distributed equally
|
|
285
|
+
center_force = -0.25 * (f1 + f4)
|
|
286
|
+
forces[self.i2] += center_force
|
|
287
|
+
forces[self.i3] += center_force
|
|
288
|
+
|
|
289
|
+
def __repr__(self):
|
|
290
|
+
return f'DihedralSpring(ids: {self.i1}/{self.i2}/{self.i3}/{self.i4}, eq_angle: {self.eq_angle})'
|
|
291
|
+
|
|
122
292
|
def get_ase_calc(embedder):
|
|
123
293
|
'''
|
|
124
294
|
Attach the correct ASE calculator
|
|
@@ -145,7 +315,7 @@ def get_ase_calc(embedder):
|
|
|
145
315
|
'(See https://github.com/grimme-lab/xtb-python)'))
|
|
146
316
|
|
|
147
317
|
from firecode.solvents import (solvent_synonyms, xtb_solvents,
|
|
148
|
-
|
|
318
|
+
xtb_supported)
|
|
149
319
|
|
|
150
320
|
solvent = solvent_synonyms[solvent] if solvent in solvent_synonyms else solvent
|
|
151
321
|
solvent = 'none' if solvent is None else solvent
|
|
@@ -154,7 +324,19 @@ def get_ase_calc(embedder):
|
|
|
154
324
|
raise Exception(f'Solvent \'{solvent}\' not supported by XTB. Supported solvents are:\n{xtb_supported}')
|
|
155
325
|
|
|
156
326
|
return XTB(method=method, solvent=solvent)
|
|
157
|
-
|
|
327
|
+
|
|
328
|
+
if calculator == 'AIMNET2':
|
|
329
|
+
try:
|
|
330
|
+
from aimnet2_firecode.interface import get_aimnet2_calc
|
|
331
|
+
except ImportError:
|
|
332
|
+
raise Exception(('Cannot import AIMNET2 python bindings. Install them with:\n'
|
|
333
|
+
'>>> pip install aimnet2_firecode\n'))
|
|
334
|
+
|
|
335
|
+
if hasattr(embedder.dispatcher, "aimnet2_calc"):
|
|
336
|
+
return embedder.dispatcher.aimnet2_calc
|
|
337
|
+
|
|
338
|
+
else:
|
|
339
|
+
return get_aimnet2_calc(method=method, logfunction=embedder.log)
|
|
158
340
|
|
|
159
341
|
command = COMMANDS[calculator]
|
|
160
342
|
|
|
@@ -212,104 +394,6 @@ def get_ase_calc(embedder):
|
|
|
212
394
|
|
|
213
395
|
return calc
|
|
214
396
|
|
|
215
|
-
# def ase_adjust_spacings(embedder, structure, atomnos, constrained_indices, title=0, traj=None):
|
|
216
|
-
# '''
|
|
217
|
-
# embedder: firecode embedder object
|
|
218
|
-
# structure: TS candidate coordinates to be adjusted
|
|
219
|
-
# atomnos: 1-d array with element numbering for the TS
|
|
220
|
-
# constrained_indices: (n,2)-shaped array of indices to be distance constrained
|
|
221
|
-
# mols_graphs: list of NetworkX graphs, ordered as the single molecules in the TS
|
|
222
|
-
# title: number to be used for referring to this structure in the embedder log
|
|
223
|
-
# traj: if set to a string, traj+'.traj' is used as a filename for the refinement trajectory.
|
|
224
|
-
# '''
|
|
225
|
-
# atoms = Atoms(atomnos, positions=structure)
|
|
226
|
-
|
|
227
|
-
# atoms.calc = get_ase_calc(embedder)
|
|
228
|
-
|
|
229
|
-
# springs = [Spring(indices[0], indices[1], dist) for indices, dist in embedder.target_distances.items()]
|
|
230
|
-
# # adding springs to adjust the pairings for which we have target distances
|
|
231
|
-
|
|
232
|
-
# # if there are no springs, it is faster (and equivalent) to just do a classical full opitimization
|
|
233
|
-
# if not springs:
|
|
234
|
-
# from firecode.optimization_methods import optimize
|
|
235
|
-
# return optimize(
|
|
236
|
-
# structure,
|
|
237
|
-
# atomnos,
|
|
238
|
-
# embedder.options.calculator,
|
|
239
|
-
# method=embedder.options.theory_level,
|
|
240
|
-
# mols_graphs=embedder.graphs if embedder.embed != 'monomolecular' else None,
|
|
241
|
-
# procs=embedder.procs,
|
|
242
|
-
# solvent=embedder.options.solvent,
|
|
243
|
-
# max_newbonds=embedder.options.max_newbonds,
|
|
244
|
-
# check=(embedder.embed != 'refine'),
|
|
245
|
-
|
|
246
|
-
# logfunction=lambda s: embedder.log(s, p=False),
|
|
247
|
-
# title=f'Candidate_{title}'
|
|
248
|
-
# )
|
|
249
|
-
|
|
250
|
-
# nci_indices = [indices for letter, indices in embedder.pairings_table.items() if letter.islower()]
|
|
251
|
-
# halfsprings = [HalfSpring(i1, i2, 2.5) for i1, i2 in nci_indices]
|
|
252
|
-
# # HalfSprings get atoms involved in NCIs together if they are more than 2.5A apart,
|
|
253
|
-
# # but lets them achieve their natural equilibrium distance when closer
|
|
254
|
-
|
|
255
|
-
# psc = PreventScramblingConstraint(graphize(structure, atomnos),
|
|
256
|
-
# atoms,
|
|
257
|
-
# double_bond_protection=embedder.options.double_bond_protection,
|
|
258
|
-
# fix_angles=embedder.options.fix_angles_in_deformation)
|
|
259
|
-
|
|
260
|
-
# atoms.set_constraint(springs + halfsprings + [psc])
|
|
261
|
-
|
|
262
|
-
# t_start_opt = time.perf_counter()
|
|
263
|
-
# try:
|
|
264
|
-
# with LBFGS(atoms, maxstep=0.2, logfile=None, trajectory=traj) as opt:
|
|
265
|
-
|
|
266
|
-
# opt.run(fmax=0.05, steps=500)
|
|
267
|
-
# # initial coarse refinement with
|
|
268
|
-
# # Springs, Half Springs and PSC
|
|
269
|
-
|
|
270
|
-
# for spring in springs:
|
|
271
|
-
# spring.tighten()
|
|
272
|
-
# atoms.set_constraint(springs)
|
|
273
|
-
# # Tightening Springs to improve
|
|
274
|
-
# # spacings accuracy, removing PSC
|
|
275
|
-
# # spacings accuracy, removing PSC
|
|
276
|
-
|
|
277
|
-
# opt.run(fmax=0.05, steps=200)
|
|
278
|
-
# # final accurate refinement
|
|
279
|
-
|
|
280
|
-
# iterations = opt.nsteps
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
# new_structure = atoms.get_positions()
|
|
284
|
-
|
|
285
|
-
# success = scramble_check(new_structure, atomnos, constrained_indices, embedder.graphs)
|
|
286
|
-
# if iterations == 200:
|
|
287
|
-
# exit_str = 'MAX ITER'
|
|
288
|
-
# elif success:
|
|
289
|
-
# exit_str = 'REFINED'
|
|
290
|
-
# else:
|
|
291
|
-
# exit_str = 'SCRAMBLED'
|
|
292
|
-
|
|
293
|
-
# if iterations == 200:
|
|
294
|
-
# exit_str = 'MAX ITER'
|
|
295
|
-
# elif success:
|
|
296
|
-
# exit_str = 'REFINED'
|
|
297
|
-
# else:
|
|
298
|
-
# exit_str = 'SCRAMBLED'
|
|
299
|
-
|
|
300
|
-
# except PropertyNotImplementedError:
|
|
301
|
-
# exit_str = 'CRASHED'
|
|
302
|
-
|
|
303
|
-
# embedder.log(f' - {title} {exit_str} ({iterations} iterations, {time_to_string(time.perf_counter()-t_start_opt)})', p=False)
|
|
304
|
-
# embedder.log(f' - {title} {exit_str} ({iterations} iterations, {time_to_string(time.perf_counter()-t_start_opt)})', p=False)
|
|
305
|
-
|
|
306
|
-
# if exit_str == 'CRASHED':
|
|
307
|
-
# return None, None, False
|
|
308
|
-
|
|
309
|
-
# energy = atoms.get_total_energy() * 23.06054194532933 #eV to kcal/mol
|
|
310
|
-
|
|
311
|
-
# return new_structure, energy, success
|
|
312
|
-
|
|
313
397
|
def ase_saddle(embedder, coords, atomnos, constrained_indices=None, mols_graphs=None, title='temp', logfile=None, traj=None, freq=False, maxiterations=200):
|
|
314
398
|
'''
|
|
315
399
|
Runs a first order saddle optimization through the ASE package
|
|
@@ -632,9 +716,27 @@ def PreventScramblingConstraint(graph, atoms, double_bond_protection=False, fix_
|
|
|
632
716
|
|
|
633
717
|
return FixInternals(dihedrals_deg=dihedrals_deg, angles_deg=angles_deg, bonds=bonds, epsilon=1)
|
|
634
718
|
|
|
635
|
-
def ase_popt(
|
|
636
|
-
|
|
637
|
-
|
|
719
|
+
def ase_popt(
|
|
720
|
+
embedder,
|
|
721
|
+
coords,
|
|
722
|
+
atomnos,
|
|
723
|
+
|
|
724
|
+
constrained_indices=None,
|
|
725
|
+
constrained_distances=None,
|
|
726
|
+
|
|
727
|
+
constrained_angles_indices=None,
|
|
728
|
+
constrained_angles_values=None,
|
|
729
|
+
|
|
730
|
+
constrained_dihedrals_indices=None,
|
|
731
|
+
constrained_dihedrals_values=None,
|
|
732
|
+
|
|
733
|
+
steps=500,
|
|
734
|
+
safe=False,
|
|
735
|
+
safe_mask=None,
|
|
736
|
+
traj=None,
|
|
737
|
+
logfunction=None,
|
|
738
|
+
title='temp',
|
|
739
|
+
):
|
|
638
740
|
'''
|
|
639
741
|
embedder: firecode embedder object
|
|
640
742
|
coords:
|
|
@@ -650,11 +752,26 @@ def ase_popt(embedder, coords, atomnos, constrained_indices=None,
|
|
|
650
752
|
constraints = []
|
|
651
753
|
|
|
652
754
|
if constrained_indices is not None:
|
|
755
|
+
constrained_distances = constrained_distances or [None for _ in constrained_indices]
|
|
653
756
|
for i, c in enumerate(constrained_indices):
|
|
654
757
|
i1, i2 = c
|
|
655
|
-
tgt_dist = norm_of(coords[i1]-coords[i2])
|
|
758
|
+
tgt_dist = constrained_distances[i] or norm_of(coords[i1]-coords[i2])
|
|
656
759
|
constraints.append(Spring(i1, i2, tgt_dist))
|
|
657
760
|
|
|
761
|
+
if constrained_angles_indices is not None:
|
|
762
|
+
constrained_angles_values = constrained_angles_values or [None for _ in constrained_angles_indices]
|
|
763
|
+
for i, c in enumerate(constrained_angles_indices):
|
|
764
|
+
i1, i2, i3 = c
|
|
765
|
+
tgt_angle = constrained_angles_values[i] or point_angle(coords[i1], coords[i2], coords[i3])
|
|
766
|
+
constraints.append(PlanarAngleSpring(i1, i2, i3, tgt_angle))
|
|
767
|
+
|
|
768
|
+
if constrained_dihedrals_indices is not None:
|
|
769
|
+
constrained_dihedrals_values = constrained_dihedrals_values or [None for _ in constrained_dihedrals_indices]
|
|
770
|
+
for i, c in enumerate(constrained_dihedrals_indices):
|
|
771
|
+
i1, i2, i3, i4 = c
|
|
772
|
+
tgt_angle = constrained_dihedrals_values[i] or dihedral((coords[i1], coords[i2], coords[i3], coords[i4]))
|
|
773
|
+
constraints.append(DihedralSpring(i1, i2, i3, i4, tgt_angle))
|
|
774
|
+
|
|
658
775
|
if safe:
|
|
659
776
|
constraints.append(PreventScramblingConstraint(graphize(coords, atomnos, safe_mask),
|
|
660
777
|
atoms,
|
|
@@ -789,7 +906,7 @@ def ase_bend(embedder, original_mol, conf, pivot, threshold, title='temp', traj=
|
|
|
789
906
|
traj_obj.write()
|
|
790
907
|
|
|
791
908
|
# check if we are stuck
|
|
792
|
-
if np.max(np.abs(
|
|
909
|
+
if np.max(np.abs(norm_of(atoms.get_positions() - mol.atomcoords[0], axis=1))) < 0.01:
|
|
793
910
|
unproductive_iterations += 1
|
|
794
911
|
|
|
795
912
|
if unproductive_iterations == 10:
|
|
@@ -43,8 +43,8 @@ def automep(embedder, n_images=9):
|
|
|
43
43
|
ts_guess, _, _ = xtb_opt(
|
|
44
44
|
coords,
|
|
45
45
|
mol.atomnos,
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
constrained_dihedrals_indices=dihedrals+exocyclic,
|
|
47
|
+
constrained_dihedrals_values=target_angles,
|
|
48
48
|
method=embedder.options.theory_level,
|
|
49
49
|
solvent=embedder.options.solvent,
|
|
50
50
|
procs=embedder.procs
|