Skip to content

Commit 6664393

Browse files
YuLiu98mohanchen
andauthored
Feature: LDOS mode for pw (deepmodeling#6167)
* Feature: ldos mode for pw * Feature: add a para to control ldos search line * Docs: update docs about ldos_line * Test: update unittest --------- Co-authored-by: Mohan Chen <mohanchen@pku.edu.cn>
1 parent 4227a87 commit 6664393

File tree

10 files changed

+473
-77
lines changed

10 files changed

+473
-77
lines changed

docs/advanced/input_files/input-main.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@
177177
- [dos\_emax\_ev](#dos_emax_ev)
178178
- [dos\_nche](#dos_nche)
179179
- [stm\_bias](#stm_bias)
180+
- [ldos\_line](#ldos_line)
180181
- [NAOs](#naos)
181182
- [bessel\_nao\_ecut](#bessel_nao_ecut)
182183
- [bessel\_nao\_tolerence](#bessel_nao_tolerence)
@@ -1705,9 +1706,13 @@ These variables are used to control the output of properties.
17051706

17061707
### out_ldos
17071708

1708-
- **Type**: Boolean
1709-
- **Description**: Whether to output the local density of states for given bias in cube file format, which is controlled by [stm_bias](#stm_bias).
1710-
- **Default**: False
1709+
- **Type**: Integer
1710+
- **Description**: Whether to output the local density of states (LDOS), optionally output precision can be set by a second parameter, default is 3.
1711+
- 0: no output
1712+
- 1: output the partial charge density for given bias (controlled by [stm_bias](#stm_bias)) in cube file format, which can be used to plot scanning tunneling spectroscopys to mimick STM images using the Python script [plot.py](../../../tools/stm/plot.py).
1713+
- 2: output LDOS along a line in real space (controlled by [ldos_line](#ldos_line)). Parameters used to control DOS output are also valid for LDOS.
1714+
- 3: output both two LDOS modes above.
1715+
- **Default**: 0
17111716

17121717
### out_band
17131718

@@ -1986,6 +1991,13 @@ These variables are used to control the calculation of DOS. [Detailed introducti
19861991
- **Default**: 1.0
19871992
- **Unit**: V
19881993

1994+
### ldos_line
1995+
1996+
- **Type**: Real*6 Integer(optional)
1997+
- **Description**: Specify the path of the three-dimensional space and display LDOS in the form of a two-dimensional color chart, see details in [out_ldos](#out_ldos). The first three paramenters are the direct coordinates of the start point, the next three paramenters are the direct coordinates of the end point, and the final one is the number of points along the path, whose default is 100.
1998+
- **Default**: 0.0 0.0 0.0 0.0 0.0 1.0 100
1999+
2000+
19892001
[back to top](#full-list-of-input-keywords)
19902002

19912003
## NAOs

source/module_esolver/esolver_ks_pw.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -947,11 +947,10 @@ void ESolver_KS_PW<T, Device>::after_all_runners(UnitCell& ucell)
947947
//----------------------------------------------------------
948948
if (PARAM.inp.out_ldos[0])
949949
{
950-
ModuleIO::Cal_ldos<std::complex<double>>::cal_ldos_pw(
951-
reinterpret_cast<elecstate::ElecStatePW<std::complex<double>>*>(this->pelec),
952-
this->psi[0],
953-
this->Pgrid,
954-
ucell);
950+
ModuleIO::cal_ldos_pw(reinterpret_cast<elecstate::ElecStatePW<std::complex<double>>*>(this->pelec),
951+
this->psi[0],
952+
this->Pgrid,
953+
ucell);
955954
}
956955

957956
//----------------------------------------------------------

source/module_io/cal_ldos.cpp

Lines changed: 267 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "cal_ldos.h"
22

3+
#include "cal_dos.h"
34
#include "cube_io.h"
45
#include "module_elecstate/module_dm/cal_dm_psi.h"
56
#include "module_hamilt_lcao/module_gint/temp_gint/gint_interface.h"
@@ -8,54 +9,6 @@
89

910
namespace ModuleIO
1011
{
11-
template <typename T>
12-
void Cal_ldos<T>::cal_ldos_pw(const elecstate::ElecStatePW<std::complex<double>>* pelec,
13-
const psi::Psi<std::complex<double>>& psi,
14-
const Parallel_Grid& pgrid,
15-
const UnitCell& ucell)
16-
{
17-
for (int ie = 0; ie < PARAM.inp.stm_bias[2]; ie++)
18-
{
19-
// energy range for ldos (efermi as reference)
20-
const double en = PARAM.inp.stm_bias[0] + ie * PARAM.inp.stm_bias[1];
21-
const double emin = en < 0 ? en : 0;
22-
const double emax = en > 0 ? en : 0;
23-
24-
std::vector<double> ldos(pelec->charge->nrxx);
25-
std::vector<std::complex<double>> wfcr(pelec->basis->nrxx);
26-
27-
for (int ik = 0; ik < pelec->klist->get_nks(); ++ik)
28-
{
29-
psi.fix_k(ik);
30-
const double efermi = pelec->eferm.get_efval(pelec->klist->isk[ik]);
31-
int nbands = psi.get_nbands();
32-
33-
for (int ib = 0; ib < nbands; ib++)
34-
{
35-
pelec->basis->recip2real(&psi(ib, 0), wfcr.data(), ik);
36-
37-
const double eigenval = (pelec->ekb(ik, ib) - efermi) * ModuleBase::Ry_to_eV;
38-
double weight = en > 0 ? pelec->klist->wk[ik] - pelec->wg(ik, ib) : pelec->wg(ik, ib);
39-
weight /= ucell.omega;
40-
41-
if (eigenval >= emin && eigenval <= emax)
42-
{
43-
for (int ir = 0; ir < pelec->basis->nrxx; ir++)
44-
{
45-
ldos[ir] += weight * norm(wfcr[ir]);
46-
}
47-
}
48-
}
49-
}
50-
51-
std::stringstream fn;
52-
fn << PARAM.globalv.global_out_dir << "LDOS_" << en << "eV"
53-
<< ".cube";
54-
55-
const int precision = PARAM.inp.out_ldos[1];
56-
ModuleIO::write_vdata_palgrid(pgrid, ldos.data(), 0, PARAM.inp.nspin, 0, fn.str(), 0, &ucell, precision, 0);
57-
}
58-
}
5912

6013
#ifdef __LCAO
6114
template <typename T>
@@ -147,4 +100,269 @@ void Cal_ldos<T>::cal_ldos_lcao(const elecstate::ElecStateLCAO<T>* pelec,
147100

148101
template class Cal_ldos<double>; // Gamma_only case
149102
template class Cal_ldos<std::complex<double>>; // multi-k case
150-
} // namespace ModuleIO
103+
104+
// pw case
105+
void cal_ldos_pw(const elecstate::ElecStatePW<std::complex<double>>* pelec,
106+
const psi::Psi<std::complex<double>>& psi,
107+
const Parallel_Grid& pgrid,
108+
const UnitCell& ucell)
109+
{
110+
if (PARAM.inp.out_ldos[0] == 1 || PARAM.inp.out_ldos[0] == 3)
111+
{
112+
ModuleIO::stm_mode_pw(pelec, psi, pgrid, ucell);
113+
}
114+
if (PARAM.inp.out_ldos[0] == 2 || PARAM.inp.out_ldos[0] == 3)
115+
{
116+
ModuleIO::ldos_mode_pw(pelec, psi, pgrid, ucell);
117+
}
118+
}
119+
120+
void stm_mode_pw(const elecstate::ElecStatePW<std::complex<double>>* pelec,
121+
const psi::Psi<std::complex<double>>& psi,
122+
const Parallel_Grid& pgrid,
123+
const UnitCell& ucell)
124+
{
125+
for (int ie = 0; ie < PARAM.inp.stm_bias[2]; ie++)
126+
{
127+
// energy range for ldos (efermi as reference)
128+
const double en = PARAM.inp.stm_bias[0] + ie * PARAM.inp.stm_bias[1];
129+
const double emin = en < 0 ? en : 0;
130+
const double emax = en > 0 ? en : 0;
131+
132+
std::vector<double> ldos(pelec->charge->nrxx);
133+
std::vector<std::complex<double>> wfcr(pelec->basis->nrxx);
134+
135+
for (int ik = 0; ik < pelec->klist->get_nks(); ++ik)
136+
{
137+
psi.fix_k(ik);
138+
const double efermi = pelec->eferm.get_efval(pelec->klist->isk[ik]);
139+
const int nbands = psi.get_nbands();
140+
141+
for (int ib = 0; ib < nbands; ib++)
142+
{
143+
pelec->basis->recip2real(&psi(ib, 0), wfcr.data(), ik);
144+
145+
const double eigenval = (pelec->ekb(ik, ib) - efermi) * ModuleBase::Ry_to_eV;
146+
double weight = en > 0 ? pelec->klist->wk[ik] - pelec->wg(ik, ib) : pelec->wg(ik, ib);
147+
weight /= ucell.omega;
148+
149+
if (eigenval >= emin && eigenval <= emax)
150+
{
151+
for (int ir = 0; ir < pelec->basis->nrxx; ir++)
152+
{
153+
ldos[ir] += weight * norm(wfcr[ir]);
154+
}
155+
}
156+
}
157+
}
158+
159+
std::stringstream fn;
160+
fn << PARAM.globalv.global_out_dir << "LDOS_" << en << "eV"
161+
<< ".cube";
162+
163+
const int precision = PARAM.inp.out_ldos[1];
164+
ModuleIO::write_vdata_palgrid(pgrid, ldos.data(), 0, PARAM.inp.nspin, 0, fn.str(), 0, &ucell, precision, 0);
165+
}
166+
}
167+
168+
void ldos_mode_pw(const elecstate::ElecStatePW<std::complex<double>>* pelec,
169+
const psi::Psi<std::complex<double>>& psi,
170+
const Parallel_Grid& pgrid,
171+
const UnitCell& ucell)
172+
{
173+
double emax = 0.0;
174+
double emin = 0.0;
175+
176+
prepare_dos(GlobalV::ofs_running,
177+
pelec->eferm,
178+
pelec->ekb,
179+
pelec->klist->get_nks(),
180+
PARAM.inp.nbands,
181+
PARAM.inp.dos_edelta_ev,
182+
PARAM.inp.dos_scale,
183+
emax,
184+
emin);
185+
186+
const int ndata = static_cast<int>((emax - emin) / PARAM.inp.dos_edelta_ev) + 1;
187+
const double sigma = sqrt(2.0) * PARAM.inp.dos_sigma;
188+
const double sigma2 = sigma * sigma;
189+
const double sigma_PI = sqrt(ModuleBase::PI) * sigma;
190+
191+
std::vector<double> start = {PARAM.inp.ldos_line[0], PARAM.inp.ldos_line[1], PARAM.inp.ldos_line[2]};
192+
std::vector<double> end = {PARAM.inp.ldos_line[3], PARAM.inp.ldos_line[4], PARAM.inp.ldos_line[5]};
193+
const int npoints = PARAM.inp.ldos_line[6];
194+
195+
// calculate grid points
196+
std::vector<std::vector<int>> points(npoints, std::vector<int>(3, 0));
197+
std::vector<std::vector<double>> shifts(npoints, std::vector<double>(3, 0));
198+
get_grid_points(start, end, npoints, pgrid.nx, pgrid.ny, pgrid.nz, points, shifts);
199+
200+
std::vector<std::vector<double>> ldos(npoints, std::vector<double>(ndata, 0));
201+
202+
// calculate ldos
203+
std::vector<double> tmp(pelec->charge->nrxx);
204+
std::vector<std::complex<double>> wfcr(pelec->basis->nrxx);
205+
for (int ik = 0; ik < pelec->klist->get_nks(); ++ik)
206+
{
207+
psi.fix_k(ik);
208+
const double efermi = pelec->eferm.get_efval(pelec->klist->isk[ik]);
209+
const int nbands = psi.get_nbands();
210+
211+
for (int ib = 0; ib < nbands; ib++)
212+
{
213+
pelec->basis->recip2real(&psi(ib, 0), wfcr.data(), ik);
214+
const double weight = pelec->klist->wk[ik] / ucell.omega;
215+
216+
for (int ir = 0; ir < pelec->basis->nrxx; ir++)
217+
{
218+
tmp[ir] += weight * norm(wfcr[ir]);
219+
}
220+
221+
std::vector<double> results(npoints, 0);
222+
trilinear_interpolate(points, shifts, pgrid, tmp, results);
223+
224+
const double eigenval = pelec->ekb(ik, ib) * ModuleBase::Ry_to_eV;
225+
226+
for (int ie = 0; ie < ndata; ++ie)
227+
{
228+
const double en = emin + ie * PARAM.inp.dos_edelta_ev;
229+
const double de = en - eigenval;
230+
const double de2 = de * de;
231+
const double gauss = exp(-de2 / sigma2) / sigma_PI;
232+
for (int ip = 0; ip < npoints; ++ip)
233+
{
234+
ldos[ip][ie] += results[ip] * gauss;
235+
}
236+
}
237+
}
238+
}
239+
240+
std::ofstream ofs_ldos;
241+
std::stringstream fn;
242+
fn << PARAM.globalv.global_out_dir << "LDOS.txt";
243+
if (GlobalV::MY_RANK == 0)
244+
{
245+
ofs_ldos.open(fn.str().c_str());
246+
247+
for (int ip = 0; ip < npoints; ++ip)
248+
{
249+
for (int ie = 0; ie < ndata; ++ie)
250+
{
251+
ofs_ldos << ldos[ip][ie] << " ";
252+
}
253+
ofs_ldos << std::endl;
254+
}
255+
ofs_ldos.close();
256+
}
257+
}
258+
259+
void get_grid_points(const std::vector<double>& start,
260+
const std::vector<double>& end,
261+
const int& npoints,
262+
const int& nx,
263+
const int& ny,
264+
const int& nz,
265+
std::vector<std::vector<int>>& points,
266+
std::vector<std::vector<double>>& shifts)
267+
{
268+
std::vector<int> ndim = {nx, ny, nz};
269+
auto grid_points = [](const std::vector<double>& coor,
270+
const std::vector<int>& ndim,
271+
std::vector<int>& points,
272+
std::vector<double>& shift) {
273+
for (int i = 0; i < 3; i++)
274+
{
275+
shift[i] = coor[i] * ndim[i];
276+
while (shift[i] >= ndim[i])
277+
{
278+
shift[i] -= ndim[i];
279+
}
280+
while (shift[i] < 0)
281+
{
282+
shift[i] += ndim[i];
283+
}
284+
points[i] = static_cast<int>(shift[i]);
285+
shift[i] -= points[i];
286+
}
287+
};
288+
289+
if (npoints == 1)
290+
{
291+
grid_points(start, ndim, points[0], shifts[0]);
292+
}
293+
else
294+
{
295+
std::vector<double> delta = {end[0] - start[0], end[1] - start[1], end[2] - start[2]};
296+
for (int i = 0; i < npoints; i++)
297+
{
298+
const double ratio = static_cast<double>(i) / (npoints - 1);
299+
std::vector<double> current = {0, 0, 0};
300+
for (int j = 0; j < 3; j++)
301+
{
302+
current[j] = start[j] + ratio * delta[j];
303+
}
304+
grid_points(current, ndim, points[i], shifts[i]);
305+
}
306+
}
307+
}
308+
309+
void trilinear_interpolate(const std::vector<std::vector<int>>& points,
310+
const std::vector<std::vector<double>>& shifts,
311+
const Parallel_Grid& pgrid,
312+
const std::vector<double>& data,
313+
std::vector<double>& results)
314+
{
315+
const int nx = pgrid.nx;
316+
const int ny = pgrid.ny;
317+
const int nz = pgrid.nz;
318+
const int nyz = ny * nz;
319+
const int nxyz = nx * ny * nz;
320+
321+
// reduce
322+
std::vector<double> data_full(nxyz);
323+
#ifdef __MPI
324+
if (GlobalV::MY_POOL == 0 && GlobalV::MY_BNDGROUP == 0)
325+
{
326+
pgrid.reduce(data_full.data(), data.data());
327+
}
328+
MPI_Barrier(MPI_COMM_WORLD);
329+
#else
330+
std::memcpy(data_full.data(), data.data(), nxyz * sizeof(double));
331+
#endif
332+
333+
auto grid_points = [&data_full, &nyz, &nz](const int& ix, const int& iy, const int& iz) {
334+
return data_full[ix * nyz + iy * nz + iz];
335+
};
336+
337+
// trilinear interpolation
338+
const int npoints = points.size();
339+
results.resize(npoints, 0.0);
340+
if (GlobalV::MY_RANK == 0)
341+
{
342+
for (int l = 0; l < npoints; ++l)
343+
{
344+
for (int i = 0; i < 2; ++i)
345+
{
346+
double weight = (i * shifts[l][0] + (1 - i) * (1 - shifts[l][0]));
347+
for (int j = 0; j < 2; ++j)
348+
{
349+
weight *= (j * shifts[l][1] + (1 - j) * (1 - shifts[l][1]));
350+
for (int k = 0; k < 2; ++k)
351+
{
352+
weight *= (k * shifts[l][2] + (1 - k) * (1 - shifts[l][2]));
353+
354+
const int ix = points[l][0] + i;
355+
const int iy = points[l][1] + j;
356+
const int iz = points[l][2] + k;
357+
results[l] += weight * grid_points(ix, iy, iz);
358+
}
359+
}
360+
}
361+
}
362+
}
363+
#ifdef __MPI
364+
MPI_Bcast(results.data(), npoints, MPI_DOUBLE, 0, MPI_COMM_WORLD);
365+
#endif
366+
}
367+
368+
} // namespace ModuleIO

0 commit comments

Comments
 (0)