From aedaecc14417c1d750fa11c222de2a0615130687 Mon Sep 17 00:00:00 2001 From: Qiming Sun Date: Sat, 23 Mar 2024 11:21:47 -0700 Subject: [PATCH] Update dft_parser; Add tests for dft_parser --- pyscf/dft/dft_parser.py | 20 ++++++++++++-------- pyscf/dft/libxc.py | 9 ++++++--- pyscf/dft/test/test_libxc.py | 14 ++++++++++++++ pyscf/dft/xcfun.py | 10 +++++++++- 4 files changed, 41 insertions(+), 12 deletions(-) diff --git a/pyscf/dft/dft_parser.py b/pyscf/dft/dft_parser.py index 8d702efcbb..4135a93ac9 100644 --- a/pyscf/dft/dft_parser.py +++ b/pyscf/dft/dft_parser.py @@ -29,13 +29,11 @@ @lru_cache(128) def parse_dft(dft_method): ''' conventional dft method -> - (xc, enable nlc, dispersion, with 3-body dispersion) + (xc, enable nlc, (xc for dftd3, dispersion version, with 3-body dispersion)) ''' if not isinstance(dft_method, str): - return dft_method, None, None, False + return dft_method, None, (dft_method, None, False) method_lower = dft_method.lower() - xc = dft_method - disp = None # special cases: # - wb97x-d is not supported yet @@ -53,24 +51,30 @@ def parse_dft(dft_method): if method_lower == 'wb97x-d3bj': return 'wb97x-v', False, ('wb97x', 'd3bj', False) - # J. Chem. Theory Comput. 2013, 9, 1, 263–272 + # J. Chem. Theory Comput. 2013, 9, 1, 263-272 if method_lower in ['wb97x-d3']: raise NotImplementedError('wb97x-d3 is not supported yet.') if method_lower.endswith('-3c'): raise NotImplementedError('*-3c methods are not supported yet.') + xc = dft_method + disp = None for d in DISP_VERSIONS: if method_lower.endswith(d): disp = d xc = method_lower.replace(f'-{d}','') - return xc, None, (xc, disp, False) if method_lower.endswith(d+'2b'): disp = d xc = method_lower.replace(f'-{d}2b', '') - return xc, None, (xc, disp, False) if method_lower.endswith(d+'atm'): disp = d xc = method_lower.replace(f'-{d}atm', '') - return xc, None, (xc, disp, True) + + if disp is not None: + if xc in ('b97m', 'wb97m', 'wb97x'): + return xc+'-v', False, (xc, disp, False) + else: + return xc, None, (xc, disp, False) + return xc, None, (xc, None, False) diff --git a/pyscf/dft/libxc.py b/pyscf/dft/libxc.py index 8e4bae369c..17a9b08a8f 100644 --- a/pyscf/dft/libxc.py +++ b/pyscf/dft/libxc.py @@ -923,9 +923,9 @@ def is_gga(xc_code): @lru_cache(100) def is_nlc(xc_code): enable_nlc = dft_parser.parse_dft(xc_code)[1] - if enable_nlc is False: + if not (enable_nlc is None and enable_nlc): return False - + # identify nlc by xc_code itself if enable_nlc is None if isinstance(xc_code, str): if xc_code.isdigit(): return _itrf.LIBXC_is_nlc(ctypes.c_int(int(xc_code))) @@ -1092,7 +1092,6 @@ def parse_xc(description): (hybrid, alpha, omega), ((libxc-Id, fac), (libxc-Id, fac), ...) ''' # noqa: E501 - description = dft_parser.parse_dft(description)[0] hyb = [0, 0, 0] # hybrid, alpha, omega (== SR_HF, LR_HF, omega) if description is None: return tuple(hyb), () @@ -1111,6 +1110,8 @@ def parse_xc(description): 'To restore the VWN5 definition, you can put the setting ' '"B3LYP_WITH_VWN5 = True" in pyscf_conf.py') + description = dft_parser.parse_dft(description)[0] + def assign_omega(omega, hyb_or_sr, lr=0): if hyb[2] == omega or omega == 0: hyb[0] += hyb_or_sr @@ -1237,6 +1238,8 @@ def possible_c_for(key): parse_token(token, 'C') else: for token in description.replace('-', '+-').replace(';+', ';').split('+'): + # dftd3 cannot be used in a custom xc description + assert '-d3' not in token parse_token(token, 'compound XC', search_xc_alias=True) if hyb[2] == 0: # No omega is assigned. LR_HF is 0 for normal Coulomb operator hyb[1] = 0 diff --git a/pyscf/dft/test/test_libxc.py b/pyscf/dft/test/test_libxc.py index 4bacb402e5..a5404155ab 100644 --- a/pyscf/dft/test/test_libxc.py +++ b/pyscf/dft/test/test_libxc.py @@ -341,6 +341,20 @@ def test_m06(self): self.assertAlmostEqual(abs(numpy.hstack([fxc[i] for i in [0,1,2,4,6,9]])-fxc_ref).max(), 0, 7) self.assertAlmostEqual(abs(numpy.hstack([kxc[i] for i in [0,1,2,3,5,7,10,12,15,19]])-kxc_ref).max(), 0, 6) + def test_dft_parser(self): + from pyscf.dft.dft_parser import parse_dft + self.assertEqual(parse_dft('wb97m-d3bj'), ('wb97m-v', False, ('wb97m', 'd3bj', False))) + self.assertEqual(dft.libxc.parse_xc('wb97m-d3bj')[1][0][0] == 531) + self.assertTrue(not dft.libxc.is_nlc('wb97m-d3bj')) + + self.assertEqual(parse_dft('wb97-d3zerom'), ('wb97', None, ('wb97', 'd3zerom', False))) + self.assertTrue(not dft.libxc.is_nlc('wb97-d3zerom')) + + self.assertEqual(parse_dft('wb97m-d3bjatm'), ('wb97m-v', False, ('wb97m', 'd3bj', True))) + self.assertTrue(not dft.libxc.is_nlc('wb97m-d3bjatm')) + + self.assertEqual(parse_dft('wb97x-d3zero2b'), ('wb97x-v', False, ('wb97x', 'd3zero', False))) + self.assertTrue(not dft.libxc.is_nlc('wb97x-d3zero2b')) if __name__ == "__main__": print("Test libxc") diff --git a/pyscf/dft/xcfun.py b/pyscf/dft/xcfun.py index c49c5e9ab8..0022683600 100644 --- a/pyscf/dft/xcfun.py +++ b/pyscf/dft/xcfun.py @@ -28,7 +28,7 @@ import numpy from pyscf import lib from pyscf.dft.xc.utils import remove_dup, format_xc_code -from pyscf.dft import xc_deriv +from pyscf.dft import xc_deriv, dft_parser from pyscf import __config__ _itrf = lib.load_library('libxcfun_itrf') @@ -318,6 +318,9 @@ def is_gga(xc_code): VV10_XC.update([(5000+i, VV10_XC[key]) for i, key in enumerate(VV10_XC)]) def is_nlc(xc_code): + enable_nlc = dft_parser.parse_dft(xc_code)[1] + if not (enable_nlc is None and enable_nlc): + return False fn_facs = parse_xc(xc_code)[1] return any(xid >= 5000 for xid, c in fn_facs) @@ -420,6 +423,8 @@ def parse_xc(description): elif not isinstance(description, str): #isinstance(description, (tuple,list)): return parse_xc('%s,%s' % tuple(description)) + description = dft_parser.parse_dft(description)[0] + def assign_omega(omega, hyb_or_sr, lr=0): if hyb[2] == omega or omega == 0: hyb[0] += hyb_or_sr @@ -430,6 +435,7 @@ def assign_omega(omega, hyb_or_sr, lr=0): hyb[2] = omega else: raise ValueError('Different values of omega found for RSH functionals') + fn_facs = [] def parse_token(token, suffix, search_xc_alias=False): if token: @@ -503,6 +509,8 @@ def parse_token(token, suffix, search_xc_alias=False): parse_token(token, 'C') else: for token in description.replace('-', '+-').replace(';+', ';').split('+'): + # dftd3 cannot be used in a custom xc description + assert '-d3' not in token parse_token(token, 'XC', search_xc_alias=True) if hyb[2] == 0: # No omega is assigned. LR_HF is 0 for normal Coulomb operator hyb[1] = 0