firecode 1.5.0__tar.gz → 1.5.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 (87) hide show
  1. {firecode-1.5.0 → firecode-1.5.1}/CHANGELOG.md +2 -1
  2. {firecode-1.5.0 → firecode-1.5.1}/PKG-INFO +8 -5
  3. {firecode-1.5.0 → firecode-1.5.1}/docs/examples.rst +7 -14
  4. firecode-1.5.1/docs/installation.rst +85 -0
  5. {firecode-1.5.0 → firecode-1.5.1}/docs/usage.rst +9 -13
  6. {firecode-1.5.0 → firecode-1.5.1}/firecode/ase_manipulations.py +50 -143
  7. {firecode-1.5.0 → firecode-1.5.1}/firecode/atropisomer_module.py +2 -1
  8. {firecode-1.5.0 → firecode-1.5.1}/firecode/calculators/_ase_uma.py +6 -2
  9. {firecode-1.5.0 → firecode-1.5.1}/firecode/calculators/_xtb.py +4 -3
  10. {firecode-1.5.0 → firecode-1.5.1}/firecode/embedder.py +6 -6
  11. {firecode-1.5.0 → firecode-1.5.1}/firecode/hypermolecule_class.py +1 -9
  12. {firecode-1.5.0 → firecode-1.5.1}/firecode/mep_relaxer.py +6 -4
  13. {firecode-1.5.0 → firecode-1.5.1}/firecode/operators.py +8 -38
  14. {firecode-1.5.0 → firecode-1.5.1}/firecode/optimization_methods.py +20 -12
  15. {firecode-1.5.0 → firecode-1.5.1}/firecode/quotes.json +21 -26
  16. firecode-1.5.0/firecode/smarts.py → firecode-1.5.1/firecode/rdkit_tools.py +109 -4
  17. {firecode-1.5.0 → firecode-1.5.1}/firecode/standalone_optimizer.py +3 -2
  18. {firecode-1.5.0 → firecode-1.5.1}/firecode/torsion_module.py +2 -2
  19. {firecode-1.5.0 → firecode-1.5.1}/firecode/utils.py +3 -51
  20. {firecode-1.5.0 → firecode-1.5.1}/pixi.lock +2 -2
  21. {firecode-1.5.0 → firecode-1.5.1}/pyproject.toml +14 -5
  22. firecode-1.5.0/docs/installation.rst +0 -104
  23. firecode-1.5.0/firecode/automep.py +0 -133
  24. firecode-1.5.0/firecode/explode_search.py +0 -218
  25. {firecode-1.5.0 → firecode-1.5.1}/.ase/gui.py +0 -0
  26. {firecode-1.5.0 → firecode-1.5.1}/.gitignore +0 -0
  27. {firecode-1.5.0 → firecode-1.5.1}/.readthedocs.yaml +0 -0
  28. {firecode-1.5.0 → firecode-1.5.1}/LICENSE +0 -0
  29. {firecode-1.5.0 → firecode-1.5.1}/MANIFEST.in +0 -0
  30. {firecode-1.5.0 → firecode-1.5.1}/README.md +0 -0
  31. {firecode-1.5.0 → firecode-1.5.1}/docs/conf.py +0 -0
  32. {firecode-1.5.0 → firecode-1.5.1}/docs/images/atropo.png +0 -0
  33. {firecode-1.5.0 → firecode-1.5.1}/docs/images/complex_embed_cd.png +0 -0
  34. {firecode-1.5.0 → firecode-1.5.1}/docs/images/embeds.svg +0 -0
  35. {firecode-1.5.0 → firecode-1.5.1}/docs/images/intro_embed.PNG +0 -0
  36. {firecode-1.5.0 → firecode-1.5.1}/docs/images/logo.png +0 -0
  37. {firecode-1.5.0 → firecode-1.5.1}/docs/images/orbitals.png +0 -0
  38. {firecode-1.5.0 → firecode-1.5.1}/docs/images/peptide.png +0 -0
  39. {firecode-1.5.0 → firecode-1.5.1}/docs/images/peptide_chemdraw.png +0 -0
  40. {firecode-1.5.0 → firecode-1.5.1}/docs/images/plot.svg +0 -0
  41. {firecode-1.5.0 → firecode-1.5.1}/docs/images/qz_firecode.gif +0 -0
  42. {firecode-1.5.0 → firecode-1.5.1}/docs/images/trimolecular.png +0 -0
  43. {firecode-1.5.0 → firecode-1.5.1}/docs/index.rst +0 -0
  44. {firecode-1.5.0 → firecode-1.5.1}/docs/introduction.rst +0 -0
  45. {firecode-1.5.0 → firecode-1.5.1}/docs/license.rst +0 -0
  46. {firecode-1.5.0 → firecode-1.5.1}/docs/operators_keywords.rst +0 -0
  47. {firecode-1.5.0 → firecode-1.5.1}/docs/requirements.txt +0 -0
  48. {firecode-1.5.0 → firecode-1.5.1}/firecode/__init__.py +0 -0
  49. {firecode-1.5.0 → firecode-1.5.1}/firecode/__main__.py +0 -0
  50. {firecode-1.5.0 → firecode-1.5.1}/firecode/algebra.py +0 -0
  51. {firecode-1.5.0 → firecode-1.5.1}/firecode/calculators/__init__.py +0 -0
  52. {firecode-1.5.0 → firecode-1.5.1}/firecode/calculators/_orca.py +0 -0
  53. {firecode-1.5.0 → firecode-1.5.1}/firecode/calculators/dummy_ase_calc.py +0 -0
  54. {firecode-1.5.0 → firecode-1.5.1}/firecode/concurrent_test.py +0 -0
  55. {firecode-1.5.0 → firecode-1.5.1}/firecode/embedder_options.py +0 -0
  56. {firecode-1.5.0 → firecode-1.5.1}/firecode/embeds.py +0 -0
  57. {firecode-1.5.0 → firecode-1.5.1}/firecode/errors.py +0 -0
  58. {firecode-1.5.0 → firecode-1.5.1}/firecode/graph_manipulations.py +0 -0
  59. {firecode-1.5.0 → firecode-1.5.1}/firecode/modify_settings.py +0 -0
  60. {firecode-1.5.0 → firecode-1.5.1}/firecode/multiembed.py +0 -0
  61. {firecode-1.5.0 → firecode-1.5.1}/firecode/numba_functions.py +0 -0
  62. {firecode-1.5.0 → firecode-1.5.1}/firecode/parameters.py +0 -0
  63. {firecode-1.5.0 → firecode-1.5.1}/firecode/pka.py +0 -0
  64. {firecode-1.5.0 → firecode-1.5.1}/firecode/profiler.py +0 -0
  65. {firecode-1.5.0 → firecode-1.5.1}/firecode/pt.py +0 -0
  66. {firecode-1.5.0 → firecode-1.5.1}/firecode/quotes.py +0 -0
  67. {firecode-1.5.0 → firecode-1.5.1}/firecode/reactive_atoms_classes.py +0 -0
  68. {firecode-1.5.0 → firecode-1.5.1}/firecode/references.py +0 -0
  69. {firecode-1.5.0 → firecode-1.5.1}/firecode/settings.py +0 -0
  70. {firecode-1.5.0 → firecode-1.5.1}/firecode/solvents.py +0 -0
  71. {firecode-1.5.0 → firecode-1.5.1}/firecode/tests/C2H4.xyz +0 -0
  72. {firecode-1.5.0 → firecode-1.5.1}/firecode/tests/chelotropic/C2H4.xyz +0 -0
  73. {firecode-1.5.0 → firecode-1.5.1}/firecode/tests/chelotropic/HCOOOH.xyz +0 -0
  74. {firecode-1.5.0 → firecode-1.5.1}/firecode/tests/chelotropic/chelotropic.txt +0 -0
  75. {firecode-1.5.0 → firecode-1.5.1}/firecode/tests/cyclical/C2H4.xyz +0 -0
  76. {firecode-1.5.0 → firecode-1.5.1}/firecode/tests/cyclical/cyclical.txt +0 -0
  77. {firecode-1.5.0 → firecode-1.5.1}/firecode/tests/dihedral/C2F2H4.xyz +0 -0
  78. {firecode-1.5.0 → firecode-1.5.1}/firecode/tests/dihedral/dihedral.txt +0 -0
  79. {firecode-1.5.0 → firecode-1.5.1}/firecode/tests/string/CH3Cl.xyz +0 -0
  80. {firecode-1.5.0 → firecode-1.5.1}/firecode/tests/string/HCOOH.xyz +0 -0
  81. {firecode-1.5.0 → firecode-1.5.1}/firecode/tests/string/string.txt +0 -0
  82. {firecode-1.5.0 → firecode-1.5.1}/firecode/tests/trimolecular/CH3Cl.xyz +0 -0
  83. {firecode-1.5.0 → firecode-1.5.1}/firecode/tests/trimolecular/HCOOH.xyz +0 -0
  84. {firecode-1.5.0 → firecode-1.5.1}/firecode/tests/trimolecular/trimolecular.txt +0 -0
  85. {firecode-1.5.0 → firecode-1.5.1}/firecode/tests.py +0 -0
  86. {firecode-1.5.0 → firecode-1.5.1}/firecode/units.py +0 -0
  87. {firecode-1.5.0 → firecode-1.5.1}/icon.ico +0 -0
