SIRIUS 7.5.0
Electronic structure library and applications
specfunc.hpp
Go to the documentation of this file.
1// Copyright (c) 2013-2017 Anton Kozhevnikov, Thomas Schulthess
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without modification, are permitted provided that
5// the following conditions are met:
6//
7// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
8// following disclaimer.
9// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
10// and the following disclaimer in the documentation and/or other materials provided with the distribution.
11//
12// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
13// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
14// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
15// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
16// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
17// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
18// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19
20#ifndef __SPECFUNC_HPP__
21#define __SPECFUNC_HPP__
22
23/** \file specfunc.hpp
24 *
25 * \brief Special functions.
26 */
27#include <vector>
28#include <cmath>
29#include <gsl/gsl_sf_coupling.h>
30#include <gsl/gsl_sf_legendre.h>
31#include <gsl/gsl_sf_hermite.h>
32#include "core/typedefs.hpp"
33#include "core/constants.hpp"
34#include "core/rte/rte.hpp"
35#include "core/r3/r3.hpp"
36#include "SDDK/memory.hpp"
37
38namespace sirius {
39
40/// Special functions.
41namespace sf {
42
43/// Maximum number of \f$ \ell, m \f$ combinations for a given \f$ \ell_{max} \f$
44inline int lmmax(int lmax)
45{
46 return (lmax + 1) * (lmax + 1);
47}
48
49/// Get composite lm index by angular index l and azimuthal index m.
50inline int lm(int l, int m)
51{
52 return (l * l + l + m);
53}
54
55/// Get maximum orbital quantum number by the maximum lm index.
56inline int lmax(int lmmax__)
57{
58 RTE_ASSERT(lmmax__ >= 0);
59 int lmax = static_cast<int>(std::sqrt(static_cast<double>(lmmax__)) + 1e-8) - 1;
60 if (lmmax(lmax) != lmmax__) {
61 std::stringstream s;
62 s << "wrong lmmax: " << lmmax__;
63 RTE_THROW(s);
64 }
65 return lmax;
66}
67
68/// Get array of orbital quantum numbers for each lm component.
69inline std::vector<int> l_by_lm(int lmax__)
70{
71 std::vector<int> v(lmmax(lmax__));
72 for (int l = 0; l <= lmax__; l++) {
73 for (int m = -l; m <= l; m++) {
74 v[lm(l, m)] = l;
75 }
76 }
77 return v;
78}
79
80inline double hermiteh(int n, double x)
81{
82 // phycisists Hermite polynomials,
83 // https://www.gnu.org/software/gsl/doc/html/specfunc.html#c.gsl_sf_hermite
84 return gsl_sf_hermite(n, x);
85}
86
87inline std::vector<double> hermiteh_array(int n, double x)
88{
89 std::vector<double> result(n);
90 gsl_sf_hermite_array(n, x, result.data());
91 return result;
92}
93
94inline double hermiteh_series(int n, double x, const double* a)
95{
96 return gsl_sf_hermite_series(n, x, a);
97}
98
99/// Generate associated Legendre polynomials.
100/** Normalised associated Legendre polynomials obey the following recursive relations:
101 \f[
102 P_{m}^{m}(x) = -\sqrt{1 + \frac{1}{2m}} y P_{m-1}^{m-1}(x)
103 \f]
104 \f[
105 P_{m+1}^{m}(x) = \sqrt{2 m + 3} x P_{m}^{m}(x)
106 \f]
107 \f[
108 P_{\ell}^{m}(x) = a_{\ell}^{m}\big(xP_{\ell-1}^{m}(x) - b_{\ell}^{m}P_{\ell - 2}^{m}(x)\big)
109 \f]
110 where
111 \f{eqnarray*}{
112 a_{\ell}^{m} &=& \sqrt{\frac{4 \ell^2 - 1}{\ell^2 - m^2}} \\
113 b_{\ell}^{m} &=& \sqrt{\frac{(\ell-1)^2-m^2}{4(\ell-1)^2-1}} \\
114 x &=& \cos \theta \\
115 y &=& \sin \theta
116 \f}
117 and
118 \f[
119 P_{0}^{0} = \sqrt{\frac{1}{4\pi}}
120 \f]
121 */
122template <typename T, typename F>
123inline void legendre_plm(int lmax__, double x__, F&& ilm__, T* plm__)
124{
125 /* reference paper:
126 Associated Legendre Polynomials and Spherical Harmonics Computation for Chemistry Applications
127 Taweetham Limpanuparb, Josh Milthorpe
128 https://arxiv.org/abs/1410.1748
129 */
130
131 /*
132 when computing recurrent relations, keep this picture in mind:
133 ...
134 l=5 m=0,1,2,3,4,5
135 l=4 m=0,1,2,3,4
136 l=3 m=0,1,2,3
137 l=2 m=0,1,2
138 l=1 m=0,1
139 l=0 m=0
140 */
141
142 double y = std::sqrt(1 - x__ * x__);
143
144 plm__[ilm__(0, 0)] = 0.28209479177387814347; // 1.0 / std::sqrt(fourpi)
145
146 /* compute P_{l,l} (diagonal) */
147 for (int l = 1; l <= lmax__; l++) {
148 plm__[ilm__(l, l)] = -std::sqrt(1 + 0.5 / l) * y * plm__[ilm__(l - 1, l - 1)];
149 }
150 /* compute P_{l+1,l} (upper diagonal) */
151 for (int l = 0; l < lmax__; l++) {
152 plm__[ilm__(l + 1, l)] = std::sqrt(2.0 * l + 3) * x__ * plm__[ilm__(l, l)];
153 }
154 for (int m = 0; m <= lmax__ - 2; m++) {
155 for (int l = m + 2; l <= lmax__; l++) {
156 double alm = std::sqrt(static_cast<double>((2 * l - 1) * (2 * l + 1)) / (l * l - m * m));
157 double blm = std::sqrt(static_cast<double>((l - 1 - m) * (l - 1 + m)) / ((2 * l - 3) * (2 * l - 1)));
158 plm__[ilm__(l, m)] = alm * (x__ * plm__[ilm__(l - 1, m)] - blm * plm__[ilm__(l - 2, m)]);
159 }
160 }
161}
162
163/// Generate auxiliary Legendre polynomials which are necessary for spherical harmomic derivatives.
164/** Generate the following functions:
165 \f{eqnarray*}{
166 P_{\ell m}^1(x) &=& y P_{\ell}^{m}(x)' \\
167 P_{\ell m}^2(x) &=& \frac{P_{\ell}^{m}(x)}{y}
168 \f}
169 where \f$ x = \cos \theta \f$, \f$ y = \sin \theta \f$ and \f$ P_{\ell}^{m}(x) \f$ are normalized Legendre
170 polynomials.
171
172 Both functions obey the recursive relations similar to \f$ P_{\ell}^{m}(x) \f$ (see sirius::legendre_plm() for
173 details). For \f$ P_{\ell m}^1(x) \f$ this is:
174 \f[
175 P_{m m}^1(x) = \sqrt{1 + \frac{1}{2m}} \Big( -y P_{m-1,m-1}^{1}(x)' + x P_{m-1}^{m-1}(x) \Big)
176 \f]
177 \f[
178 P_{m+1, m}^1(x) = \sqrt{2 m + 3} \Big( x P_{m,m}^{1}(x) + y P_{m}^{m}(x) \Big)
179 \f]
180 \f[
181 P_{\ell, m}^{1}(x) = a_{\ell}^{m}\Big(x P_{\ell-1,m}^{1}(x) + yP_{\ell-1}^{m}(x) -
182 b_{\ell}^{m} P_{\ell - 2,m}^{1}(x) \Big)
183 \f]
184 where
185 \f[
186 y = \sqrt{1 - x^2}
187 \f]
188 and
189 \f[
190 P_{0,0}^1 = 0
191 \f]
192
193 And for \f$ P_{\ell m}^2(x) \f$ the recursion is:
194 \f[
195 P_{m,m}^2(x) = -\sqrt{1 + \frac{1}{2m}} P_{m-1}^{m-1}(x)
196 \f]
197 \f[
198 P_{m+1, m}^2(x) = \sqrt{2 m + 3} x P_{m,m}^{2}(x)
199 \f]
200 \f[
201 P_{\ell, m}^2(x) = a_{\ell}^{m}\Big(x P_{\ell-1,m}^{2}(x) - b_{\ell}^{m}P_{\ell - 2,m}^{2}(x) \Big)
202 \f]
203 where
204 \f[
205 P_{0,0}^2 = 0
206 \f]
207
208 See sirius::legendre_plm() for basic definitions.
209 */
210template <typename T, typename F>
211inline void legendre_plm_aux(int lmax__, double x__, F&& ilm__, T const* plm__, T* p1lm__, T* p2lm__)
212{
213 double y = std::sqrt(1 - x__ * x__);
214
215 p1lm__[ilm__(0, 0)] = 0;
216 p2lm__[ilm__(0, 0)] = 0;
217
218 for (int l = 1; l <= lmax__; l++) {
219 auto a = std::sqrt(1 + 0.5 / l);
220 auto b = plm__[ilm__(l - 1, l - 1)];
221 /* compute y P_{l,l}' (diagonal) */
222 p1lm__[ilm__(l, l)] = a * (-y * p1lm__[ilm__(l - 1, l - 1)] + x__ * b);
223 /* compute P_{l,l}' / y (diagonal) */
224 p2lm__[ilm__(l, l)] = -a * b;
225 }
226 for (int l = 0; l < lmax__; l++) {
227 auto a = std::sqrt(2.0 * l + 3);
228 /* compute y P_{l+1,l}' (upper diagonal) */
229 p1lm__[ilm__(l + 1, l)] = a * (x__ * p1lm__[ilm__(l, l)] + y * plm__[ilm__(l, l)]);
230 /* compute P_{l+1,l}' / y (upper diagonal) */
231 p2lm__[ilm__(l + 1, l)] = a * x__ * p2lm__[ilm__(l, l)];
232 }
233 for (int m = 0; m <= lmax__ - 2; m++) {
234 for (int l = m + 2; l <= lmax__; l++) {
235 double alm = std::sqrt(static_cast<double>((2 * l - 1) * (2 * l + 1)) / (l * l - m * m));
236 double blm = std::sqrt(static_cast<double>((l - 1 - m) * (l - 1 + m)) / ((2 * l - 3) * (2 * l - 1)));
237 p1lm__[ilm__(l, m)] = alm * (x__ * p1lm__[ilm__(l - 1, m)] + y * plm__[ilm__(l - 1, m)] -
238 blm * p1lm__[ilm__(l - 2, m)]);
239 p2lm__[ilm__(l, m)] = alm * (x__ * p2lm__[ilm__(l - 1, m)] - blm * p2lm__[ilm__(l - 2, m)]);
240 }
241 }
242}
243
244/// Reference implementation of complex spherical harmonics.
245/** Complex spherical harmonics are defined as:
246 \f[
247 Y_{\ell m}(\theta,\phi) = P_{\ell}^{m}(\cos \theta) e^{im\phi}
248 \f]
249 where \f$P_{\ell}^m(x) \f$ are associated Legendre polynomials.
250
251 Mathematica code:
252 \verbatim
253 norm[l_, m_] := 4*Pi*Integrate[LegendreP[l, m, x]*LegendreP[l, m, x], {x, 0, 1}]
254 Ylm[l_, m_, t_, p_] := LegendreP[l, m, Cos[t]]*E^(I*m*p)/Sqrt[norm[l, m]]
255 Do[Print[ComplexExpand[
256 FullSimplify[SphericalHarmonicY[l, m, t, p] - Ylm[l, m, t, p],
257 Assumptions -> {0 <= t <= Pi}]]], {l, 0, 5}, {m, -l, l}]
258 \endverbatim
259
260 Complex spherical harmonics obey the following symmetry:
261 \f[
262 Y_{\ell -m}(\theta,\phi) = (-1)^m Y_{\ell m}^{*}(\theta,\phi)
263 \f]
264 Mathematica code:
265 \verbatim
266 Do[Print[ComplexExpand[
267 FullSimplify[
268 SphericalHarmonicY[l, -m, t, p] - (-1)^m*
269 Conjugate[SphericalHarmonicY[l, m, t, p]],
270 Assumptions -> {0 <= t <= Pi}]]], {l, 0, 4}, {m, 0, l}]
271 \endverbatim
272 */
273inline void spherical_harmonics_ref(int lmax, double theta, double phi, std::complex<double>* ylm)
274{
275 double x = std::cos(theta);
276
277 std::vector<double> result_array(gsl_sf_legendre_array_n(lmax));
278 gsl_sf_legendre_array(GSL_SF_LEGENDRE_SPHARM, lmax, x, &result_array[0]);
279
280 for (int l = 0; l <= lmax; l++) {
281 ylm[sf::lm(l, 0)] = result_array[gsl_sf_legendre_array_index(l, 0)];
282 }
283
284 for (int m = 1; m <= lmax; m++) {
285 std::complex<double> z = std::exp(std::complex<double>(0.0, m * phi)) * std::pow(-1, m);
286 for (int l = m; l <= lmax; l++) {
287 ylm[sf::lm(l, m)] = result_array[gsl_sf_legendre_array_index(l, m)] * z;
288 if (m % 2) {
289 ylm[sf::lm(l, -m)] = -std::conj(ylm[sf::lm(l, m)]);
290 } else {
291 ylm[sf::lm(l, -m)] = std::conj(ylm[sf::lm(l, m)]);
292 }
293 }
294 }
295}
296
297/// Optimized implementation of complex spherical harmonics.
298inline void spherical_harmonics(int lmax, double theta, double phi, std::complex<double>* ylm)
299{
300 double x = std::cos(theta);
301
302 sf::legendre_plm(lmax, x, sf::lm, ylm);
303
304 double c0 = std::cos(phi);
305 double c1 = 1;
306 double s0 = -std::sin(phi);
307 double s1 = 0;
308 double c2 = 2 * c0;
309
310 int phase{-1};
311
312 for (int m = 1; m <= lmax; m++) {
313 double c = c2 * c1 - c0;
314 c0 = c1;
315 c1 = c;
316 double s = c2 * s1 - s0;
317 s0 = s1;
318 s1 = s;
319 for (int l = m; l <= lmax; l++) {
320 double p = std::real(ylm[sf::lm(l, m)]);
321 double p1 = p * phase;
322 ylm[sf::lm(l, m)] = std::complex<double>(p * c, p * s);
323 ylm[sf::lm(l, -m)] = std::complex<double>(p1 * c, -p1 * s);
324 }
325 phase = -phase;
326 }
327}
328
329/// Reference implementation of real spherical harmonics Rlm
330/** Real spherical harminics are defined as:
331 \f[
332 R_{\ell m}(\theta,\phi) = \left\{
333 \begin{array}{lll}
334 \sqrt{2} \Re Y_{\ell m}(\theta,\phi) = \sqrt{2} P_{\ell}^{m}(\cos \theta) \cos m\phi & m > 0 \\
335 P_{\ell}^{0}(\cos \theta) & m = 0 \\
336 \sqrt{2} \Im Y_{\ell m}(\theta,\phi) = \sqrt{2} (-1)^{|m|} P_{\ell}^{|m|}(\cos \theta) (-\sin |m|\phi) & m < 0
337 \end{array}
338 \right.
339 \f]
340
341 Mathematica code:
342 \verbatim
343 (* definition of real spherical harmonics, use Plm(l,m) for m\
344 \[GreaterEqual]0 only *)
345
346 norm[l_, m_] :=
347 4*Pi*Integrate[
348 LegendreP[l, Abs[m], x]*LegendreP[l, Abs[m], x], {x, 0, 1}]
349 legendre[l_, m_, x_] := LegendreP[l, Abs[m], x]/Sqrt[norm[l, m]]
350
351 (* reference definition *)
352
353 RRlm[l_, m_, th_, ph_] :=
354 If[m > 0, Sqrt[2]*ComplexExpand[Re[SphericalHarmonicY[l, m, th, ph]]
355 ], If[m < 0,
356 Sqrt[2]*ComplexExpand[Im[SphericalHarmonicY[l, m, th, ph]]],
357 If[m == 0, ComplexExpand[Re[SphericalHarmonicY[l, 0, th, ph]]]]]]
358
359 (* definition without ComplexExpand *)
360
361 Rlm[l_, m_, th_, ph_] :=
362 If[m > 0, legendre[l, m, Cos[th]]*Sqrt[2]*Cos[m*ph],
363 If[m < 0, (-1)^m*legendre[l, m, Cos[th]]*Sqrt[2]*(-Sin[Abs[m]*ph]),
364 If[m == 0, legendre[l, 0, Cos[th]]]]]
365
366 (* check that both definitions are identical *)
367 Do[
368 Print[FullSimplify[Rlm[l, m, a, b] - RRlm[l, m, a, b],
369 Assumptions -> {0 <= a <= Pi, 0 <= b <= 2*Pi}]], {l, 0, 5}, {m, -l,
370 l}]
371
372 \endverbatim
373 */
374inline void spherical_harmonics_ref(int lmax, double theta, double phi, double* rlm)
375{
376 /* reference code */
377 int lmmax = (lmax + 1) * (lmax + 1);
378
379 std::vector<std::complex<double>> ylm(lmmax);
380 sf::spherical_harmonics_ref(lmax, theta, phi, &ylm[0]);
381
382 double const t = std::sqrt(2.0);
383
384 rlm[0] = y00;
385
386 for (int l = 1; l <= lmax; l++) {
387 rlm[sf::lm(l, 0)] = ylm[sf::lm(l, 0)].real();
388 for (int m = 1; m <= l; m++) {
389 rlm[sf::lm(l, m)] = t * ylm[sf::lm(l, m)].real();
390 rlm[sf::lm(l, -m)] = t * ylm[sf::lm(l, -m)].imag();
391 }
392 }
393}
394
395/// Optimized implementation of real spherical harmonics.
396inline void spherical_harmonics(int lmax, double theta, double phi, double* rlm)
397{
398 double x = std::cos(theta);
399
400 sf::legendre_plm(lmax, x, sf::lm, rlm);
401
402 double c0 = std::cos(phi);
403 double c1 = 1;
404 double s0 = -std::sin(phi);
405 double s1 = 0;
406 double c2 = 2 * c0;
407
408 double const t = std::sqrt(2.0);
409
410 int phase{-1};
411
412 for (int m = 1; m <= lmax; m++) {
413 double c = c2 * c1 - c0;
414 c0 = c1;
415 c1 = c;
416 double s = c2 * s1 - s0;
417 s0 = s1;
418 s1 = s;
419 for (int l = m; l <= lmax; l++) {
420 double p = rlm[sf::lm(l, m)];
421 rlm[sf::lm(l, m)] = t * p * c;
422 rlm[sf::lm(l, -m)] = -t * p * s * phase;
423 }
424 phase = -phase;
425 }
426}
427
428/// Generate \f$ \cos(m x) \f$ for m in [1, n] using recursion.
429inline sddk::mdarray<double, 1> cosxn(int n__, double x__)
430{
431 assert(n__ > 0);
432 sddk::mdarray<double, 1> data(n__);
433
434 double c0 = std::cos(x__);
435 double c1 = 1;
436 double c2 = 2 * c0;
437 for (int m = 0; m < n__; m++) {
438 data[m] = c2 * c1 - c0;
439 c0 = c1;
440 c1 = data[m];
441 }
442 return data;
443}
444
445/// Generate \f$ \sin(m x) \f$ for m in [1, n] using recursion.
446inline sddk::mdarray<double, 1> sinxn(int n__, double x__)
447{
448 assert(n__ > 0);
449 sddk::mdarray<double, 1> data(n__);
450
451 double s0 = -std::sin(x__);
452 double s1 = 0;
453 double c2 = 2 * std::cos(x__);
454
455 for (int m = 0; m < n__; m++) {
456 data[m] = c2 * s1 - s0;
457 s0 = s1;
458 s1 = data[m];
459 }
460 return data;
461}
462
463/// Compute the derivatives of real spherical harmonics over the components of cartesian vector.
464/** The following derivative is computed:
465 \f[
466 \frac{\partial R_{\ell m}(\theta_r, \phi_r)}{\partial r_{\mu}} =
467 \frac{\partial R_{\ell m}(\theta_r, \phi_r)}{\partial \theta_r} \frac{\partial \theta_r}{\partial r_{\mu}} +
468 \frac{\partial R_{\ell m}(\theta_r, \phi_r)}{\partial \phi_r} \frac{\partial \phi_r}{\partial r_{\mu}}
469 \f]
470 The derivatives of angles are:
471 \f[
472 \frac{\partial \theta_r}{\partial r_{x}} = \frac{\cos(\phi_r) \cos(\theta_r)}{r} \\
473 \frac{\partial \theta_r}{\partial r_{y}} = \frac{\cos(\theta_r) \sin(\phi_r)}{r} \\
474 \frac{\partial \theta_r}{\partial r_{z}} = -\frac{\sin(\theta_r)}{r}
475 \f]
476 and
477 \f[
478 \frac{\partial \phi_r}{\partial r_{x}} = -\frac{\sin(\phi_r)}{\sin(\theta_r) r} \\
479 \frac{\partial \phi_r}{\partial r_{y}} = \frac{\cos(\phi_r)}{\sin(\theta_r) r} \\
480 \frac{\partial \phi_r}{\partial r_{z}} = 0
481 \f]
482 The derivative of \f$ \phi \f$ has discontinuities at \f$ \theta = 0, \theta=\pi \f$. This, however, is not a problem, because
483 multiplication by the the derivative of \f$ R_{\ell m} \f$ removes it. The following functions have to be hardcoded:
484 \f[
485 \frac{\partial R_{\ell m}(\theta, \phi)}{\partial \theta} \\
486 \frac{\partial R_{\ell m}(\theta, \phi)}{\partial \phi} \frac{1}{\sin(\theta)}
487 \f]
488
489 Spherical harmonics have a separable form:
490 \f[
491 R_{\ell m}(\theta, \phi) = P_{\ell}^{m}(\cos \theta) f(\phi)
492 \f]
493 The derivative over \f$ \theta \f$ is then:
494 \f[
495 \frac{\partial R_{\ell m}(\theta, \phi)}{\partial \theta} = \frac{\partial P_{\ell}^{m}(x)}{\partial x}
496 \frac{\partial x}{\partial \theta} f(\phi) = -\sin \theta \frac{\partial P_{\ell}^{m}(x)}{\partial x} f(\phi)
497 \f]
498 where \f$ x = \cos \theta \f$
499
500 Mathematica script for spherical harmonic derivatives:
501 \verbatim
502 Rlm[l_, m_, th_, ph_] :=
503 If[m > 0, Sqrt[2]*ComplexExpand[Re[SphericalHarmonicY[l, m, th, ph]]],
504 If[m < 0, Sqrt[2]*ComplexExpand[Im[SphericalHarmonicY[l, m, th, ph]]],
505 If[m == 0, ComplexExpand[Re[SphericalHarmonicY[l, 0, th, ph]]]]
506 ]
507 ]
508 Do[Print[FullSimplify[D[Rlm[l, m, theta, phi], theta]]], {l, 0, 4}, {m, -l, l}]
509 Do[Print[FullSimplify[TrigExpand[D[Rlm[l, m, theta, phi], phi]/Sin[theta]]]], {l, 0, 4}, {m, -l, l}]
510 \endverbatim
511 */
512inline void dRlm_dr(int lmax__, r3::vector<double>& r__, sddk::mdarray<double, 2>& data__, bool divide_by_r__ = true)
513{
514 /* get spherical coordinates of the Cartesian vector */
515 auto vrs = r3::spherical_coordinates(r__);
516
517 if (vrs[0] < 1e-12) {
518 data__.zero();
519 return;
520 }
521
522 int lmmax = (lmax__ + 1) * (lmax__ + 1);
523
524 double theta = vrs[1];
525 double phi = vrs[2];
526
527 double sint = std::sin(theta);
528 double sinp = std::sin(phi);
529 double cost = std::cos(theta);
530 double cosp = std::cos(phi);
531
532 /* nominators of angle derivatives */
533 r3::vector<double> dtheta_dr({cost * cosp, cost * sinp, -sint});
534 r3::vector<double> dphi_dr({-sinp, cosp, 0});
535
536 std::vector<double> dRlm_dt(lmmax);
537 std::vector<double> dRlm_dp_sin_t(lmmax);
538
539 std::vector<double> plm((lmax__ + 1) * (lmax__ + 2) / 2);
540 std::vector<double> dplm((lmax__ + 1) * (lmax__ + 2) / 2);
541 std::vector<double> plm_y((lmax__ + 1) * (lmax__ + 2) / 2);
542
543 auto ilm = [](int l, int m){return l * (l + 1) / 2 + m;};
544
545 dRlm_dt[0] = 0;
546 dRlm_dp_sin_t[0] = 0;
547
548 /* compute Legendre polynomials */
549 sf::legendre_plm(lmax__, cost, ilm, plm.data());
550 /* compute sin(theta) * (dPlm/dx) and Plm / sin(theta) */
551 sf::legendre_plm_aux(lmax__, cost, ilm, plm.data(), dplm.data(), plm_y.data());
552
553 double c0 = cosp;
554 double c1 = 1;
555 double s0 = -sinp;
556 double s1 = 0;
557 double c2 = 2 * c0;
558
559 double const t = std::sqrt(2.0);
560
561 for (int l = 0; l <= lmax__; l++) {
562 dRlm_dt[sf::lm(l, 0)] = -dplm[ilm(l, 0)];
563 dRlm_dp_sin_t[sf::lm(l, 0)] = 0;
564 }
565
566 int phase{-1};
567 for (int m = 1; m <= lmax__; m++) {
568 double c = c2 * c1 - c0;
569 c0 = c1;
570 c1 = c;
571 double s = c2 * s1 - s0;
572 s0 = s1;
573 s1 = s;
574 for (int l = m; l <= lmax__; l++) {
575 double p = -dplm[ilm(l, m)];
576 dRlm_dt[sf::lm(l, m)] = t * p * c;
577 dRlm_dt[sf::lm(l, -m)] = -t * p * s * phase;
578 p = plm_y[ilm(l, m)];
579 dRlm_dp_sin_t[sf::lm(l, m)] = -t * p * s * m;
580 dRlm_dp_sin_t[sf::lm(l, -m)] = -t * p * c * m * phase;
581 }
582
583 phase = -phase;
584 }
585
586 if (!divide_by_r__) {
587 vrs[0] = 1;
588 }
589
590 for (int mu = 0; mu < 3; mu++) {
591 for (int lm = 0; lm < lmmax; lm++) {
592 data__(lm, mu) = (dRlm_dt[lm] * dtheta_dr[mu] + dRlm_dp_sin_t[lm] * dphi_dr[mu]) / vrs[0];
593 }
594 }
595}
596
597inline void dRlm_dr_numerical(int lmax__, r3::vector<double>& r__, sddk::mdarray<double, 2>& data__, bool divide_by_r__ = true)
598{
599 /* get spherical coordinates of the Cartesian vector */
600 auto vrs = r3::spherical_coordinates(r__);
601
602 if (vrs[0] < 1e-12) {
603 data__.zero();
604 return;
605 }
606 double dr = 1e-5 * vrs[0];
607
608 int lmmax = (lmax__ + 1) * (lmax__ + 1);
609
610 auto pref = divide_by_r__ ? (1.0 / vrs[0]) : 1.0;
611
612 for (int x = 0; x < 3; x++) {
613
614 r3::vector<double> v1 = r__;
615 v1[x] += dr;
616
617 r3::vector<double> v2 = r__;
618 v2[x] -= dr;
619
620 auto vs1 = r3::spherical_coordinates(v1);
621 auto vs2 = r3::spherical_coordinates(v2);
622 std::vector<double> rlm1(lmmax);
623 std::vector<double> rlm2(lmmax);
624
625 sf::spherical_harmonics(lmax__, vs1[1], vs1[2], &rlm1[0]);
626 sf::spherical_harmonics(lmax__, vs2[1], vs2[2], &rlm2[0]);
627
628 for (int lm = 0; lm < lmmax; lm++) {
629 data__(lm, x) = pref * (rlm1[lm] - rlm2[lm]) / 2 / dr;
630 }
631 }
632}
633
634} // namespace sf
635
636} // namespace sirius
637
638#endif // __SPECFUNC_HPP__
void zero(memory_t mem__, size_t idx0__, size_t n__)
Zero n elements starting from idx0.
Definition: memory.hpp:1316
Various constants.
Memory management functions and classes.
auto spherical_coordinates(vector< double > vc)
Transform Cartesian coordinates [x,y,z] to spherical coordinates [r,theta,phi].
Definition: r3.hpp:590
int lmax(int lmmax__)
Get maximum orbital quantum number by the maximum lm index.
Definition: specfunc.hpp:56
int lmmax(int lmax)
Maximum number of combinations for a given .
Definition: specfunc.hpp:44
void legendre_plm(int lmax__, double x__, F &&ilm__, T *plm__)
Generate associated Legendre polynomials.
Definition: specfunc.hpp:123
void spherical_harmonics_ref(int lmax, double theta, double phi, std::complex< double > *ylm)
Reference implementation of complex spherical harmonics.
Definition: specfunc.hpp:273
int lm(int l, int m)
Get composite lm index by angular index l and azimuthal index m.
Definition: specfunc.hpp:50
std::vector< int > l_by_lm(int lmax__)
Get array of orbital quantum numbers for each lm component.
Definition: specfunc.hpp:69
sddk::mdarray< double, 1 > sinxn(int n__, double x__)
Generate for m in [1, n] using recursion.
Definition: specfunc.hpp:446
sddk::mdarray< double, 1 > cosxn(int n__, double x__)
Generate for m in [1, n] using recursion.
Definition: specfunc.hpp:429
void dRlm_dr(int lmax__, r3::vector< double > &r__, sddk::mdarray< double, 2 > &data__, bool divide_by_r__=true)
Compute the derivatives of real spherical harmonics over the components of cartesian vector.
Definition: specfunc.hpp:512
void legendre_plm_aux(int lmax__, double x__, F &&ilm__, T const *plm__, T *p1lm__, T *p2lm__)
Generate auxiliary Legendre polynomials which are necessary for spherical harmomic derivatives.
Definition: specfunc.hpp:211
void spherical_harmonics(int lmax, double theta, double phi, std::complex< double > *ylm)
Optimized implementation of complex spherical harmonics.
Definition: specfunc.hpp:298
Namespace of the SIRIUS library.
Definition: sirius.f90:5
const double y00
First spherical harmonic .
Definition: constants.hpp:51
auto conj(double x__)
Return complex conjugate of a number. For a real value this is the number itself.
Definition: math_tools.hpp:165
Simple classes and functions to work with vectors and matrices of the R^3 space.
Eror and warning handling during run-time execution.
Contains typedefs, enums and simple descriptors.