VM2D 1.14
Vortex methods for 2D flows simulation
Loading...
Searching...
No Matches
special_functions.cpp
Go to the documentation of this file.
1#include "special_functions.h"
2#include "simple_math.h"
3#include <cmath>
4#include <utility>
5#include <oneapi/tbb/parallel_for.h>
6
7namespace fmm {
8
10{
12 Knm(0, 0) = 1.0;
13 for (int n = 1; n < N; ++n)
14 {
15 for (int m = 0; m <= n - 1; ++m)
16 Knm(n, m) = Knm(n - 1, m) * (n - m) / (n + m);
17 Knm(n, n) = Knm(n, n - 1) / (2 * n);
18 }
19 for (int n = 1; n < N; ++n)
20 {
21 for (int m = 0; m <= n; ++m)
22 Knm(n, m) = sqrt(Knm(n, m));
23 }
24 return Knm;
25}
26
28{
30 Anm(0, 0) = 1.0;
31 for (int n = 1; n < 2 * N; ++n)
32 {
33 for (int m = 0; m <= n - 1; ++m)
34 Anm(n, m) = Anm(n - 1, m) * (n - m) * (n + m);
35 Anm(n, n) = Anm(n, n - 1) * 2 * n;
36 }
37 for (int n = 1; n < 2 * N; ++n)
38 {
39 for (int m = 0; m <= n; ++m)
40 Anm(n, m) = ni(n) / sqrt(Anm(n, m));
41 }
42 return Anm;
43}
44
45//std::pair<std::vector<std::vector<double>>,std::vector<std::vector<double>>> compute_wignerd_coefs(int N)
46//{
47// std::vector<std::vector<double>> res1(N);
48// std::vector<std::vector<double>> res2(N);
49// int idx1, idx2;
50// for (int n = 0; n < N; ++n)
51// {
52// res1[n].resize((1 + 2 * n) * (1 + n));
53// res2[n].resize((1 + 2 * n) * (1 + n));
54// idx1 = 2 * n + 1;
55// auto& r1 = res1[n];
56// auto& r2 = res2[n];
57// for (int m = 0; m <= n - 1; ++m)
58// {
59// idx2 = idx1 * (m + 1) + n;
60// r2[idx2 - n] = - 1.0 * (m - n) / sqrt(n * (n + 1) - m * (m + 1));
61// for (int k = -n + 1; k <= n; ++k)
62// {
63// r1[idx2 + k] = 1.0 * sqrt((n * (n + 1.0) - k * (k - 1)) / (n * (n + 1) - m * (m + 1)));
64// r2[idx2 + k] = - 1.0 * (m + k) / sqrt(n * (n + 1) - m * (m + 1));
65// }
66// }
67// }
68// return {res1, res2};
69//}
70
71//const auto wignerdcoefs = compute_wignerd_coefs(100);
72
73//std::vector<double> ComputeWignerD(int N, double theta)
74//{
75// assert(std::abs(theta) <= std::numbers::pi);
76// // length can be computed as
77// // \sum_{n=0}^{N-1} \sum_{m=0}^n \sum_{k=-n}^n (1) = N * (1 + N) * (4 * N - 1) / 6
78// std::vector<double> D(N * (1 + N) * (4 * N - 1) / 6);
79//
80// // theta must be in [0;pi/2], other cases are considered using properties of d-matrix
81// // see properties here: https://en.wikipedia.org/wiki/Wigner_D-matrix
82// bool isnegative = theta < 0;
83// theta = std::abs(theta);
84//
85// bool closetopi = false;
86// if (theta > std::numbers::pi / 2)
87// {
88// theta = std::numbers::pi - theta;
89// closetopi = true;
90// }
91//
92// if (fabs(theta) < 1e-3)
93// {
94// int idx0, idx1, idx2;
95// for (int n = 0; n < N; ++n)
96// {
97// idx0 = n * (1 + n) * (4 * n - 1) / 6;
98// idx1 = 2 * n + 1;
99// for (int m = 0; m <= n; ++m)
100// {
101// idx2 = idx0 + idx1 * m + n;
102// D[idx2 + m] = 1.0;
103// }
104// }
105// }
106// else
107// {
108// const auto& [wignerdcoef_1,wignerdcoef_2] = wignerdcoefs;
109// auto Pnm = ComputePnm(N, theta);
110//
111// int idx0, idx1, idx2, idx_pred;
112// for (int n = 0; n < N; ++n)
113// {
114// idx1 = n + n * (1 + n) * (4 * n - 1) / 6;
115// for (int k = -n; k < 0; ++k)
116// {
117// D[idx1 + k] = ni(k) * Pnm(n, std::abs(k)) * detail::Knm(n,std::abs(k));
118// }
119// for (int k = 0; k <= n; ++k)
120// {
121// D[idx1 + k] = Pnm(n, std::abs(k)) * detail::Knm(n,std::abs(k));
122// }
123// }
124//
125// double x = sin(theta) / (1.0 + cos(theta));
126// for (int n = 0; n < N; ++n)
127// {
128// idx0 = n * (1 + n) * (4 * n - 1) / 6;
129// idx1 = 2 * n + 1;
130// const auto& c1 = wignerdcoef_1[n];
131// const auto& c2 = wignerdcoef_2[n];
132// for (int m = 0; m <= n - 1; ++m)
133// {
134// int idxc = idx1 * (m + 1) + n;
135// idx2 = idx0 + idxc;
136// idx_pred = idx0 + idx1 * m + n;
137//
138// D[idx2 - n] = x * c2[idxc - n] * D[idx_pred - n];
139// for (int k = -n + 1; k <= n; ++k)
140// {
141// D[idx2 + k] = c1[idxc + k] * D[idx_pred + k - 1] + c2[idxc + k] * x * D[idx_pred + k];
142// }
143//
144// }
145// }
146// }
147//
148// int idx0, idx1, idx2;
149// auto Dcopy = D;
150// if (closetopi) // D^n_{m,k}(pi - theta) = (-1)^(n+m) * D^n_{m,-k}(theta)
151// {
152// for (int n = 1; n < N; ++n)
153// {
154// idx0 = n * (1 + n) * (4 * n - 1) / 6;
155// idx1 = 2 * n + 1;
156// for (int m = 0; m <= n; ++m)
157// {
158// idx2 = idx0 + idx1 * m + n;
159// for (int k = -n; k <= n; ++k)
160// {
161// D[idx2 + k] = ni(n + m) * Dcopy[idx2 - k];
162// }
163// }
164// }
165// }
166//
167// for (int n = 1; n < N; ++n)
168// {
169// idx0 = n * (1 + n) * (4 * n - 1) / 6;
170// idx1 = 2 * n + 1;
171// for (int m = 0; m <= n; ++m)
172// {
173// idx2 = idx0 + idx1 * m + n;
174// for (int k = -n; k <= n; ++k)
175// {
176// // define eps(m) = (-1)^(m-|m|)/2, 1/eps(m) = eps(m),
177// // because Ynm in Laplace FMM is different from Ynm in physics (denote it Ynm_p)
178// // and they are connected as Ynm_p = eps(m) * sqrt((2n+1)/4pi) * Ynm,
179// // Wigner D-matrix are introduced for Ynm_p such that Ynm_p = sum_k Ynk_p*Dnkm,
180// // or equivalently eps(m)*Ynm = sum_k eps(k)*Ynk*Dnkm <=> Ynm = sum_k Ynk*[eps(m)*eps(k)*Dnkm]
181// // so Wigner D-matrix in fmm should have multiplier eps(m)*eps(k)
182// // since we consider only m>=0, we have only eps(k) left (eps(m) = 1 for m >= 0)
183// D[idx2 + k] *= ni((k - std::abs(k)) / 2);
184//
185// if (isnegative) // D^n_{m,k}(-theta) = (-1)^(m - k) * D^n_{m,k}(theta)
186// D[idx2 + k] *= ni(m - k);
187// }
188// }
189// }
190//
191// return D;
192//}
193
194std::vector<double> compute_wignerd_coef(int N)
195{
196 std::vector<double> g(N * (N + 1) / 2);
197 g[0] = 1;
198 for (int n = 1; n < N; ++n)
199 {
200 g[n * (n + 1) / 2] = sqrt((2 * n - 1.) / (2 * n)) * g[n * (n - 1) / 2];
201 for (int m = 1; m <= n; ++m)
202 {
203 g[n * (n + 1) / 2 + m] = sqrt((n - m + 1.) / (n + m)) * g[n * (n + 1) / 2 + m - 1];
204 }
205 }
206 return g;
207}
208
210
211std::vector<double> ComputeWignerD(int N, double theta)
212{
213 assert(std::abs(theta) <= std::numbers::pi);
214 // size can be computed as
215 // \sum_{n=0}^{N-1} \sum_{m=0}^n \sum_{k=-n}^n (1) = N * (1 + N) * (4 * N - 1) / 6
216 // linear index (n,m,k) can be found as
217 // \sum_{n'=0}^{n-1} \sum_{m=0}^{n'} \sum_{k=-n'}^{n'} 1 +
218 // + \sum_{m'=0}^{m-1} \sum_{k=-n}^n 1 +
219 // + \sum_{k'=-n}^{k-1} 1 =
220 // = 1/6 n (1 + n) (-1 + 4 n) + m (1 + 2 n) + (k + n)
221 std::vector<double> D(N * (1 + N) * (4 * N - 1) / 6);
222
223 // theta must be in [0;pi/2], other cases are considered using properties of d-matrix
224 // see properties here: https://en.wikipedia.org/wiki/Wigner_D-matrix
225 bool isnegative = theta < 0;
226 theta = std::abs(theta);
227
228 bool closetopi = false;
229 if (theta > std::numbers::pi / 2)
230 {
231 theta = std::numbers::pi - theta;
232 closetopi = true;
233 }
234
235 if (fabs(theta) < 1e-3) // theta = 0 => d^n_mm = 1, other zero
236 {
237 int idx0, idx1, idx2;
238 for (int n = 0; n < N; ++n)
239 {
240 idx0 = n * (1 + n) * (4 * n - 1) / 6 + n;
241 idx1 = 2 * n + 1;
242 for (int m = 0; m <= n; ++m)
243 {
244 idx2 = idx0 + idx1 * m;
245 D[idx2 + m] = 1.0;
246 }
247 }
248 }
249 else
250 {
251 const auto& g = wignerd_auxillary_coef;
252 int idx0, idx1, idx2, idx3;
253 double s = sin(theta);
254 double c = 1.0 + cos(theta);
255 double f = s / c;
256 std::vector<double> sn(2 * N), cn(2 * N);
257 sn[N] = cn[N] = 1.0;
258 for (int i = 1; i < N; ++i)
259 {
260 sn[N + i] = sn[N + i - 1] * s;
261 sn[N - i] = sn[N - i + 1] / s;
262 cn[N + i] = cn[N + i - 1] * c;
263 cn[N - i] = cn[N - i + 1] / c;
264 }
265
266
267 // 1) compute d^n_mn
268 for (int n = 0; n < N; ++n)
269 {
270 idx0 = n * (1 + n) * (4 * n - 1) / 6 + 2 * n;
271 idx1 = 1 + 2 * n;
272 for (int m = 0; m <= n; ++m)
273 {
274 D[idx0 + m * idx1] = ni(n+m) * g[n * (n + 1) / 2 + m] * cn[N + m] * sn[N + n - m];
275 }
276 }
277
278 // 2) compute d^n_n,m-1
279 for (int n = 0; n < N; ++n)
280 {
281 idx0 = n * (1 + n) * (4 * n - 1) / 6 + n * (2 * n + 1) + n;
282 for (int m = n; m > -n; --m)
283 {
284 D[idx0 + m - 1] = (n + m) / sqrt(n * (n + 1.) - m * (m - 1.)) * f * D[idx0 + m];
285 }
286 }
287
288 // 3) compute d^n_m,k-1
289 for (int n = 0; n < N; ++n)
290 {
291 idx0 = n * (1 + n) * (4 * n - 1) / 6 + n;
292 idx1 = 2 * n + 1;
293 for (int m = n - 1; m >= 0; --m)
294 {
295 idx2 = m * idx1;
296 idx3 = (m + 1) * idx1;
297 for (int k = n; k > -n; --k)
298 {
299 D[idx0 + idx2 + k - 1] = sqrt((n * (n + 1.) - m * (m + 1.)) / (n * (n + 1.) - k * (k - 1.))) * D[idx0 + idx3 + k] +
300 (m + k) / sqrt(n * (n + 1.) - k * (k - 1.)) * f * D[idx0 + idx2 + k];
301 }
302 }
303 }
304
305 }
306
307 int idx0, idx1, idx2;
308 auto Dcopy = D;
309 if (closetopi) // D^n_{m,k}(pi - theta) = (-1)^(n+m) * D^n_{m,-k}(theta)
310 {
311 for (int n = 1; n < N; ++n)
312 {
313 idx0 = n * (1 + n) * (4 * n - 1) / 6;
314 idx1 = 2 * n + 1;
315 for (int m = 0; m <= n; ++m)
316 {
317 idx2 = idx0 + idx1 * m + n;
318 for (int k = -n; k <= n; ++k)
319 {
320 D[idx2 + k] = ni(n + m) * Dcopy[idx2 - k];
321 }
322 }
323 }
324 }
325
326 for (int n = 1; n < N; ++n)
327 {
328 idx0 = n * (1 + n) * (4 * n - 1) / 6;
329 idx1 = 2 * n + 1;
330 for (int m = 0; m <= n; ++m)
331 {
332 idx2 = idx0 + idx1 * m + n;
333 for (int k = -n; k <= n; ++k)
334 {
335 // define eps(m) = (-1)^(m-|m|)/2, 1/eps(m) = eps(m),
336 // because Ynm in Laplace FMM is different from Ynm in physics (denote it Ynm_p)
337 // and they are connected as Ynm_p = eps(m) * sqrt((2n+1)/4pi) * Ynm,
338 // Wigner D-matrix are introduced for Ynm_p such that Ynm_p = sum_k Ynk_p*Dnkm,
339 // or equivalently eps(m)*Ynm = sum_k eps(k)*Ynk*Dnkm <=> Ynm = sum_k Ynk*[eps(m)*eps(k)*Dnkm]
340 // so Wigner D-matrix in fmm should have multiplier eps(m)*eps(k)
341 // since we consider only m>=0, we have only eps(k) left (eps(m) = 1 for m >= 0)
342 D[idx2 + k] *= ni((k - std::abs(k)) / 2);
343
344 if (isnegative) // D^n_{m,k}(-theta) = (-1)^(m - k) * D^n_{m,k}(theta)
345 D[idx2 + k] *= ni(m - k);
346 }
347 }
348 }
349
350 return D;
351}
352
353std::vector<std::complex<double>> ComputeExp(int N, double phi)
354{
355 std::vector<std::complex<double>> res(N);
356 for (int i = 0; i < N; ++i)
357 res[i] = std::complex<double>(cos(i * phi), sin(i * phi));
358 return res;
359}
360
361namespace detail {
362
364{
365 for (int i = -3; i < 4; ++i)
366 for (int j = -3; j < 4; ++j)
367 for (int k = -3; k < 4; ++k) {
368 if (std::abs(i) <= 1 && std::abs(j) <= 1 && std::abs(k) <= 1)
369 continue;
370 auto rho = DecToSph(Vector3d(i, j, k));
371 auto theta = rho[1];
372 auto phi = rho[2];
373 auto key = doublehash(theta);
374 dmatrix[key] = ComputeWignerD(N, theta);
375 dmatrix[-key] = ComputeWignerD(N, -theta);
376 key = doublehash(phi);
377 rotation_exponents[key] = ComputeExp(N, phi);
378 rotation_exponents[-key] = ComputeExp(N, -phi);
379 }
380 //std::cout << dmatrix.size() << std::endl;
381}
382
383} // detail
384
385} // fmm
constexpr anm_wrapper< _3d_MAX_MULTIPOLE_NUM > Anm
std::unordered_map< int, std::vector< double > > dmatrix
constexpr knm_wrapper< _3d_MAX_MULTIPOLE_NUM > Knm
std::unordered_map< int, std::vector< std::complex< double > > > rotation_exponents
void InitMathConstants(int N)
Definition avx.h:5
std::vector< double > ComputeWignerD(int N, double theta)
constexpr double ni(int n)
Definition simple_math.h:99
std::vector< double > compute_wignerd_coef(int N)
Vector3d DecToSph(const Vector3d &vec)
treevector< double > ComputeKnm(int N)
std::vector< std::complex< double > > ComputeExp(int N, double phi)
int doublehash(double val)
Definition utils.h:124
treevector< double > ComputeAnm(int N)
Vector3< double > Vector3d
Definition utils.h:63
const auto wignerd_auxillary_coef