MathUtils
A library of mathematical utilities.
Loading...
Searching...
No Matches
funs.hpp
Go to the documentation of this file.
1#pragma once
2
7
10#include "mathutils/spherical_harmonics_index_lookup_table.hpp"
11#include <Eigen/Dense>
12#include <array>
13#include <cmath>
14#include <complex>
15#include <coroutine>
16#include <cstdint>
17#include <stdexcept>
18
21// Simple non-vectorized functions //
24namespace mathutils {
32template <typename T> inline int sign(T x) { return (x > T{0}) - (x < T{0}); }
33
34} // namespace mathutils
40
43// Special functions ////////////////
46namespace mathutils {
47namespace special {
48
52inline int minus_one_to_int_pow(int n) {
53 // Returns -1 if n is odd, 1 if n is even
54 return 1 - 2 * (n & 1);
55}
56
64inline double log_factorial(uint64_t n) {
65 return (n < LOG_FACTORIAL_LOOKUP_TABLE_SIZE)
66 ? LOG_FACTORIAL_LOOKUP_TABLE[n]
67 : std::lgamma(static_cast<double>(n + 1)); // lgamma(n+1) = log(n!)
68}
69
78template <typename T> inline double Heaviside(T x) {
79 return 0.5 + 0.5 * ((x > T{0}) - (x < T{0}));
80}
81
93template <typename T> inline double ReLog(const T &x) {
94 return (x == T{0}) ? -std::numeric_limits<double>::infinity()
95 : std::log(std::abs(x));
96}
97
104template <typename T>
105inline std::pair<double, int> ReLogRe_ImLogRe_over_pi(const T &x) {
106 if constexpr (std::is_floating_point_v<T> || std::is_integral_v<T>) {
107 // return (x == T{0}) ? {-std::numeric_limits<double>::infinity(), 0}
108 // : {std::log(std::abs(x)), static_cast<int>(x < T{0})};
109 return (x == T{0})
110 ? std::make_pair(-std::numeric_limits<double>::infinity(), 0)
111 : std::make_pair(std::log(std::abs(x)),
112 static_cast<int>(x < T{0}));
113 } else {
114 double Re_x = static_cast<double>(x);
115 // return (Re_x == 0.0)
116 // ? {-std::numeric_limits<double>::infinity(), 0}
117 // : {std::log(std::abs(Re_x)), static_cast<int>(Re_x < 0.0)};
118 return (Re_x == 0.0)
119 ? std::make_pair(-std::numeric_limits<double>::infinity(), 0)
120 : std::make_pair(std::log(std::abs(Re_x)),
121 static_cast<int>(Re_x < 0.0));
122 }
123}
124
125template <typename T>
126inline std::pair<double, int> ReLogRe_ImLogRe_over_pi_of_x_to_pow(const T &x,
127 int n) {
128 if constexpr (std::is_floating_point_v<T> || std::is_integral_v<T>) {
129 return (n == 0) ? std::make_pair(0.0, 0)
130 : (x == T{0})
131 ? std::make_pair(-n * std::numeric_limits<double>::infinity(), 0)
132 : std::make_pair(n * std::log(std::abs(x)),
133 static_cast<int>(x < T{0} && (n & 1)));
134 } else {
135 double Re_x = static_cast<double>(x);
136 return (n == 0) ? std::make_pair(0.0, 0)
137 : (Re_x == 0.0)
138 ? std::make_pair(-n * std::numeric_limits<double>::infinity(), 0)
139 : std::make_pair(n * std::log(std::abs(Re_x)),
140 static_cast<int>(Re_x < 0.0 && (n & 1)));
141 }
142}
143
145// Spherical harmonics //
147
156inline int spherical_harmonic_index_n_LM(int l, int m) {
157 if (m < -l || m > l) {
158 throw std::out_of_range("m must be in the range [-l, l]");
159 }
160 if (l < 0) {
161 throw std::out_of_range("l must be non-negative");
162 }
163 return l * (l + 1) + m;
164}
165
174inline std::array<int, 2> spherical_harmonic_index_lm_N(int n) {
175 if (n < 0) {
176 throw std::out_of_range("n must be non-negative");
177 }
178 if (n > SPHERICAL_HARMONIC_INDEX_N_MAX) {
179 int l = static_cast<int>(std::floor(std::sqrt(n)));
180 return {l, n - l * (l + 1)};
181 }
182 return {SPHERICAL_HARMONIC_INDEX_LM_N_LOOKUP_TABLE[n][0],
183 SPHERICAL_HARMONIC_INDEX_LM_N_LOOKUP_TABLE[n][1]};
184}
185
195inline double reduced_spherical_Pmm(int m, double theta) {
196 // if (theta == 0.0) {
197 // if (m == 0) {
198 // return 1.0 / std::sqrt(4 * M_PI);
199 // }
200 // return 0.0;
201 // }
202 // double sigma = (1 - 2 * (m & 1)); // this is just std::pow(-1, m);
203 // return sigma * std::exp(m * std::log(std::sin(theta)) -
204 // 0.5 * std::log(4 * M_PI) - m * std::log(2) +
205 // 0.5 * std::lgamma(2 * m + 2) - std::lgamma(m + 1));
206 return (theta == 0.0)
207 ? (m == 0) / std::sqrt(4 * M_PI)
208 : (1 - 2 * (m & 1)) *
209 std::exp(m * std::log(std::sin(theta)) -
210 0.5 * std::log(4 * M_PI) - m * std::log(2) +
211 0.5 * std::lgamma(2 * m + 2) - std::lgamma(m + 1));
212}
213
214// inline std::pair<double, double>
215// init_Plm_recursion_three_term_upward_ell(int m, double theta) {
216// double P_m_m =
217// (theta == 0.0)
218// ? (m == 0) / std::sqrt(4 * M_PI)
219// : (1 - 2 * (m & 1)) *
220// std::exp(m * std::log(std::sin(theta)) -
221// 0.5 * std::log(4 * M_PI) - m * std::log(2) +
222// 0.5 * std::lgamma(2 * m + 2) - std::lgamma(m + 1));
223// double P_m_plus_one_m = std::sqrt(2 * m + 3) * std::cos(theta) * P_m_m;
224// return {P_m_m, P_m_plus_one_m};
225// }
226
235 int l_end,
236 double theta) {
237 if (l_start < 0 || l_end < l_start) {
238 co_return;
239 }
240 int m = l_start;
241 double P_ell_minus_one_m = reduced_spherical_Pmm(m, theta);
242 // ell = l_start
243 co_yield P_ell_minus_one_m;
244 if (l_start == l_end) {
245 co_return;
246 }
247 double cos_theta = std::cos(theta);
248 double P_ell_m = cos_theta * std::sqrt(2 * m + 3) * P_ell_minus_one_m;
249 // ell = l_start + 1
250 co_yield P_ell_m;
251 for (int ell = m + 2; ell <= l_end; ++ell) {
252 const double den_ell_m = (ell - m) * 1.0 * (ell + m);
253 const double c_ell_m =
254 std::sqrt(((2.0 * ell - 1.0) * (2.0 * ell + 1.0)) / den_ell_m);
255 const double den_ell_minus_one_m =
256 (ell - m) * 1.0 * (ell + m) * (2.0 * ell - 3.0);
257 const double c_ell_minus_one_m =
258 std::sqrt(((ell - m - 1.0) * (ell + m - 1.0) * (2.0 * ell + 1.0)) /
259 den_ell_minus_one_m);
260 const double q =
261 cos_theta * c_ell_m * P_ell_m - c_ell_minus_one_m * P_ell_minus_one_m;
262 P_ell_minus_one_m = P_ell_m;
263 P_ell_m = q;
264 co_yield P_ell_m;
265 }
266}
267
269enumerate_reduced_spherical_Plm_recursion_three_term_upward_ell(int l_start,
270 int l_end,
271 double theta) {
272 if (l_start < 0 || l_end < l_start) {
273 co_return;
274 }
275 int m = l_start;
276 double P_ell_minus_one_m = reduced_spherical_Pmm(m, theta);
277 // ell = l_start
278 co_yield {l_start, P_ell_minus_one_m};
279 if (l_start == l_end) {
280 co_return;
281 }
282 double cos_theta = std::cos(theta);
283 double P_ell_m = cos_theta * std::sqrt(2 * m + 3) * P_ell_minus_one_m;
284 // ell = l_start + 1
285 co_yield {l_start + 1, P_ell_m};
286 for (int ell = m + 2; ell <= l_end; ++ell) {
287 const double den_ell_m = (ell - m) * 1.0 * (ell + m);
288 const double c_ell_m =
289 std::sqrt(((2.0 * ell - 1.0) * (2.0 * ell + 1.0)) / den_ell_m);
290 const double den_ell_minus_one_m =
291 (ell - m) * 1.0 * (ell + m) * (2.0 * ell - 3.0);
292 const double c_ell_minus_one_m =
293 std::sqrt(((ell - m - 1.0) * (ell + m - 1.0) * (2.0 * ell + 1.0)) /
294 den_ell_minus_one_m);
295 const double q =
296 cos_theta * c_ell_m * P_ell_m - c_ell_minus_one_m * P_ell_minus_one_m;
297 P_ell_minus_one_m = P_ell_m;
298 P_ell_m = q;
299 co_yield {ell, P_ell_m};
300 }
301}
302
313inline double reduced_spherical_Plm(int l, int m, double theta) {
314 // double cos_theta = std::cos(theta);
315 // // P_{m m}
316 // double P_ell_minus_one_m = reduced_spherical_Pmm(m, theta);
317 // if (l == m) {
318 // return P_ell_minus_one_m;
319 // }
320 // // P_{m+1 m}
321 // double P_ell_m = cos_theta * std::sqrt(2 * m + 3) * P_ell_minus_one_m;
322 // for (int ell = m + 2; ell <= l; ++ell) {
323 // // double q =
324 // // cos_theta *
325 // // std::sqrt((2 * ell - 1) * (2 * ell + 1) / ((ell - m) * (ell +
326 // // m))) * P_ell_m -
327 // // std::sqrt((ell - m - 1) * (ell + m - 1) * (2 * ell + 1) /
328 // // ((ell - m) * (ell + m) * (2 * ell - 3))) *
329 // // P_ell_minus_one_m;
330 // const double den_ell_m = (ell - m) * 1.0 * (ell + m);
331 // const double c_ell_m =
332 // std::sqrt(((2.0 * ell - 1.0) * (2.0 * ell + 1.0)) / den_ell_m);
333 // const double den_ell_minus_one_m =
334 // (ell - m) * 1.0 * (ell + m) * (2.0 * ell - 3.0);
335 // const double c_ell_minus_one_m =
336 // std::sqrt(((ell - m - 1.0) * (ell + m - 1.0) * (2.0 * ell + 1.0)) /
337 // den_ell_minus_one_m);
338 // const double q =
339 // cos_theta * c_ell_m * P_ell_m - c_ell_minus_one_m *
340 // P_ell_minus_one_m;
341 // P_ell_minus_one_m = P_ell_m;
342 // P_ell_m = q;
343 // }
344 // return P_ell_m;
345 double P_ell_m{0.0};
347 m, l, theta)) {
348 // ell_start = m
349 // ell_end = l
350 P_ell_m = P;
351 }
352 return P_ell_m;
353}
354
365inline double spherical_Plm(int l, int m, double theta) {
366 int abs_m = std::abs(m);
367 // int sigma = 1;
368 // if (m < 0) {
369 // sigma *= std::pow(-1, m);
370 // }
371 // if (theta > M_PI_2) {
372 // sigma *= std::pow(-1, l-abs_m);
373 // }
374 // sign difference between spherical_Plm and reduced_spherical_Plm
375 // int sigma =
376 // ((m < 0) && (m & 1)) ^ ((theta > M_PI_2) && ((l - abs_m) & 1)) ? -1 :
377 // 1;
378 // // transform theta to [0, pi/2]
379 // theta = std::min(theta, M_PI - theta);
380 // m = abs_m;
381 // return sigma * reduced_spherical_Plm(l, abs_m, theta);
382 return ((m < 0) && (m & 1)) ^ ((theta > M_PI_2) && ((l - abs_m) & 1))
383 ? -reduced_spherical_Plm(l, abs_m, std::min(theta, M_PI - theta))
384 : reduced_spherical_Plm(l, abs_m, std::min(theta, M_PI - theta));
385}
386
397inline double recursive_real_Ylm(int l, int m, double theta, double phi) {
398 // int abs_m = std::abs(m);
399 // double Plm = spherical_Plm(l, abs_m, theta);
400 // if (m < 0) {
401 // return (1 - 2 * (m & 1)) * std::sqrt(2) * std::sin(abs_m * phi) * Plm;
402 // } else if (m > 0) {
403 // return (1 - 2 * (m & 1)) * std::sqrt(2) * std::cos(abs_m * phi) * Plm;
404 // }
405 // // m == 0
406 // return Plm;
407 if (m < 0) {
408 return -std::sqrt(2) * std::sin(m * phi) * spherical_Plm(l, m, theta);
409 } else if (m > 0) {
410 return (1 - 2 * (m & 1)) * std::sqrt(2) * std::cos(m * phi) *
411 spherical_Plm(l, m, theta);
412 }
413 // m == 0
414 return spherical_Plm(l, m, theta);
415}
416
427inline double real_Ylm(int l, int m, double theta, double phi) {
428 // check l and m
429 if (l < 0) {
430 throw std::out_of_range("l must be non-negative");
431 }
432 if (m < -l || m > l) {
433 throw std::out_of_range("m must be in the range [-l, l]");
434 }
435 if (theta < 0 || theta > M_PI) {
436 throw std::out_of_range("theta must be in the range [0, pi]");
437 }
438 return recursive_real_Ylm(l, m, theta, phi);
439}
440
450Eigen::VectorXd real_Ylm(int l, int m,
451 const Eigen::MatrixXd &thetaphi_coord_P) {
452 // check l and m
453 if (l < 0) {
454 throw std::out_of_range("l must be non-negative");
455 }
456 if (m < -l || m > l) {
457 throw std::out_of_range("m must be in the range [-l, l]");
458 }
459 // check thetaphi_coord_P
460 if (thetaphi_coord_P.cols() != 2) {
461 throw std::invalid_argument("thetaphi_coord_P must have 2 columns");
462 }
463 Eigen::VectorXd result(thetaphi_coord_P.rows());
464 for (int i = 0; i < thetaphi_coord_P.rows(); ++i) {
465 double theta = thetaphi_coord_P(i, 0);
466 if (theta < 0 || theta > M_PI) {
467 throw std::out_of_range("theta must be in the range [0, pi]");
468 }
469 double phi = thetaphi_coord_P(i, 1);
470 result(i) = recursive_real_Ylm(l, m, theta, phi);
471 }
472 return result;
473}
474
486Eigen::MatrixXd compute_all_real_Ylm(int l_max,
487 const Eigen::MatrixXd &thetaphi_coord_P) {
488 // check l_max
489 if (l_max < 0) {
490 throw std::out_of_range("l_max must be non-negative");
491 }
492 // check thetaphi_coord_P
493 if (thetaphi_coord_P.cols() != 2) {
494 throw std::invalid_argument("thetaphi_coord_P must have 2 columns");
495 }
496 int num_points = thetaphi_coord_P.rows();
497 if (num_points <= 0) {
498 throw std::invalid_argument("thetaphi_coord_P must have at least one row");
499 }
501 int num_modes = l_max * (l_max + 2) + 1; // = n_LM(l_max, l_max)+1
502 Eigen::MatrixXd Y(num_points, num_modes);
503 Y.setZero();
504
505 for (int p{0}; p < num_points; ++p) {
506 double theta = thetaphi_coord_P(p, 0);
507 if (theta < 0 || theta > M_PI) {
508 throw std::out_of_range("theta must be in the range [0, pi]");
509 }
510 double phi = thetaphi_coord_P(p, 1);
511 double reduced_theta =
512 std::min(theta, M_PI - theta); // transform theta to [0, pi/2]
513 // m = 0
514 for (auto [l, reduced_P] :
515 enumerate_reduced_spherical_Plm_recursion_three_term_upward_ell(
516 0, l_max, reduced_theta)) {
517 int n = l * (l + 1);
518 Y(p, n) = ((theta > M_PI_2) && (l & 1)) ? -reduced_P : reduced_P;
519 }
520 // m = +/-1, ... , +/-l_max
521 for (int m = 1; m <= l_max; ++m) {
522 for (auto [l, reduced_P] :
523 enumerate_reduced_spherical_Plm_recursion_three_term_upward_ell(
524 m, l_max, reduced_theta)) {
525 int n_plus = l * (l + 1) + m;
526 int n_minus = l * (l + 1) - m;
527 // Q = (-1)^m * sqrt(2) * P_{l|m|}(theta)
528 double Q = ((m & 1) ^ ((theta > M_PI_2) && ((l - m) & 1)))
529 ? -std::sqrt(2) * reduced_P
530 : std::sqrt(2) * reduced_P;
531 // Y_{l |m|} = (-1)^m * sqrt(2) * P_{l|m|}*cos(|m|*phi)
532 Y(p, n_plus) = std::cos(m * phi) * Q;
533 // Y_{l, -|m|} = (-1)^m * sqrt(2) * P_{l|m|}*sin(|m|*phi)
534 Y(p, n_minus) = std::sin(m * phi) * Q;
535 }
536 }
537 } // end for p
538 return Y;
539}
540
541Eigen::MatrixXcd compute_all_Ylm(int l_max,
542 const Eigen::MatrixXd &thetaphi_coord_P) {
543 // check l_max
544 if (l_max < 0) {
545 throw std::out_of_range("l_max must be non-negative");
546 }
547 // check thetaphi_coord_P
548 if (thetaphi_coord_P.cols() != 2) {
549 throw std::invalid_argument("thetaphi_coord_P must have 2 columns");
550 }
551 int num_points = thetaphi_coord_P.rows();
552 if (num_points <= 0) {
553 throw std::invalid_argument("thetaphi_coord_P must have at least one row");
554 }
556 int num_modes = l_max * (l_max + 2) + 1; // = n_LM(l_max, l_max)+1
557 Eigen::MatrixXcd Y(num_points, num_modes);
558 Y.setZero();
559
560 for (int p{0}; p < num_points; ++p) {
561 double theta = thetaphi_coord_P(p, 0);
562 if (theta < 0 || theta > M_PI) {
563 throw std::out_of_range("theta must be in the range [0, pi]");
564 }
565 double phi = thetaphi_coord_P(p, 1);
566 double reduced_theta =
567 std::min(theta, M_PI - theta); // transform theta to [0, pi/2]
568 // m = 0
569 for (auto [l, reduced_P] :
570 enumerate_reduced_spherical_Plm_recursion_three_term_upward_ell(
571 0, l_max, reduced_theta)) {
572 int n = l * (l + 1);
573 Y(p, n) = ((theta > M_PI_2) && (l & 1)) ? -reduced_P : reduced_P;
574 }
575 // m = +/-1, ... , +/-l_max
576 for (int m = 1; m <= l_max; ++m) {
577 for (auto [l, reduced_P] :
578 enumerate_reduced_spherical_Plm_recursion_three_term_upward_ell(
579 m, l_max, reduced_theta)) {
580 int n_plus = l * (l + 1) + m;
581 int n_minus = l * (l + 1) - m;
582 // Y_{l |m|} = P_{l|m|}*exp(i|m|phi)
583 Y(p, n_plus) =
584 ((theta > M_PI_2) && ((l - m) & 1))
585 ? -reduced_P * std::exp(std::complex<double>(0, m * phi))
586 : reduced_P * std::exp(std::complex<double>(0, m * phi));
587 // Y_{l, -|m|} = (-1)^m conj(Y_{l|m|})
588 Y(p, n_minus) =
589 (m & 1) ? -std::conj(Y(p, n_plus)) : std::conj(Y(p, n_plus));
590 }
591 }
592 } // end for p
593 return Y;
594}
595
606inline std::complex<double> recursive_Ylm(int l, int m, double theta,
607 double phi) {
608 return spherical_Plm(l, m, theta) *
609 std::exp(std::complex<double>(0, m * phi));
610}
611
622inline std::complex<double> Ylm(int l, int m, double theta, double phi) {
623 // check l and m
624 if (l < 0) {
625 throw std::out_of_range("l must be non-negative");
626 }
627 if (m < -l || m > l) {
628 throw std::out_of_range("m must be in the range [-l, l]");
629 }
630 if (theta < 0 || theta > M_PI) {
631 throw std::out_of_range("theta must be in the range [0, pi]");
632 }
633 return recursive_Ylm(l, m, theta, phi);
634}
635
645Eigen::VectorXcd Ylm(int l, int m, const Eigen::MatrixXd &thetaphi_coord_P) {
646
647 // check l and m
648 if (l < 0) {
649 throw std::out_of_range("l must be non-negative");
650 }
651 if (m < -l || m > l) {
652 throw std::out_of_range("m must be in the range [-l, l]");
653 }
654 // check thetaphi_coord_P
655 if (thetaphi_coord_P.cols() != 2) {
656 throw std::invalid_argument("thetaphi_coord_P must have 2 columns");
657 }
658 Eigen::VectorXcd result(thetaphi_coord_P.rows());
659 for (int i = 0; i < thetaphi_coord_P.rows(); ++i) {
660 double theta = thetaphi_coord_P(i, 0);
661 if (theta < 0 || theta > M_PI) {
662 throw std::out_of_range("theta must be in the range [0, pi]");
663 }
664 double phi = thetaphi_coord_P(i, 1);
665 result(i) = recursive_Ylm(l, m, theta, phi);
666 }
667 return result;
668}
669
674
675inline double series_real_Ylm(int l, int m, double theta, double phi) {
676 if (l < 0) {
677 throw std::out_of_range("l must be non-negative");
678 }
679 if (m < -l || m > l) {
680 throw std::out_of_range("m must be in the range [-l, l]");
681 }
682 if (theta < 0 || theta > M_PI) {
683 throw std::out_of_range("theta must be in the range [0, pi]");
684 }
685 int abs_m = std::abs(m);
686 double cos_theta = std::cos(theta);
687 double sin_theta = std::sin(theta);
688 auto [log_abs_cos_theta, arg_over_pi_cos_theta] =
689 ReLogRe_ImLogRe_over_pi(cos_theta);
690 auto [log_abs_sin_theta, arg_over_pi_sin_theta] =
691 ReLogRe_ImLogRe_over_pi(sin_theta);
692 double Xlm = 0.0;
693 for (int k{0}; k <= (l - abs_m) / 2; k++) {
694 int ImLog_over_pi_Xklm =
695 k + (l - abs_m) * arg_over_pi_cos_theta + abs_m * arg_over_pi_sin_theta;
696 double ReLog_Xklm =
697 -0.5 * std::log(4 * M_PI) - (abs_m + 2 * k) * std::log(2) +
698 0.5 * std::log(2 * l + 1) + 0.5 * log_factorial(l + abs_m) +
699 0.5 * log_factorial(l - abs_m) - log_factorial(l - abs_m - 2 * k) -
700 log_factorial(abs_m + k) - log_factorial(k);
701 // +
702 // (l - abs_m - 2 * k) * log_abs_cos_theta +
703 // (abs_m + 2 * k) * log_abs_sin_theta;
704 int pow_cos = (l - abs_m - 2 * k);
705 int pow_sin = (abs_m + 2 * k);
706 ReLog_Xklm += (pow_cos > 0) ? (pow_cos * log_abs_cos_theta) : 0.0;
707 ReLog_Xklm += (pow_sin > 0) ? (pow_sin * log_abs_sin_theta) : 0.0;
708 Xlm += minus_one_to_int_pow(ImLog_over_pi_Xklm) * std::exp(ReLog_Xklm);
709 }
710 return (m < 0) ? std::sqrt(2) * std::sin(abs_m * phi) * Xlm
711 : (m > 0) ? std::sqrt(2) * std::cos(abs_m * phi) * Xlm
712 : Xlm;
713}
714
715Eigen::VectorXd series_real_Ylm(int l, int m,
716 const Eigen::MatrixXd &thetaphi_coord_P) {
717
718 if (l < 0) {
719 throw std::out_of_range("l must be non-negative");
720 }
721 if (m < -l || m > l) {
722 throw std::out_of_range("m must be in the range [-l, l]");
723 }
724 if (thetaphi_coord_P.cols() != 2) {
725 throw std::invalid_argument("thetaphi_coord_P must have 2 columns");
726 }
727 int abs_m = std::abs(m);
728 int num_points = thetaphi_coord_P.rows();
729 Eigen::VectorXd Y(num_points);
730 Y.setZero();
731 for (int p = 0; p < num_points; ++p) {
732 double theta = thetaphi_coord_P(p, 0);
733 double phi = thetaphi_coord_P(p, 1);
734 if (theta < 0 || theta > M_PI) {
735 throw std::out_of_range("theta must be in the range [0, pi]");
736 }
737 double cos_theta = std::cos(theta);
738 double sin_theta = std::sin(theta);
739 auto [log_abs_cos_theta, arg_over_pi_cos_theta] =
740 ReLogRe_ImLogRe_over_pi(cos_theta);
741 auto [log_abs_sin_theta, arg_over_pi_sin_theta] =
742 ReLogRe_ImLogRe_over_pi(sin_theta);
743 double Xlm = 0.0;
744 for (int k{0}; k <= (l - abs_m) / 2; k++) {
745 int ImLog_over_pi_Xklm = k + (l - abs_m) * arg_over_pi_cos_theta +
746 abs_m * arg_over_pi_sin_theta;
747 double ReLog_Xklm =
748 -0.5 * std::log(4 * M_PI) - (abs_m + 2 * k) * std::log(2) +
749 0.5 * std::log(2 * l + 1) + 0.5 * log_factorial(l + abs_m) +
750 0.5 * log_factorial(l - abs_m) - log_factorial(l - abs_m - 2 * k) -
751 log_factorial(abs_m + k) - log_factorial(k);
752 // +
753 // (l - abs_m - 2 * k) * log_abs_cos_theta +
754 // (abs_m + 2 * k) * log_abs_sin_theta;
755 int pow_cos = (l - abs_m - 2 * k);
756 int pow_sin = (abs_m + 2 * k);
757 ReLog_Xklm += (pow_cos > 0) ? (pow_cos * log_abs_cos_theta) : 0.0;
758 ReLog_Xklm += (pow_sin > 0) ? (pow_sin * log_abs_sin_theta) : 0.0;
759 Xlm += minus_one_to_int_pow(ImLog_over_pi_Xklm) * std::exp(ReLog_Xklm);
760 }
761 Y(p) = (m < 0) ? std::sqrt(2) * std::sin(abs_m * phi) * Xlm
762 : (m > 0) ? std::sqrt(2) * std::cos(abs_m * phi) * Xlm
763 : Xlm; // m == 0
764 }
765 return Y;
766}
767
768inline std::complex<double> series_Ylm(int l, int m, double theta, double phi) {
769 if (l < 0) {
770 throw std::out_of_range("l must be non-negative");
771 }
772 if (m < -l || m > l) {
773 throw std::out_of_range("m must be in the range [-l, l]");
774 }
775 if (theta < 0 || theta > M_PI) {
776 throw std::out_of_range("theta must be in the range [0, pi]");
777 }
778 int abs_m = std::abs(m);
779 double cos_theta = std::cos(theta);
780 double sin_theta = std::sin(theta);
781 auto [log_abs_cos_theta, arg_over_pi_cos_theta] =
782 ReLogRe_ImLogRe_over_pi(cos_theta);
783 auto [log_abs_sin_theta, arg_over_pi_sin_theta] =
784 ReLogRe_ImLogRe_over_pi(sin_theta);
785 double Xlm = 0.0;
786 for (int k{0}; k <= (l - abs_m) / 2; k++) {
787 int ImLog_over_pi_Xklm =
788 k + (l - abs_m) * arg_over_pi_cos_theta + abs_m * arg_over_pi_sin_theta;
789 double ReLog_Xklm = -0.5 * std::log(4 * M_PI) - abs_m * std::log(2) +
790 0.5 * std::log(2 * l + 1) +
791 0.5 * log_factorial(l + abs_m) +
792 0.5 * log_factorial(l - abs_m) - 2 * k * std::log(2) -
793 log_factorial(l - abs_m - 2 * k) -
794 log_factorial(abs_m + k) - log_factorial(k);
795 int pow_cos = (l - abs_m - 2 * k);
796 int pow_sin = (abs_m + 2 * k);
797 ReLog_Xklm += (pow_cos > 0) ? (pow_cos * log_abs_cos_theta) : 0.0;
798 ReLog_Xklm += (pow_sin > 0) ? (pow_sin * log_abs_sin_theta) : 0.0;
799 Xlm += minus_one_to_int_pow(ImLog_over_pi_Xklm) * std::exp(ReLog_Xklm);
800 }
801 return (m < 0) ? std::exp(std::complex<double>(0, m * phi)) * Xlm
802 : (m > 0)
803 ? static_cast<std::complex<double>>(minus_one_to_int_pow(m)) *
804 std::exp(std::complex<double>(0, m * phi)) * Xlm
805 : static_cast<std::complex<double>>(Xlm);
806}
807
808Eigen::VectorXcd series_Ylm(int l, int m,
809 const Eigen::MatrixXd &thetaphi_coord_P) {
810
811 if (l < 0) {
812 throw std::out_of_range("l must be non-negative");
813 }
814 if (m < -l || m > l) {
815 throw std::out_of_range("m must be in the range [-l, l]");
816 }
817 if (thetaphi_coord_P.cols() != 2) {
818 throw std::invalid_argument("thetaphi_coord_P must have 2 columns");
819 }
820 int abs_m = std::abs(m);
821 int num_points = thetaphi_coord_P.rows();
822 Eigen::VectorXcd Y(num_points);
823 Y.setZero();
824 for (int p = 0; p < num_points; ++p) {
825 double theta = thetaphi_coord_P(p, 0);
826 double phi = thetaphi_coord_P(p, 1);
827 if (theta < 0 || theta > M_PI) {
828 throw std::out_of_range("theta must be in the range [0, pi]");
829 }
830 double cos_theta = std::cos(theta);
831 double sin_theta = std::sin(theta);
832 auto [log_abs_cos_theta, arg_over_pi_cos_theta] =
833 ReLogRe_ImLogRe_over_pi(cos_theta);
834 auto [log_abs_sin_theta, arg_over_pi_sin_theta] =
835 ReLogRe_ImLogRe_over_pi(sin_theta);
836 for (int k{0}; k <= (l - abs_m) / 2; k++) {
837 // adding (m + abs_m) / 2 here takes care of (-1)^m for m>0
838 int ImLog_over_pi_Xklm = (m + abs_m) / 2 + k +
839 (l - abs_m) * arg_over_pi_cos_theta +
840 abs_m * arg_over_pi_sin_theta;
841 double ReLog_Xklm =
842 -0.5 * std::log(4 * M_PI) - (abs_m + 2 * k) * std::log(2) +
843 0.5 * std::log(2 * l + 1) + 0.5 * log_factorial(l + abs_m) +
844 0.5 * log_factorial(l - abs_m) - log_factorial(l - abs_m - 2 * k) -
845 log_factorial(abs_m + k) - log_factorial(k);
846 // +
847 // (l - abs_m - 2 * k) * log_abs_cos_theta +
848 // (abs_m + 2 * k) * log_abs_sin_theta;
849 int pow_cos = (l - abs_m - 2 * k);
850 int pow_sin = (abs_m + 2 * k);
851 ReLog_Xklm += (pow_cos > 0) ? (pow_cos * log_abs_cos_theta) : 0.0;
852 ReLog_Xklm += (pow_sin > 0) ? (pow_sin * log_abs_sin_theta) : 0.0;
853 Y(p) += minus_one_to_int_pow(ImLog_over_pi_Xklm) * std::exp(ReLog_Xklm);
854 }
855 Y(p) *= std::exp(std::complex<double>(0, m * phi));
856 }
857 return Y;
858}
859
871Eigen::MatrixXd
873 const Eigen::MatrixXd &thetaphi_coord_P) {
874 // check l_max
875 if (l_max < 0) {
876 throw std::out_of_range("l_max must be non-negative");
877 }
878 // check thetaphi_coord_P
879 if (thetaphi_coord_P.cols() != 2) {
880 throw std::invalid_argument("thetaphi_coord_P must have 2 columns");
881 }
882 int num_points = thetaphi_coord_P.rows();
883 if (num_points <= 0) {
884 throw std::invalid_argument("thetaphi_coord_P must have at least one row");
885 }
887 int num_modes = l_max * (l_max + 2) + 1; // = n_LM(l_max, l_max)+1
888 Eigen::MatrixXd Y(num_points, num_modes);
889 Y.setZero();
890
891 for (int p{0}; p < num_points; ++p) {
892 double theta = thetaphi_coord_P(p, 0);
893 if (theta < 0 || theta > M_PI) {
894 throw std::out_of_range("theta must be in the range [0, pi]");
895 }
896 double phi = thetaphi_coord_P(p, 1);
897 double cos_theta = std::cos(theta);
898 double sin_theta = std::sin(theta);
899 // get ReLogRe_ImLogRe_over_pi for cos(theta) and sin(theta)
900 auto [log_abs_cos_theta, arg_over_pi_cos_theta] =
901 ReLogRe_ImLogRe_over_pi(cos_theta);
902 auto [log_abs_sin_theta, arg_over_pi_sin_theta] =
903 ReLogRe_ImLogRe_over_pi(sin_theta);
904 for (int l{0}; l <= l_max; l++) {
905 // m = 0 case
906 int n = l * (l + 1);
907 for (int k{0}; k <= l / 2; k++) {
908 int ImLog_over_pi_Xklm = k + l * arg_over_pi_cos_theta;
909 double ReLog_Xklm = -0.5 * std::log(4 * M_PI) - (2 * k) * std::log(2) +
910 0.5 * std::log(2 * l + 1) + 0.5 * log_factorial(l) +
911 0.5 * log_factorial(l) - log_factorial(l - 2 * k) -
913 // +
914 // (l - 2 * k) * log_abs_cos_theta +
915 // (2 * k) * log_abs_sin_theta;
916 int pow_cos = l - 2 * k;
917 int pow_sin = 2 * k;
918 ReLog_Xklm += (pow_cos > 0) ? (pow_cos * log_abs_cos_theta) : 0.0;
919 ReLog_Xklm += (pow_sin > 0) ? (pow_sin * log_abs_sin_theta) : 0.0;
920 Y(p, n) +=
921 minus_one_to_int_pow(ImLog_over_pi_Xklm) * std::exp(ReLog_Xklm);
922 }
923 for (int m{1}; m <= l; m++) {
924 int n_plus = l * (l + 1) + m;
925 int n_minus = l * (l + 1) - m;
926 for (int k{0}; k <= (l - m) / 2; k++) {
927 int ImLog_over_pi_Xklm =
928 k + (l - m) * arg_over_pi_cos_theta + m * arg_over_pi_sin_theta;
929 double ReLog_Xklm =
930 -0.5 * std::log(4 * M_PI) - (m + 2 * k) * std::log(2) +
931 0.5 * std::log(2 * l + 1) + 0.5 * log_factorial(l + m) +
932 0.5 * log_factorial(l - m) - log_factorial(l - m - 2 * k) -
933 log_factorial(m + k) - log_factorial(k);
934 // +
935 // (l - m - 2 * k) * log_abs_cos_theta +
936 // (m + 2 * k) * log_abs_sin_theta;
937 int pow_cos = (l - m - 2 * k);
938 int pow_sin = (m + 2 * k);
939 ReLog_Xklm += (pow_cos > 0) ? (pow_cos * log_abs_cos_theta) : 0.0;
940 ReLog_Xklm += (pow_sin > 0) ? (pow_sin * log_abs_sin_theta) : 0.0;
941 Y(p, n_plus) +=
942 minus_one_to_int_pow(ImLog_over_pi_Xklm) * std::exp(ReLog_Xklm);
943 }
944 Y(p, n_minus) = std::sqrt(2) * std::sin(m * phi) * Y(p, n_plus);
945 Y(p, n_plus) *= std::sqrt(2) * std::cos(m * phi);
946 }
947 }
948 }
949 return Y;
950}
951
963Eigen::MatrixXcd
964compute_all_series_Ylm(int l_max, const Eigen::MatrixXd &thetaphi_coord_P) {
965 // check l_max
966 if (l_max < 0) {
967 throw std::out_of_range("l_max must be non-negative");
968 }
969 // check thetaphi_coord_P
970 if (thetaphi_coord_P.cols() != 2) {
971 throw std::invalid_argument("thetaphi_coord_P must have 2 columns");
972 }
973 int num_points = thetaphi_coord_P.rows();
974 if (num_points <= 0) {
975 throw std::invalid_argument("thetaphi_coord_P must have at least one row");
976 }
978 int num_modes = l_max * (l_max + 2) + 1; // = n_LM(l_max, l_max)+1
979 Eigen::MatrixXcd Y(num_points, num_modes);
980 Y.setZero();
981
982 for (int p{0}; p < num_points; ++p) {
983 double theta = thetaphi_coord_P(p, 0);
984 if (theta < 0 || theta > M_PI) {
985 throw std::out_of_range("theta must be in the range [0, pi]");
986 }
987 double phi = thetaphi_coord_P(p, 1);
988 double cos_theta = std::cos(theta);
989 double sin_theta = std::sin(theta);
990 // get ReLogRe_ImLogRe_over_pi for cos(theta) and sin(theta)
991 auto [log_abs_cos_theta, arg_over_pi_cos_theta] =
992 ReLogRe_ImLogRe_over_pi(cos_theta);
993 auto [log_abs_sin_theta, arg_over_pi_sin_theta] =
994 ReLogRe_ImLogRe_over_pi(sin_theta);
995 for (int l{0}; l <= l_max; l++) {
996 // m = 0 case
997 int n = l * (l + 1);
998 for (int k{0}; k <= l / 2; k++) {
999 int ImLog_over_pi_Xklm = k + l * arg_over_pi_cos_theta;
1000 double ReLog_Xklm = -0.5 * std::log(4 * M_PI) - (2 * k) * std::log(2) +
1001 0.5 * std::log(2 * l + 1) + 0.5 * log_factorial(l) +
1002 0.5 * log_factorial(l) - log_factorial(l - 2 * k) -
1004 // +
1005 // (l - 2 * k) * log_abs_cos_theta +
1006 // (2 * k) * log_abs_sin_theta;
1007 int pow_cos = l - 2 * k;
1008 int pow_sin = 2 * k;
1009 ReLog_Xklm += (pow_cos > 0) ? (pow_cos * log_abs_cos_theta) : 0.0;
1010 ReLog_Xklm += (pow_sin > 0) ? (pow_sin * log_abs_sin_theta) : 0.0;
1011 Y(p, n) +=
1012 minus_one_to_int_pow(ImLog_over_pi_Xklm) * std::exp(ReLog_Xklm);
1013 }
1014 // m=-l,...,l
1015 for (int m{1}; m <= l; m++) {
1016 int n_plus = l * (l + 1) + m;
1017 int n_minus = l * (l + 1) - m;
1018 for (int k{0}; k <= (l - m) / 2; k++) {
1019 int ImLog_over_pi_Xklm =
1020 k + (l - m) * arg_over_pi_cos_theta + m * arg_over_pi_sin_theta;
1021 double ReLog_Xklm =
1022 -0.5 * std::log(4 * M_PI) - (m + 2 * k) * std::log(2) +
1023 0.5 * std::log(2 * l + 1) + 0.5 * log_factorial(l + m) +
1024 0.5 * log_factorial(l - m) - log_factorial(l - m - 2 * k) -
1025 log_factorial(m + k) - log_factorial(k);
1026 // +
1027 // (l - m - 2 * k) * log_abs_cos_theta +
1028 // (m + 2 * k) * log_abs_sin_theta;
1029 int pow_cos = (l - m - 2 * k);
1030 int pow_sin = (m + 2 * k);
1031 ReLog_Xklm += (pow_cos > 0) ? (pow_cos * log_abs_cos_theta) : 0.0;
1032 ReLog_Xklm += (pow_sin > 0) ? (pow_sin * log_abs_sin_theta) : 0.0;
1033 Y(p, n_plus) +=
1034 minus_one_to_int_pow(ImLog_over_pi_Xklm) * std::exp(ReLog_Xklm);
1035 }
1036 Y(p, n_minus) =
1037 Y(p, n_plus) * std::exp(std::complex<double>(0, -m * phi));
1038 Y(p, n_plus) *=
1039 static_cast<std::complex<double>>(minus_one_to_int_pow(m)) *
1040 std::exp(std::complex<double>(0, m * phi));
1041 }
1042 }
1043 }
1044 return Y;
1045}
1046
1054inline double phi_independent_Ylm(int l, int m, double theta) {
1055 double epsilon = 1e-8;
1056 double minus_log4 = std::log(0.25);
1057
1058 int abs_m = std::abs(m);
1059
1060 int ell_minus_m = l - abs_m;
1061 double cos_theta = std::cos(theta);
1062 double abs_cos_theta = std::abs(cos_theta);
1063 // double sin_theta = std::sin(theta); // always positive for theta in [0, pi]
1064 double abs_sin_theta = std::sin(theta); // std::abs(sin_theta);
1065
1066 double rlm = 1.0 / (2.0 * std::sqrt(M_PI));
1067 if ((m > 0) && (abs_m % 2 == 1)) {
1068 rlm *= -1;
1069 }
1070 // if ((m < 0) && (abs_m % 2 == 1)) {
1071 // rlm *= -1;
1072 // }
1073 // if (abs_m % 2 == 1) {
1074 // rlm *= -1;
1075 // }
1076 if ((cos_theta < 0) && (ell_minus_m % 2 == 1)) {
1077 rlm *= -1;
1078 }
1079 int k_bound = (ell_minus_m) / 2;
1080 int sk = 1;
1081 // double log_qk;
1082 // double sum_Qk;
1083 double log_qk = 0.5 * std::log(2 * l + 1) + 0.5 * log_factorial(l + abs_m) -
1084 abs_m * std::log(2) - 0.5 * log_factorial(ell_minus_m) -
1085 log_factorial(abs_m);
1086
1092 // If cos(theta) ~ +/-sin(theta) replace sin(theta) with tan(theta)
1093 if (std::abs(abs_cos_theta - abs_sin_theta) < epsilon) {
1094 double tan_theta = std::tan(theta);
1095 double abs_tan_theta = std::abs(tan_theta);
1096 double log_abs_cos_theta = std::log(abs_cos_theta);
1097 double log_abs_tan_theta = std::log(abs_tan_theta);
1098 log_qk += l * log_abs_cos_theta + abs_m * log_abs_tan_theta;
1099 double sum_Qk = sk * std::exp(log_qk);
1100 for (int k = 1; k <= k_bound; ++k) {
1101 sk *= -1;
1102 log_qk += 2 * log_abs_tan_theta;
1103 log_qk += minus_log4 + std::log(ell_minus_m - 2 * k + 2) +
1104 std::log(ell_minus_m - 2 * k + 1) - std::log(abs_m + k) -
1105 std::log(k);
1106 sum_Qk += sk * std::exp(log_qk);
1107 }
1108 return rlm * sum_Qk;
1109 }
1110
1111 // if cos(theta) ~ 0, sin(theta) ~ 1
1112 if (abs_cos_theta < epsilon) {
1113 double log_abs_sin_theta = std::log(abs_sin_theta);
1114 log_qk += abs_m * log_abs_sin_theta;
1115 double abs_cos_theta_pow = std::pow(abs_cos_theta, ell_minus_m);
1116 double sum_Qk = sk * std::exp(log_qk) * abs_cos_theta_pow;
1117 for (int k = 1; k <= k_bound; ++k) {
1118 sk *= -1;
1119 log_qk += minus_log4 + std::log(ell_minus_m - 2 * k + 2) +
1120 std::log(ell_minus_m - 2 * k + 1) - std::log(abs_m + k) -
1121 std::log(k);
1122 abs_cos_theta_pow = std::pow(abs_cos_theta, ell_minus_m - 2 * k);
1123 sum_Qk += sk * std::exp(log_qk) * abs_cos_theta_pow;
1124 }
1125
1126 return rlm * sum_Qk;
1127 }
1128
1129 // if sin(theta) ~ 0, cos(theta) ~ +/-1
1130 if (abs_sin_theta < epsilon) {
1131 // double log_abs_cos_theta = std::log(abs_cos_theta);
1132 // log_qk += ell_minus_m * log_abs_cos_theta;
1133 // double abs_sin_theta_pow = std::pow(abs_sin_theta, abs_m);
1134 // double sum_Qk = sk * std::exp(log_qk);
1135 // for (int k = 1; k <= k_bound; ++k) {
1136 // sk *= -1;
1137 // log_qk += -2 * log_abs_cos_theta;
1138 // log_qk += minus_log4 + std::log(ell_minus_m - 2 * k + 2) +
1139 // std::log(ell_minus_m - 2 * k + 1) - std::log(abs_m + k) -
1140 // std::log(k);
1141 // abs_sin_theta_pow = std::pow(abs_sin_theta, abs_m + 2 * k);
1142 // sum_Qk += sk * std::exp(log_qk) * abs_sin_theta_pow;
1143 // }
1144 // return rlm * sum_Qk;
1145 // double log_abs_cos_theta = std::log(abs_cos_theta);
1146 // log_qk += ell_minus_m * log_abs_cos_theta;
1147 double trig_term =
1148 std::pow(abs_cos_theta, ell_minus_m) * std::pow(abs_sin_theta, abs_m);
1149 double sum_Qk = sk * std::exp(log_qk) * trig_term;
1150 for (int k = 1; k <= k_bound; ++k) {
1151 sk *= -1;
1152 log_qk += minus_log4 + std::log(ell_minus_m - 2 * k + 2) +
1153 std::log(ell_minus_m - 2 * k + 1) - std::log(abs_m + k) -
1154 std::log(k);
1155 trig_term = std::pow(abs_cos_theta, ell_minus_m - 2 * k) *
1156 std::pow(abs_sin_theta, abs_m + 2 * k);
1157 sum_Qk += sk * std::exp(log_qk) * trig_term;
1158 }
1159 return rlm * sum_Qk;
1160 }
1161
1167 // if log(cos(theta)) and log(sin(theta)) are defined
1168 double log_abs_cos_theta = std::log(abs_cos_theta);
1169 double log_abs_sin_theta = std::log(abs_sin_theta);
1170 log_qk += ell_minus_m * log_abs_cos_theta + abs_m * log_abs_sin_theta;
1171 double sum_Qk = sk * std::exp(log_qk);
1172 for (int k = 1; k <= k_bound; ++k) {
1173 sk *= -1;
1174 log_qk += -2 * log_abs_cos_theta + 2 * log_abs_sin_theta;
1175 log_qk += minus_log4 + std::log(ell_minus_m - 2 * k + 2) +
1176 std::log(ell_minus_m - 2 * k + 1) - std::log(abs_m + k) -
1177 std::log(k);
1178 sum_Qk += sk * std::exp(log_qk);
1179 }
1180
1181 return rlm * sum_Qk;
1182}
1183
1184inline std::complex<double> old_Ylm(int l, int m, double theta, double phi) {
1185 if (theta > M_PI || theta < 0.0) {
1186 throw std::out_of_range("theta must be in the range [0, pi]");
1187 }
1188 return std::exp(std::complex<double>(0, m * phi)) *
1189 phi_independent_Ylm(l, m, theta);
1190}
1191
1192inline double old_real_Ylm(int l, int m, double theta, double phi) {
1193
1194 if (theta > M_PI || theta < 0.0) {
1195 throw std::out_of_range("theta must be in the range [0, pi]");
1196 }
1197 int abs_m = std::abs(m);
1198 double val = phi_independent_Ylm(l, abs_m, theta);
1199 if (abs_m % 2 == 1) {
1200 val *= -1;
1201 }
1202 if (m < 0) {
1203 return val * std::sqrt(2) * std::sin(abs_m * phi);
1204 }
1205 if (m == 0) {
1206 return val;
1207 }
1208 return val * std::sqrt(2) * std::cos(abs_m * phi);
1209}
1210
1211Eigen::MatrixXd
1212old_compute_all_real_Ylm(int l_max, const Eigen::MatrixXd &thetaphi_coord_P) {
1213 // check l_max
1214 if (l_max < 0) {
1215 throw std::out_of_range("l_max must be non-negative");
1216 }
1217 // check thetaphi_coord_P
1218 if (thetaphi_coord_P.cols() != 2) {
1219 throw std::invalid_argument("thetaphi_coord_P must have 2 columns");
1220 }
1221 int num_points = thetaphi_coord_P.rows();
1222 if (num_points <= 0) {
1223 throw std::invalid_argument("thetaphi_coord_P must have at least one row");
1224 }
1226 int num_modes = l_max * (l_max + 2) + 1; // = n_LM(l_max, l_max)+1
1227 Eigen::MatrixXd Y(num_points, num_modes);
1228 Y.setZero();
1229 for (int l{0}; l <= l_max; l++) {
1230 for (int m{-l}; m <= l; m++) {
1231 int n = l * (l + 1) + m;
1232 for (int p{0}; p < num_points; ++p) {
1233 double theta = thetaphi_coord_P(p, 0);
1234 double phi = thetaphi_coord_P(p, 1);
1235 Y(p, n) = old_real_Ylm(l, m, theta, phi);
1236 }
1237 }
1238 }
1239 return Y;
1240}
1241
1242} // namespace special
1243} // namespace mathutils
Template class for a coroutine that yields values of type T.
Definition simple_generator.hpp:25
double real_Ylm(int l, int m, double theta, double phi)
Compute the real spherical harmonics for and .
Definition funs.hpp:427
std::complex< double > recursive_Ylm(int l, int m, double theta, double phi)
Compute the spherical harmonics for and .
Definition funs.hpp:606
double Heaviside(T x)
The Heaviside step function. Returns 0.0 for negative values, 1.0 for positive values,...
Definition funs.hpp:78
double phi_independent_Ylm(int l, int m, double theta)
Compute Ylm(theta, phi)/exp(i*theta*phi) for m>=0 and 0<=theta<=pi.
Definition funs.hpp:1054
Eigen::MatrixXd compute_all_series_real_Ylm(int l_max, const Eigen::MatrixXd &thetaphi_coord_P)
Compute real spherical harmonics for at the given coordinates.
Definition funs.hpp:872
int spherical_harmonic_index_n_LM(int l, int m)
Compute the index of (l,m) in [(0,0), (1,-1), (1,0), (1,1), (2,-2), ...].
Definition funs.hpp:156
std::complex< double > Ylm(int l, int m, double theta, double phi)
Compute the spherical harmonics for and .
Definition funs.hpp:622
double spherical_Plm(int l, int m, double theta)
Compute the associated Legendre polynomial with spherical harmonics normalization for and .
Definition funs.hpp:365
int sign(T x)
Compute the sign (1,-1, or 0) of a value.
Definition funs.hpp:32
Eigen::MatrixXd compute_all_real_Ylm(int l_max, const Eigen::MatrixXd &thetaphi_coord_P)
Compute real spherical harmonics for at the given coordinates.
Definition funs.hpp:486
double reduced_spherical_Pmm(int m, double theta)
Compute the associated Legendre polynomial with spherical harmonics normalization for and ....
Definition funs.hpp:195
mathutils::SimpleGenerator< double > generate_reduced_spherical_Plm_recursion_three_term_upward_ell(int l_start, int l_end, double theta)
Generate the associated Legendre polynomial with spherical harmonics normalization for and .
Definition funs.hpp:234
Eigen::MatrixXcd compute_all_series_Ylm(int l_max, const Eigen::MatrixXd &thetaphi_coord_P)
Compute spherical harmonics for at the given coordinates.
Definition funs.hpp:964
double ReLog(const T &x)
Compute the real part of the complex logarithm . Returns for .
Definition funs.hpp:93
double recursive_real_Ylm(int l, int m, double theta, double phi)
Compute the real spherical harmonics for and .
Definition funs.hpp:397
int minus_one_to_int_pow(int n)
Compute for integer n.
Definition funs.hpp:52
double reduced_spherical_Plm(int l, int m, double theta)
Compute the associated Legendre polynomial with spherical harmonics normalization for and .
Definition funs.hpp:313
std::pair< double, int > ReLogRe_ImLogRe_over_pi(const T &x)
Compute and .
Definition funs.hpp:105
std::array< int, 2 > spherical_harmonic_index_lm_N(int n)
Compute the n-th element of the sequence [(0,0), (1,-1), (1,0), (1,1), (2,-2), ......
Definition funs.hpp:174
double log_factorial(uint64_t n)
Compute for a non-negative integer n. Uses a lookup table for small values and lgamma for larger val...
Definition funs.hpp:64
Generator functions using C++20 coroutines.