Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix XTB single point optimization functionality #17

Merged
merged 1 commit into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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