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

Added assign_frequency_scale_factor() to Level #750

Merged
merged 12 commits into from
Jul 27, 2024
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 @@
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 @@
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 @@
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 @@
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)

Check warning on line 584 in arc/level.py

View check run for this annotation

Codecov / codecov/patch

arc/level.py#L583-L584

Added lines #L583 - L584 were not covered by tests
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
Loading