@@ -10,7 +10,8 @@
10
10
  - Removed openbabel dependency.
11
11
  - Imports bugfixes.
12
12
  - Restructured setup to use [Pixi](https://pixi.prefix.dev/latest/).
13
- <!-- add documentation: SCRAMBLECHECK kw -->
13
+ - Removed SMILES to 3D conversion.
14
+ <!-- add documentation: SCRAMBLECHECK kw, fsm>, neb>, rdkit_search>-->
14
15
 
15
16
  ## FIRECODE 1.4.0 🔥 (January 25 2026) - Big cleanup and reorganization!
16
17
  - Similarity pruning is now done via the standalone [PRISM](https://github.com/ntampellini/prism_pruner) library.
@@ -1,16 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: firecode
3
- Version: 1.5.0
3
+ Version: 1.5.1
4
4
  Summary: FIRECODE: Filtering Refiner and Embedder for Conformationally Dense Ensembles
5
5
  Author-email: Nicolò Tampellini <nicolo.tampellini@yale.edu>
6
6
  License-Expression: LGPL-3.0-or-later
7
7
  License-File: LICENSE
8
8
  Requires-Python: <3.13,>=3.10
9
9
  Requires-Dist: ase
10
- Requires-Dist: fairchem-core
11
- Requires-Dist: importlib-metadata
12
10
  Requires-Dist: inquirerpy
13
- Requires-Dist: llvmlite
14
11
  Requires-Dist: matplotlib
15
12
  Requires-Dist: networkx
16
13
  Requires-Dist: numpy
@@ -20,7 +17,13 @@ Requires-Dist: psutil
20
17
  Requires-Dist: rdkit>=2025.9.3
21
18
  Requires-Dist: rich
22
19
  Requires-Dist: scipy
23
- Requires-Dist: typing-extensions
20
+ Provides-Extra: aimnet2
21
+ Requires-Dist: aimnet[ase]; extra == 'aimnet2'
22
+ Provides-Extra: full
23
+ Requires-Dist: aimnet[ase]; extra == 'full'
24
+ Requires-Dist: fairchem-core; extra == 'full'
25
+ Provides-Extra: uma
26
+ Requires-Dist: fairchem-core; extra == 'uma'
24
27
  Description-Content-Type: text/markdown
25
28
 
26
29
 
@@ -11,13 +11,13 @@ For detailed descriptions of the operators and keywords present in the inputs, s
11
11
 
12
12
  Work is in progress to expand this section with more examples.
13
13
 
14
- 1. Generation of a 3D structure from SMILES, conformational search and refinement
15
- +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
14
+ 1. Conformational search and refinement
15
+ +++++++++++++++++++++++++++++++++++++++
16
16
 
17
17
  ::
18
18
 
19
19
  LEVEL=GFN-FF
20
- refine> rsearch> opt> O=C(O)[C@H](NC([C@H](NC([C@@H](C)N)=O)C)=O)C
20
+ refine> rsearch> opt> tris_ala.xyz
21
21
 
22
22
  # This is a comment line!
23
23
 
@@ -25,11 +25,9 @@ Work is in progress to expand this section with more examples.
25
25
  # If XTB is not set as the default calculator, you can specify it
26
26
  # here adding CALC=XTB in the keyword line.
27
27
 
28
- # First, the SMILES string is converted into a 3D structure
29
- # (H2N-Ala-Ala-Ala-OH), then operators are applied starting
30
- # from the inside out:
28
+ # The operators are applied starting from the inside out:
31
29
 
32
- # opt> - the generated 3D structure will be optimized
30
+ # opt> - the structure will be optimized
33
31
 
34
32
  # rsearch> - a knowledge-based, torsion-based conformational search
35
33
  # is carried out. The number of conformers generated can be adjusted
@@ -75,7 +73,7 @@ Work is in progress to expand this section with more examples.
75
73
 
76
74
  ::
77
75
 
78
- SADDLE KCAL=10
76
+ KCAL=10
79
77
  scan> atropisomer.xyz 1 2 9 10
80
78
 
81
79
  # scan> : (four indices specified) performs two dihedral
@@ -84,12 +82,7 @@ Work is in progress to expand this section with more examples.
84
82
  # 10 kcal/mol (KCAL keyword) form the lowest energy
85
83
  # structure are re-scanned at increased accuracy (1°
86
84
  # increments).
87
-
88
- # SADDLE: Each maxima is then optimized to a saddle point.
89
-
90
- # It is also possible to replace SADDLE with NEB to use scan
91
- # points to run a NEB in an automated way.
92
-
85
+
93
86
  .. figure:: /images/atropo.png
94
87
  :alt: Example output structure
95
88
  :width: 75%
@@ -0,0 +1,85 @@
1
+ .. _installation:
2
+
3
+ Installation
4
+ ============
5
+
6
+ This program is written in Python (currently >=3.12). The recommended way to install it is through conda/mamba and uv.
7
+
8
+ ::
9
+
10
+ # create a new conda environment and install firecode with uv
11
+ conda create --name firecode python=3.12 uv
12
+ conda activate firecode
13
+ uv pip install firecode
14
+
15
+ After installation, run the guided utility to finalize the setup:
16
+
17
+ ::
18
+
19
+ firecode --setup
20
+
21
+
22
+ Optional dependencies (WIP)
23
+ ===========================
24
+
25
+ CREST
26
+ -----
27
+
28
+ This is free software. See the `GitHub repository <https://github.com/grimme-lab/crest>`__.
29
+ Currently only CREST 2.12 is supported, working on supporting the latest version is in progress.
30
+
31
+ ::
32
+
33
+ # install mamba if you don't have it already
34
+ conda install -c conda-forge mamba
35
+
36
+ mamba install -c conda-forge crest==2.12
37
+
38
+
39
+ AIMNET2 / UMA models
40
+ --------------------
41
+
42
+ The software can be interfaced with AIMNET2 and UMA models. Work is in progress to port AIMNET2 models to the new codebase.
43
+ An interface to the UMA models is available provided that the models are present on the system at the location specified in
44
+ the settings.py file (change with ``firecode --settings``). The UMA models can be obtained from HuggingFace at https://huggingface.co/facebook/UMA.
45
+
46
+
47
+
48
+ XTB (recommended for Force Field and semiempirical calculations)
49
+ ----------------------------------------------------------------
50
+
51
+ This is free software. See the `GitHub
52
+ repository <https://github.com/grimme-lab/xtb>`__ and the
53
+ `documentation <https://xtb-docs.readthedocs.io/en/latest/contents.html>`__
54
+ for how to install it on your machine. The package and its python bindings are available through conda as well.
55
+
56
+ ::
57
+
58
+ # install mamba if you don't have it already
59
+ conda install -c conda-forge mamba
60
+
61
+ mamba install -c conda-forge xtb xtb-python
62
+
63
+ TBLITE
64
+ ------
65
+
66
+ Light-weight version of the xTB, free for academic use. See the `GitHub repository <https://github.com/tblite/tblite>`__.
67
+
68
+ ::
69
+
70
+ # install mamba if you don't have it already
71
+ conda install -c conda-forge mamba
72
+
73
+ mamba install -c conda-forge tblite tblite-python
74
+
75
+
76
+ ORCA
77
+ ----
78
+
79
+ This software is only free for academic use at an academic institution.
80
+ Detailed instructions on how to install and set up ORCA can be found in
81
+ `the official
82
+ website <https://sites.google.com/site/orcainputlibrary/setting-up-orca>`__.
83
+ Make sure to install and set up OpenMPI along with ORCA if you wish to
84
+ exploit multiple cores on your machine **(Note: semiempirical methods
85
+ cannot yet be parallelized in ORCA!)**
@@ -7,7 +7,7 @@ The program can be run from terminal, with the command:
7
7
 
8
8
  ::
9
9
 
10
- python -m firecode myinput.txt -n [custom_title]
10
+ firecode myinput.txt -n [custom_title]
11
11
 
12
12
  A custom name for the run can be optionally provided with the -n flag, otherwise a time
13
13
  stamp will be used to name the output files.
@@ -17,16 +17,14 @@ instruction in a string after the ``-cl``/``--command_line`` argument:
17
17
 
18
18
  ::
19
19
 
20
- python -m firecode -cl "csearch> molecule.xyz"
21
- python -m firecode --command_line "csearch> molecule.xyz"
20
+ firecode -cl "csearch> molecule.xyz"
22
21
 
23
22
  In this case, an input file will be written for you by the program.
24
23
 
25
24
  Input formatting
26
25
  ----------------
27
26
 
28
- The input can be any text file. The extension is arbitrary but I suggest
29
- sticking with ``.txt``.
27
+ The input can be any text file, but sticking with ``.txt`` or ``.inp`` is recommended.
30
28
 
31
29
  - Any blank line will be ignored
32
30
  - Any line starting with ``#`` will be ignored
@@ -34,17 +32,15 @@ sticking with ``.txt``.
34
32
 
35
33
  Then, molecule files are specified. A molecule line is made up of these elements, in this order:
36
34
 
37
- - An operator (optional)
35
+ - Zero or more operators (*i.e.* ``csearch>``, ``opt>``, etc.) separated by spaces
38
36
  - The molecule file name (required)
39
- - Indices (numbers) and pairings (letters) for the molecule (optional)
37
+ - Optional indices (numbers) and pairings (letters) for the molecule
38
+ - Optional properties of the molecule (*i.e.* ``charge=1``, ``property=value``)
40
39
 
41
- An example with all three is ``opt> butadiene.xyz 6a 8b``.
40
+ An example with all four is ``refine> rsearch> butadiene.xyz 6a 8b charge=1``.
42
41
 
43
- FIRECODE can work with all molecular formats read by
44
- `cclib <https://github.com/cclib/cclib>`__, but best practice is using
45
- only the ``.xyz`` file format, particularly for multimolecular files
46
- containing different conformers of the same molecule. **Molecule indices
47
- are counted starting from zero!**
42
+ FIRECODE works with ``.xyz`` files. **Molecule indices
43
+ are zero-based! (counted starting from zero!)**
48
44
 
49
45
  Operators
50
46
  +++++++++
@@ -27,6 +27,7 @@ import time
27
27
  import warnings
28
28
  from copy import deepcopy
29
29
  from shutil import rmtree
30
+ from subprocess import getoutput
30
31
 
31
32
  import matplotlib.pyplot as plt
32
33
  import numpy as np
@@ -46,7 +47,7 @@ from prism_pruner.utils import (align_structures, get_double_bonds_indices,
46
47
 
47
48
  from firecode.algebra import norm_of, point_angle
48
49
  from firecode.calculators.__init__ import NewFolderContext
49
- from firecode.calculators.dummy_ase_calc import DummyCalculator
50
+ from firecode.calculators._xtb import xtb_gsolv
50
51
  from firecode.units import EH_TO_EV, EH_TO_KCAL, EV_TO_KCAL
51
52
  from firecode.utils import (HiddenPrints, cartesian_product, clean_directory,
52
53
  molecule_check, read_xyz, suppress_stdout_stderr,
@@ -701,107 +702,16 @@ def set_charge_and_mult_on_ase_atoms(ase_atoms, charge, mult):
701
702
  mult_list[0] = mult - 1
702
703
  ase_atoms.set_initial_magnetic_moments(mult_list)
703
704
 
704
- def ase_popt(
705
- embedder,
706
- atoms,
707
- coords,
708
- charge: int,
709
- mult: int,
710
-
711
- constrained_indices=None,
712
- constrained_distances=None,
713
-
714
- constrained_angles_indices=None,
715
- constrained_angles_values=None,
716
-
717
- constrained_dihedrals_indices=None,
718
- constrained_dihedrals_values=None,
719
-
720
- ase_constraints=None,
721
-
722
- steps=500,
723
- safe=False,
724
- safe_mask=None,
725
- traj=None,
726
- logfunction=None,
727
- title='temp',
728
-
729
- **kwargs,
730
- ):
731
- '''
732
- embedder: firecode embedder object
733
- coords:
734
- atoms:
735
- constrained_indices:
736
- safe: if True, adds a potential that prevents atoms from scrambling
737
- safe_mask: bool array, with False for atoms to be excluded when calculating bonds to preserve
738
- traj: if set to a string, traj is used as a filename for the bending trajectory.
739
- not only the atoms will be printed, but also all the orbitals and the active pivot.
740
- '''
741
- ase_atoms = Atoms(atoms, positions=coords)
742
- set_charge_and_mult_on_ase_atoms(ase_atoms, charge, mult)
743
-
744
- # set calculator
745
- ase_atoms.calc = embedder.dispatcher.get_ase_calc(embedder.options.theory_level, embedder.options.solvent)
746
- constraints = []
747
-
748
- if constrained_indices is not None:
749
- constrained_distances = constrained_distances or [None for _ in constrained_indices]
750
- for i, c in enumerate(constrained_indices):
751
- i1, i2 = c
752
- tgt_dist = constrained_distances[i] or norm_of(coords[i1]-coords[i2])
753
- constraints.append(Spring(i1, i2, tgt_dist))
754
-
755
- if constrained_angles_indices is not None:
756
- constrained_angles_values = constrained_angles_values or [None for _ in constrained_angles_indices]
757
- for i, c in enumerate(constrained_angles_indices):
758
- i1, i2, i3 = c
759
- tgt_angle = constrained_angles_values[i] or point_angle(coords[i1], coords[i2], coords[i3])
760
- constraints.append(PlanarAngleSpring(i1, i2, i3, tgt_angle))
761
-
762
- if constrained_dihedrals_indices is not None:
763
- constrained_dihedrals_values = constrained_dihedrals_values or [None for _ in constrained_dihedrals_indices]
764
- for i, c in enumerate(constrained_dihedrals_indices):
765
- i1, i2, i3, i4 = c
766
- tgt_angle = constrained_dihedrals_values[i] or dihedral((coords[i1], coords[i2], coords[i3], coords[i4]))
767
- constraints.append(DihedralSpring(i1, i2, i3, i4, tgt_angle))
768
-
769
- if ase_constraints is not None:
770
- constraints.extend(ase_constraints)
771
-
772
- if safe:
773
- constraints.append(PreventScramblingConstraint(graphize(atoms, coords, safe_mask),
774
- atoms,
775
- double_bond_protection=embedder.options.double_bond_protection,
776
- fix_angles=embedder.options.fix_angles_in_deformation))
777
-
778
- ase_atoms.set_constraint(constraints)
779
-
780
- t_start_opt = time.perf_counter()
781
- with LBFGS(ase_atoms, maxstep=0.1, logfile=None, trajectory=traj) as opt:
782
- opt.run(fmax=0.05, steps=steps)
783
- iterations = opt.nsteps
705
+ return ase_atoms
784
706
 
785
- new_structure = ase_atoms.get_positions()
786
- success = (iterations < 499)
787
-
788
- if logfunction is not None:
789
- exit_str = 'REFINED' if success else 'MAX ITER'
790
- logfunction(f' - {title} {exit_str} ({iterations} iterations, {time_to_string(time.perf_counter()-t_start_opt)})')
791
-
792
- if embedder.options.final_sp_level is None:
793
- energy = ase_atoms.get_total_energy() * EV_TO_KCAL
794
-
795
- else:
796
- ase_atoms.calc = embedder.dispatcher.get_ase_calc(embedder.options.final_sp_level, embedder.options.solvent)
797
- energy = ase_atoms.get_total_energy() * EV_TO_KCAL
798
-
799
- return new_structure, energy, success
800
-
801
- def ase_popt_lite(
707
+ def ase_popt(
802
708
  atoms,
803
709
  coords,
804
710
  ase_calc,
711
+ charge,
712
+ mult,
713
+ solvent=None,
714
+ add_alpb_solvation=False,
805
715
 
806
716
  constrained_indices=None,
807
717
  constrained_distances=None,
@@ -813,33 +723,23 @@ def ase_popt_lite(
813
723
  constrained_dihedrals_values=None,
814
724
 
815
725
  ase_constraints=None,
816
- new_bond_preventer=None,
817
726
 
818
- steps=500,
727
+ maxiter=None,
819
728
  traj=None,
820
729
  logfunction=None,
821
730
  title='temp',
822
-
823
- dummy_first=False,
731
+ debug=False,
824
732
 
825
733
  **kwargs,
826
734
  ):
827
735
  '''
828
- embedder: firecode embedder object
829
- coords:
830
- atoms:
831
- constrained_indices:
832
-
833
- dummy_first: run a cycle with a fast, empty optimizer first, still enforcing constraints
834
736
 
835
737
  '''
836
- atoms = Atoms(atoms, positions=coords)
837
738
 
838
- if dummy_first:
839
- atoms.calc = DummyCalculator()
840
- new_bond_preventer = new_bond_preventer or []
841
- else:
842
- atoms.calc = ase_calc
739
+ ase_atoms = Atoms(atoms, positions=coords)
740
+ ase_atoms.calc = ase_calc
741
+ ase_atoms = set_charge_and_mult_on_ase_atoms(ase_atoms, charge, mult)
742
+ maxiter = maxiter or 750
843
743
 
844
744
  constraints = []
845
745
 
@@ -867,41 +767,49 @@ def ase_popt_lite(
867
767
  if ase_constraints is not None:
868
768
  constraints.extend(ase_constraints)
869
769
 
870
- atoms.set_constraint(constraints)
770
+ ase_atoms.set_constraint(constraints)
871
771
 
872
- t_start_opt = time.perf_counter()
873
- with suppress_stdout_stderr():
772
+ # create working folder and cd into it
773
+ with NewFolderContext(title, delete_after=(not debug)):
874
774
 
875
- if dummy_first:
775
+ t_start_opt = time.perf_counter()
876
776
 
877
- # dummy with just bond distances
878
- with LBFGS(atoms, maxstep=0.1, logfile=None, trajectory=traj[:-5]+"_dummy.traj") as opt:
879
- opt.run(fmax=0.2, steps=50)
777
+ with suppress_stdout_stderr():
778
+ with LBFGS(ase_atoms, maxstep=0.2, logfile=None, trajectory=traj) as opt:
779
+ opt.run(fmax=0.05, steps=maxiter)
780
+ iterations = opt.nsteps
880
781
 
881
- # add optional NewBondPreventer and do some more steps
882
- atoms.set_constraint(constraints + [new_bond_preventer])
782
+ new_structure = ase_atoms.get_positions()
783
+ success = (iterations < maxiter-1)
883
784
 
884
- with LBFGS(atoms, maxstep=0.1, logfile=None, trajectory=traj[:-5]+"_dummy.traj") as opt:
885
- opt.run(fmax=0.2, steps=30)
785
+ if logfunction is not None:
786
+ exit_str = 'REFINED' if success else 'MAX ITER'
787
+ logfunction(f' - {title} {exit_str} ({iterations} iterations, {time_to_string(time.perf_counter()-t_start_opt)})')
886
788
 
887
- # remove additional NewBondPreventer constraint and set the desired calc
888
- atoms.set_constraint(constraints)
889
- atoms.calc = ase_calc
789
+ energy = ase_atoms.get_total_energy() * EV_TO_KCAL
890
790
 
891
- with LBFGS(atoms, maxstep=0.1, logfile=None, trajectory=traj) as opt:
892
- opt.run(fmax=0.05, steps=steps)
893
- iterations = opt.nsteps
791
+ if traj is not None:
792
+ getoutput(f"ase convert {traj} {traj}.xyz")
793
+
794
+ if solvent is not None and add_alpb_solvation:
795
+ gsolv = xtb_gsolv(
796
+ atoms,
797
+ new_structure,
798
+ model='alpb',
799
+ charge=charge,
800
+ mult=mult,
801
+ solvent=solvent,
802
+ title=title,
803
+ assert_convergence=True,
804
+ )
805
+ energy += gsolv
894
806
 
895
- new_structure = atoms.get_positions()
896
- success = (iterations < 499)
897
807
 
898
- if logfunction is not None:
899
- exit_str = 'REFINED' if success else 'MAX ITER'
900
- logfunction(f' - {title} {exit_str} ({iterations} iterations, {time_to_string(time.perf_counter()-t_start_opt)})')
808
+ return new_structure, energy, success
901
809
 
902
- energy = atoms.get_total_energy() * EV_TO_KCAL
903
810
 
904
- return new_structure, energy, success
811
+ def ase_popt_with_alpb(*args, **kwargs):
812
+ return ase_popt(*args, add_alpb_solvation=True, **kwargs)
905
813
 
906
814
 
907
815
  def ase_bend(embedder, original_mol, conf, pivot, threshold, title='temp', traj=None, check=True):
@@ -1371,7 +1279,6 @@ def fsm_operator(embedder, optimize_endpoints=True):
1371
1279
  from mlfsm.cos import FreezingString
1372
1280
  from mlfsm.opt import CartesianOptimizer, InternalsOptimizer, Optimizer
1373
1281
 
1374
-
1375
1282
  # interp = 'ric'
1376
1283
  interp = 'cart'
1377
1284
  maxiter = 100 # default = 3
@@ -1393,7 +1300,7 @@ def fsm_operator(embedder, optimize_endpoints=True):
1393
1300
 
1394
1301
  reactant, _, _ = ase_popt(
1395
1302
  embedder=embedder,
1396
- atoms=mol.atoms,
1303
+ ase_atoms=mol.atoms,
1397
1304
  coords=reactant,
1398
1305
  charge=mol.charge,
1399
1306
  mult=mol.mult,
@@ -1401,7 +1308,7 @@ def fsm_operator(embedder, optimize_endpoints=True):
1401
1308
 
1402
1309
  product, _, _ = ase_popt(
1403
1310
  embedder=embedder,
1404
- atoms=mol.atoms,
1311
+ ase_atoms=mol.atoms,
1405
1312
  coords=product,
1406
1313
  charge=mol.charge,
1407
1314
  mult=mol.mult,
@@ -1410,10 +1317,10 @@ def fsm_operator(embedder, optimize_endpoints=True):
1410
1317
  reactant, product = align_structures(np.stack((reactant, product)))
1411
1318
 
1412
1319
  reactant = Atoms(mol.atoms, reactant)
1413
- set_charge_and_mult_on_ase_atoms(reactant, charge, mult)
1320
+ reactant = set_charge_and_mult_on_ase_atoms(reactant, charge, mult)
1414
1321
 
1415
1322
  product = Atoms(mol.atoms, product)
1416
- set_charge_and_mult_on_ase_atoms(product, charge, mult)
1323
+ product = set_charge_and_mult_on_ase_atoms(product, charge, mult)
1417
1324
 
1418
1325
 
1419
1326
  workdir = f'{mol.rootname}_freezing_string'
@@ -37,6 +37,7 @@ from prism_pruner.utils import align_structures, time_to_string
37
37
  from firecode.ase_manipulations import ase_neb
38
38
  from firecode.errors import ZeroCandidatesError
39
39
  from firecode.optimization_methods import optimize
40
+ from firecode.units import EV_TO_KCAL
40
41
  from firecode.utils import clean_directory, loadbar, molecule_check, write_xyz
41
42
 
42
43
 
@@ -357,7 +358,7 @@ def ase_dih_scan(embedder,
357
358
 
358
359
  iterations = opt.nsteps
359
360
 
360
- energies.append(atoms.get_total_energy() * 23.06054194532933) # eV to kcal/mol
361
+ energies.append(atoms.get_total_energy() * EV_TO_KCAL)
361
362
 
362
363
  if logfile is not None:
363
364
  elapsed = time() - t_start_step
@@ -13,6 +13,7 @@ from firecode.ase_manipulations import (DihedralSpring, PlanarAngleSpring,
13
13
  from firecode.calculators.__init__ import NewFolderContext
14
14
  from firecode.calculators._xtb import xtb_gsolv
15
15
  from firecode.settings import UMA_MODEL_PATH
16
+ from firecode.units import EV_TO_KCAL
16
17
 
17
18
 
18
19
  def uma_opt(
@@ -120,7 +121,7 @@ def uma_opt(
120
121
  exit_str = 'REFINED' if success else 'MAX ITER'
121
122
  logfunction(f' - {title} {exit_str} ({iterations} iterations, {time_to_string(time.perf_counter()-t_start_opt)})')
122
123
 
123
- energy = ase_atoms.get_total_energy() * 23.06054194532933 #eV to kcal/mol
124
+ energy = ase_atoms.get_total_energy() * EV_TO_KCAL
124
125
 
125
126
  if traj is not None:
126
127
  os.system(f"ase convert {traj} {title}_trj.xyz")
@@ -158,7 +159,10 @@ def get_uma_calc(method="omol", logfunction=None):
158
159
 
159
160
  except ImportError as err:
160
161
  print(err)
161
- raise ImportError('To run the UMA models, please install fairchem:\n >>> pip install fairchem-core')
162
+ raise ImportError('To run the UMA models, please install fairchem:\n'
163
+ ' >>> uv pip install fairchem-core\n' \
164
+ 'or alternatively, install the "uma" version of firecode:\n'
165
+ ' >>> uv pip install firecode[uma]\n')
162
166
 
163
167
  gpu_bool = cuda.is_available()
164
168
 
@@ -30,6 +30,7 @@ import numpy as np
30
30
  from firecode.algebra import norm_of, normalize
31
31
  from firecode.calculators.__init__ import NewFolderContext
32
32
  from firecode.graph_manipulations import get_sum_graph
33
+ from firecode.units import EH_TO_KCAL
33
34
  from firecode.utils import clean_directory, read_xyz, write_xyz
34
35
 
35
36
 
@@ -377,7 +378,7 @@ def read_from_xtbtraj(filename):
377
378
  xyzblock = lines[first_coord_line:]
378
379
 
379
380
  coords = np.array([line.split()[1:] for line in xyzblock], dtype=float)
380
- energy = float(lines[first_coord_line-1].split()[1]) * 627.5096080305927 # Eh to kcal/mol
381
+ energy = float(lines[first_coord_line-1].split()[1]) * EH_TO_KCAL
381
382
 
382
383
  return coords, energy
383
384
 
@@ -390,7 +391,7 @@ def energy_grepper(filename, signal_string, position):
390
391
  while True:
391
392
  line = f.readline()
392
393
  if signal_string in line:
393
- return float(line.split()[position]) * 627.5096080305927 # Eh to kcal/mol
394
+ return float(line.split()[position]) * EH_TO_KCAL
394
395
  if not line:
395
396
  raise Exception(f'Could not find \'{signal_string}\' in file ({filename}).')
396
397
 
@@ -552,7 +553,7 @@ def crest_mtd_search(
552
553
 
553
554
  '''
554
555
 
555
- with NewFolderContext(title):
556
+ with NewFolderContext(title, delete_after=False):
556
557
 
557
558
  if constrained_indices is not None:
558
559
  if len(constrained_indices) == 0:
@@ -57,11 +57,10 @@ from firecode.numba_functions import (compenetration_check, count_clashes,
57
57
  from firecode.operators import operate
58
58
  from firecode.optimization_methods import Opt_func_dispatcher, fitness_check
59
59
  from firecode.parameters import orb_dim_dict
60
- from firecode.pt import pt
61
60
  from firecode.references import references
62
61
  from firecode.settings import DEFAULT_LEVELS, PROCS
63
62
  from firecode.torsion_module import get_quadruplets
64
- from firecode.utils import (Constraint, _saturation_check, ase_view,
63
+ from firecode.utils import (Constraint, saturation_check, ase_view,
65
64
  auto_newline, cartesian_product, clean_directory,
66
65
  loadbar, scramble_check, timing_wrapper, write_xyz)
67
66
 
@@ -399,7 +398,7 @@ class Embedder:
399
398
  for mol in self.objects:
400
399
  charge = int(mol.charge) if hasattr(mol, "charge") else self.options.charge
401
400
 
402
- if _saturation_check(mol.atoms, charge):
401
+ if saturation_check(mol.atoms, charge):
403
402
  self.log(f"--> {mol.filename}: saturation check passed (even saturation index with CHG={charge}, MULT={self.options.mult})")
404
403
 
405
404
  # this may be a radical (and we would expect an even multiplicity) or something is wrong
@@ -2223,6 +2222,7 @@ class RunEmbedding(Embedder):
2223
2222
  ase_calc=self.dispatcher.ase_calc,
2224
2223
  solvent=self.options.solvent,
2225
2224
  charge=self.options.charge,
2225
+ mult=self.options.mult,
2226
2226
  maxiter=maxiter,
2227
2227
  conv_thr=conv_thr,
2228
2228
 
@@ -2291,8 +2291,8 @@ class RunEmbedding(Embedder):
2291
2291
  else:
2292
2292
  self.energies[i] = 1E10
2293
2293
 
2294
- ### Update checkpoint every (20*max_workers) optimized structures, and give an estimate of the remaining time
2295
- chk_freq = int(self.avail_cpus//4) * self.options.checkpoint_frequency
2294
+ ### Update checkpoint every 50 optimized structures, and give an estimate of the remaining time
2295
+ chk_freq = self.options.checkpoint_frequency
2296
2296
  if i % chk_freq == chk_freq-1:
2297
2297
 
2298
2298
  with open(self.outname, 'w') as f:
@@ -2553,7 +2553,7 @@ class RunEmbedding(Embedder):
2553
2553
  self.write_mol_info()
2554
2554
 
2555
2555
  if self.embed is None:
2556
- self.log('--> No embed requested, exiting.\n')
2556
+ self.log('--> No embed or refinement requested, exiting.\n')
2557
2557
  self.normal_termination()
2558
2558
 
2559
2559
  if self.embed == 'error':
@@ -32,7 +32,7 @@ from firecode.errors import CCReadError, NoOrbitalError
32
32
  from firecode.graph_manipulations import is_sigmatropic, is_vicinal
33
33
  from firecode.pt import pt
34
34
  from firecode.reactive_atoms_classes import get_atom_type
35
- from firecode.utils import read_xyz, smi_to_3d
35
+ from firecode.utils import read_xyz
36
36
 
37
37
  warnings.simplefilter("ignore", UserWarning)
38
38
 
@@ -107,14 +107,6 @@ class Hypermolecule:
107
107
  if '.' in filename:
108
108
  raise SyntaxError((f'Molecule {filename} cannot be read. Please check your syntax.'))
109
109
 
110
- try:
111
- filename = smi_to_3d(filename, "generated_3D_coords")
112
- print(f"--> Embedded SMILES string to 3D structure, saved as {filename}")
113
-
114
- except Exception:
115
- raise SyntaxError((f'The program is trying to read something that is not a valid molecule input ({filename}). ' +
116
- 'If this looks like a keyword, it is probably faulted by a syntax error.'))
117
-
118
110
  self.rootname = filename.split('.')[0]
119
111
  self.filename = filename
120
112
  self.debug_logfunction = debug_logfunction