Skip to content

Commit

Permalink
Add tests (duartegroup#147)
Browse files Browse the repository at this point in the history
* Test TS

* Refactor TS optimisation method
  • Loading branch information
t-young31 authored May 31, 2022
1 parent 7f9b497 commit 931ceec
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 30 deletions.
5 changes: 4 additions & 1 deletion autode/species/complex.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,10 @@ def _generate_conformers(self):

for points in iterprod(points_on_sphere, repeat=n-1):

conf = Conformer(species=self, name=f'{self.name}_conf{m}')
conf = Conformer(name=f'{self.name}_conf{m}',
charge=self.charge,
mult=self.mult)
conf.solvent = self.solvent
conf.atoms = get_complex_conformer_atoms(self._molecules,
rotations,
points)
Expand Down
68 changes: 39 additions & 29 deletions autode/transition_states/transition_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ def __init__(self,
bond_rearr=ts_guess.bond_rearrangement,
mult=ts_guess.mult)

self.conformers = None

if bond_rearr is not None:
self.bond_rearrangement = bond_rearr

Expand Down Expand Up @@ -72,47 +70,32 @@ def _update_graph(self):
logger.info(f'Molecular graph updated with active bonds')
return None

def _run_opt_ts_calc(self, method, name_ext):
"""Run an optts calculation and attempt to set the geometry, energy and
normal modes"""
@property
def _active_bonds(self) -> Optional[list]:
"""Active bonds that are present in the associated bond rearrangement"""

if self.bond_rearrangement is None:
logger.warning('Cannot add redundant internal coordinates for the '
'active bonds with no bond rearrangement')
bond_ids = None
return None

else:
bond_ids = self.bond_rearrangement.all
return self.bond_rearrangement.all

def _run_opt_ts_calc(self, method, name_ext):
"""Run an optts calculation and attempt to set the geometry, energy and
normal modes"""

optts_calc = Calculation(name=f'{self.name}_{name_ext}',
molecule=self,
method=method,
n_cores=Config.n_cores,
keywords=method.keywords.opt_ts,
bond_ids_to_add=bond_ids,
bond_ids_to_add=self._active_bonds,
other_input_block=method.keywords.optts_block)
optts_calc.run()

if not optts_calc.optimisation_converged():
if optts_calc.optimisation_nearly_converged():
logger.info('Optimisation nearly converged')
if self.could_have_correct_imag_mode:
logger.info('Still have correct imaginary mode, trying '
'more optimisation steps')

self.atoms = optts_calc.get_final_atoms()
optts_calc = Calculation(name=f'{self.name}_{name_ext}_reopt',
molecule=self,
method=method,
n_cores=Config.n_cores,
keywords=method.keywords.opt_ts,
bond_ids_to_add=bond_ids,
other_input_block=method.keywords.optts_block)
optts_calc.run()
else:
logger.info('Lost imaginary mode')
else:
self.warnings += f'TS for {self.name} was not fully converged.'
logger.info('Optimisation did not converge')
optts_calc = self._reoptimise(optts_calc, name_ext, method)

try:
self.atoms = optts_calc.get_final_atoms()
Expand All @@ -124,6 +107,33 @@ def _run_opt_ts_calc(self, method, name_ext):

return

def _reoptimise(self, calc, name_ext, method) -> Calculation:
"""Rerun a calculation for more steps"""

if not calc.optimisation_nearly_converged():
self.warnings += f'TS for {self.name} was not fully converged.'
logger.info('Optimisation did not converge')
return calc

logger.info('Optimisation nearly converged')
if self.could_have_correct_imag_mode:
logger.info('Still have correct imaginary mode, trying '
'more optimisation steps')

self.atoms = calc.get_final_atoms()
calc = Calculation(name=f'{self.name}_{name_ext}_reopt',
molecule=self,
method=method,
n_cores=Config.n_cores,
keywords=method.keywords.opt_ts,
bond_ids_to_add=self._active_bonds,
other_input_block=method.keywords.optts_block)
calc.run()
else:
logger.info('Lost imaginary mode')

return calc

def _generate_conformers(self, n_confs=None):
"""Generate conformers at the TS """
from autode.conformers.conf_gen import get_simanl_conformer
Expand Down
21 changes: 21 additions & 0 deletions tests/test_calculation.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from autode.config import Config
import autode.exceptions as ex
from autode.utils import work_in_tmp_dir
from .testutils import requires_with_working_xtb_install
from copy import deepcopy
import pytest
import os
Expand Down Expand Up @@ -315,3 +316,23 @@ def test_exec_too_much_memory_requested_py38():

# Python 3.8 can't use os.sysconf, so check that external can still be run
run_external(['ls'], output_filename='tmp.txt')


@requires_with_working_xtb_install
@work_in_tmp_dir()
def test_calculations_have_unique_names():

xtb = XTB()
mol = Molecule(smiles='O')

mol.single_point(method=xtb)
mol.single_point(method=xtb) # calculation should be skipped

"""For some insane reason the following code works if executed in python
directly but not if run within pytest"""
# neutral_energy = mol.energy.copy()
#
# mol.charge = 1
# mol.single_point(method=xtb) # Calculation should be rerun
# cation_energy = mol.energy
# assert cation_energy > neutral_energy
146 changes: 146 additions & 0 deletions tests/test_ts.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import numpy as np


from typing import List

from autode.atoms import Atom
from autode.transition_states.templates import get_ts_templates
from autode.transition_states.ts_guess import TSguess
Expand All @@ -9,12 +14,15 @@
from autode.species.molecule import Reactant, Product
from autode.species.complex import ReactantComplex, ProductComplex
from autode.config import Config
from autode.wrappers.keywords import SinglePointKeywords, KeywordsSet
from autode.calculation import Calculation
from autode.wrappers.base import ElectronicStructureMethod
from autode.wrappers.ORCA import ORCA
from autode.wrappers.implicit_solvent_types import cpcm
from autode.transition_states.base import imag_mode_generates_other_bonds
from autode.transition_states.base import displaced_species_along_mode
from autode.wrappers.G09 import G09
from autode.utils import work_in_tmp_dir
from . import testutils
import pytest
import os
Expand Down Expand Up @@ -310,3 +318,141 @@ def test_truncated_ts():
# locate TS should assign a TS as linking reactants and products, so
# checking that the TS exists is sufficient
assert reaction.ts is not None


class TestMethod(ElectronicStructureMethod):

__test__ = False

def __init__(self):
super().__init__(implicit_solvation_type=None,
keywords_set=KeywordsSet(),
name='test',
path=None)

def __repr__(self):
return ''

def generate_input(self, calc, molecule):
pass

def get_output_filename(self, calc) -> str:
with open('tmp_output', 'w') as file:
print('tmp', file=file)
return 'tmp_output'

def get_input_filename(self, calc) -> str:
with open('tmp_input', 'w') as file:
print('tmp', file=file)
return 'tmp_input'

def get_version(self, calc) -> str:
pass

def execute(self, calc) -> None:
pass

def calculation_terminated_normally(self, calc) -> bool:
pass

def optimisation_converged(self, calc) -> bool:
pass

def optimisation_nearly_converged(self, calc) -> bool:
return False

def get_final_atoms(self, calc) -> List:
return calc.molecule.atoms

def get_atomic_charges(self, calc) -> List:
pass

def get_energy(self, calc) -> float:
pass

def get_gradients(self, calc) -> np.ndarray:
pass

def get_hessian(self, calc) -> np.ndarray:
pass


class TestMethodConvergedNotNearly(TestMethod):

def optimisation_converged(self, calc) -> bool:
return True

def optimisation_nearly_converged(self, calc) -> bool:
return False


class TestMethodNearlyConverged(TestMethod):

def optimisation_nearly_converged(self, calc) -> bool:
return True

def optimisation_converged(self, calc) -> bool:
return False


class TestTSCorrectMode(TransitionState):

__test__ = False

def __init__(self):
super().__init__(ts_guess=TSguess(atoms=[Atom('H')]))

@property
def could_have_correct_imag_mode(self) -> bool:
return True

@property
def has_correct_imag_mode(self) -> bool:
return True


class TestTSInCorrectMode(TestTSCorrectMode):

@property
def could_have_correct_imag_mode(self) -> bool:
return False

@property
def has_correct_imag_mode(self) -> bool:
return False


@work_in_tmp_dir()
def test_ts_reoptimise():

# A TS with the correct mode that is converged rather than 'nearly'
# converged should not need any reoptimisation
_ts = TestTSCorrectMode()
conv_method = TestMethodConvergedNotNearly()
calc = Calculation(name='tmp',
molecule=_ts,
method=conv_method,
keywords=SinglePointKeywords())
calc.run()

assert calc.input.exists
assert calc.output.exists

new_calc = _ts._reoptimise(calc, name_ext='tmp', method=method)
assert id(calc) == id(new_calc)

# but with one that has nearly converged and has the correct mode
nearly_conv_method = TestMethodNearlyConverged()
assert nearly_conv_method.optimisation_nearly_converged(calc)
assert not nearly_conv_method.optimisation_converged(calc)
assert _ts.has_correct_imag_mode

# then the new calculation should "run"
calc.method = nearly_conv_method
new_calc = _ts._reoptimise(calc, name_ext='tmp', method=nearly_conv_method)
assert id(calc) != id(new_calc)

# but not if the imaginary mode is lost
_ts = TestTSInCorrectMode()
new_calc = _ts._reoptimise(calc, name_ext='tmp', method=nearly_conv_method)
assert id(calc) == id(new_calc)

0 comments on commit 931ceec

Please sign in to comment.