Skip to content

Commit e6fde5e

Browse files
committed
Refactor vdW XC name normalization (consistent with what libdftd4 does)
1 parent 5a76019 commit e6fde5e

7 files changed

Lines changed: 147 additions & 17 deletions

File tree

source/source_hamilt/module_vdw/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
set(vdw_sources
2+
vdw_xcname.cpp
23
vdwd2_parameters.cpp
34
vdwd2.cpp
45
vdwd3_data.cpp

source/source_hamilt/module_vdw/test/vdw_test.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,15 @@ TEST_F(vdwd3Test, D30GetEnergy)
425425
EXPECT_NEAR(ene, -0.022043153033290883, 1E-10);
426426
}
427427

428+
TEST_F(vdwd3Test, D30LibxcFunctionalName)
429+
{
430+
input.dft_functional = "XC_GGA_X_PBE+XC_GGA_C_PBE";
431+
432+
auto vdw_solver = vdw::make_vdw(ucell, input);
433+
const vdw::VdwResult result = vdw_solver->evaluate(vdw::VdwRequest(false, false));
434+
EXPECT_NEAR(result.energy, -0.022043153033290883, 1E-10);
435+
}
436+
428437
TEST_F(vdwd3Test, D30GetForce)
429438
{
430439
auto vdw_solver = vdw::make_vdw(ucell, input);
@@ -678,6 +687,16 @@ TEST_F(vdwd4Test, D4GetEnergy)
678687
EXPECT_NEAR(ene, -0.049988405722573105, 1E-10);
679688
}
680689

690+
TEST_F(vdwd4Test, D4LibxcFunctionalName)
691+
{
692+
input.vdw_d4_xc = "default";
693+
input.dft_functional = "XC_GGA_X_PBE+XC_GGA_C_PBE";
694+
695+
auto vdw_solver = vdw::make_vdw(ucell, input);
696+
const vdw::VdwResult result = vdw_solver->evaluate(vdw::VdwRequest(false, false));
697+
EXPECT_NEAR(result.energy, -0.049988405722573105, 1E-10);
698+
}
699+
681700
TEST_F(vdwd4Test, D4GetEnergyForChargedSystem)
682701
{
683702
input.nelec = 7.0;

source/source_hamilt/module_vdw/test/vdwd3_evaluator_test.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "source_hamilt/module_vdw/vdwd3_data.h"
22
#include "source_hamilt/module_vdw/vdwd3_evaluator.h"
33
#include "source_hamilt/module_vdw/vdwd3_parameters.h"
4+
#include "source_hamilt/module_vdw/vdw_xcname.h"
45

56
#include "gtest/gtest.h"
67

@@ -204,6 +205,18 @@ Structure x23_acetic()
204205

205206
} // namespace
206207

