Coverage for gpaw/lfc.py: 80%
727 statements
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-14 00:18 +0000
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-14 00:18 +0000
1from math import pi
3import numpy as np
4from ase.units import Bohr
6import gpaw.cgpaw as cgpaw
7from gpaw import debug
8from gpaw.grid_descriptor import GridDescriptor, GridBoundsError
9from gpaw.gpu import cupy_is_fake
10from gpaw.new import trace
11from gpaw.utilities import smallest_safe_grid_spacing
13"""
15=== =================================================
16 M Global localized function number.
17 W Global volume number.
18 G Global grid point number.
19 g Local (inside sphere) grid point number.
20 i Index into list of current spheres for current G.
21=== =================================================
23l
24m
25b
26w
28Global grid point number (*G*) for a 7*6 grid::
29 -------------
30 |5 . . . . . .|
31 |4 . . . . . .|
32 |3 9 . . . . .|
33 |2 8 . . . . .|
34 |1 7 . . . . .|
35 |0 6 . . . . .|
36 -------------
38For this example *G* runs from 0 to 41.
40Here is a sphere inside the box with grid points (*g*) numbered from 0
41to 7::
43 -------------
44 |. . . . . . .|
45 |. . . . 5 . .|
46 |. . . 1 4 7 .|
47 |. . . 0 3 6 .|
48 |. . . . 2 . .|
49 |. . . . . . .|
50 -------------
52~ _ ^ ~ ~
53p v g n F
54 i L c M
56i
57d d d d d
58s s
59 s s
60"""
63class Sphere:
64 def __init__(self, spline_j):
65 self.spline_j = spline_j
66 self.spos_c = None
67 self.rank = None # Rank corresponding to center
68 self.ranks = None # Ranks with at least some overlap
69 self.Mmax = None
70 self.A_wgm = None
71 self.G_wb = None
72 self.M_w = None
73 self.sdisp_wc = None
74 self.normalized = False
75 self.I_M = None
77 def set_position(self, spos_c, gd, cut):
78 if self.spos_c is not None and not (self.spos_c - spos_c).any():
79 return False
81 self.A_wgm = []
82 self.G_wb = []
83 self.M_w = []
84 self.sdisp_wc = []
86 ng = 0
87 M = 0
88 for spline in self.spline_j:
89 rcut = spline.get_cutoff()
90 l = spline.get_angular_momentum_number()
91 for beg_c, end_c, sdisp_c in gd.get_boxes(spos_c, rcut, cut):
92 pos_v = np.dot(spos_c - sdisp_c, gd.cell_cv)
93 A_gm, G_b = cgpaw.spline_to_grid(
94 spline.spline,
95 beg_c, end_c, pos_v,
96 np.ascontiguousarray(gd.h_cv),
97 gd.n_c, gd.beg_c)
98 if len(G_b) > 0:
99 self.A_wgm.append(A_gm)
100 self.G_wb.append(G_b)
101 self.M_w.append(M)
102 self.sdisp_wc.append(sdisp_c)
103 ng += A_gm.shape[0]
104 assert A_gm.shape[0] > 0
105 M += 2 * l + 1
107 self.Mmax = M
109 self.rank = gd.get_rank_from_position(spos_c)
110 if ng == 0:
111 self.ranks = None # What about making empty lists instead?
112 self.A_wgm = None
113 self.G_wb = None
114 self.M_w = None
115 self.sdisp_wc = None
117 self.spos_c = spos_c.copy()
118 self.normalized = False
119 return True
121 def get_function_count(self):
122 return sum([2 * spline.get_angular_momentum_number() + 1
123 for spline in self.spline_j])
125 def normalize(self, integral, a, dv, comm):
126 """Normalize localized functions."""
127 if self.normalized or integral < 1e-15:
128 self.normalized = True
129 yield None
130 yield None
131 yield None
132 return
134 I_M = np.zeros(self.Mmax)
136 nw = len(self.A_wgm) // len(self.spline_j)
137 assert nw * len(self.spline_j) == len(self.A_wgm)
139 for M, A_gm in zip(self.M_w, self.A_wgm):
140 I_m = A_gm.sum(axis=0)
141 I_M[M:M + len(I_m)] += I_m * dv
143 requests = []
144 if len(self.ranks) > 0:
145 I_rM = np.empty((len(self.ranks), self.Mmax))
146 for r, J_M in zip(self.ranks, I_rM):
147 requests.append(comm.receive(J_M, r, a, False))
148 if self.rank != comm.rank:
149 requests.append(comm.send(I_M, self.rank, a, False))
151 yield None
153 for request in requests:
154 comm.wait(request)
156 requests = []
157 if len(self.ranks) > 0:
158 I_M += I_rM.sum(axis=0)
159 for r in self.ranks:
160 requests.append(comm.send(I_M, r, a, False))
161 if self.rank != comm.rank:
162 requests.append(comm.receive(I_M, self.rank, a, False))
164 yield None
166 for request in requests:
167 comm.wait(request)
169 w = 0
170 for M, A_gm in zip(self.M_w, self.A_wgm):
171 if M == 0:
172 A_gm *= integral / I_M[0]
173 else:
174 A_gm -= (I_M[M:M + A_gm.shape[1]] / integral *
175 self.A_wgm[w % nw])
176 w += 1
177 self.normalized = True
178 self.I_M = I_M
179 yield None
181 def estimate_gridpointcount(self, gd):
182 points = 0.0
183 for spline in self.spline_j:
184 l = spline.get_angular_momentum_number()
185 rc = spline.get_cutoff()
186 points += 4.0 * np.pi / 3.0 * rc**3 / gd.dv * (2 * l + 1)
187 return points
190# Quick hack: base class to share basic functionality across LFC classes
191class BaseLFC:
192 def dict(self, shape=(), derivative=False, zero=False):
193 if isinstance(shape, int):
194 shape = (shape,)
195 if derivative:
196 assert not zero
197 c_axiv = {}
198 for a in self.my_atom_indices:
199 ni = self.get_function_count(a)
200 c_axiv[a] = np.empty(shape + (ni, 3), self.dtype)
201 return c_axiv
202 else:
203 c_axi = {}
204 for a in self.my_atom_indices:
205 ni = self.get_function_count(a)
206 c_axi[a] = np.empty(shape + (ni,), self.dtype)
207 if zero:
208 c_axi[a][:] = 0.0
209 return c_axi
211 def estimate_memory(self, mem):
212 points = 0
213 for sphere in self.sphere_a:
214 points += sphere.estimate_gridpointcount(self.gd)
215 nbytes = points * mem.floatsize
216 mem.setsize(nbytes / self.gd.comm.size) # Assume equal distribution
219class LocalizedFunctionsCollection(BaseLFC):
220 """LocalizedFunctionsCollection
222 Utilizes that localized functions can be stored on a spherical subset of
223 the uniform grid, as opposed to LocalizedFunctionsCollection which is just
224 a wrapper around the old localized_functions which use rectangular grids.
226 """
227 def __init__(self, gd, spline_aj, kd=None, cut=False, dtype=float,
228 integral=None, forces=None, xp=np):
229 self.gd = gd
230 self.kd = kd
231 self.sphere_a = [Sphere(spline_j) for spline_j in spline_aj]
232 self.cut = cut
233 self.dtype = dtype
234 self.Mmax = None
235 self.xp = xp
237 if kd is None:
238 self.ibzk_qc = np.zeros((1, 3))
239 self.gamma = True
240 else:
241 self.ibzk_qc = kd.ibzk_qc
242 self.gamma = kd.gamma
244 # Global or local M-indices?
245 self.use_global_indices = False
247 if integral is not None:
248 if isinstance(integral, (float, int)):
249 self.integral_a = np.empty(len(spline_aj))
250 self.integral_a[:] = integral
251 else:
252 self.integral_a = np.array(integral)
253 else:
254 self.integral_a = None
256 self.my_atom_indices = None
257 self.lfc = None
259 def set_positions(self, spos_ac, atom_partition=None) -> bool:
260 """Set positions and return True if any atoms have migrated to
261 another rank.
262 """
263 assert len(spos_ac) == len(self.sphere_a)
264 spos_ac = np.asarray(spos_ac)
265 movement = False
266 old_ranks = [sphere.rank for sphere in self.sphere_a]
267 for a, (spos_c, sphere) in enumerate(zip(spos_ac, self.sphere_a)):
268 try:
269 movement |= sphere.set_position(spos_c, self.gd, self.cut)
270 except GridBoundsError as e:
271 e.args = (f'Atom {a} too close to edge: {e}',)
272 raise
274 if self.my_atom_indices is None:
275 self._update(spos_ac)
276 return False
278 if movement:
279 self._update(spos_ac)
280 for rank, sphere in zip(old_ranks, self.sphere_a):
281 if rank != sphere.rank:
282 return True
283 return False
285 def _update(self, spos_ac):
286 nB = 0
287 nW = 0
288 self.my_atom_indices = []
289 self.atom_indices = []
290 M = 0
291 self.M_a = []
292 for a, sphere in enumerate(self.sphere_a):
293 self.M_a.append(M)
294 if sphere.rank == self.gd.comm.rank:
295 self.my_atom_indices.append(a)
296 G_wb = sphere.G_wb
297 if G_wb:
298 nB += sum([len(G_b) for G_b in G_wb])
299 nW += len(G_wb)
300 self.atom_indices.append(a)
302 if not self.use_global_indices:
303 M += sphere.Mmax
305 if self.use_global_indices:
306 M += sphere.Mmax
308 self.Mmax = M
310 natoms = len(spos_ac)
311 # Holm-Nielsen check:
312 if (self.gd.comm.sum_scalar(float(sum(self.my_atom_indices))) !=
313 natoms * (natoms - 1) // 2):
314 raise ValueError('Holm-Nielsen check failed. Grid might be '
315 'too coarse. Use h < %.3f'
316 % (smallest_safe_grid_spacing * Bohr))
318 self.M_W = np.empty(nW, np.intc)
319 self.G_B = np.empty(nB, np.intc)
320 self.W_B = np.empty(nB, np.intc)
321 self.A_Wgm = []
322 sdisp_Wc = np.empty((nW, 3), int)
323 self.pos_Wv = np.empty((nW, 3))
325 B1 = 0
326 W = 0
327 for a in self.atom_indices:
328 sphere = self.sphere_a[a]
329 self.A_Wgm.extend(sphere.A_wgm)
330 nw = len(sphere.M_w)
331 self.M_W[W:W + nw] = self.M_a[a] + np.array(sphere.M_w)
332 sdisp_Wc[W:W + nw] = sphere.sdisp_wc
333 self.pos_Wv[W:W + nw] = np.dot(spos_ac[a] -
334 np.array(sphere.sdisp_wc),
335 self.gd.cell_cv)
336 for G_b in sphere.G_wb:
337 B2 = B1 + len(G_b)
338 self.G_B[B1:B2] = G_b
339 self.W_B[B1:B2:2] = W
340 self.W_B[B1 + 1:B2 + 1:2] = -W - 1
341 B1 = B2
342 W += 1
343 assert B1 == nB
345 if self.gamma:
346 if self.dtype == float:
347 self.phase_qW = np.empty((0, nW), complex)
348 else:
349 # TDDFT calculation:
350 self.phase_qW = np.ones((1, nW), complex)
351 else:
352 self.phase_qW = np.exp(2j * pi * np.inner(self.ibzk_qc, sdisp_Wc))
354 indices = np.argsort(self.G_B, kind='mergesort')
355 self.G_B = self.G_B[indices]
356 self.W_B = self.W_B[indices]
358 # Find out which ranks have a piece of the
359 # localized functions:
360 x_a = np.zeros(natoms, bool)
361 x_a[self.atom_indices] = True
362 x_a[self.my_atom_indices] = False
363 x_ra = np.empty((self.gd.comm.size, natoms), bool)
364 self.gd.comm.all_gather(x_a, x_ra)
365 for a in self.atom_indices:
366 sphere = self.sphere_a[a]
367 if sphere.rank == self.gd.comm.rank:
368 sphere.ranks = x_ra[:, a].nonzero()[0]
369 else:
370 sphere.ranks = []
372 if self.integral_a is not None:
373 iterators = []
374 for a in self.atom_indices:
375 iterator = self.sphere_a[a].normalize(self.integral_a[a], a,
376 self.gd.dv,
377 self.gd.comm)
378 iterators.append(iterator)
379 for i in range(3):
380 for iterator in iterators:
381 next(iterator)
383 self.lfc = cgpaw.LFC(self.A_Wgm, self.M_W, self.G_B, self.W_B,
384 self.gd.dv, self.phase_qW, self.xp is not np)
386 return sdisp_Wc
388 def M_to_ai(self, src_xM, dst_axi):
389 xshape = src_xM.shape[:-1]
390 src_xM = src_xM.reshape(np.prod(xshape), self.Mmax)
391 for a in self.my_atom_indices:
392 M1 = self.M_a[a]
393 M2 = M1 + self.sphere_a[a].Mmax
394 dst_axi[a] = src_xM[:, M1:M2].copy()
396 def ai_to_M(self, src_axi, dst_xM):
397 xshape = dst_xM.shape[:-1]
398 dst_xM = dst_xM.reshape(np.prod(xshape), self.Mmax)
399 for a in self.my_atom_indices:
400 M1 = self.M_a[a]
401 M2 = M1 + self.sphere_a[a].Mmax
402 dst_xM[:, M1:M2] = src_axi[a]
404 def add(self, a_xG, c_axi=1.0, q=-1):
405 """Add localized functions to extended arrays.
407 ::
409 -- a a
410 a (G) += > c Phi (G)
411 x -- xi i
412 a,i
413 """
415 assert not self.use_global_indices
416 if q == -1:
417 assert self.dtype == float
419 if isinstance(c_axi, float):
420 assert q == -1 and a_xG.ndim == 3
421 c_xM = self.xp.empty(self.Mmax)
422 c_xM.fill(c_axi)
423 if self.xp is np:
424 self.lfc.add(c_xM, a_xG, q)
425 elif cupy_is_fake:
426 self.lfc.add(c_xM._data, a_xG._data, q)
427 else:
428 self.lfc.add_gpu(c_xM.data.ptr,
429 c_xM.shape,
430 a_xG.data.ptr,
431 a_xG.shape, q)
432 return
434 dtype = a_xG.dtype
436 if debug:
437 assert a_xG.ndim >= 3
438 assert sorted(c_axi.keys()) == self.my_atom_indices
439 for c_xi in c_axi.values():
440 assert c_xi.dtype == dtype
442 comm = self.gd.comm
443 xshape = a_xG.shape[:-3]
444 assert len(xshape) <= 1
445 requests = []
446 M1 = 0
447 b_axi = {}
448 for a in self.atom_indices:
449 c_xi = c_axi.get(a)
450 sphere = self.sphere_a[a]
451 M2 = M1 + sphere.Mmax
452 if c_xi is None:
453 c_xi = self.xp.empty(xshape + (sphere.Mmax,), dtype)
454 b_axi[a] = c_xi
455 requests.append(comm.receive(c_xi, sphere.rank, a, False))
456 else:
457 for r in sphere.ranks:
458 requests.append(comm.send(c_xi.copy(), r, a, False))
460 M1 = M2
462 for request in requests:
463 comm.wait(request)
465 c_xM = self.xp.empty(xshape + (self.Mmax,), dtype)
466 M1 = 0
467 for a in self.atom_indices:
468 c_xi = c_axi.get(a)
469 sphere = self.sphere_a[a]
470 M2 = M1 + sphere.Mmax
471 if c_xi is None:
472 c_xi = b_axi[a]
473 c_xM[..., M1:M2] = c_xi
474 M1 = M2
476 if self.xp is np:
477 self.lfc.add(c_xM, a_xG, q)
478 elif cupy_is_fake:
479 self.lfc.add(c_xM._data, a_xG._data, q)
480 else:
481 self.lfc.add_gpu(c_xM.data.ptr,
482 c_xM.shape,
483 a_xG.data.ptr,
484 a_xG.shape, q)
486 def add_derivative(self, a, v, a_xG, c_axi=1.0, q=-1):
487 """Add derivative of localized functions on atom to extended arrays.
489 Parameters:
491 a: int
492 Atomic index of the derivative
493 v: int
494 Cartesian coordinate of the derivative (0, 1 or 2)
496 This function adds the following sum to the extended arrays::
498 -- a a
499 a (G) += > c dPhi (G)
500 x -- xi iv
501 i
503 where::
505 a d a
506 dPhi (G) = -- Phi (g)
507 iv dv i
509 is the derivative of the Phi^a and v is either x, y, or z.
511 """
513 assert v in [0, 1, 2]
514 assert not self.use_global_indices
516 if q == -1:
517 assert self.dtype == float
519 if isinstance(c_axi, float):
520 assert q == -1
521 c_xM = np.empty(self.Mmax)
522 c_xM.fill(c_axi)
523 self.lfc.add(c_xM, a_xG, q)
524 return
526 dtype = a_xG.dtype
528 if debug:
529 assert a_xG.ndim >= 3
530 assert dtype == self.dtype
531 if isinstance(c_axi, dict):
532 assert sorted(c_axi.keys()) == self.my_atom_indices
533 for c_xi in c_axi.values():
534 assert c_xi.dtype == dtype
536 cspline_M = []
537 for a_ in self.atom_indices:
538 for spline in self.sphere_a[a_].spline_j:
539 nm = 2 * spline.get_angular_momentum_number() + 1
540 cspline_M.extend([spline.spline] * nm)
542 # Temp solution - set all coefficient to zero except for those at
543 # atom a
545 # Coefficient indices for atom a
546 M1 = self.M_a[a]
547 M2 = M1 + self.sphere_a[a].Mmax
549 if isinstance(c_axi, float):
550 assert q == -1
551 c_xM = np.zeros(self.Mmax)
552 c_xM[..., M1:M2] = c_axi
553 else:
554 xshape = a_xG.shape[:-3]
555 c_xM = np.zeros(xshape + (self.Mmax,), dtype)
556 c_xM[..., M1:M2] = c_axi[a]
558 gd = self.gd
560 self.lfc.add_derivative(c_xM, a_xG, np.ascontiguousarray(gd.h_cv),
561 gd.n_c, cspline_M,
562 gd.beg_c, self.pos_Wv, a, v, q)
564 def integrate(self, a_xG, c_axi, q=-1, add_to=False):
565 """Calculate integrals of arrays times localized functions.
567 ::
569 / a*
570 c_axi = | dG a (G) Phi (G)
571 / x i
573 """
574 assert not add_to
575 assert not self.use_global_indices
576 if q == -1:
577 assert self.dtype == float
579 xshape = a_xG.shape[:-3]
580 dtype = a_xG.dtype
582 if debug:
583 assert self.dtype == dtype
584 assert a_xG.ndim >= 3
585 assert sorted(c_axi.keys()) == self.my_atom_indices
586 for c_xi in c_axi.values():
587 assert c_xi.shape[:-1] == xshape
589 comm = self.gd.comm
591 c_xM = self.xp.zeros(xshape + (self.Mmax,), dtype)
592 if self.xp is np:
593 self.lfc.integrate(a_xG, c_xM, q)
594 elif cupy_is_fake:
595 self.lfc.integrate(a_xG._data, c_xM._data, q)
596 else:
597 self.lfc.integrate_gpu(a_xG.data.ptr,
598 a_xG.shape,
599 c_xM.data.ptr,
600 c_xM.shape, q)
601 c_xM *= self.gd.dv
603 rank = comm.rank
604 srequests = []
605 rrequests = []
606 c_arxi = {}
607 b_axi = {}
608 M1 = 0
609 for a in self.atom_indices:
610 sphere = self.sphere_a[a]
611 M2 = M1 + sphere.Mmax
612 if sphere.rank != rank:
613 c_xi = c_xM[..., M1:M2].copy()
614 b_axi[a] = c_xi
615 srequests.append(comm.send(c_xi,
616 sphere.rank, a, False))
617 else:
618 if len(sphere.ranks) > 0:
619 c_rxi = self.xp.empty(
620 sphere.ranks.shape + xshape + (M2 - M1,),
621 dtype)
622 c_arxi[a] = c_rxi
623 for r, b_xi in zip(sphere.ranks, c_rxi):
624 rrequests.append(comm.receive(b_xi, r, a, False))
625 M1 = M2
627 for request in rrequests:
628 comm.wait(request)
630 M1 = 0
631 for a in self.atom_indices:
632 c_xi = c_axi.get(a)
633 sphere = self.sphere_a[a]
634 M2 = M1 + sphere.Mmax
635 if c_xi is not None:
636 if len(sphere.ranks) > 0:
637 if c_xM.shape[-1] > M1:
638 c_xi[:] = c_xM[..., M1:M2] + c_arxi[a].sum(axis=0)
639 elif c_xM.shape[-1] > M1:
640 c_xi[:] = c_xM[..., M1:M2]
641 M1 = M2
643 for request in srequests:
644 comm.wait(request)
646 def derivative(self, a_xG, c_axiv, q=-1):
647 """Calculate x-, y-, and z-derivatives of localized function integrals.
649 ::
651 / a*
652 c_axiv = | dG a (G) dPhi (G)
653 / x iv
655 where::
658 a d a
659 dPhi (G) = -- Phi (g)
660 iv dv i
663 and v is either x, y, or z, and R^a_v is the center of Phi^a.
665 Notice that d Phi^a_i / dR^a_v == - d Phi^a_i / d v.
667 """
669 assert not self.use_global_indices
671 if debug:
672 assert a_xG.ndim >= 3
673 assert sorted(c_axiv.keys()) == self.my_atom_indices
675 if self.integral_a is not None:
676 # assert q == -1
677 assert a_xG.ndim == 3
678 assert a_xG.dtype == float
679 self._normalized_derivative(a_xG, c_axiv)
680 return
682 dtype = a_xG.dtype
684 xshape = a_xG.shape[:-3]
685 c_xMv = np.zeros(xshape + (self.Mmax, 3), dtype)
686 cspline_M = []
687 for a in self.atom_indices:
688 for spline in self.sphere_a[a].spline_j:
689 nm = 2 * spline.get_angular_momentum_number() + 1
690 cspline_M.extend([spline.spline] * nm)
692 gd = self.gd
693 self.lfc.derivative(a_xG, c_xMv, np.ascontiguousarray(gd.h_cv),
694 gd.n_c, cspline_M,
695 gd.beg_c, self.pos_Wv, q)
697 comm = self.gd.comm
698 rank = comm.rank
699 srequests = []
700 rrequests = []
701 c_arxiv = {} # see also https://arXiv.org
702 b_axiv = {}
703 M1 = 0
704 for a in self.atom_indices:
705 sphere = self.sphere_a[a]
706 M2 = M1 + sphere.Mmax
707 if sphere.rank != rank:
708 c_xiv = c_xMv[..., M1:M2, :].copy()
709 b_axiv[a] = c_xiv
710 srequests.append(comm.send(c_xiv,
711 sphere.rank, a, False))
712 else:
713 if len(sphere.ranks) > 0:
714 c_rxiv = np.empty(sphere.ranks.shape + xshape +
715 (M2 - M1, 3), dtype)
716 c_arxiv[a] = c_rxiv
717 for r, b_xiv in zip(sphere.ranks, c_rxiv):
718 rrequests.append(comm.receive(b_xiv, r, a, False))
719 M1 = M2
721 for request in rrequests:
722 comm.wait(request)
724 M1 = 0
725 for a in self.atom_indices:
726 c_xiv = c_axiv.get(a)
727 sphere = self.sphere_a[a]
728 M2 = M1 + sphere.Mmax
729 if c_xiv is not None:
730 if len(sphere.ranks) > 0:
731 c_xiv[:] = c_xMv[..., M1:M2, :] + c_arxiv[a].sum(axis=0)
732 else:
733 c_xiv[:] = c_xMv[..., M1:M2, :]
734 M1 = M2
736 for request in srequests:
737 comm.wait(request)
739 def _normalized_derivative(self, a_G, c_aiv):
740 """Calculate x-, y-, and z-derivatives of localized function integrals.
742 Calculates the derivatives of this integral::
744 a / _ _ a - _a
745 A = | dr a(r) f (r - R ),
746 lm / lm
748 a
749 dA
750 lm
751 c_aiv = ----,
752 a
753 R
754 v
756 where v is either x, y, or z and i=l**2+m. Note that the
757 actual integrals used are normalized::
759 a
760 ~a a I
761 f = f ---,
762 00 00 a
763 I
764 00
766 and for l > 0::
768 a
769 I
770 ~a a a lm
771 f = f - f ---,
772 lm lm 00 a
773 I
774 00
776 where
778 ::
780 a / _ -a _ _a
781 I = | dr f (r - R ),
782 lm / lm
785 so the derivative look pretty ugly!
786 """
788 c_Mv = np.zeros((self.Mmax, 7))
790 cspline_M = []
791 for a in self.atom_indices:
792 for spline in self.sphere_a[a].spline_j:
793 nm = 2 * spline.get_angular_momentum_number() + 1
794 cspline_M.extend([spline.spline] * nm)
795 gd = self.gd
796 self.lfc.normalized_derivative(a_G, c_Mv,
797 np.ascontiguousarray(gd.h_cv), gd.n_c,
798 cspline_M,
799 gd.beg_c, self.pos_Wv)
801 comm = self.gd.comm
802 rank = comm.rank
803 srequests = []
804 rrequests = []
805 c_ariv = {}
806 b_aiv = {}
807 M1 = 0
808 for a in self.atom_indices:
809 sphere = self.sphere_a[a]
810 M2 = M1 + sphere.Mmax
811 if sphere.rank != rank:
812 c_iv = c_Mv[M1:M2].copy()
813 b_aiv[a] = c_iv
814 srequests.append(comm.send(c_iv, sphere.rank, a, False))
815 else:
816 if len(sphere.ranks) > 0:
817 c_riv = np.empty((len(sphere.ranks), M2 - M1, 7))
818 c_ariv[a] = c_riv
819 for r, b_iv in zip(sphere.ranks, c_riv):
820 rrequests.append(comm.receive(b_iv, r, a, False))
821 M1 = M2
823 for request in rrequests:
824 comm.wait(request)
826 M1 = 0
827 for a in self.atom_indices:
828 c_iv = c_aiv.get(a)
829 sphere = self.sphere_a[a]
830 M2 = M1 + sphere.Mmax
831 if c_iv is not None:
832 I = self.integral_a[a]
833 if I > 1e-15:
834 if len(sphere.ranks) > 0:
835 c_Mv[M1:M2] += c_ariv[a].sum(axis=0)
836 I_L = sphere.I_M
837 I0 = I_L[0]
838 c_Lv = c_Mv[M1:M2, :3]
839 b_Lv = c_Mv[M1:M2, 3:6]
840 A0 = c_Mv[M1, 6]
841 c_iv[0, :] = (I / I0 * c_Lv[0] -
842 I / I0**2 * b_Lv[0] * A0)
843 c_iv[1:, :] = (c_Lv[1:] -
844 np.outer(I_L[1:] / I0, c_Lv[0]) -
845 A0 / I0 * b_Lv[1:] +
846 A0 / I0**2 * np.outer(I_L[1:], b_Lv[0]))
847 else:
848 c_iv[:] = 0.0
850 M1 = M2
852 for request in srequests:
853 comm.wait(request)
855 def second_derivative(self, a_xG, c_axivv, q=-1):
856 """Calculate second derivatives.
858 Works only for this type of input for now::
860 second_derivative(self, a_G, c_avv, q=-1)
862 ::
864 2 a _ _a
865 / _ _ d f (r-R )
866 c_avv = | dr a(r) ----------
867 / a a
868 dR dR
869 i j
870 """
872 assert not self.use_global_indices
874 if debug:
875 assert a_xG.ndim == 3
876 assert a_xG.dtype == self.dtype
877 assert sorted(c_axivv.keys()) == self.my_atom_indices
879 dtype = a_xG.dtype
881 c_Mvv = np.zeros((self.Mmax, 3, 3), dtype)
883 cspline_M = []
884 for a in self.atom_indices:
885 # Works only for atoms with a single function
886 assert len(self.sphere_a[a].spline_j) == 1
887 spline = self.sphere_a[a].spline_j[0]
888 # that is spherical symmetric
889 assert spline.get_angular_momentum_number() == 0
890 cspline_M.append(spline.spline)
892 gd = self.gd
894 self.lfc.second_derivative(a_xG, c_Mvv, np.ascontiguousarray(gd.h_cv),
895 gd.n_c, cspline_M,
896 gd.beg_c, self.pos_Wv, q)
898 comm = self.gd.comm
899 rank = comm.rank
900 srequests = []
901 rrequests = []
902 c_arvv = {}
903 b_avv = {}
904 M1 = 0
906 for a in self.atom_indices:
907 sphere = self.sphere_a[a]
908 M2 = M1 + sphere.Mmax
909 if sphere.rank != rank:
910 c_vv = c_Mvv[M1:M2].copy()
911 b_avv[a] = c_vv
912 srequests.append(comm.send(c_vv, sphere.rank, a, False))
913 else:
914 if len(sphere.ranks) > 0:
915 c_rvv = np.empty(sphere.ranks.shape + (3, 3), dtype)
916 c_arvv[a] = c_rvv
917 for r, b_vv in zip(sphere.ranks, c_rvv):
918 rrequests.append(comm.receive(b_vv, r, a, False))
919 M1 = M2
921 for request in rrequests:
922 comm.wait(request)
924 M1 = 0
925 for a in self.atom_indices:
926 c_vv = c_axivv.get(a)
927 sphere = self.sphere_a[a]
928 M2 = M1 + sphere.Mmax
929 if c_vv is not None:
930 if len(sphere.ranks) > 0:
931 c_vv[:] = c_Mvv[M1] + c_arvv[a].sum(axis=0)
932 else:
933 c_vv[:] = c_Mvv[M1]
934 M1 = M2
936 for request in srequests:
937 comm.wait(request)
939 def griditer(self):
940 """Iterate over grid points."""
941 self.g_W = np.zeros(len(self.M_W), np.intc)
942 self.current_lfindices = []
943 G1 = 0
944 for W, G in zip(self.W_B, self.G_B):
945 G2 = G
947 yield G1, G2
949 self.g_W[self.current_lfindices] += G2 - G1
951 if W >= 0:
952 self.current_lfindices.append(W)
953 else:
954 self.current_lfindices.remove(-1 - W)
956 G1 = G2
958 def get_function_count(self, a):
959 return self.sphere_a[a].get_function_count()
962class BasisFunctions(LocalizedFunctionsCollection):
963 def __init__(self, gd, spline_aj, kd=None, cut=False, dtype=float,
964 integral=None, forces=None, xp=np):
965 LocalizedFunctionsCollection.__init__(self, gd, spline_aj,
966 kd, cut,
967 dtype, integral,
968 forces, xp=xp)
969 self.use_global_indices = True
971 self.Mstart = None
972 self.Mstop = None
974 @trace
975 def set_positions(self, spos_ac):
976 LocalizedFunctionsCollection.set_positions(self, spos_ac)
977 self.Mstart = 0
978 self.Mstop = self.Mmax
980 def _update(self, spos_ac):
981 sdisp_Wc = LocalizedFunctionsCollection._update(self, spos_ac)
983 if not self.gamma or self.dtype == complex:
984 self.x_W, self.sdisp_xc = self.create_displacement_arrays(sdisp_Wc)
985 return sdisp_Wc
987 def create_displacement_arrays(self, sdisp_Wc=None):
988 if sdisp_Wc is None:
989 sdisp_Wc = np.empty((len(self.M_W), 3), int)
991 W = 0
992 for a in self.atom_indices:
993 sphere = self.sphere_a[a]
994 nw = len(sphere.M_w)
995 sdisp_Wc[W:W + nw] = sphere.sdisp_wc
996 W += nw
998 if len(sdisp_Wc) > 0:
999 n_c = sdisp_Wc.max(0) - sdisp_Wc.min(0)
1000 else:
1001 n_c = np.zeros(3, int)
1002 N_c = 2 * n_c + 1
1003 stride_c = np.array([N_c[1] * N_c[2], N_c[2], 1])
1004 x_W = np.dot(sdisp_Wc, stride_c).astype(np.intc)
1005 # use a neighbor list instead?
1006 x1 = np.dot(n_c, stride_c)
1007 sdisp_xc = np.zeros((x1 + 1, 3), int)
1008 r_x, sdisp_xc[:, 2] = divmod(np.arange(x1, 2 * x1 + 1), N_c[2])
1009 sdisp_xc.T[:2] = divmod(r_x, N_c[1])
1010 sdisp_xc -= n_c
1012 return x_W, sdisp_xc
1014 def set_matrix_distribution(self, Mstart, Mstop):
1015 assert self.Mmax is not None
1016 self.Mstart = Mstart
1017 self.Mstop = Mstop
1019 def add_to_density(self, nt_sG, f_asi):
1020 r"""Add linear combination of squared localized functions to density.
1022 :::
1024 ~ --- a a 2
1025 n (r) += > f [Φ(r)]
1026 s --- si i
1027 a,i
1029 """
1030 assert np.all(self.gd.n_c == nt_sG.shape[1:])
1031 nspins = len(nt_sG)
1032 f_sM = np.empty((nspins, self.Mmax))
1033 for a in self.atom_indices:
1034 sphere = self.sphere_a[a]
1035 M1 = self.M_a[a]
1036 M2 = M1 + sphere.Mmax
1037 f_sM[:, M1:M2] = f_asi[a]
1039 for nt_G, f_M in zip(nt_sG, f_sM):
1040 self.lfc.construct_density1(f_M, nt_G)
1042 def construct_density(self, rho_MM, nt_G, q):
1043 """Calculate electron density from density matrix.
1045 rho_MM: ndarray
1046 Density matrix.
1047 nt_G: ndarray
1048 Pseudo electron density.
1050 ::
1052 ~ -- *
1053 n(r) += > Phi (r) rho Phi (r)
1054 -- M1 M1M2 M2
1055 M1,M2
1056 """
1057 self.lfc.construct_density(rho_MM, nt_G, q, self.Mstart, self.Mstop)
1059 def integrate2(self, a_xG, c_xM, q=-1):
1060 """Calculate integrals of arrays times localized functions.
1062 ::
1064 / *
1065 c_xM += <Phi | a > = | dG Phi (G) a (G)
1066 M x / M x
1067 """
1068 xshape, Gshape = a_xG.shape[:-3], a_xG.shape[-3:]
1069 Nx = int(np.prod(xshape))
1070 a_xG = a_xG.reshape((Nx,) + Gshape)
1071 c_xM = c_xM.reshape(Nx, -1)
1072 for a_G, c_M in zip(a_xG, c_xM):
1073 self.lfc.integrate(a_G, c_M, q)
1075 def calculate_potential_matrices(self, vt_G):
1076 """Calculate lower part of potential matrix.
1078 ::
1080 /
1081 ~ | * _ ~ _ _ _
1082 V = | Phi (r) v(r) Phi (r) dr for mu >= nu
1083 mu nu | mu nu
1084 /
1086 Overwrites the elements of the target matrix Vt_MM. """
1087 assert np.all(vt_G.shape == self.gd.n_c), (vt_G.shape, self.gd.n_c)
1088 if self.gamma and self.dtype == float:
1089 Vt_xMM = np.zeros((1, self.Mstop - self.Mstart, self.Mmax))
1090 self.lfc.calculate_potential_matrix(vt_G, Vt_xMM[0], -1,
1091 self.Mstart, self.Mstop)
1092 else:
1093 Vt_xMM = np.zeros((len(self.sdisp_xc),
1094 self.Mstop - self.Mstart,
1095 self.Mmax))
1096 self.lfc.calculate_potential_matrices(vt_G, Vt_xMM, self.x_W,
1097 self.Mstart, self.Mstop)
1098 return Vt_xMM
1100 def calculate_potential_matrix(self, vt_G, Vt_MM, q):
1101 """Calculate lower part of potential matrix.
1103 ::
1105 /
1106 ~ | * _ ~ _ _ _
1107 V = | Phi (r) v(r) Phi (r) dr for mu >= nu
1108 mu nu | mu nu
1109 /
1111 Overwrites the elements of the target matrix Vt_MM. """
1112 Vt_MM[:] = 0.0
1113 self.lfc.calculate_potential_matrix(vt_G, Vt_MM, q,
1114 self.Mstart, self.Mstop)
1116 def lcao_to_grid(self,
1117 C_xM: np.ndarray,
1118 psit_xG: np.ndarray,
1119 q: int,
1120 block_size: int = 10) -> None:
1121 r"""Deploy basis functions onto grids according to coefficients.
1123 ::
1125 ----
1126 ~ _ \ _
1127 psi (r) += ) C Phi (r)
1128 n / n mu mu
1129 ----
1130 mu
1131 """
1133 if C_xM.size == 0:
1134 return
1136 if psit_xG.dtype != self.dtype:
1137 raise TypeError(
1138 f'psit_xG has type {psit_xG.dtype}, '
1139 f'but expected one of {self.dtype}')
1141 if C_xM.dtype != self.dtype:
1142 raise TypeError(
1143 f'C_xM has type {C_xM.dtype}, '
1144 f'but expected one of {self.dtype}')
1146 xshape = C_xM.shape[:-1]
1147 assert psit_xG.shape[:-3] == xshape, (psit_xG.shape, xshape)
1149 C_xM = C_xM.reshape((-1,) + C_xM.shape[-1:])
1150 psit_xG = psit_xG.reshape((-1,) + psit_xG.shape[-3:])
1152 if self.gamma or len(C_xM) == 1:
1153 for C_M, psit_G in zip(C_xM, psit_xG):
1154 self.lfc.lcao_to_grid(C_M, psit_G, q)
1155 else:
1156 # Do sum over unit cells first followed by sum over bands
1157 # in blocks of block_size orbitals at the time:
1158 assert C_xM.flags.contiguous
1159 assert psit_xG.flags.contiguous
1160 self.lfc.lcao_to_grid_k(C_xM, psit_xG, q, block_size)
1162 def calculate_potential_matrix_derivative(self, vt_G, DVt_vMM, q):
1163 """Calculate derivatives of potential matrix elements.
1165 ::
1167 / * _
1168 | Phi (r)
1169 ~c | mu ~ _ _ _
1170 DV += | -------- v(r) Phi (r) dr
1171 mu nu | dr nu
1172 / c
1174 Results are added to DVt_vMM.
1175 """
1176 cspline_M = []
1177 for a, sphere in enumerate(self.sphere_a):
1178 for j, spline in enumerate(sphere.spline_j):
1179 nm = 2 * spline.get_angular_momentum_number() + 1
1180 cspline_M.extend([spline.spline] * nm)
1181 gd = self.gd
1182 for v in range(3):
1183 self.lfc.calculate_potential_matrix_derivative(
1184 vt_G, DVt_vMM[v],
1185 np.ascontiguousarray(gd.h_cv),
1186 gd.n_c, q, v,
1187 np.array(cspline_M),
1188 gd.beg_c,
1189 self.pos_Wv,
1190 self.Mstart,
1191 self.Mstop)
1193 def calculate_force_contribution(self, vt_G, rhoT_MM, q):
1194 """Calculate derivatives of potential matrix elements.
1196 ::
1198 / * _
1199 | Phi (r)
1200 ~c | mu ~ _ _ _
1201 DV += | -------- v(r) Phi (r) dr
1202 mu nu | dr nu
1203 / c
1205 Results are added to DVt_vMM.
1206 """
1207 assert np.all(vt_G.shape == self.gd.n_c)
1208 cspline_M = []
1209 for a, sphere in enumerate(self.sphere_a):
1210 for j, spline in enumerate(sphere.spline_j):
1211 nm = 2 * spline.get_angular_momentum_number() + 1
1212 cspline_M.extend([spline.spline] * nm)
1213 gd = self.gd
1214 Mstart = self.Mstart
1215 Mstop = self.Mstop
1216 F_vM = np.zeros((3, Mstop - Mstart))
1217 assert self.Mmax == rhoT_MM.shape[1]
1218 assert Mstop - Mstart == rhoT_MM.shape[0]
1219 assert rhoT_MM.flags.c_contiguous
1220 for v in range(3):
1221 self.lfc.calculate_potential_matrix_force_contribution(
1222 vt_G, rhoT_MM, F_vM[v],
1223 np.ascontiguousarray(gd.h_cv),
1224 gd.n_c, q, v,
1225 np.array(cspline_M),
1226 gd.beg_c,
1227 self.pos_Wv,
1228 Mstart,
1229 Mstop)
1231 F_av = np.zeros((len(self.M_a), 3))
1232 a = 0
1233 for a, M1 in enumerate(self.M_a):
1234 M1 -= Mstart
1235 M2 = M1 + self.sphere_a[a].Mmax
1236 if M2 < 0:
1237 continue
1238 M1 = max(0, M1)
1239 F_av[a, :] = 2.0 * F_vM[:, M1:M2].sum(axis=1)
1240 return F_av
1243def LFC(gd, spline_aj, kd=None,
1244 cut=False, dtype=float,
1245 integral=None, forces=False):
1246 if isinstance(gd, GridDescriptor):
1247 return LocalizedFunctionsCollection(gd, spline_aj, kd,
1248 cut, dtype, integral, forces)
1249 else:
1250 return gd.get_lfc(gd, spline_aj)
1253def test():
1254 from gpaw.grid_descriptor import GridDescriptor
1256 ngpts = 40
1257 h = 1 / ngpts
1258 N_c = (ngpts, ngpts, ngpts)
1259 a = h * ngpts
1260 gd = GridDescriptor(N_c, (a, a, a))
1262 from gpaw.spline import Spline
1263 a = np.array([1, 0.9, 0.8, 0.0])
1264 s = Spline.from_data(0, 0.2, a)
1265 x = LocalizedFunctionsCollection(gd, [[s], [s]])
1266 x.set_positions([(0.5, 0.45, 0.5), (0.5, 0.55, 0.5)])
1267 n_G = gd.zeros()
1268 x.add(n_G)
1269 import matplotlib.pyplot as plt
1270 plt.contourf(n_G[20, :, :])
1271 plt.axis('equal')
1272 plt.show()
1275if __name__ == '__main__':
1276 test()