Skip to content

Commit

Permalink
Fix XTB single point optimization functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
smcolby committed Sep 21, 2023
1 parent 1db2464 commit a11c908
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 70 deletions.
152 changes: 97 additions & 55 deletions isicle/md.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,23 +134,29 @@ def save_geometry(self, fmt='xyz'):
isicle.io.save(geomfile, self.geom)
self.geom.path = geomfile

def _configure_xtb(self, forcefield='gfn2', optlevel='normal', charge=None, solvation=None):
def _configure_xtb(self, forcefield='gfn2', optlevel='normal', charge=0, solvation=None,
ignore_topology=False, cycles=None, dryrun=False):
"""
Set command line for xtb simulations.
Parameters
----------
forcefield : str
GFN forcefield for the optimization
Default: gff
Supported forcefields: gfn2, gfn1, gff
GFN forcefield for the optimization.
Supported : gfn2, gfn1, gff
optlevel : str
Optimization convergence level
Default : normal
Optimization convergence level.
Supported : crude, sloppy, loose, lax, normal, tight, vtight extreme
charge : int
Charge of molecular system.
Default : 0 (Neutral charge)
solvation : str
Specify solvent for simulation.
ignore_topolgy : bool
Turn off only the initial topology check prior to the conformational search.
cycles : int
Maximum number of optimization cycles.
dryrun : bool
Signal whether to perform a dry run.
"""