208+
TEST(VdwXcName, NormalizesAbacusAndLibxcSpellings)
209+
{
210+
EXPECT_EQ(vdw::normalize_xc_name("PBE"), "pbe");
211+
EXPECT_EQ(vdw::normalize_xc_name(" XC_HYB_GGA_XC_PBEH "), "hyb_gga_xc_pbeh");
212+
EXPECT_EQ(vdw::normalize_xc_name("GGA_X_PBE+GGA_C_PBE"),
213+
"gga_x_pbe:gga_c_pbe");
214+
EXPECT_EQ(vdw::normalize_xc_name("XC_GGA_X_PBE + XC_GGA_C_PBE"),
215+
"gga_x_pbe:gga_c_pbe");
216+
EXPECT_EQ(vdw::normalize_xc_name("XC_GGA_X_PBE_R:XC_GGA_C_PBE"),
217+
"gga_x_pbe_r:gga_c_pbe");
218+
}
219+
207220
TEST(D3Parameters, PbeAndLibxcAliases)
208221
{
209222
Parameters zero;
@@ -236,6 +249,12 @@ TEST(D3Parameters, PbeAndLibxcAliases)
236249
EXPECT_EQ(canonical, "bop");
237250
EXPECT_DOUBLE_EQ(rational.a1, 0.487);
238251

252+
ASSERT_TRUE(vdw::d3::lookup_parameters("XC_GGA_X_PBE_R+XC_GGA_C_PBE",
253+
Damping::Rational,
254+
rational,
255+
canonical));
256+
EXPECT_EQ(canonical, "revpbe");
257+
239258
// Legacy ABACUS LibXC spellings: the numerical parameter set is stored
240259
// under `wb97x` by s-dftd3, but only for the corresponding damping form.
241260
ASSERT_TRUE(vdw::d3::lookup_parameters("XC_HYB_GGA_XC_WB97X_V",
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#include "vdw_xcname.h"
2+
3+
#include <algorithm>
4+
#include <cctype>
5+
#include <string>
6+
7+
namespace vdw
8+
{
9+
namespace
10+
{
11+
12+
std::string trim_ascii(const std::string& value)
13+
{
14+
const auto first = std::find_if_not(value.begin(), value.end(), [](unsigned char character) {
15+
return std::isspace(character) != 0;
16+
});
17+
if (first == value.end())
18+
{
19+
return std::string();
20+
}
21+
22+
const auto last = std::find_if_not(value.rbegin(), value.rend(), [](unsigned char character) {
23+
return std::isspace(character) != 0;
24+
}).base();
25+
return std::string(first, last);
26+
}
27+
28+
std::string normalize_component(std::string value)
29+
{
30+
value = trim_ascii(value);
31+
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char character) {
32+
return static_cast<char>(std::tolower(character));
33+
});
34+
35+
if (value.compare(0, 3, "xc_") == 0)
36+
{
37+
value.erase(0, 3);
38+
}
39+
return value;
40+
}
41+
42+
} // namespace
43+
44+
std::string normalize_xc_name(const std::string& input)
45+
{
46+
const std::size_t plus = input.find('+');
47+
const std::size_t colon = input.find(':');
48+
const std::size_t separator = plus != std::string::npos ? plus : colon;
49+
50+
if (separator == std::string::npos)
51+
{
52+
return normalize_component(input);
53+
}
54+
55+
return normalize_component(input.substr(0, separator)) + ":"
56+
+ normalize_component(input.substr(separator + 1));
57+
}
58+
59+
} // namespace vdw
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#ifndef ABACUS_VDW_XCNAME_H
2+
#define ABACUS_VDW_XCNAME_H
3+
4+
#include <string>
5+
6+
namespace vdw
7+
{
8+
9+
/**
10+
* @brief Normalize an XC-functional spelling at the ABACUS/libXC boundary.
11+
*
12+
* This function intentionally performs syntax normalization only. It lowercases
13+
* ASCII names, removes an optional leading "XC_" from each libXC component,
14+
* trims surrounding whitespace, and writes two-component libXC functionals
15+
* with ':' as the separator used by libdftd4.
16+
*
17+
* Examples:
18+
* "PBE" -> "pbe"
19+
* "XC_HYB_GGA_XC_PBEH" -> "hyb_gga_xc_pbeh"
20+
* "GGA_X_PBE+GGA_C_PBE" -> "gga_x_pbe:gga_c_pbe"
21+
* "XC_GGA_X_PBE+XC_GGA_C_PBE" -> "gga_x_pbe:gga_c_pbe"
22+
*
23+
* Semantic aliases (for example revPBE or damping-specific wB97X aliases)
24+
* belong to the dispersion-model parameter layer rather than here.
25+
*/
26+
std::string normalize_xc_name(const std::string& input);
27+
28+
} // namespace vdw
29+
30+
#endif // ABACUS_VDW_XCNAME_H

source/source_hamilt/module_vdw/vdwd3_parameters.cpp

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "vdwd3_parameters.h"
2+
#include "vdw_xcname.h"
23

34
#include <algorithm>
45
#include <cctype>
@@ -72,9 +73,9 @@ std::string libxc_component(const std::string& component, const std::string& mar
7273

7374
std::string canonicalize_libxc_pair(const std::string& input)
7475
{
75-
const std::size_t plus = input.find('+');
76-
std::string first = input.substr(0, plus);
77-
std::string second = input.substr(plus + 1);
76+
const std::size_t separator = input.find(':');
77+
std::string first = input.substr(0, separator);
78+
std::string second = input.substr(separator + 1);
7879
const std::string first_lower = lower_ascii(first);
7980
if (first_lower.find("_c_") != std::string::npos)
8081
{
@@ -92,25 +93,26 @@ std::string canonicalize_libxc_pair(const std::string& input)
9293
// libxc_component(). Only mixed exchange/correlation names need an
9394
// explicit mapping; identical component names are handled above.
9495
static const std::map<std::string, std::string> special = {
95-
{"b88+lyp", "blyp"}, {"b88+p86", "bp86"},
96-
{"b88+pbe", "bpbe"}, {"pber+pbe", "revpbe"},
97-
{"rpbe+pbe", "rpbe"}, {"optx+lyp", "olyp"},
98-
{"mpw91+pw91", "mpwpw"}, {"ms2+regtpss", "ms2"},
99-
{"ms2h+regtpss", "ms2h"}, {"pbe+oppbe", "pbeop"},
100-
{"b88+opb88", "bop"},
96+
{"b88:p86", "bp86"}, {"b88:lyp", "blyp"},
97+
{"b88:pbe", "bpbe"}, {"pber:pbe", "revpbe"},
98+
{"rpbe:pbe", "rpbe"}, {"optx:lyp", "olyp"},
99+
{"mpw91:pw91", "mpwpw"}, {"ms2:regtpss", "ms2"},
100+
{"ms2h:regtpss", "ms2h"}, {"pbe:oppbe", "pbeop"},
101+
{"b88:opb88", "bop"},
101102
};
102-
const auto found = special.find(exchange + "+" + correlation);
103+
const auto found = special.find(exchange + ":" + correlation);
103104
return found == special.end() ? std::string() : found->second;
104105
}
105106

106107
} // namespace
107108

108109
std::string canonicalize_method_name(const std::string& input)
109110
{
111+
const std::string xc_name = normalize_xc_name(input);
110112
std::string canonical;
111-
if (input.find('+') != std::string::npos)
113+
if (xc_name.find(':') != std::string::npos)
112114
{
113-
const std::string pair = canonicalize_libxc_pair(input);
115+
const std::string pair = canonicalize_libxc_pair(xc_name);
114116
if (!pair.empty())
115117
{
116118
canonical = pair;
@@ -119,10 +121,9 @@ std::string canonicalize_method_name(const std::string& input)
119121

120122
if (canonical.empty())
121123
{
122-
const std::string lower = lower_ascii(input);
123-
const std::size_t xc = lower.find("_xc_");
124-
canonical = xc == std::string::npos ? normalized(input)
125-
: normalized(input.substr(xc + 4));
124+
const std::size_t xc = xc_name.find("_xc_");
125+
canonical = xc == std::string::npos ? normalized(xc_name)
126+
: normalized(xc_name.substr(xc + 4));
126127
}
127128

128129
// ABACUS calls its HSE06 implementation "HSE" in user input and

source/source_hamilt/module_vdw/vdwd4.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "vdwd4.h"
2+
#include "vdw_xcname.h"
23

34
#include "source_base/constants.h"
45
#include "source_base/element_name.h"
@@ -81,7 +82,7 @@ double cutoff_to_bohr(const std::string& value, const std::string& unit)
8182
} // namespace
8283

8384
Vdwd4::Vdwd4(const UnitCell& unit_in, const std::string& xc_name, const Input_para& input)
84-
: Vdw(unit_in), xc_name_(to_lower(xc_name)), model_name_(to_lower(input.vdw_d4_model))
85+
: Vdw(unit_in), xc_name_(normalize_xc_name(xc_name)), model_name_(to_lower(input.vdw_d4_model))
8586
{
8687
cutoff_disp2_ = cutoff_to_bohr(input.vdw_cutoff_radius, input.vdw_radius_unit);
8788
cutoff_disp3_ = std::min(40.0, cutoff_disp2_);

0 commit comments

Comments
 (0)