Skip to content

Commit 36af8d3

Browse files
jinzx10kirk0830
andauthored
Feature: Building numerical atomic orbitals from spherical Bessel coefficients (deepmodeling#3296)
* add SphbesRadials class * minor fix * test code annotation updated --------- Co-authored-by: Yike Huang <[email protected]>
1 parent 0c2154f commit 36af8d3

File tree

6 files changed

+600
-1
lines changed

6 files changed

+600
-1
lines changed

source/module_basis/module_nao/atomic_radials.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ void AtomicRadials::build(const std::string& file, const int itype, std::ofstrea
3434

3535
if (!is_open)
3636
{
37-
ModuleBase::WARNING_QUIT("AtomicRadials::read", "Couldn't open orbital file: " + file);
37+
ModuleBase::WARNING_QUIT("AtomicRadials::build", "Couldn't open orbital file: " + file);
3838
}
3939

4040
if (ptr_log)
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
#include "module_basis/module_nao/sphbes_radials.h"
2+
3+
#include <algorithm>
4+
#include <functional>
5+
#include <regex>
6+
#include <iterator>
7+
8+
#include "module_base/math_sphbes.h"
9+
#include "module_base/parallel_common.h"
10+
#include "module_base/tool_quit.h"
11+
12+
SphbesRadials& SphbesRadials::operator=(const SphbesRadials& rhs)
13+
{
14+
if (this != &rhs) {
15+
RadialSet::operator=(rhs);
16+
dr_ = rhs.dr_;
17+
sigma_ = rhs.sigma_;
18+
coeff_ = rhs.coeff_;
19+
}
20+
return *this;
21+
}
22+
23+
void SphbesRadials::build(const std::string& file, const int itype, std::ofstream* ptr_log, const int rank)
24+
{
25+
cleanup();
26+
coeff_.clear();
27+
28+
std::ifstream ifs;
29+
bool is_open = false;
30+
31+
if (rank == 0) {
32+
ifs.open(file);
33+
is_open = ifs.is_open();
34+
}
35+
36+
#ifdef __MPI
37+
Parallel_Common::bcast_bool(is_open);
38+
#endif
39+
40+
if (!is_open)
41+
{
42+
ModuleBase::WARNING_QUIT("SphbesRadials::build", "Couldn't open orbital file: " + file);
43+
}
44+
45+
if (ptr_log)
46+
{
47+
(*ptr_log) << "\n\n\n\n";
48+
(*ptr_log) << " >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" << std::endl;
49+
(*ptr_log) << " | |" << std::endl;
50+
(*ptr_log) << " | SETUP NUMERICAL ATOMIC ORBITALS |" << std::endl;
51+
(*ptr_log) << " | |" << std::endl;
52+
(*ptr_log) << " | Orbital information includes the cutoff radius, angular momentum, |" << std::endl;
53+
(*ptr_log) << " | zeta number, spherical Bessel coefficients and smoothing parameter. |" << std::endl;
54+
(*ptr_log) << " | |" << std::endl;
55+
(*ptr_log) << " <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" << std::endl;
56+
(*ptr_log) << "\n\n\n\n";
57+
}
58+
59+
itype_ = itype;
60+
read_coeff(ifs, ptr_log, rank);
61+
build_radset();
62+
63+
if (rank == 0)
64+
{
65+
ifs.close();
66+
}
67+
}
68+
69+
70+
void SphbesRadials::read_coeff(std::ifstream& ifs, std::ofstream* ptr_log, const int rank)
71+
{
72+
std::string info, tmp;
73+
if (rank == 0)
74+
{
75+
// reach the Coefficient block (between <Coefficient rcut=...> and </Coefficient>)
76+
while ((ifs >> tmp) && tmp != "<Coefficient") {}
77+
78+
// read the rest part of the Coefficient block at once (before </Coefficient>)
79+
std::getline(ifs, info, '<');
80+
}
81+
82+
#ifdef __MPI
83+
Parallel_Common::bcast_string(info);
84+
#endif
85+
86+
// extract rcut & sigma from the pattern KEYWORD=" VALUE "
87+
if ( (tmp = extract(info, "rcut")).empty() )
88+
{ // rcut must be provided by the file; quit if not found.
89+
ModuleBase::WARNING_QUIT("SphbesRadials::read_coeff", "Fails to read the cutoff radius (rcut).");
90+
}
91+
rcut_max_ = std::stod(tmp);
92+
sigma_ = (tmp = extract(info, "sigma")).empty() ? sigma_ : std::stod(tmp); // use default if not found
93+
symbol_ = extract(info, "element");
94+
95+
// split the string by white spaces, tabs or new lines into a vector of substrings
96+
std::vector<std::string> v = split(info, " \n\t");
97+
98+
// find the indices of all occurences of "Type" (plus the one-past-last index)
99+
std::vector<size_t> delim; // delimiters
100+
std::for_each(v.begin(), v.end(), [&delim, &v] (std::string const& s)
101+
{ if (s == "Type") delim.push_back(&s - &v[0]); }); // for_each is guaranteed to be sequential
102+
delim.push_back(v.size());
103+
104+
// NOTE: Zeta-Orbital in some ORBITAL_RESULTS.txt is one-based numbering
105+
// which needs to be converted to a zero-based numbering.
106+
// Here we keep track of this index ourselves.
107+
int l_last = -1;
108+
int izeta = -1;
109+
for (size_t i = 0; i < delim.size() - 1; ++i)
110+
{
111+
int l = std::stoi(v[delim[i] + 4]);
112+
izeta = (l == l_last) ? izeta + 1 : 0;
113+
l_last = l;
114+
115+
std::vector<double> coeff_q(delim[i+1] - delim[i] - 6);
116+
std::transform(v.begin() + delim[i] + 6, v.begin() + delim[i+1], coeff_q.begin(),
117+
[](std::string const& s) { return std::stod(s); });
118+
coeff_.emplace(std::make_pair(l, izeta), std::move(coeff_q));
119+
}
120+
}
121+
122+
123+
std::string SphbesRadials::extract(std::string const& str, std::string const& keyword) {
124+
std::smatch match;
125+
std::string regex_string = keyword + "=\" *([^= ]+) *\"";
126+
std::regex re(regex_string);
127+
std::regex_search(str, match, re);
128+
return match.empty() ? "" : match[1].str();
129+
}
130+
131+
132+
std::vector<std::string> SphbesRadials::split(std::string const& str, const char* delim) {
133+
std::vector<std::string> v;
134+
std::string::size_type start = 0, end = 0;
135+
while ((start = str.find_first_not_of(delim, end)) != std::string::npos) {
136+
end = str.find_first_of(delim, start);
137+
v.push_back(str.substr(start, end - start));
138+
}
139+
return v;
140+
}
141+
142+
std::vector<double> SphbesRadials::sphbes_comb(const int l,
143+
std::vector<double> const& coeff_q,
144+
double rcut,
145+
double dr,
146+
std::vector<double> const& q)
147+
{
148+
#ifdef __DEBUG
149+
assert(coeff_q.size() == q.size());
150+
assert(l >= 0 && rcut >= 0.0 && dr > 0.0);
151+
#endif
152+
int nr = static_cast<int>(rcut / dr) + 1;
153+
std::vector<double> r(nr);
154+
std::for_each(r.begin(), r.end(), [&r, dr] (double& x) { x = (&x - r.data()) * dr; });
155+
156+
std::vector<double> tmp(nr, 0.0);
157+
std::vector<double> f(nr, 0.0);
158+
159+
// f[ir] = \sum_{iq} coeff[iq] * j_{l}(q[i] * r[ir])
160+
for (size_t iq = 0; iq != q.size(); ++iq)
161+
{
162+
ModuleBase::Sphbes::sphbesj(nr, r.data(), q[iq], l, tmp.data());
163+
for (size_t ir = 0; ir != tmp.size(); ++ir)
164+
{
165+
f[ir] += coeff_q[iq] * tmp[ir];
166+
}
167+
}
168+
169+
return f;
170+
}
171+
172+
double SphbesRadials::smooth(double r, double rcut, double sigma)
173+
{
174+
return (r < rcut) * (sigma == 0 ? 1.0 : 1.0 - std::exp(-0.5 * std::pow( (r - rcut) / sigma, 2)));
175+
}
176+
177+
void SphbesRadials::build_radset()
178+
{
179+
// symbol_ is set in read_coeff()
180+
// itype_ is set in build()
181+
// rcut_max_ is set in read_coeff() (there's only one rcut for all orbitals)
182+
183+
lmax_ = std::max_element(coeff_.begin(), coeff_.end())->first.first; // std::pair uses lexicographical order
184+
185+
delete[] nzeta_;
186+
nzeta_ = new int[lmax_ + 1](); // zero initialized
187+
for (auto const& p : coeff_)
188+
{
189+
nzeta_[p.first.first]++;
190+
}
191+
nzeta_max_ = *std::max_element(nzeta_, nzeta_ + lmax_ + 1);
192+
indexing();
193+
194+
int nr = static_cast<int>(rcut_max_ / dr_) + 1;
195+
std::vector<double> r(nr);
196+
std::for_each(r.begin(), r.end(), [&r, this] (double& x) { x = (&x - r.data()) * dr_; });
197+
198+
nchi_ = coeff_.size();
199+
chi_ = new NumericalRadial[nchi_];
200+
for (auto const& p : coeff_) // p has the form of ( (l, izeta), coeff_q )
201+
{
202+
int l = p.first.first;
203+
int izeta = p.first.second;
204+
auto& coeff_q = p.second;
205+
206+
// find wave numbers such that j_l(q * rcut) = 0
207+
std::vector<double> q(coeff_q.size());
208+
ModuleBase::Sphbes::sphbes_zeros(l, coeff_q.size(), q.data());
209+
std::for_each(q.begin(), q.end(), [this] (double& qi) { qi /= rcut_max_; });
210+
211+
// linear combination of spherical Bessel functions
212+
std::vector<double> f = sphbes_comb(l, coeff_q, rcut_max_, dr_, q);
213+
214+
// smooth the function at rcut
215+
std::transform(r.begin(), r.end(), f.begin(), f.begin(),
216+
[this] (double ri, double fi) { return fi * smooth(ri, rcut_max_, sigma_); });
217+
218+
chi_[index(l, izeta)].build(l, true, nr, r.data(), f.data(), 0, izeta, symbol_, itype_, false);
219+
chi_[index(l, izeta)].normalize();
220+
}
221+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#ifndef SPHBES_RADIALS_H_
2+
#define SPHBES_RADIALS_H_
3+
4+
#include <map>
5+
#include <vector>
6+
#include <string>
7+
8+
#include "module_basis/module_nao/radial_set.h"
9+
10+
/**
11+
* @brief Numerical radials from spherical Bessel coefficients.
12+
*
13+
*/
14+
class SphbesRadials : public RadialSet
15+
{
16+
public:
17+
SphbesRadials() {}
18+
SphbesRadials(const SphbesRadials& other):
19+
RadialSet(other), sigma_(other.sigma_), dr_(other.dr_), coeff_(other.coeff_) {}
20+
21+
SphbesRadials& operator=(const SphbesRadials& rhs);
22+
SphbesRadials* clone() const { return new SphbesRadials(*this); } // covariant return type
23+
24+
~SphbesRadials() {} // ~RadialSet() is called automatically
25+
26+
/**
27+
* @brief Builds the class from a spherical Bessel coefficient file
28+
*
29+
* @param[in] file orbital file name
30+
* @param[in] itype element index in calculation
31+
* @param[in] dr radial grid spacing
32+
* @param[in] ptr_log output file stream for logging
33+
* @param[in] rank MPI rank
34+
*/
35+
void build(const std::string& file,
36+
const int itype = 0,
37+
std::ofstream* ptr_log = nullptr,
38+
const int rank = 0
39+
);
40+
41+
void set_dr(const double dr) { dr_ = dr; }
42+
43+
/**
44+
* @name Getters
45+
*/
46+
///@{
47+
double sigma() const { return sigma_; }
48+
double dr() const { return dr_; }
49+
std::vector<double> const& coeff(const int l, const int izeta) const { return coeff_.at(std::make_pair(l, izeta)); }
50+
///@}
51+
52+
private:
53+
54+
/// Smoothing parameter.
55+
double sigma_ = 0.0;
56+
57+
/// Radial grid spacing.
58+
double dr_ = 0.01;
59+
60+
/// Spherical Bessel coefficients coeff_[{l,zeta}][q]
61+
std::map<std::pair<int,int>, std::vector<double>> coeff_;
62+
63+
/// Reads spherical Bessel coefficients, cutoff radius & smoothing parameter from a file stream.
64+
void read_coeff(std::ifstream& ifs,
65+
std::ofstream* ptr_log = nullptr,
66+
const int rank = 0);
67+
68+
///
69+
void build_radset();
70+
71+
/// Extracts a substring (VALUE) from a string of the form KEYWORD=" VALUE ".
72+
std::string extract(std::string const& str, std::string const& keyword);
73+
74+
/// Splits a string into a vector of substrings with given delimiters.
75+
std::vector<std::string> split(std::string const& str, const char* delim = " \n\t");
76+
77+
/// Computes the combination of spherical Bessel functions on a uniform grid.
78+
std::vector<double> sphbes_comb(const int l,
79+
std::vector<double> const& coeff_q,
80+
double rcut,
81+
double dr,
82+
std::vector<double> const& q
83+
);
84+
85+
/// Smoothing function.
86+
double smooth(double r, double rcut, double sigma);
87+
88+
};
89+
90+
#endif
91+

source/module_basis/module_nao/test/CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@ AddTest(
2626
LIBS ${math_libs} device base
2727
)
2828

29+
AddTest(
30+
TARGET sphbes_radials
31+
SOURCES
32+
sphbes_radials_test.cpp
33+
../sphbes_radials.cpp
34+
../radial_set.cpp
35+
../numerical_radial.cpp
36+
LIBS ${math_libs} device base
37+
)
38+
2939
AddTest(
3040
TARGET radial_collection
3141
SOURCES
@@ -94,3 +104,4 @@ AddTest(
94104
../numerical_radial.cpp
95105
LIBS ${math_libs} device base container orb
96106
)
107+

0 commit comments

Comments
 (0)