LCOV - code coverage report
Current view: top level - src/grpp - grpp_spherical_harmonics.c (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:85b8a9b) Lines: 75.8 % 128 97
Test Date: 2026-06-14 06:48:14 Functions: 85.7 % 7 6

            Line data    Source code
       1              : /*----------------------------------------------------------------------------*/
       2              : /*  CP2K: A general program to perform molecular dynamics simulations         */
       3              : /*  Copyright 2000-2026 CP2K developers group <https://cp2k.org>              */
       4              : /*                                                                            */
       5              : /*  SPDX-License-Identifier: MIT                                              */
       6              : /*----------------------------------------------------------------------------*/
       7              : 
       8              : /*
       9              :  *  libgrpp - a library for the evaluation of integrals over
      10              :  *            generalized relativistic pseudopotentials.
      11              :  *
      12              :  *  Copyright (C) 2021-2023 Alexander Oleynichenko
      13              :  */
      14              : 
      15              : /*
      16              :  * Constructs tables with the expansion coefficients of real spherical harmonics
      17              :  * in the basis of (cartesian) unitary spherical polynomials.
      18              :  *
      19              :  * For more details about the algorithm used, see:
      20              :  * R. Flores-Moreno et al, J. Comput. Chem. 27, 1009 (2006),
      21              :  * doi: 10.1002/jcc.20410
      22              :  */
      23              : 
      24              : #include <math.h>
      25              : #include <stdio.h>
      26              : #include <stdlib.h>
      27              : #include <string.h>
      28              : 
      29              : #ifndef M_PI
      30              : #define M_PI 3.1415926535897932384626433
      31              : #endif
      32              : 
      33              : #ifndef M_SQRT1_2
      34              : #define M_SQRT1_2 0.70710678118654752440
      35              : #endif
      36              : 
      37              : #include "grpp_binomial.h"
      38              : #include "grpp_factorial.h"
      39              : #include "grpp_spherical_harmonics.h"
      40              : #include "libgrpp.h"
      41              : /*
      42              :  * Tables with pretabulated expansion coefficients
      43              :  */
      44              : 
      45              : static rsh_coef_table_t **rsh_coef_tables = NULL;
      46              : static int rsh_tables_lmax = -1;
      47              : 
      48              : /*
      49              :  * Function pre-definitions
      50              :  */
      51              : rsh_coef_table_t *libgrpp_tabulate_real_spherical_harmonic_coeffs(int L);
      52              : 
      53              : static int *generate_cartesian_combinations(int L, int *num);
      54              : 
      55              : /**
      56              :  * Constructs the set of tables with C_{l,m}^{lx,ly,lz} coefficients
      57              :  * (up to maximum angular momentum Lmax).
      58              :  * (pretabulation step)
      59              :  */
      60           22 : void libgrpp_create_real_spherical_harmonic_coeffs_tables(int Lmax) {
      61           22 :   if (Lmax <= rsh_tables_lmax) {
      62              :     // nothing to do
      63              :   } else {
      64              :     // expand tables: realloc memory and add tables for the highest L values
      65           22 :     rsh_coef_tables = (rsh_coef_table_t **)realloc(
      66           22 :         rsh_coef_tables, (Lmax + 1) * sizeof(rsh_coef_table_t *));
      67              : 
      68          924 :     for (int L = rsh_tables_lmax + 1; L <= Lmax; L++) {
      69          902 :       rsh_coef_tables[L] = libgrpp_tabulate_real_spherical_harmonic_coeffs(L);
      70              :     }
      71           22 :     rsh_tables_lmax = Lmax;
      72              :   }
      73           22 : }
      74              : 
      75              : /**
      76              :  * Calculates all C_{l,m}^{lx,ly,lz} coefficients for the given angular momentum
      77              :  * L.
      78              :  */
      79          902 : rsh_coef_table_t *libgrpp_tabulate_real_spherical_harmonic_coeffs(int L) {
      80          902 :   int ncart = (L + 1) * (L + 2) / 2;
      81              : 
      82          902 :   rsh_coef_table_t *coef_table =
      83          902 :       (rsh_coef_table_t *)calloc(1, sizeof(rsh_coef_table_t));
      84          902 :   coef_table->n_cart_comb = ncart;
      85          902 :   coef_table->cartesian_comb = generate_cartesian_combinations(L, &ncart);
      86          902 :   coef_table->coeffs = (double *)calloc((2 * L + 1) * ncart, sizeof(double));
      87              : 
      88        37884 :   for (int m = -L; m <= L; m++) {
      89     16598604 :     for (int icomb = 0; icomb < ncart; icomb++) {
      90     16561622 :       int lx = coef_table->cartesian_comb[3 * icomb];
      91     16561622 :       int ly = coef_table->cartesian_comb[3 * icomb + 1];
      92              :       // int lz = coef_table->cartesian_comb[3 * icomb + 2];
      93     16561622 :       double u_lm_lx_ly_lz = libgrpp_spherical_to_cartesian_coef(L, m, lx, ly);
      94     16561622 :       int index = (m + L) * ncart + icomb;
      95     16561622 :       coef_table->coeffs[index] = u_lm_lx_ly_lz;
      96              :     }
      97              :   }
      98              : 
      99          902 :   return coef_table;
     100              : }
     101              : 
     102              : /**
     103              :  * Access to the table for the angular momentum value L.
     104              :  */
     105     55477394 : rsh_coef_table_t *libgrpp_get_real_spherical_harmonic_table(int L) {
     106     55477394 :   if (L > rsh_tables_lmax) {
     107            0 :     printf("get_real_spherical_harmonic_table(): %d > Lmax\n", L);
     108            0 :     return NULL;
     109              :   }
     110              : 
     111     55477394 :   return rsh_coef_tables[L];
     112              : }
     113              : 
     114              : /**
     115              :  * For the given real spherical harmonic (RSH) S_lm calculates the coefficient
     116              :  * C_{l,m}^{lx,ly,lz} before the unitary spherical polynomial (USP) in its
     117              :  * expansion.
     118              :  *
     119              :  * The formula is taken from:
     120              :  * R. Flores-Moreno et al, J. Comput. Chem. 27, 1009 (2006)
     121              :  * doi: 10.1002/jcc.20410
     122              :  * (formula 32)
     123              :  */
     124     16561622 : double libgrpp_spherical_to_cartesian_coef(int l, int m, int lx, int ly) {
     125     16561622 :   int j = lx + ly - abs(m);
     126     16561622 :   if (j % 2 != 0) {
     127              :     return 0.0;
     128              :   }
     129      8285662 :   j /= 2;
     130              : 
     131      8285662 :   if (!((m > 0 && (abs(m) - lx) % 2 == 0) || (m == 0 && lx % 2 == 0) ||
     132      4074840 :         (m < 0 && (abs(m) - lx) % 2 != 0))) {
     133              :     return 0.0;
     134              :   }
     135              : 
     136      4147682 :   double prefactor =
     137      4147682 :       sqrt((2 * l + 1) / (2 * M_PI) * libgrpp_factorial(l - abs(m)) /
     138      4147682 :            libgrpp_factorial(l + abs(m)));
     139      4147682 :   prefactor /= pow(2, l) * libgrpp_factorial(l);
     140              : 
     141      4147682 :   double u_lm_lx_ly_lz = 0.0;
     142     29535660 :   for (int i = j; i <= (l - abs(m)) / 2; i++) {
     143              :     // any term that implies the factorial of a negative number is neglected
     144     25387978 :     if (2 * l - 2 * i < 0) {
     145              :       u_lm_lx_ly_lz = 0.0;
     146              :       break;
     147              :     }
     148     25387978 :     if (l - abs(m) - 2 * i < 0) {
     149              :       u_lm_lx_ly_lz = 0.0;
     150              :       break;
     151              :     }
     152              : 
     153     50775956 :     double factor_1 =
     154     25387978 :         libgrpp_binomial(l, i) * libgrpp_binomial(i, j) * pow(-1, i) *
     155     25387978 :         libgrpp_factorial_ratio(2 * l - 2 * i, l - abs(m) - 2 * i);
     156              : 
     157     25387978 :     double sum = 0.0;
     158    104558762 :     for (int k = 0; k <= j; k++) {
     159     79170784 :       sum += libgrpp_binomial(j, k) * libgrpp_binomial(abs(m), lx - 2 * k) *
     160     79170784 :              pow(-1, (abs(m) - lx + 2 * k) / 2);
     161              :     }
     162              : 
     163     25387978 :     u_lm_lx_ly_lz += factor_1 * sum;
     164              :   }
     165              : 
     166      4147682 :   u_lm_lx_ly_lz *= prefactor;
     167      4147682 :   if (m == 0 && (lx % 2 == 0)) {
     168        72842 :     u_lm_lx_ly_lz *= M_SQRT1_2; // x 1/sqrt(2)
     169              :   }
     170              : 
     171              :   return u_lm_lx_ly_lz;
     172              : }
     173              : 
     174              : /**
     175              :  * Calculates value of the real spherical harmonic S_lm at the point k/|k| of
     176              :  * the unit sphere.
     177              :  */
     178            0 : double libgrpp_evaluate_real_spherical_harmonic(const int l, const int m,
     179              :                                                 const double *k) {
     180            0 :   double unitary_kx;
     181            0 :   double unitary_ky;
     182            0 :   double unitary_kz;
     183            0 :   double kx_powers[200];
     184            0 :   double ky_powers[200];
     185            0 :   double kz_powers[200];
     186              : 
     187            0 :   rsh_coef_table_t *rsh_coef_l = libgrpp_get_real_spherical_harmonic_table(l);
     188              : 
     189            0 :   double length_k = sqrt(k[0] * k[0] + k[1] * k[1] + k[2] * k[2]);
     190              : 
     191            0 :   if (length_k > LIBGRPP_ZERO_THRESH) {
     192            0 :     unitary_kx = k[0] / length_k;
     193            0 :     unitary_ky = k[1] / length_k;
     194            0 :     unitary_kz = k[2] / length_k;
     195              :   } else {
     196              :     unitary_kx = 0.0;
     197              :     unitary_ky = 0.0;
     198              :     unitary_kz = 0.0;
     199              :   }
     200              : 
     201            0 :   kx_powers[0] = 1.0;
     202            0 :   ky_powers[0] = 1.0;
     203            0 :   kz_powers[0] = 1.0;
     204              : 
     205            0 :   for (int i = 1; i <= l; i++) {
     206            0 :     kx_powers[i] = kx_powers[i - 1] * unitary_kx;
     207            0 :     ky_powers[i] = ky_powers[i - 1] * unitary_ky;
     208            0 :     kz_powers[i] = kz_powers[i - 1] * unitary_kz;
     209              :   }
     210              : 
     211            0 :   double value = 0.0;
     212              : 
     213            0 :   int ncart = rsh_coef_l->n_cart_comb;
     214            0 :   for (int icomb = 0; icomb < ncart; icomb++) {
     215            0 :     int r = rsh_coef_l->cartesian_comb[3 * icomb];
     216            0 :     int s = rsh_coef_l->cartesian_comb[3 * icomb + 1];
     217            0 :     int t = rsh_coef_l->cartesian_comb[3 * icomb + 2];
     218            0 :     double y_lm_rst = rsh_coef_l->coeffs[(m + l) * ncart + icomb];
     219              : 
     220            0 :     value += y_lm_rst * kx_powers[r] * ky_powers[s] * kz_powers[t];
     221              :   }
     222              : 
     223            0 :   return value;
     224              : }
     225              : 
     226              : /**
     227              :  * Calculates values of the real spherical harmonic S_lm at the point k/|k| of
     228              :  * the unit sphere for all m = -l, ..., +l
     229              :  */
     230      1585692 : void libgrpp_evaluate_real_spherical_harmonics_array(const int l,
     231              :                                                      const double *k,
     232              :                                                      double *rsh_array) {
     233      1585692 :   double unitary_kx;
     234      1585692 :   double unitary_ky;
     235      1585692 :   double unitary_kz;
     236      1585692 :   double kx_powers[200];
     237      1585692 :   double ky_powers[200];
     238      1585692 :   double kz_powers[200];
     239              : 
     240      1585692 :   rsh_coef_table_t *rsh_coef_l = libgrpp_get_real_spherical_harmonic_table(l);
     241              : 
     242      1585692 :   double length_k = sqrt(k[0] * k[0] + k[1] * k[1] + k[2] * k[2]);
     243              : 
     244      1585692 :   if (length_k > LIBGRPP_ZERO_THRESH) {
     245      1086538 :     double inv_length = 1.0 / length_k;
     246      1086538 :     unitary_kx = k[0] * inv_length;
     247      1086538 :     unitary_ky = k[1] * inv_length;
     248      1086538 :     unitary_kz = k[2] * inv_length;
     249              :   } else {
     250              :     unitary_kx = 0.0;
     251              :     unitary_ky = 0.0;
     252              :     unitary_kz = 0.0;
     253              :   }
     254              : 
     255      1585692 :   kx_powers[0] = 1.0;
     256      1585692 :   ky_powers[0] = 1.0;
     257      1585692 :   kz_powers[0] = 1.0;
     258              : 
     259      4510818 :   for (int i = 1; i <= l; i++) {
     260      2925126 :     kx_powers[i] = kx_powers[i - 1] * unitary_kx;
     261      2925126 :     ky_powers[i] = ky_powers[i - 1] * unitary_ky;
     262      2925126 :     kz_powers[i] = kz_powers[i - 1] * unitary_kz;
     263              :   }
     264              : 
     265      1585692 :   memset(rsh_array, 0, (2 * l + 1) * sizeof(double));
     266              : 
     267      1585692 :   int ncart = rsh_coef_l->n_cart_comb;
     268      1585692 :   int *rst_array = rsh_coef_l->cartesian_comb;
     269              : 
     270     12255026 :   for (int icomb = 0; icomb < ncart; icomb++) {
     271     10669334 :     int r = rst_array[3 * icomb];
     272     10669334 :     int s = rst_array[3 * icomb + 1];
     273     10669334 :     int t = rst_array[3 * icomb + 2];
     274              : 
     275     10669334 :     double k_xyz = kx_powers[r] * ky_powers[s] * kz_powers[t];
     276              : 
     277     91656460 :     for (int m = -l; m <= l; m++) {
     278     80987126 :       double y_lm_rst = rsh_coef_l->coeffs[(m + l) * ncart + icomb];
     279     80987126 :       rsh_array[m + l] += y_lm_rst * k_xyz;
     280              :     }
     281              :   }
     282      1585692 : }
     283              : 
     284          902 : static int *generate_cartesian_combinations(int L, int *num) {
     285          902 :   *num = (L + 1) * (L + 2) / 2;
     286              : 
     287          902 :   int *combinations = (int *)calloc(*num, 3 * sizeof(int));
     288              : 
     289          902 :   int n = 0;
     290        19844 :   for (int i = 0; i <= L; i++) {
     291       543004 :     for (int j = 0; j <= L; j++) {
     292     16833124 :       for (int k = 0; k <= L; k++) {
     293     16309062 :         if (i + j + k == L) {
     294       271502 :           combinations[3 * n + 0] = i;
     295       271502 :           combinations[3 * n + 1] = j;
     296       271502 :           combinations[3 * n + 2] = k;
     297       271502 :           n++;
     298              :         }
     299              :       }
     300              :     }
     301              :   }
     302              : 
     303          902 :   return combinations;
     304              : }
        

Generated by: LCOV version 2.0-1