Skip to content

Commit 5db5aae

Browse files
authored
fix a xc_equal bug (#217)
* fix xc_equal bug * fix xclist default
1 parent cd15d33 commit 5db5aae

4 files changed

Lines changed: 46 additions & 9 deletions

File tree

‎pyscf/dh/test/test_xccode.py‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import unittest
2-
from pyscf.dh.xccode import XCList, parse_xc_dh
2+
from pyscf.dh.xccode import XCList, parse_xc_dh, xc_equal
33

44

55
class TestXCCode(unittest.TestCase):
@@ -46,6 +46,14 @@ def xc_io(inp, out, code_scf=False):
4646
xc_io("0.5*Slater + 0.8*B88, 0.3*DFTD4(rs6=1.5)", "0.8*B88 + 0.5*SLATER, 0.3*DFTD4(RS6=1.5)")
4747
xc_io("0.75*HF + 0.25*PBE,", "0.75*HF + 0.25*PBE,")
4848

49+
def test_xc_equal(self):
50+
# alias canonicalization through pyscf XC_CODES
51+
self.assertTrue(xc_equal("B3LYP", "B3LYPg"))
52+
self.assertTrue(xc_equal("PBE0", "PBE1PBE"))
53+
self.assertTrue(xc_equal("HF", "HF,"))
54+
self.assertFalse(xc_equal("B3LYP", "B3LYP5"))
55+
self.assertFalse(xc_equal("B3LYP", "PBE0"))
56+
4957
def test_d3_behavior(self):
5058
# Code string + D3 without XC= → NotImplementedError
5159
with self.assertRaises(NotImplementedError):

‎pyscf/dh/xccode/parse.py‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,17 @@ def _hash_val(xid):
8282

8383

8484
def xc_equal(a, b):
85+
r"""Compare two SCF-level XC functionals for equality after alias canonicalization.
86+
87+
Both arguments must be plain SCF functional names or code strings; DH
88+
functional names are not accepted here. Extract the SCF part first, e.g.
89+
``XCList("XYG3", code_scf=True).token``.
90+
"""
8591
if a is None and b is None:
8692
return True
8793
if a is None or b is None:
8894
return False
89-
return XCList(a, code_scf=True) == XCList(b, code_scf=True)
95+
return XCList(a) == XCList(b)
9096

9197

9298
def _check_unsupported(name):

‎pyscf/dh/xccode/xccode.py‎

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
import warnings
2828
from typing import List
2929
from .xctype import XCType
30-
from .xcinfo import XCInfo, REGEX_XC
30+
from .xcinfo import XCInfo, REGEX_XC, _canonical_xc_name
3131
from .xcjson import _NAME_WITH_DASH, FUNCTIONALS_DICT
3232

3333

@@ -50,7 +50,7 @@ def xc_list(self, xc_list):
5050
info.round()
5151
self._xc_list = xc_list
5252

53-
def __init__(self, token=None, code_scf=None, **kwargs):
53+
def __init__(self, token=None, code_scf=False, **kwargs):
5454
self.xc_list = []
5555
if token:
5656
if not isinstance(code_scf, bool):
@@ -339,11 +339,17 @@ def __imul__(self, other: float):
339339
return self
340340

341341
def __eq__(self, other: "XCList"):
342-
tokens_self = [str(info) for info in self.xc_list]
343-
tokens_other = [str(info) for info in other.xc_list]
344-
tokens_self.sort()
345-
tokens_other.sort()
346-
return "".join(tokens_self) == "".join(tokens_other)
342+
def info_key(info: XCInfo):
343+
return (
344+
info.fac,
345+
_canonical_xc_name(info.name),
346+
tuple(str(p) for p in info.parameters),
347+
tuple(sorted((k.upper(), str(v)) for k, v in info.parameters_keyword.items())),
348+
)
349+
350+
tokens_self = sorted(info_key(info) for info in self.xc_list)
351+
tokens_other = sorted(info_key(info) for info in other.xc_list)
352+
return tokens_self == tokens_other
347353

348354
# endregion
349355

‎pyscf/dh/xccode/xcinfo.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,28 @@
4141
from .xctype import XCType
4242
from .xcjson import ADV_CORR_ALIAS, ADV_CORR_DICT
4343
from pyscf import dft
44+
from pyscf.dft.libxc import XC_CODES
4445

4546

4647
REGEX_XC = r"((,?)([+-]*)([0-9.]+\*)?([\w@]+)(\([\w+\-\*.,;=]+\))?)"
4748

4849

50+
def _canonical_xc_name(name: str) -> str:
51+
r"""Resolve an XC name through PySCF's XC_CODES alias table into a canonical form.
52+
53+
For example, "B3LYP" and "B3LYPG" both resolve to the same libxc functional
54+
id (402), unless B3LYP_WITH_VWN5 is set, in which case "B3LYP" resolves to
55+
the VWN5 variant. Names absent from XC_CODES (HF, MP2, registered custom
56+
functionals, ...) are returned unchanged.
57+
"""
58+
key = name.upper()
59+
seen = set()
60+
while key in XC_CODES and key not in seen:
61+
seen.add(key)
62+
key = str(XC_CODES[key]).upper()
63+
return key
64+
65+
4966
@dataclass
5067
class XCInfo:
5168
"""

0 commit comments

Comments
 (0)