Skip to content

Commit aeabe6c

Browse files
committed
Update
1 parent 6f18987 commit aeabe6c

4 files changed

Lines changed: 140 additions & 26 deletions

File tree

arc/statmech/arkane.py

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -682,18 +682,9 @@ def _section_contains_key(file_path: str, section_start: str, section_end: str,
682682

683683
def _get_qm_corrections_files() -> List[str]:
684684
"""
685-
Return quantum corrections data.py paths, preferring ARC-local data.
686-
687-
Preference order:
688-
1) ARC-local database/data directories (if present)
689-
2) RMG-database
685+
Return quantum corrections data.py paths from the RMG database.
690686
"""
691687
candidates = [
692-
os.path.join(ARC_PATH, 'arc', 'database', 'input', 'quantum_corrections', 'data.py'),
693-
os.path.join(ARC_PATH, 'arc', 'database', 'quantum_corrections', 'data.py'),
694-
os.path.join(ARC_PATH, 'arc', 'data', 'input', 'quantum_corrections', 'data.py'),
695-
os.path.join(ARC_PATH, 'arc', 'data', 'quantum_corrections', 'data.py'),
696-
os.path.join(ARC_PATH, 'data', 'quantum_corrections.py'),
697688
os.path.join(RMG_DB_PATH, 'input', 'quantum_corrections', 'data.py'),
698689
]
699690
return [path for path in candidates if os.path.isfile(path)]
@@ -779,7 +770,7 @@ def _iter_level_keys_from_section(file_path: str,
779770
def _available_years_for_level(level: "Level",
780771
file_path: str,
781772
section_start: str,
782-
section_end: str) -> list[Optional[int]]:
773+
section_end: str) -> List[Optional[int]]:
783774
"""
784775
Return a sorted list of available year suffixes for a given Level in a section.
785776
"""
@@ -820,7 +811,7 @@ def _available_years_for_level(level: "Level",
820811
return sorted(years, key=lambda y: (-1 if y is None else y))
821812

822813

823-
def _format_years(years: list[Optional[int]]) -> str:
814+
def _format_years(years: List[Optional[int]]) -> str:
824815
"""
825816
Format a list of years for logging.
826817
"""
@@ -845,7 +836,14 @@ def _find_best_level_key_for_sp_level(level: "Level",
845836

846837
target_method_norm = _normalize_method(level.method)
847838
target_base, method_year = _split_method_year(target_method_norm)
848-
target_year = getattr(level, 'year', None) if getattr(level, 'year', None) is not None else method_year
839+
explicit_year = getattr(level, 'year', None)
840+
if explicit_year is not None and method_year is not None and explicit_year != method_year:
841+
raise InputError(
842+
f"Conflicting year specifications for level '{level}': "
843+
f"explicit year={explicit_year}, method suffix year={method_year}. "
844+
"Please remove the year suffix from the method name or update the 'year' attribute to match."
845+
)
846+
target_year = explicit_year if explicit_year is not None else method_year
849847
target_basis_norm = _normalize_basis(level.basis)
850848
target_software = level.software.lower() if level.software else None
851849

@@ -927,14 +925,16 @@ def get_arkane_model_chemistry(sp_level: 'Level',
927925
"""
928926
Get Arkane model chemistry string with database validation.
929927
930-
Reads quantum_corrections/data.py as plain text (prefers ARC-local overrides,
931-
then RMG-database), searches for
928+
Reads quantum_corrections/data.py as plain text, searches for
932929
LevelOfTheory(...) keys, and matches:
933930
- method: ignoring hyphens and optional 4-digit year suffix
934931
- basis: ignoring hyphens and spaces
935932
936-
If multiple entries only differ by year, the one with the *latest* year
937-
is chosen (year=0 if no year in that entry).
933+
When a year is explicitly specified in the Level, only entries with that exact
934+
year are matched. If no year is specified and an entry without a year exists,
935+
that entry is used. Only when no year is specified and no no-year entry exists,
936+
if multiple entries differ only by year, the one with the *latest* year is
937+
chosen (treating entries with no year as year=0).
938938
939939
Args:
940940
sp_level (Level): Level of theory for energy.
@@ -946,6 +946,7 @@ def get_arkane_model_chemistry(sp_level: 'Level',
946946
"""
947947
qm_corr_files = _get_qm_corrections_files()
948948
if not qm_corr_files:
949+
logger.warning('No quantum corrections database files found; skipping Arkane model chemistry lookup.')
949950
return None
950951

951952
atom_energies_start = "atom_energies = {"
@@ -975,7 +976,8 @@ def get_arkane_model_chemistry(sp_level: 'Level',
975976
f"available years: {_format_years(years)}. "
976977
f"Specify a year to select a matching entry."
977978
)
978-
return _level_to_str(sp_level)
979+
# No matching AEC level in Arkane DB for this composite method
980+
return None
979981
return best_energy
980982

981983
# ---- Case 1: User supplied explicit frequency scale factor ----
@@ -1068,8 +1070,7 @@ def check_arkane_bacs(sp_level: 'Level',
10681070
"""
10691071
Check that Arkane has AECs and BACs for the given sp level of theory.
10701072
1071-
Uses plain-text parsing of quantum_corrections/data.py (prefers ARC-local overrides,
1072-
then RMG-database), matching LevelOfTheory
1073+
Uses plain-text parsing of quantum_corrections/data.py, matching LevelOfTheory
10731074
keys by:
10741075
- method base (ignore hyphens + optional year)
10751076
- basis (normalized)

arc/statmech/arkane_test.py

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,29 @@
77

88
import os
99
import shutil
10+
import tempfile
1011
import unittest
1112

13+
from arc.exceptions import InputError
1214
from arc.common import ARC_PATH
1315
from arc.level import Level
1416
from arc.reaction import ARCReaction
1517
from arc.species import ARCSpecies
1618
from arc.statmech.adapter import StatmechEnum
1719
from arc.statmech.arkane import ArkaneAdapter
18-
from arc.statmech.arkane import _level_to_str, _section_contains_key, get_arkane_model_chemistry
20+
from arc.statmech.arkane import (
21+
_available_years_for_level,
22+
_find_best_level_key_for_sp_level,
23+
_get_qm_corrections_files,
24+
_level_to_str,
25+
_normalize_basis,
26+
_normalize_method,
27+
_parse_lot_params,
28+
_section_contains_key,
29+
_split_method_year,
30+
get_arkane_model_chemistry,
31+
)
32+
from unittest.mock import patch
1933

2034

2135
class TestEnumerationClasses(unittest.TestCase):
@@ -162,6 +176,105 @@ def test_get_arkane_model_chemistry_latest_year(self):
162176
freq_scale_factor=1.0)
163177
self.assertEqual(model_chemistry, "LevelOfTheory(method='cbsqb3',software='gaussian')")
164178

179+
def test_level_helpers(self):
180+
"""Test helper functions for method/basis/year parsing."""
181+
self.assertEqual(_normalize_method("DLPNO-CCSD(T)-F12"), "dlpnoccsd(t)f12")
182+
self.assertEqual(_normalize_method("dlpnoccsd(t)f122023"), "dlpnoccsd(t)f122023")
183+
184+
base, year = _split_method_year("dlpnoccsd(t)f122023")
185+
self.assertEqual(base, "dlpnoccsd(t)f12")
186+
self.assertEqual(year, 2023)
187+
base, year = _split_method_year("dlpnoccsd(t)f12")
188+
self.assertEqual(base, "dlpnoccsd(t)f12")
189+
self.assertIsNone(year)
190+
191+
self.assertEqual(_normalize_basis("cc-pVTZ-F12"), "ccpvtzf12")
192+
self.assertEqual(_normalize_basis("ccpvtz f12"), "ccpvtzf12")
193+
194+
params = _parse_lot_params(
195+
"LevelOfTheory(method='dlpnoccsd(t)f122023',basis='ccpvtzf12',software='orca')"
196+
)
197+
self.assertEqual(params["method"], "dlpnoccsd(t)f122023")
198+
self.assertEqual(params["basis"], "ccpvtzf12")
199+
self.assertEqual(params["software"], "orca")
200+
201+
def test_level_key_selection(self):
202+
"""Test matching of LevelOfTheory keys by year and no-year preference."""
203+
section = '\n'.join([
204+
'atom_energies = {',
205+
" \"LevelOfTheory(method='cbsqb3',software='gaussian')\": {},",
206+
" \"LevelOfTheory(method='cbsqb32023',software='gaussian')\": {},",
207+
"}",
208+
"pbac = {",
209+
])
210+
with tempfile.NamedTemporaryFile(mode="w+", delete=False) as f:
211+
f.write(section)
212+
path = f.name
213+
try:
214+
level = Level(method="CBS-QB3", software="gaussian")
215+
best = _find_best_level_key_for_sp_level(level, path, "atom_energies = {", "pbac = {")
216+
self.assertEqual(best, "LevelOfTheory(method='cbsqb3',software='gaussian')")
217+
218+
level_year = Level(method="CBS-QB3", software="gaussian", year=2023)
219+
best_year = _find_best_level_key_for_sp_level(level_year, path, "atom_energies = {", "pbac = {")
220+
self.assertEqual(best_year, "LevelOfTheory(method='cbsqb32023',software='gaussian')")
221+
222+
years = _available_years_for_level(level, path, "atom_energies = {", "pbac = {")
223+
self.assertEqual(years, [None, 2023])
224+
finally:
225+
os.remove(path)
226+
227+
def test_conflicting_year_spec(self):
228+
"""Test conflicting year in method suffix vs explicit year."""
229+
section = '\n'.join([
230+
'atom_energies = {',
231+
" \"LevelOfTheory(method='b97d32023',software='gaussian')\": {},",
232+
"}",
233+
"pbac = {",
234+
])
235+
with tempfile.NamedTemporaryFile(mode="w+", delete=False) as f:
236+
f.write(section)
237+
path = f.name
238+
try:
239+
level = Level(method="b97d32023", software="gaussian", year=2022)
240+
with self.assertRaises(InputError):
241+
_find_best_level_key_for_sp_level(level, path, "atom_energies = {", "pbac = {")
242+
finally:
243+
os.remove(path)
244+
245+
def test_qm_corrections_file_path(self):
246+
"""Test quantum corrections files are read from the RMG database path."""
247+
with tempfile.TemporaryDirectory() as rmg_root:
248+
rmg_qc = os.path.join(rmg_root, 'input', 'quantum_corrections', 'data.py')
249+
os.makedirs(os.path.dirname(rmg_qc), exist_ok=True)
250+
with open(rmg_qc, 'w') as f:
251+
f.write('# rmg qc\n')
252+
253+
with patch('arc.statmech.arkane.RMG_DB_PATH', rmg_root):
254+
paths = _get_qm_corrections_files()
255+
self.assertTrue(paths)
256+
self.assertEqual(paths[0], rmg_qc)
257+
258+
def test_get_arkane_model_chemistry_from_qm_file(self):
259+
"""Test reading LevelOfTheory keys from a quantum corrections file."""
260+
section = '\n'.join([
261+
'atom_energies = {',
262+
" \"LevelOfTheory(method='cbsqb3',software='gaussian')\": {},",
263+
"}",
264+
"pbac = {",
265+
])
266+
with tempfile.NamedTemporaryFile(mode="w+", delete=False) as f:
267+
f.write(section)
268+
path = f.name
269+
try:
270+
with patch('arc.statmech.arkane._get_qm_corrections_files', return_value=[path]):
271+
model_chemistry = get_arkane_model_chemistry(
272+
sp_level=Level(method='CBS-QB3'),
273+
freq_scale_factor=1.0,
274+
)
275+
self.assertEqual(model_chemistry, "LevelOfTheory(method='cbsqb3',software='gaussian')")
276+
finally:
277+
os.remove(path)
165278
def test_generate_arkane_input(self):
166279
"""Test generating Arkane input"""
167280
statmech_dir = os.path.join(ARC_PATH, 'arc', 'testing', 'arkane_input_tests_delete')

docs/source/advanced.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,9 @@ is equivalent to::
154154
sp_level = {'method': 'wb97xd', 'basis': 'def2svp'}
155155

156156
Note: Year suffixes in the method (e.g., ``wb97xd32023``) are meant for Arkane database matching
157-
and are not valid QC methods. Do not include year suffixes in ``level_of_theory``; instead, set
158-
``arkane_level_of_theory`` with a ``year`` value if you need a specific correction year.
157+
and are not valid QC methods. Do not include year suffixes in ``level_of_theory``; instead, specify a
158+
``year`` key on ``sp_level`` if you need a specific correction year. ``arkane_level_of_theory`` can still
159+
be used to explicitly override Arkane behavior.
159160

160161
Note: If ``level_of_theory`` does not contain any deliminator (neither ``//`` nor ``\/``), it is interpreted as a
161162
composite method.

docs/source/examples.rst

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,8 @@ To specify a composite method, simply define something like::
8686

8787
Note: Do not include year suffixes in ``level_of_theory`` (e.g., ``wb97xd32023``). Year suffixes are
8888
for Arkane database matching only and are not valid QC methods. If you need a specific correction year,
89-
set ``arkane_level_of_theory`` with a ``year`` value instead.
90-
If ``year`` is omitted, ARC will prefer the no-year Arkane entry for that method/basis; if none exists,
91-
ARC will fall back to the latest available year in the Arkane database.
89+
specify a ``year`` key on ``sp_level``. If ``year`` is omitted, ARC will prefer the no-year Arkane entry for
90+
that method/basis; if none exists, ARC will fall back to the latest available year in the Arkane database.
9291

9392
Note that for composite methods the ``freq_level`` and ``scan_level`` may have different
9493
default values than for non-composite methods (defined in settings.py). Note: an independent

0 commit comments

Comments
 (0)