Bonjour,
Pour le moment, je travaille sur l'implémentation d'une somme d'Ewald, À un moment, je dois implémenter la somme suivante,
Ou $q_i$ et $q_j$ sont les charges des atomes $i$ et $j$, $r_{ij}$ est la distance entre l'atome $i$ et $j$ ($\vec{r}_{ij} = \vec{r}_j - \vec{r}_i$) et $\vec{k}$ est le vecteur réciproque, tel que $\vec{k} = (n_x \frac{2\pi}{L_x}; n_y \frac{2\pi}{L_y}; n_z \frac{2\pi}{L_z})$ ou $n_i$ est un nombre entier compris entre -1 et 1 (on pourrait choisir des nombre plus grand, mais c'est plus facile pour débugger). On exclu $n_x = n_y = n_z = 0$ de la somme (parce que dans l'expression complète de la somme d'Ewald, il y a un $|\vec{k}|^{-1}$).
Très justement, la publication que je suis me propose la simplification suivante,
… Ce qui est tout à fait correct si on prend le temps de l'écrire sur une feuille de papier et en se rappellant que $\cos{(k\cdot\vec{r}_{ij})} = \cos{(k\cdot\vec{r}_{j}-k\cdot\vec{r}_{i})}$ et que $\cos{(a-b)} = \cos{a}\cos{b} + \sin{a}\sin{b}$.
Implémentée (très) grossièrement en python, on a les codes suivant…
- Pour $\sum_{\vec{k} \neq 0}\sum_{ij} q_i\,q_j\,\cos(\vec{k}\cdot\vec{r}_{ij})$,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | import math atoms = ([1, 2, 1], [-1, 0, -1], [1, 1, 1], [0, 0, 0]) charges = (.7, -.3, -.2, -.2) # il faut que ça soit électriquement neutre, que ∑ q_i = 0 reciprocal = [1/3, 1/3, 1/3] n = [-1, 0, 1] U1 = 0 for nx in n: for ny in n: for nz in n: if nx == ny == nz == 0: continue n_ = [nx, ny, nz] k = [n_[a] * reciprocal[a] for a in range(3)] for i in range(len(atoms)): for j in range(len(atoms)): k_dot_r = k[0] * (atoms[j][0]-atoms[i][0]) \ + k[1] * (atoms[j][1]-atoms[i][1]) \ + k[2] *(atoms[j][2]-atoms[i][2]) U1 += charges[i] * charges[j] * math.cos(k_dot_r) print(U1) |
- Et pour $\sum_{\vec{k}\neq 0}\left[\sum_i q_1\,cos{(\vec{k}\cdot\vec{r}_i)}\right]^2 + \left[\sum_i q_1\,sin{(\vec{k}\cdot\vec{r}_i)}\right]^2$, on aura
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | # ... suite du précédent U2 = 0 for nx in n: for ny in n: for nz in n: if nx == ny == nz == 0: continue n_ = [nx, ny, nz] k = [n_[a] * reciprocal[a] for a in range(3)] cos_part = 0.0 sin_part = 0.0 for i in range(len(atoms)): k_dot_r = k[0] * atoms[i][0] + k[1] * atoms[i][1] + k[2] * atoms[i][2] cos_part += charges[i] * math.cos(k_dot_r) sin_part += charges[i] * math.sin(k_dot_r) U2 += cos_part**2 + sin_part**2 print(U2) |
… Et on obtient bien 2 fois la même réponse (logique), à l'erreur numérique près (les résultats divergent après la 12ième décimale ou un truc du genre).
La question que je me pose, c'est pourquoi j'obtient la même réponse avec le code suivant,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | # ... C'est toujours la suite du précédent U3 = 0 for nx in n: for ny in n: for nz in n: if nx == ny == nz == 0: continue n_ = [nx, ny, nz] k = [n_[a] * reciprocal[a] for a in range(3)] sin_p_part = 0.0 for i in range(len(atoms)): k_dot_r = k[0] * atoms[i][0] + k[1] * atoms[i][1] + k[2] * atoms[i][2] sin_p_part += charges[i] * math.sin(k_dot_r + math.pi/4) U3 += 2 * sin_p_part **2 print(U3) |
… Qui correspondrait à la simplification suivante,
J'ai "trouvé" cette simplification sur base d'une erreur de raisonnement, pensant que $\sin(x)+\cos(x) = \sqrt{2}\,\sin{\left(x+\frac{\pi}{4}\right)}$, ce qui est correct en soit, mais inapplicable à mon cas, puisque je ne peux pas dire que,
Ce qui est pour moi, à première vue, une grosse erreur. Et pourtant, ça fonctionne ! Et ça m'intéresse, puisque en terme de performances ça ne me fait plus qu'un seul cosinus par atome à évaluer, contre un sinus et un cosinus dans le second cas et une pelletée dans le premier (dans cas réel, le nombre de fois qu'on fait cette évaluation est important, le nombre de $\vec{k}$ est important et le nombre d'atomes est important, donc ça commence à compter).
Alors, si quelqu'un pouvait m'expliquer pourquoi ça fonctionne ou l'endroit ou je me suis trompé en implémentant mon code, je vous avoue que ça me rassurerait. Merci d'avance
PS: bien entendu, j'ai testé en changeant le nombre de charges, les positions, le nombre de vecteurs réciproques à considérer, etc, et le code me sort toujours trois réponses identiques.