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.
Files changed (77) hide show
  1. {firecode-1.0.0 → firecode-1.1.1}/CHANGELOG.md +14 -0
  2. {firecode-1.0.0/firecode.egg-info → firecode-1.1.1}/PKG-INFO +20 -3
  3. {firecode-1.0.0 → firecode-1.1.1}/README.md +3 -2
  4. {firecode-1.0.0 → firecode-1.1.1}/docs/conf.py +1 -2
  5. {firecode-1.0.0 → firecode-1.1.1}/firecode/__main__.py +3 -1
  6. {firecode-1.0.0 → firecode-1.1.1}/firecode/algebra.py +4 -1
  7. {firecode-1.0.0 → firecode-1.1.1}/firecode/ase_manipulations.py +226 -109
  8. {firecode-1.0.0 → firecode-1.1.1}/firecode/atropisomer_module.py +0 -1
  9. {firecode-1.0.0 → firecode-1.1.1}/firecode/automep.py +2 -2
  10. {firecode-1.0.0 → firecode-1.1.1}/firecode/calculators/_xtb.py +269 -253
  11. firecode-1.1.1/firecode/eig_based_RMSD_test.ipynb +464 -0
  12. {firecode-1.0.0 → firecode-1.1.1}/firecode/embedder.py +168 -31
  13. {firecode-1.0.0 → firecode-1.1.1}/firecode/embedder_options.py +7 -3
  14. {firecode-1.0.0 → firecode-1.1.1}/firecode/graph_manipulations.py +4 -4
  15. {firecode-1.0.0 → firecode-1.1.1}/firecode/hypermolecule_class.py +1 -0
  16. {firecode-1.0.0 → firecode-1.1.1}/firecode/multiembed.py +1 -0
  17. {firecode-1.0.0 → firecode-1.1.1}/firecode/operators.py +64 -14
  18. {firecode-1.0.0 → firecode-1.1.1}/firecode/optimization_methods.py +18 -2
  19. {firecode-1.0.0 → firecode-1.1.1}/firecode/reactive_atoms_classes.py +12 -9
  20. {firecode-1.0.0 → firecode-1.1.1}/firecode/solvents.py +1 -1
  21. {firecode-1.0.0 → firecode-1.1.1}/firecode/torsion_module.py +23 -18
  22. {firecode-1.0.0 → firecode-1.1.1}/firecode/utils.py +310 -1
  23. {firecode-1.0.0 → firecode-1.1.1/firecode.egg-info}/PKG-INFO +20 -3
  24. {firecode-1.0.0 → firecode-1.1.1}/firecode.egg-info/SOURCES.txt +1 -0
  25. {firecode-1.0.0 → firecode-1.1.1}/firecode.egg-info/requires.txt +5 -2
  26. {firecode-1.0.0 → firecode-1.1.1}/setup.py +6 -3
  27. {firecode-1.0.0 → firecode-1.1.1}/LICENSE +0 -0
  28. {firecode-1.0.0 → firecode-1.1.1}/MANIFEST.in +0 -0
  29. {firecode-1.0.0 → firecode-1.1.1}/docs/examples.rst +0 -0
  30. {firecode-1.0.0 → firecode-1.1.1}/docs/index.rst +0 -0
  31. {firecode-1.0.0 → firecode-1.1.1}/docs/installation.rst +0 -0
  32. {firecode-1.0.0 → firecode-1.1.1}/docs/introduction.rst +0 -0
  33. {firecode-1.0.0 → firecode-1.1.1}/docs/license.rst +0 -0
  34. {firecode-1.0.0 → firecode-1.1.1}/docs/operators_keywords.rst +0 -0
  35. {firecode-1.0.0 → firecode-1.1.1}/docs/requirements.txt +0 -0
  36. {firecode-1.0.0 → firecode-1.1.1}/docs/usage.rst +0 -0
  37. {firecode-1.0.0 → firecode-1.1.1}/firecode/TEST_NOTEBOOK.ipynb +0 -0
  38. {firecode-1.0.0 → firecode-1.1.1}/firecode/__init__.py +0 -0
  39. {firecode-1.0.0 → firecode-1.1.1}/firecode/_gaussian.py +0 -0
  40. {firecode-1.0.0 → firecode-1.1.1}/firecode/calculators/__init__.py +0 -0
  41. {firecode-1.0.0 → firecode-1.1.1}/firecode/calculators/_gaussian.py +0 -0
  42. {firecode-1.0.0 → firecode-1.1.1}/firecode/calculators/_mopac.py +0 -0
  43. {firecode-1.0.0 → firecode-1.1.1}/firecode/calculators/_openbabel.py +0 -0
  44. {firecode-1.0.0 → firecode-1.1.1}/firecode/calculators/_orca.py +0 -0
  45. {firecode-1.0.0 → firecode-1.1.1}/firecode/concurrent_test.py +0 -0
  46. {firecode-1.0.0 → firecode-1.1.1}/firecode/embeds.py +0 -0
  47. {firecode-1.0.0 → firecode-1.1.1}/firecode/errors.py +0 -0
  48. {firecode-1.0.0 → firecode-1.1.1}/firecode/mep_relaxer.py +0 -0
  49. {firecode-1.0.0 → firecode-1.1.1}/firecode/modify_settings.py +0 -0
  50. {firecode-1.0.0 → firecode-1.1.1}/firecode/mprof.py +0 -0
  51. {firecode-1.0.0 → firecode-1.1.1}/firecode/nci.py +0 -0
  52. {firecode-1.0.0 → firecode-1.1.1}/firecode/numba_functions.py +0 -0
  53. {firecode-1.0.0 → firecode-1.1.1}/firecode/parameters.py +0 -0
  54. {firecode-1.0.0 → firecode-1.1.1}/firecode/pka.py +0 -0
  55. {firecode-1.0.0 → firecode-1.1.1}/firecode/profiler.py +0 -0
  56. {firecode-1.0.0 → firecode-1.1.1}/firecode/pruning.py +0 -0
  57. {firecode-1.0.0 → firecode-1.1.1}/firecode/pt.py +0 -0
  58. {firecode-1.0.0 → firecode-1.1.1}/firecode/quotes.json +0 -0
  59. {firecode-1.0.0 → firecode-1.1.1}/firecode/quotes.py +0 -0
  60. {firecode-1.0.0 → firecode-1.1.1}/firecode/references.py +0 -0
  61. {firecode-1.0.0 → firecode-1.1.1}/firecode/rmsd.py +0 -0
  62. {firecode-1.0.0 → firecode-1.1.1}/firecode/settings.py +0 -0
  63. {firecode-1.0.0 → firecode-1.1.1}/firecode/tests/C2F2H4.xyz +0 -0
  64. {firecode-1.0.0 → firecode-1.1.1}/firecode/tests/C2H4.xyz +0 -0
  65. {firecode-1.0.0 → firecode-1.1.1}/firecode/tests/CH3Cl.xyz +0 -0
  66. {firecode-1.0.0 → firecode-1.1.1}/firecode/tests/HCOOH.xyz +0 -0
  67. {firecode-1.0.0 → firecode-1.1.1}/firecode/tests/HCOOOH.xyz +0 -0
  68. {firecode-1.0.0 → firecode-1.1.1}/firecode/tests/chelotropic.txt +0 -0
  69. {firecode-1.0.0 → firecode-1.1.1}/firecode/tests/cyclical.txt +0 -0
  70. {firecode-1.0.0 → firecode-1.1.1}/firecode/tests/dihedral.txt +0 -0
  71. {firecode-1.0.0 → firecode-1.1.1}/firecode/tests/string.txt +0 -0
  72. {firecode-1.0.0 → firecode-1.1.1}/firecode/tests/trimolecular.txt +0 -0
  73. {firecode-1.0.0 → firecode-1.1.1}/firecode/tests.py +0 -0
  74. {firecode-1.0.0 → firecode-1.1.1}/firecode.egg-info/dependency_links.txt +0 -0
  75. {firecode-1.0.0 → firecode-1.1.1}/firecode.egg-info/top_level.txt +0 -0
  76. {firecode-1.0.0 → firecode-1.1.1}/pyproject.toml +0 -0
  77. {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.0.0
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.54.1
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
  [![License: GNU LGPL v3](https://img.shields.io/github/license/ntampellini/firecode)](https://opensource.org/licenses/LGPL-3.0)
7
- [![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/ntampellini/firecode)](https://www.codefactor.io/repository/github/ntampellini/firecode)
8
7
  ![Python Version](https://img.shields.io/badge/Python-3.8.10-blue)
9
8
  ![Size](https://img.shields.io/github/languages/code-size/ntampellini/firecode)
10
-
11
9
  ![Lines](https://sloc.xyz/github/ntampellini/firecode/)
10
+ [![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/ntampellini/firecode)](https://www.codefactor.io/repository/github/ntampellini/firecode)
11
+
12
12
  [![PyPI](https://img.shields.io/pypi/v/firecode)](https://pypi.org/project/firecode/)
13
13
  [![Wheel](https://img.shields.io/pypi/wheel/firecode)](https://pypi.org/project/firecode/)
14
14
  [![Documentation Status](https://readthedocs.org/projects/firecode/badge/?version=latest)](https://firecode.readthedocs.io/en/latest/?badge=latest)
15
15
  ![PyPI - Downloads](https://img.shields.io/pypi/dm/firecode)
16
16
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v1.json)](https://github.com/charliermarsh/ruff)
17
+
17
18
  ![Twitter](https://img.shields.io/twitter/url?url=https%3A%2F%2Ftwitter.com%2Fntampellini_&label=%40ntampellini_&link=https%3A%2F%2Ftwitter.com%2Fntampellini_)
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 = __version__
28
+ release = '1.1.0'
30
29
  # release='latest'
31
30
 
32
31
  # -- General configuration ---------------------------------------------------
@@ -22,8 +22,10 @@ Nicolo' Tampellini - nicolo.tampellini@yale.edu
22
22
  import argparse
23
23
  import os
24
24
  import sys
25
+ from rich.traceback import install
26
+ install(show_locals=True)
25
27
 
26
- __version__ = '1.0.0'
28
+ __version__ = '1.1.0'
27
29
 
28
30
  if __name__ == '__main__':
29
31
 
@@ -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
- get_double_bonds_indices, molecule_check,
50
- scramble_check, time_to_string, write_xyz)
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=100):
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
- xtb_supported)
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(embedder, coords, atomnos, constrained_indices=None,
636
- steps=500, targets=None, safe=False, safe_mask=None,
637
- traj=None, logfunction=None, title='temp'):
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]) if targets is None else targets[i]
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(np.linalg.norm(atoms.get_positions() - mol.atomcoords[0], axis=1))) < 0.01:
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:
@@ -373,7 +373,6 @@ def ase_dih_scan(embedder,
373
373
 
374
374
  iterations = opt.nsteps
375
375
 
376
-
377
376
  energies.append(atoms.get_total_energy() * 23.06054194532933) # eV to kcal/mol
378
377
 
379
378
  if logfile is not None:
@@ -43,8 +43,8 @@ def automep(embedder, n_images=9):
43
43
  ts_guess, _, _ = xtb_opt(
44
44
  coords,
45
45
  mol.atomnos,
46
- constrained_dihedrals=dihedrals+exocyclic,
47
- constrained_dih_angles=target_angles,
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