Skip to content

Commit 8dee498

Browse files
committed
Update
1 parent bb2e2aa commit 8dee498

4 files changed

Lines changed: 139 additions & 27 deletions

File tree

arc/statmech/arkane.py

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

689689
def _get_qm_corrections_files() -> List[str]:
690690
"""
691-
Return quantum corrections data.py paths, preferring ARC-local data.
692-
693-
Preference order:
694-
1) ARC-local database/data directories (if present)
695-
2) RMG-database
691+
Return quantum corrections data.py paths from the RMG database.
696692
"""
697693
candidates = [
698-
os.path.join(ARC_PATH, 'arc', 'database', 'input', 'quantum_corrections', 'data.py'),
699-
os.path.join(ARC_PATH, 'arc', 'database', 'quantum_corrections', 'data.py'),
700-
os.path.join(ARC_PATH, 'arc', 'data', 'input', 'quantum_corrections', 'data.py'),
701-
os.path.join(ARC_PATH, 'arc', 'data', 'quantum_corrections', 'data.py'),
702-
os.path.join(ARC_PATH, 'data', 'quantum_corrections.py'),
703694
os.path.join(RMG_DB_PATH, 'input', 'quantum_corrections', 'data.py'),
704695
]
705696
return [path for path in candidates if os.path.isfile(path)]
@@ -785,7 +776,7 @@ def _iter_level_keys_from_section(file_path: str,
785776
def _available_years_for_level(level: "Level",
786777
file_path: str,
787778
section_start: str,
788-
section_end: str) -> list[Optional[int]]:
779+
section_end: str) -> List[Optional[int]]:
789780
"""
790781
Return a sorted list of available year suffixes for a given Level in a section.
791782
"""
@@ -826,7 +817,7 @@ def _available_years_for_level(level: "Level",
826817
return sorted(years, key=lambda y: (-1 if y is None else y))
827818

828819

829-
def _format_years(years: list[Optional[int]]) -> str:
820+
def _format_years(years: List[Optional[int]]) -> str:
830821
"""
831822
Format a list of years for logging.
832823
"""
@@ -851,7 +842,14 @@ def _find_best_level_key_for_sp_level(level: "Level",
851842

852843
target_method_norm = _normalize_method(level.method)
853844
target_base, method_year = _split_method_year(target_method_norm)
854-
target_year = getattr(level, 'year', None) if getattr(level, 'year', None) is not None else method_year
845+
explicit_year = getattr(level, 'year', None)
846+
if explicit_year is not None and method_year is not None and explicit_year != method_year:
847+
raise InputError(
848+
f"Conflicting year specifications for level '{level}': "
849+
f"explicit year={explicit_year}, method suffix year={method_year}. "
850+
"Please remove the year suffix from the method name or update the 'year' attribute to match."
851+
)
852+
target_year = explicit_year if explicit_year is not None else method_year
855853
target_basis_norm = _normalize_basis(level.basis)
856854
target_software = level.software.lower() if level.software else None
857855

@@ -933,14 +931,16 @@ def get_arkane_model_chemistry(sp_level: 'Level',
933931
"""
934932
Get Arkane model chemistry string with database validation.
935933
936-
Reads quantum_corrections/data.py as plain text (prefers ARC-local overrides,
937-
then RMG-database), searches for
934+
Reads quantum_corrections/data.py as plain text, searches for
938935
LevelOfTheory(...) keys, and matches:
939936
- method: ignoring hyphens and optional 4-digit year suffix
940937
- basis: ignoring hyphens and spaces
941938
942-
If multiple entries only differ by year, the one with the *latest* year
943-
is chosen (year=0 if no year in that entry).
939+
When a year is explicitly specified in the Level, only entries with that exact
940+
year are matched. If no year is specified and an entry without a year exists,
941+
that entry is used. Only when no year is specified and no no-year entry exists,
942+
if multiple entries differ only by year, the one with the *latest* year is
943+
chosen (treating entries with no year as year=0).
944944
945945
Args:
946946
sp_level (Level): Level of theory for energy.
@@ -981,7 +981,8 @@ def get_arkane_model_chemistry(sp_level: 'Level',
981981
f"available years: {_format_years(years)}. "
982982
f"Specify a year to select a matching entry."
983983
)
984-
return _level_to_str(sp_level)
984+
# No matching AEC level in Arkane DB for this composite method
985+
return None
985986
return best_energy
986987

987988
# ---- Case 1: User supplied explicit frequency scale factor ----
@@ -1074,8 +1075,7 @@ def check_arkane_bacs(sp_level: 'Level',
10741075
"""
10751076
Check that Arkane has AECs and BACs for the given sp level of theory.
10761077
1077-
Uses plain-text parsing of quantum_corrections/data.py (prefers ARC-local overrides,
1078-
then RMG-database), matching LevelOfTheory
1078+
Uses plain-text parsing of quantum_corrections/data.py, matching LevelOfTheory
10791079
keys by:
10801080
- method base (ignore hyphens + optional year)
10811081
- basis (normalized)

arc/statmech/arkane_test.py

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +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
19-
from arc.imports import settings
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
2033

2134

2235
class TestEnumerationClasses(unittest.TestCase):
@@ -181,6 +194,105 @@ def test_get_arkane_model_chemistry_latest_year(self):
181194
freq_scale_factor=1.0)
182195
self.assertEqual(model_chemistry, "LevelOfTheory(method='cbsqb3',software='gaussian')")
183196

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