Skip to content

Commit

Permalink
Added assign_frequency_scale_factor() to Level (#750)
Browse files Browse the repository at this point in the history
Added the data as a YAML file, the respective functionality, and tests
  • Loading branch information
alongd authored Jul 27, 2024
2 parents 08e7e93 + 6109395 commit df19d8f
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 60 deletions.
3 changes: 2 additions & 1 deletion arc/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -1703,9 +1703,10 @@ def is_xyz_mol_match(mol: 'Molecule',

for element, count in element_dict_mol.items():
if element not in element_dict_xyz or element_dict_xyz[element] != count:
return False
return False
return True


def convert_to_hours(time_str:str) -> float:
"""Convert walltime string in format HH:MM:SS to hours.
Expand Down
33 changes: 23 additions & 10 deletions arc/level.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

import arkane.encorr.data as arkane_data
from arkane.encorr.bac import BAC
from arkane.encorr.corr import assign_frequency_scale_factor
from arkane.modelchem import METHODS_THAT_REQUIRE_SOFTWARE, LevelOfTheory, standardize_name

from arc.common import ARC_PATH, get_logger, get_ordered_intersection_of_two_lists, read_yaml_file
Expand Down Expand Up @@ -59,7 +58,7 @@ def __init__(self,
method_type: Optional[str] = None,
software: Optional[str] = None,
software_version: Optional[Union[int, float, str]] = None,
compatible_ess: Optional[List[str, ...]] = None,
compatible_ess: Optional[List[str]] = None,
solvation_method: Optional[str] = None,
solvent: Optional[str] = None,
solvation_scheme_level: Optional[Level] = None,
Expand Down Expand Up @@ -154,8 +153,6 @@ def __str__(self) -> str:
for key, arg in self.args.items():
if key == 'keyword':
str_ += f' {arg}'
if self.method_type is not None:
str_ += f' ({self.method_type})'
return str_

def copy(self):
Expand Down Expand Up @@ -398,12 +395,7 @@ def to_arkane_level_of_theory(self,
var_1 = None

if variant == 'freq':
# if not found, the factor is set to exactly 1
if assign_frequency_scale_factor(level_of_theory=var_1) != 1:
return var_1
if assign_frequency_scale_factor(level_of_theory=var_2) != 1:
return var_2
return None
return var_2

if variant == 'AEC':
try:
Expand Down Expand Up @@ -573,3 +565,24 @@ def get_params_from_arkane_level_of_theory_as_str(arkane_level: str) -> Dict[str
splits = arkane_level.split(f"{key}='")
level_dict[key] = splits[1].split("'")[0]
return level_dict


def assign_frequency_scale_factor(level: Union[str, Level]) -> Optional[int]:
"""
Assign a frequency scaling factor to a level of theory.
Args:
level (Union[str, Level]): The level of theory.
Returns:
Optional[int]: The frequency scaling factor.
"""
freq_scale_factors = read_yaml_file(os.path.join(ARC_PATH, 'data', 'freq_scale_factors.yml'))['freq_scale_factors']
if isinstance(level, str):
if level in freq_scale_factors:
return freq_scale_factors[level]
level = Level(repr=level)
level_str = str(level)
if level_str in freq_scale_factors:
return freq_scale_factors[level_str]
return None
43 changes: 36 additions & 7 deletions arc/level_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from arkane.modelchem import LevelOfTheory

from arc.common import ARC_PATH, read_yaml_file
from arc.level import Level, get_params_from_arkane_level_of_theory_as_str
from arc.level import Level, assign_frequency_scale_factor, get_params_from_arkane_level_of_theory_as_str


class TestLevel(unittest.TestCase):
Expand All @@ -29,7 +29,7 @@ def test_level(self):
self.assertEqual(level_1.dispersion, 'gd3bj')
self.assertEqual(level_1.software, 'gaussian')
self.assertEqual(str(level_1),
"b3lyp/def2tzvp, auxiliary_basis: aug-def2-svp, dispersion: gd3bj, software: gaussian (dft)")
"b3lyp/def2tzvp, auxiliary_basis: aug-def2-svp, dispersion: gd3bj, software: gaussian")
self.assertEqual(level_1.simple(), "b3lyp/def2tzvp")

def test_deduce_software(self):
Expand Down Expand Up @@ -99,13 +99,13 @@ def test_build(self):
self.assertEqual(level_1.method, 'wb97xd')
self.assertEqual(level_1.basis, 'def2-tzvp')
self.assertEqual(level_1.method_type, 'dft')
self.assertEqual(str(level_1), 'wb97xd/def2-tzvp, software: gaussian (dft)')
self.assertEqual(str(level_1), 'wb97xd/def2-tzvp, software: gaussian')
level_2 = Level(repr='CBS-QB3')
self.assertEqual(level_2.method, 'cbs-qb3')
self.assertIsNone(level_2.basis)
self.assertEqual(level_2.software, 'gaussian')
self.assertEqual(level_2.method_type, 'composite')
self.assertEqual(str(level_2), 'cbs-qb3, software: gaussian (composite)')
self.assertEqual(str(level_2), 'cbs-qb3, software: gaussian')
self.assertEqual(level_2.simple(), 'cbs-qb3')
level_3 = Level(repr={'method': 'DLPNO-CCSD(T)',
'basis': 'def2-TZVp',
Expand All @@ -123,8 +123,7 @@ def test_build(self):
self.assertEqual(level_3.solvation_scheme_level.basis, 'def2-tzvp')
self.assertEqual(str(level_3),
"dlpno-ccsd(t)/def2-tzvp, auxiliary_basis: def2-tzvp/c, solvation_method: smd, "
"solvent: water, solvation_scheme_level: 'apfd/def2-tzvp, software: gaussian (dft)', "
"software: orca (wavefunction)")
"solvent: water, solvation_scheme_level: 'apfd/def2-tzvp, software: gaussian', software: orca")

def test_to_arkane(self):
"""Test converting Level to LevelOfTheory"""
Expand All @@ -139,7 +138,7 @@ def test_to_arkane(self):
self.assertEqual(level_2.to_arkane_level_of_theory(variant='AEC'),
LevelOfTheory(method='cbs-qb3', software='gaussian'))
self.assertEqual(level_2.to_arkane_level_of_theory(variant='freq'),
LevelOfTheory(method='cbs-qb3', software='gaussian'))
LevelOfTheory(method='cbs-qb3'))
self.assertEqual(level_2.to_arkane_level_of_theory(variant='BAC'),
LevelOfTheory(method='cbs-qb3', software='gaussian'))
self.assertIsNone(level_2.to_arkane_level_of_theory(variant='BAC', bac_type='m')) # might change in the future
Expand Down Expand Up @@ -210,6 +209,36 @@ def test_determine_compatible_ess(self):
level_2.determine_compatible_ess()
self.assertEqual(sorted(level_2.compatible_ess), sorted(['gaussian', 'qchem', 'terachem']))

def test_str(self):
"""Test the __str__() method."""
self.assertEqual(str(Level(method='HF', basis='6-31g')), 'hf/6-31g, software: gaussian')
self.assertEqual(str(Level(method='HF', basis='6-31+g(d,p)')), 'hf/6-31+g(d,p), software: gaussian')
self.assertEqual(str(Level(method='PM6')), 'pm6, software: gaussian')
self.assertEqual(str(Level(method='b3lyp', basis='6-311+g(d,p)')), 'b3lyp/6-311+g(d,p), software: gaussian')
self.assertEqual(str(Level(method='M062x', basis='Aug-cc-pvdz')), 'm062x/aug-cc-pvdz, software: gaussian')
self.assertEqual(str(Level(method='M062x', basis='Def2TZVP')), 'm062x/def2tzvp, software: gaussian')
self.assertEqual(str(Level(method='M06-2x', basis='Def2-TZVP')), 'm06-2x/def2-tzvp, software: qchem')
self.assertEqual(str(Level(method='wb97xd', basis='def2tzvp')), 'wb97xd/def2tzvp, software: gaussian')
self.assertEqual(str(Level(method='wb97m-v', basis='def2-tzvpd')), 'wb97m-v/def2-tzvpd, software: qchem')
self.assertEqual(str(Level(method='b2plypd3', basis='cc-pvtz')), 'b2plypd3/cc-pvtz, software: gaussian')
self.assertEqual(str(Level(method='apfd', basis='def2tzvp')), 'apfd/def2tzvp, software: gaussian')
self.assertEqual(str(Level(method='mp2', basis='cc-pvdz')), 'mp2/cc-pvdz, software: gaussian')
self.assertEqual(str(Level(method='CBS-QB3')), 'cbs-qb3, software: gaussian')
self.assertEqual(str(Level(method='CCSD(T)', basis='cc-pVTZ')), 'ccsd(t)/cc-pvtz, software: molpro')
self.assertEqual(str(Level(method='CCSD(T)-F12', basis='cc-pVTZ-F12')), 'ccsd(t)-f12/cc-pvtz-f12, software: molpro')
self.assertEqual(str(Level(method='wb97xd', basis='def2tzvp', solvation_method='smd', solvent='water')),
'wb97xd/def2tzvp, solvation_method: smd, solvent: water, software: gaussian')
self.assertEqual(str(Level(basis='def2svp', compatible_ess=['gaussian', 'terachem'],method='wb97xd',
method_type='dft', software='gaussian')), 'wb97xd/def2svp, software: gaussian')

def test_assign_frequency_scale_factor(self):
"""Test the assign_frequency_scale_factor() method."""
self.assertEqual(assign_frequency_scale_factor(Level(method='CCSD(T)', basis='cc-pvtz')), 0.975)
self.assertEqual(assign_frequency_scale_factor(Level(method='CCSD(T)-F12', basis='cc-pvtz-F12')), 0.998)
self.assertEqual(assign_frequency_scale_factor(Level(method='wb97xd', basis='Def2TZVP')), 0.988)
self.assertEqual(assign_frequency_scale_factor(Level(method='CBS-QB3')), 1.004)
self.assertEqual(assign_frequency_scale_factor(Level(method='PM6')), 1.093)


if __name__ == '__main__':
unittest.main(testRunner=unittest.TextTestRunner(verbosity=2))
27 changes: 13 additions & 14 deletions arc/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
from IPython.display import display
from typing import Dict, List, Optional, Tuple, Union

from arkane.encorr.corr import assign_frequency_scale_factor
from rmgpy.reaction import Reaction
from rmgpy.species import Species

Expand All @@ -37,7 +36,7 @@
)
from arc.exceptions import InputError, SettingsError, SpeciesError
from arc.imports import settings
from arc.level import Level
from arc.level import Level, assign_frequency_scale_factor
from arc.job.factory import _registered_job_adapters
from arc.job.ssh import SSHClient
from arc.processor import process_arc_project
Expand Down Expand Up @@ -504,7 +503,8 @@ def as_dict(self) -> dict:
if not isinstance(self.freq_level, (dict, str)) else self.freq_level
if self.freq_scale_factor is not None:
restart_dict['freq_scale_factor'] = self.freq_scale_factor
if self.irc_level is not None and str(self.irc_level).split()[0] != default_levels_of_theory['irc']:
if self.irc_level is not None and len(self.irc_level.method) \
and str(self.irc_level).split()[0] != default_levels_of_theory['irc']:
restart_dict['irc_level'] = self.irc_level.as_dict() \
if not isinstance(self.irc_level, (dict, str)) else self.irc_level
if self.keep_checks:
Expand All @@ -521,7 +521,8 @@ def as_dict(self) -> dict:
if self.opt_level is not None and str(self.opt_level).split()[0] != default_levels_of_theory['opt']:
restart_dict['opt_level'] = self.opt_level.as_dict() \
if not isinstance(self.opt_level, (dict, str)) else self.opt_level
if self.orbitals_level is not None and str(self.orbitals_level).split()[0] != default_levels_of_theory['orbitals']:
if self.orbitals_level is not None and len(self.orbitals_level.method) \
and str(self.orbitals_level).split()[0] != default_levels_of_theory['orbitals']:
restart_dict['orbitals_level'] = self.orbitals_level.as_dict() \
if not isinstance(self.orbitals_level, (dict, str)) else self.orbitals_level
if self.output:
Expand All @@ -533,10 +534,12 @@ def as_dict(self) -> dict:
restart_dict['reactions'] = [rxn.as_dict() for rxn in self.reactions]
if self.running_jobs:
restart_dict['running_jobs'] = self.running_jobs
if self.scan_level is not None and str(self.scan_level).split()[0] != default_levels_of_theory['scan']:
if self.scan_level is not None and len(self.scan_level.method) \
and str(self.scan_level).split()[0] != default_levels_of_theory['scan']:
restart_dict['scan_level'] = self.scan_level.as_dict() \
if not isinstance(self.scan_level, (dict, str)) else self.scan_level
if self.sp_level is not None and str(self.sp_level).split()[0] != default_levels_of_theory['sp']:
if self.sp_level is not None and len(self.sp_level.method) \
and str(self.sp_level).split()[0] != default_levels_of_theory['sp']:
restart_dict['sp_level'] = self.sp_level.as_dict() \
if not isinstance(self.sp_level, (dict, str)) else self.sp_level
restart_dict['species'] = [spc.as_dict() for spc in self.species]
Expand Down Expand Up @@ -906,16 +909,12 @@ def check_freq_scaling_factor(self):
freq_level = self.composite_method if self.composite_method is not None \
else self.freq_level if self.freq_level is not None else None
if freq_level is not None:
arkane_freq_lot = freq_level.to_arkane_level_of_theory(variant='freq')
if arkane_freq_lot is not None:
# Arkane has this harmonic frequencies scaling factor.
self.freq_scale_factor = assign_frequency_scale_factor(level_of_theory=arkane_freq_lot)
else:
logger.info(f'Could not determine the harmonic frequencies scaling factor for '
f'{arkane_freq_lot} from Arkane.')
self.freq_scale_factor = assign_frequency_scale_factor(level=freq_level)
if self.freq_scale_factor is None:
logger.info(f'Could not determine the harmonic frequencies scaling factor for {freq_level}.')
if self.calc_freq_factor:
logger.info("Calculating it using Truhlar's method.")
logger.warning("This proceedure normally spawns QM jobs for various small species "
logger.warning("This procedure normally spawns QM jobs for various small species "
"not directly asked for by the user.\n\n")
self.freq_scale_factor = determine_scaling_factors(levels=[freq_level],
ess_settings=self.ess_settings,
Expand Down
23 changes: 11 additions & 12 deletions arc/main_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,23 +221,22 @@ def test_check_project_name(self):
def test_determine_model_chemistry_and_freq_scale_factor(self):
"""Test determining the model chemistry and the frequency scaling factor"""
arc0 = ARC(project='arc_model_chemistry_test', level_of_theory='CBS-QB3')
self.assertEqual(str(arc0.arkane_level_of_theory), "cbs-qb3, software: gaussian (composite)")
self.assertEqual(arc0.freq_scale_factor, 1.00386) # 0.99 * 1.014 = 1.00386
self.assertEqual(str(arc0.arkane_level_of_theory), "cbs-qb3, software: gaussian")
self.assertEqual(arc0.freq_scale_factor, 1.004)

arc1 = ARC(project='arc_model_chemistry_test',
level_of_theory='cbs-qb3-paraskevas')
self.assertEqual(str(arc1.arkane_level_of_theory), 'cbs-qb3-paraskevas, software: gaussian (composite)')
self.assertEqual(arc1.freq_scale_factor, 1.00386) # 0.99 * 1.014 = 1.00386
arc1 = ARC(project='arc_model_chemistry_test', level_of_theory='cbs-qb3-paraskevas')
self.assertEqual(str(arc1.arkane_level_of_theory), 'cbs-qb3-paraskevas, software: gaussian')
self.assertEqual(arc1.freq_scale_factor, 1.004)
self.assertEqual(arc1.bac_type, 'p')

arc2 = ARC(project='arc_model_chemistry_test',
level_of_theory='ccsd(t)-f12/cc-pvtz-f12//m06-2x/cc-pvtz')
self.assertEqual(str(arc2.arkane_level_of_theory), 'ccsd(t)-f12/cc-pvtz-f12, software: molpro (wavefunction)')
level_of_theory='ccsd(t)-f12/cc-pvtz-f12//m062x/cc-pvtz')
self.assertEqual(str(arc2.arkane_level_of_theory), 'ccsd(t)-f12/cc-pvtz-f12, software: molpro')
self.assertEqual(arc2.freq_scale_factor, 0.955)

arc3 = ARC(project='arc_model_chemistry_test',
sp_level='ccsd(t)-f12/cc-pvtz-f12', opt_level='wb97xd/def2tzvp')
self.assertEqual(str(arc3.arkane_level_of_theory), 'ccsd(t)-f12/cc-pvtz-f12, software: molpro (wavefunction)')
self.assertEqual(str(arc3.arkane_level_of_theory), 'ccsd(t)-f12/cc-pvtz-f12, software: molpro')
self.assertEqual(arc3.freq_scale_factor, 0.988)

def test_determine_model_chemistry_for_job_types(self):
Expand Down Expand Up @@ -279,7 +278,7 @@ def test_determine_model_chemistry_for_job_types(self):
self.assertEqual(arc2.composite_method.simple(), 'cbs-qb3')

# Test deduce levels from level of theory specification
arc3 = ARC(project='test', level_of_theory='ccsd(t)-f12/cc-pvtz-f12//wb97m-v/def2tzvpd')
arc3 = ARC(project='test', level_of_theory='ccsd(t)-f12/cc-pvtz-f12//wb97m-v/def2tzvpd', freq_scale_factor=1)
self.assertEqual(arc3.opt_level.simple(), 'wb97m-v/def2tzvpd')
self.assertEqual(arc3.freq_level.simple(), 'wb97m-v/def2tzvpd')
self.assertEqual(arc3.sp_level.simple(), 'ccsd(t)-f12/cc-pvtz-f12')
Expand Down Expand Up @@ -315,10 +314,10 @@ def test_determine_model_chemistry_for_job_types(self):
calc_freq_factor=False, compute_thermo=False)
self.assertEqual(arc9.opt_level.simple(), 'wb97xd/def2tzvp')
self.assertEqual(str(arc9.freq_level), 'b3lyp/g/cc-pvdz(fi/sf/fw), auxiliary_basis: def2-svp/c, '
'dispersion: def2-tzvp/c, software: gaussian (dft)')
'dispersion: def2-tzvp/c, software: gaussian')
self.assertEqual(str(arc9.sp_level),
'dlpno-ccsd(t)-f12/cc-pvtz-f12, auxiliary_basis: aug-cc-pvtz/c cc-pvtz-f12-cabs, '
'software: orca (wavefunction)')
'software: orca')

# Test using default frequency and orbital level for composite job, also forbid rotors job
arc10 = ARC(project='test', composite_method='cbs-qb3', calc_freq_factor=False,
Expand Down
2 changes: 2 additions & 0 deletions arc/testing/reactions/methanoate_hydrolysis/input_1.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ job_types:
freq: true
sp: true

freq_scale_factor: 1.0

species:
- label: 'H2O'
smiles: O
Expand Down
2 changes: 2 additions & 0 deletions arc/testing/reactions/methanoate_hydrolysis/input_2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ job_types:
freq: true
sp: true

freq_scale_factor: 1.0

species:
- label: 'H2O'
smiles: O
Expand Down
14 changes: 3 additions & 11 deletions arc/utils/scale.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,9 +222,8 @@ def summarize_results(lambda_zpes: list,

with open(info_file_path, 'w') as f:
f.write(HEADER)
database_text = '\n\n\nYou may copy-paste the following harmonic frequency scaling factor(s) to ' \
'the RMG-database repository\n' \
'(under the `freq_dict` in RMG-database/input/quantum_corrections/data.py):\n'
database_text = '\n\n\nYou may copy-paste the computed harmonic frequency scaling factor(s) to ARC ' \
'(under the `freq_dict` in ARC/data/freq_scale_factors.yml):\n'
database_formats = list()
harmonic_freq_scaling_factors = list()
for lambda_zpe, level, zpe_dict, execution_time\
Expand All @@ -233,13 +232,6 @@ def summarize_results(lambda_zpes: list,
fundamental_freq_scaling_factor = lambda_zpe * 0.974
harmonic_freq_scaling_factors.append(fundamental_freq_scaling_factor)
unconverged = [key for key, val in zpe_dict.items() if val is None]
arkane_level = level.to_arkane_level_of_theory().simple()
arkane_level_str = f"LevelOfTheory(method='{level.method}'"
if arkane_level.basis is not None:
arkane_level_str += f",basis='{level.basis}'"
if arkane_level.software is not None:
arkane_level_str += f",software='{level.software}'"
arkane_level_str += f")"

text = f'\n\nLevel of theory: {level}\n'
if unconverged:
Expand All @@ -250,7 +242,7 @@ def summarize_results(lambda_zpes: list,
text += f'(execution time: {execution_time})\n'
logger.info(text)
f.write(text)
database_formats.append(f""" "{arkane_level_str}": {harmonic_freq_scaling_factor:.3f}, # [4]\n""")
database_formats.append(f""" '{level}': {harmonic_freq_scaling_factor:.3f}, # [4]\n""")
logger.info(database_text)
f.write(database_text)
for database_format in database_formats:
Expand Down
Loading

0 comments on commit df19d8f

Please sign in to comment.