Skip to content

Commit 2d36a30

Browse files
authored
Merge pull request #14 from lanl/FF_preopt
Add FF pre-optimization option for final-generated architector structures.
2 parents 4d5eafb + c85975b commit 2d36a30

File tree

6 files changed

+66
-10
lines changed

6 files changed

+66
-10
lines changed

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ pip install -e .
3030
1. See tutorials for basic introduction to capabilties and code examples: `documentation/tutorials/`
3131
2. Reference for core and ligand geometry labels see: `documentation/view_default_core_ligand_types.ipynb`
3232
3. Utility for aiding in determining ligand coordination sites see: `utils/ligand_viewing_coordinating_atom_selecting.ipynb`
33+
4. Reference for debugging cases where no structures are generated see: `documentation/Debugging_Guide.ipynb`
3334

34-
* Note that ligands used in (3) can even be drawn in [Avogadro](https://avogadro.cc/) and copied as SMILES strings into this analysis.
35+
* Note that ligands used in (3) can even be drawn in [Avogadro](https://avogadro.cc/) and or most other 2D molecular editors (e.g. ChemDraw) and copied as SMILES strings into this analysis.
3536
* If other analyses are used to determine the coordinating atom indices we can't guarantee the generated structure will match what was input. If generating complexes with new ligands we HIGHLY recommend using the utility in (3)
3637

3738
## XTB (backend) Potentially Useful References:
@@ -115,6 +116,11 @@ inputDict = {
115116
"xtb_max_iterations": 250, # Max iterations for xtb SCF.
116117
"force_generation":False, # Whether to force the construction to proceed without xtb energies - defaults to UFF evaluation
117118
# in cases of XTB outright failure. Will still enforce sanity checks on output structures.
119+
"ff_preopt":False, # Whether to force forcefield (FF) pre-optimization of fully-built complexes before final xtb evalulation.
120+
# FF preoptimization helps especially in cases where longer carbon chains are present that tend to overlap.
121+
# This option will also decrease the maximum force tolerance for xtb relaxation to 0.2 and set assemble_method='GFN-FF'
122+
# By default for acceleration.
123+
118124

119125
# Covalent radii and vdw radii of the metal if nonstandard radii requested.
120126
"vdwrad_metal": vdwrad_metal,

architector/complex_construction.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import architector.io_align_mol as io_align_mol
2020
import architector.io_crest as io_crest
2121
import architector.io_symmetry as io_symmetry
22-
2322
from architector.io_calc import CalcExecutor
2423

2524
class Ligand:
@@ -240,11 +239,25 @@ def final_eval(self,single_point=False):
240239
self.initMol.dist_sanity_checks(params=self.parameters,assembly=single_point)
241240
self.initMol.graph_sanity_checks(params=self.parameters,assembly=single_point)
242241
if self.assembled:
242+
if self.parameters['ff_preopt'] and (not single_point):
243+
if self.parameters['debug']:
244+
print('Doing UFF - pre-optimization before final evaluation.')
245+
calculator = CalcExecutor(self.complexMol, parameters=self.parameters,
246+
final_sanity_check=False,
247+
relax=True, assembly=single_point,
248+
ff_preopt_run=self.parameters['ff_preopt'])
249+
if calculator:
250+
self.complexMol.ase_atoms.set_positions(calculator.mol.ase_atoms.get_positions())
243251
if self.parameters['debug']:
244252
print("Final Evaluation - Opt Molecule/Single point")
245-
self.calculator = CalcExecutor(self.complexMol,parameters=self.parameters,
246-
final_sanity_check=self.parameters['full_sanity_checks'],
247-
relax=single_point,assembly=single_point)
253+
self.calculator = CalcExecutor(self.complexMol,
254+
parameters=self.parameters,
255+
final_sanity_check=self.parameters['full_sanity_checks'],
256+
relax=single_point,
257+
assembly=single_point)
258+
if (self.parameters['debug']) and (self.calculator):
259+
print('Finished method: {} {}.'.format(self.calculator.method,
260+
self.calculator.relax))
248261
if self.parameters['debug'] and (not self.calculator.successful):
249262
print('Failed final relaxation. - Retrying with UFF/XTB')
250263
print(self.initMol.write_mol2('cool.mol2', writestring=True))

architector/io_calc.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def __init__(self, structure, parameters={}, init_sanity_check=False,
7070
final_sanity_check=False, relax=False, assembly=False,
7171
method='GFN2-xTB', xtb_solvent='none', xtb_accuracy=1.0,
7272
xtb_electronic_temperature=300, xtb_max_iterations=250,
73-
fmax=0.1, maxsteps=1000,
73+
fmax=0.1, maxsteps=1000, ff_preopt_run=False,
7474
detect_spin_charge=False, fix_m_neighbors=False,
7575
default_params=params, ase_opt_method=None,
7676
calculator=None):
@@ -107,6 +107,8 @@ def __init__(self, structure, parameters={}, init_sanity_check=False,
107107
Max force in eV/Angstrom, by default 0.1
108108
maxsteps : int, optional
109109
Total number of optimization steps to take, by default 1000
110+
ff_preopt_run : bool, optional
111+
Perform forcefield pre-optimization?, by default False
110112
detect_spin_charge : bool, optional
111113
Detect the charge and spin with openbabel routines?, by default False
112114
fix_m_neighbors : bool, optional
@@ -130,6 +132,7 @@ def __init__(self, structure, parameters={}, init_sanity_check=False,
130132
self.calculator = calculator
131133
self.relax = relax
132134
self.assembly = assembly
135+
self.ff_preopt_run = ff_preopt_run
133136
self.xtb_solvent = xtb_solvent
134137
self.xtb_accuracy = xtb_accuracy
135138
self.xtb_electronic_temperature = xtb_electronic_temperature
@@ -148,7 +151,10 @@ def __init__(self, structure, parameters={}, init_sanity_check=False,
148151
self.relax = False
149152
self.method = self.assemble_method
150153
else:
151-
self.method = self.full_method
154+
if self.ff_preopt_run:
155+
self.method = 'UFF'
156+
else:
157+
self.method = self.full_method
152158
if ase_opt_method is None: # Default to LBFGSLineSearch
153159
self.opt_method = LBFGSLineSearch
154160
else:

architector/io_obabel.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,8 @@ def build_3D(OBmol,addHydrogens=True):
379379

380380

381381
def generate_obmol_conformers(smiles, rmsd_cutoff=0.4, conf_cutoff=3000, energy_cutoff=50.0,
382-
confab_verbose = False, output_format='mol2', neutralize=False, functionalizations=None):
382+
confab_verbose = False, output_format='mol2', neutralize=False, functionalizations=None,
383+
return_energies = False):
383384
"""generate_obmol_conformers
384385
generate conformers with openbabel for given smiles
385386
using confab conformer generation routine
@@ -405,11 +406,15 @@ def generate_obmol_conformers(smiles, rmsd_cutoff=0.4, conf_cutoff=3000, energy_
405406
neutralize smiles?, by default False
406407
functionalizations : dict, optional
407408
add functionalizations?, by default None
409+
return_energies : bool, optional
410+
return the FF energies in addition to the conformers generated
408411
409412
Returns
410413
-------
411414
output_strings : list (str)
412415
list of conformers generated as whatever format desired
416+
output_energies : list (float)
417+
forcefield energies
413418
"""
414419
obmol = get_obmol_smiles(smiles,
415420
neutralize=neutralize,
@@ -419,18 +424,36 @@ def generate_obmol_conformers(smiles, rmsd_cutoff=0.4, conf_cutoff=3000, energy_
419424
FF = ob.OBForceField.FindForceField("MMFF94")
420425
FF.Setup(obmol) # Make sure setup works OK
421426
else:
422-
FF = ob.OBForceField.FindForceField("MMFF94")
427+
FF = ob.OBForceField.FindForceField("UFF")
423428
FF.Setup(obmol) # Make sure setup works OK
429+
# Run Diverse conformer generation
424430
FF.DiverseConfGen(rmsd_cutoff, conf_cutoff, energy_cutoff, confab_verbose)
425431
FF.GetConformers(obmol)
426432
confs_to_write = obmol.NumConformers()
427433
obconversion = ob.OBConversion()
428434
obconversion.SetOutFormat(output_format)
429435
output_strings = []
436+
output_energies = []
430437
for conf_num in range(confs_to_write):
431438
obmol.SetConformer(conf_num)
439+
if return_energies: # Calculate FF energies
440+
if mmff94_ok:
441+
FF = ob.OBForceField.FindForceField("MMFF94")
442+
FF.Setup(obmol) # Make sure setup works OK
443+
else:
444+
FF = ob.OBForceField.FindForceField("UFF")
445+
FF.Setup(obmol) # Make sure setup works OK
446+
energy = FF.Energy()
447+
if mmff94_ok:
448+
energy = energy * units.kcal / units.mol
449+
else:
450+
energy = energy * units.kJ / units.mol
451+
output_energies.append(energy)
432452
output_strings.append(obconversion.WriteString(obmol))
433-
return output_strings
453+
if return_energies:
454+
return output_strings,output_energies
455+
else:
456+
return output_strings
434457

435458

436459
def functionalize(OBmol,functional_group='C',smiles_inds=[0]):

architector/io_process_input.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,7 @@ def inparse(inputDict):
744744
"fmax":0.1, # eV/Angstrom - max force for relaxation.
745745
"maxsteps":1000, # Steps involved in relaxation
746746
"force_generation":False, # Whether to force the construction to proceed without xtb energies - defaults to UFF
747+
"ff_preopt":False, # Perform forcefield pre-optimization of full complex? - default False.
747748
# In cases of XTB outright failure.
748749
# For very large speedup - use GFN-FF, though this is much less stable (especially for Lanthanides)
749750
# Or UFF
@@ -853,6 +854,12 @@ def inparse(inputDict):
853854
coreTypes = [x for x in newinpDict['coreTypes'] if x in core_geo_class.trans_geos]
854855
newinpDict['coreTypes'] = coreTypes
855856

857+
# FF-preoptimization shifts
858+
if outparams['ff_preopt']:
859+
outparams['assemble_method'] = 'GFN-FF'
860+
outparams['assemble_sanity_checks'] = False # Allow for close/overlapping atoms
861+
outparams['fmax'] = 0.2 # Slightly decrease accuracy to encourage difficult convergence
862+
856863
# # Load logger
857864
# if outparams['logg']
858865
newinpDict['parameters'] = outparams

documentation/Debugging_Guide.ipynb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"Solutions:\n",
5050
"1. Play with sanity check cutoffs. You can turn off final an intermediate sanity checks with \"full_sanity_checks\":False, and \"assemble_sanity_checks\":False, respectively. You can also tune the actual cutoff values as well : \"full_graph_sanity_cutoff\":1.7 - can be tuned higher to allow for looser constraints, \"full_smallest_dist_cutoff\":0.55 - can be tuned lower for closer interatomic distances, and \"full_min_dist_cutoff\":3.5 - can be tuned higher to allow for more isolated atoms.\n",
5151
"2. Metal van-der-waals radii (\"vdwrad_metal\") and covalent radii (\"covrad_metal\") can be manually tuned in cases where a very distinct oxiation state is used. \n",
52+
"3. You might have ligands with trailing carbon chains that tend to overlap when being tile to the metal center surface. For example, the TODGA ligand can have this issue. Try setting \"ff_preopt\":True - this will generate the full complex, relax it with UFF - and only then perform the final relaxation method (usually GFN2-xTB). The UFF pre-optimization dramatically increases the chance for successful XTB relaxation in tests, but does introduce more bias into the generated structures.\n",
5253
"3. A last-dich effort can be made to simply force Architector to return basically anything with \"force_generation\":True. This can be useful to determine if something is going wrong internally in Architector. It is not recommended to use this option for production (multiple-structures runs)."
5354
]
5455
},

0 commit comments

Comments
 (0)