Expand All @@ -166,55 +172,69 @@ def _configure_xtb(self, forcefield='gfn2', optlevel='normal', charge=None, solv
# Add forcefield
s += '--' + forcefield + ' '

# Add optional charge
# Add charge
if charge is not None:
s += '--chrg ' + charge + ' '
s += '--chrg ' + str(charge) + ' '

# Add dryrun option
if dryrun:
s += '--dryrun '

# Add optional implicit solvation
if solvation is not None:
s += '--alpb ' + solvation + ' '

if ignore_topology:
s += '--noreftopo '

if cycles:
s += '--cycles ' + str(cycles) + ' '

# Add output
s += '&>' + ' '

s += '{}.{}'.format(self.basename, "out")
return s

def _configure_crest(self, ewin=6, optlevel='Normal', forcefield='gfn2',
def _configure_crest(self, forcefield='gfn2', optlevel='Normal', ewin=6,
protonate=False, deprotonate=False, tautomerize=False,
ion=None, charge=None, dryrun=False, processes=1,
solvation=None, ignore_topology=False):
ion=None, charge=None, solvation=None, ignore_topology=False,
cycles=None, dryrun=False, processes=1):
"""
Set command line for crest simulations.
Parameters
----------
ewin : int
Energy window (kcal/mol) for conformer, (de)protomer, or tautomer search.
Default : 6
forcefield : str
GFN forcefield for the optimization
Supported : gfn2, gfn1, gff
optlevel : str
Optimization convergence level
Default : normal
Supported : crude, sloppy, loose, lax, normal, tight, vtight extreme
forcefield : str
GFN forcefield for the optimization
Default: gff
Supported forcefields: gfn2, gfn1, gff
ewin : int
Energy window (kcal/mol) for conformer, (de)protomer, or tautomer search.
protonate : bool
Signal to initiate protomer search. Suggested ewin = 30.
Default : False
deprotonate : bool
Signal to initiate deprotonated conformers. Suggesting ewin = 30.
Default : False
tautomer : bool
tautomerize : bool
Signal to initiate tautomer search.
Default : False
ion : str
Keyword to couple with protonate to ionize molecule with an ion other than a proton.
See :obj:`~isicle.adduct.parse_ion` for list of ion options.
charge : int
Charge of molecular system.
Default : 0 (Neutral charge)
solvation : str
Specify solvent for simulation.
ignore_topolgy : bool
Turn off only the initial topology check prior to the conformational search.
cycles : int
Maximum number of optimization cycles.
dryrun : bool
Signal whether to perform a dry run.
processes : int
Number of parallel processes.
"""

# Start base command
Expand All @@ -237,8 +257,9 @@ def _configure_crest(self, ewin=6, optlevel='Normal', forcefield='gfn2',
if ion is not None:
s += '-swel ' + ion + ' '

# Add charge
if charge is not None:
s += '-chrg ' + str(charge) + ' '
s += '--chrg ' + str(charge) + ' '

# Add dryrun option
if dryrun:
Expand All @@ -263,6 +284,9 @@ def _configure_crest(self, ewin=6, optlevel='Normal', forcefield='gfn2',
if ignore_topology:
s += '--noreftopo '

if cycles:
s += '--cycles ' + str(cycles) + ' '

# Add output
s += '&>' + ' '

Expand All @@ -272,34 +296,39 @@ def _configure_crest(self, ewin=6, optlevel='Normal', forcefield='gfn2',

return s

def configure(self, task='optimize', forcefield='gfn2', charge=None,
ewin=6, ion=None, optlevel='Normal', dryrun=False, processes=1,
solvation=None, ignore_topology=False):
def configure(self, task='optimize', forcefield='gfn2', optlevel='Normal',
ewin=6, charge=None, ion=None, solvation=None,
ignore_topology=False, cycles=None, dryrun=False, processes=1):
"""
Generate command line
Parameters
----------
tasks : str
task : str
Set task to "optimize", "conformer", "protonate", "deprotonate", or "tautomerize".
Default : "optimize"
forcefield : str
GFN forcefield for the optimization
Default: gff
Supported forcefields: gfn2, gfn1, gff
ewin : int
Energy window (kcal/mol) for conformer(set to 6), (de)protomer(set to 30), or tautomer(set to 30) search.
Default : 6
ion : str
Ion for protomer calculation.
Supported : gfn2, gfn1, gff
optlevel : str or list of str
Set optimization level. Supply globally or per task.
ewin : int
Energy window (kcal/mol) for conformer(set to 6), (de)protomer(set to 30), or tautomer(set to 30) search
charge : int
Charge of molecular system.
ion : str
Keyword to couple with protonate to ionize molecule with an ion other than a proton.
See :obj:`~isicle.adduct.parse_ion` for list of ion options.
charge : int
Charge of molecular system.
Default : 0 (Neutral charge)
solvation : str
Specify solvent for simulation.
ignore_topolgy : bool
Turn off only the initial topology check prior to the conformational search.
cycles : int
Maximum number of optimization cycles.
dryrun : bool
Signal whether to perform a dry run.
processes : int
Number of parallel processes.
"""

if type(task) == list:
Expand All @@ -311,7 +340,12 @@ def configure(self, task='optimize', forcefield='gfn2', charge=None,

if task == 'optimize':
config = self._configure_xtb(optlevel=optlevel,
forcefield=forcefield)
forcefield=forcefield,
charge=charge,
dryrun=dryrun,
solvation=solvation,
ignore_topology=ignore_topology,
cycles=cycles)

else:
if task == 'conformer':
Expand All @@ -338,7 +372,8 @@ def configure(self, task='optimize', forcefield='gfn2', charge=None,
dryrun=dryrun,
processes=processes,
solvation=solvation,
ignore_topology=ignore_topology)
ignore_topology=ignore_topology,
cycles=cycles)
else:
raise Error(
'Task not assigned properly, please choose optimize, conformer, protonate, deprotonate, or tautomerize')
Expand Down Expand Up @@ -369,7 +404,6 @@ def finish(self):
self.temp_dir, self.basename + '.out'))

result = parser.parse()

self.__dict__.update(result)

for i in self.geom:
Expand All @@ -385,9 +419,9 @@ def finish(self):
else:
self.geom = self.geom[0]

def run(self, geom, task='optimize', forcefield='gfn2', charge=None,
ewin=6, ion=None, optlevel='Normal', dryrun=False, processes=1,
solvation=None, ignore_topology=False):
def run(self, geom, task='optimize', forcefield='gfn2', optlevel='Normal',
ewin=6, charge=None, ion=None, solvation=None, ignore_topology=False,
cycles=None, dryrun=False, processes=1):
"""
Optimize geometry via density functional theory using supplied functional
and basis set.
Expand All @@ -396,21 +430,29 @@ def run(self, geom, task='optimize', forcefield='gfn2', charge=None,
----------
geom : :obj:`~isicle.geometry.Geometry`
Molecule representation.
tasks : str
task : str
Set task to "optimize", "conformer", "protonate", "deprotonate", or "tautomerize".
forcefield : str
GFN forcefield for the optimization, including "gfn2", "gfn1", "gff".
ewin : int
Energy window (kcal/mol) for conformer(set to 6), (de)protomer(set to 30), or tautomer(set to 30) search.
ion : str
Ion for protomer calculation.
optlevel : str or list of str
Set optimization level. Supply globally or per task.
ewin : int
Energy window (kcal/mol) for conformer(set to 6), (de)protomer(set to 30), or tautomer(set to 30) search.
charge : int
Charge of molecular system.
ion : str
Keyword to couple with protonate to ionize molecule with an ion other than a proton.
See :obj:`~isicle.adduct.parse_ion` for list of ion options.
charge : int
Charge of molecular system. Defaults to 0 (neutral).
solvation : str
Specify solvent for simulation.
ignore_topolgy : bool
Turn off only the initial topology check prior to the conformational search.
cycles : int
Maximum number of optimization cycles.
dryrun : bool
Signal whether to perform a dry run.
processes : int
Number of parallel processes.
Returns
-------
Expand All @@ -428,7 +470,7 @@ def run(self, geom, task='optimize', forcefield='gfn2', charge=None,
# Configure
self.configure(task=task, forcefield=forcefield, charge=charge,
ewin=ewin, ion=ion, optlevel=optlevel, dryrun=dryrun, processes=processes,
solvation=solvation, ignore_topology=ignore_topology)
solvation=solvation, ignore_topology=ignore_topology, cycles=cycles)

# Run QM simulation
self.submit()
Expand Down
28 changes: 13 additions & 15 deletions isicle/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -739,21 +739,21 @@ def parse(self):
# Check that the file is valid first
if len(self.contents) == 0:
raise RuntimeError('No contents to parse: {}'.format(self.path))
if "terminated normally" not in self.contents[-1]:
if "ratio" not in self.contents[-2]:
raise RuntimeError('XTB job failed: {}'.format(self.path))

last_lines = ''.join(self.contents[-10:])
if ("terminat" not in last_lines) \
& ("normal" not in last_lines) \
& ("ratio" not in last_lines):
raise RuntimeError('XTB job failed: {}'.format(self.path))

self.parse_crest = False
self.parse_opt = False
self.parse_isomer = False

# Initialize result object to store info
result = {}
result['protocol'] = self._parse_protocol()

try:
result['protocol'] = self._parse_protocol()
except:
pass
try:
result['timing'] = self._parse_timing()
except:
Expand All @@ -767,14 +767,12 @@ def parse(self):
# Parse geometry from assoc. XYZ file
try:
if self.path.endswith('xyz'):

if 'geometry' in to_parse:
try:
self.xyz_path = self.path
result['geom'] = self._parse_xyz()

except:
pass
try:
self.xyz_path = self.path
result['geom'] = self._parse_xyz()

except:
pass

if self.path.endswith('out') or self.path.endswith('log'):

Expand Down

0 comments on commit a11c908

Please sign in to comment.