10#include "mathutils/spherical_harmonics_index_lookup_table.hpp"
32template <
typename T>
inline int sign(T x) {
return (x > T{0}) - (x < T{0}); }
54 return 1 - 2 * (n & 1);
65 return (n < LOG_FACTORIAL_LOOKUP_TABLE_SIZE)
66 ? LOG_FACTORIAL_LOOKUP_TABLE[n]
67 : std::lgamma(
static_cast<double>(n + 1));
79 return 0.5 + 0.5 * ((x > T{0}) - (x < T{0}));
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));
106 if constexpr (std::is_floating_point_v<T> || std::is_integral_v<T>) {
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}));
114 double Re_x =
static_cast<double>(x);
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));
126inline std::pair<double, int> ReLogRe_ImLogRe_over_pi_of_x_to_pow(
const T &x,
128 if constexpr (std::is_floating_point_v<T> || std::is_integral_v<T>) {
129 return (n == 0) ? std::make_pair(0.0, 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)));
135 double Re_x =
static_cast<double>(x);
136 return (n == 0) ? std::make_pair(0.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)));
157 if (m < -l || m > l) {
158 throw std::out_of_range(
"m must be in the range [-l, l]");
161 throw std::out_of_range(
"l must be non-negative");
163 return l * (l + 1) + m;
176 throw std::out_of_range(
"n must be non-negative");
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)};
182 return {SPHERICAL_HARMONIC_INDEX_LM_N_LOOKUP_TABLE[n][0],
183 SPHERICAL_HARMONIC_INDEX_LM_N_LOOKUP_TABLE[n][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));
237 if (l_start < 0 || l_end < l_start) {
243 co_yield P_ell_minus_one_m;
244 if (l_start == l_end) {
247 double cos_theta = std::cos(theta);
248 double P_ell_m = cos_theta * std::sqrt(2 * m + 3) * P_ell_minus_one_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);
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;
269enumerate_reduced_spherical_Plm_recursion_three_term_upward_ell(
int l_start,
272 if (l_start < 0 || l_end < l_start) {
276 double P_ell_minus_one_m = reduced_spherical_Pmm(m, theta);
278 co_yield {l_start, P_ell_minus_one_m};
279 if (l_start == l_end) {
282 double cos_theta = std::cos(theta);
283 double P_ell_m = cos_theta * std::sqrt(2 * m + 3) * P_ell_minus_one_m;
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);
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;
299 co_yield {ell, P_ell_m};
366 int abs_m = std::abs(m);
382 return ((m < 0) && (m & 1)) ^ ((theta > M_PI_2) && ((l - abs_m) & 1))
408 return -std::sqrt(2) * std::sin(m * phi) *
spherical_Plm(l, m, theta);
410 return (1 - 2 * (m & 1)) * std::sqrt(2) * std::cos(m * phi) *
427inline double real_Ylm(
int l,
int m,
double theta,
double phi) {
430 throw std::out_of_range(
"l must be non-negative");
432 if (m < -l || m > l) {
433 throw std::out_of_range(
"m must be in the range [-l, l]");
435 if (theta < 0 || theta > M_PI) {
436 throw std::out_of_range(
"theta must be in the range [0, pi]");
451 const Eigen::MatrixXd &thetaphi_coord_P) {
454 throw std::out_of_range(
"l must be non-negative");
456 if (m < -l || m > l) {
457 throw std::out_of_range(
"m must be in the range [-l, l]");
460 if (thetaphi_coord_P.cols() != 2) {
461 throw std::invalid_argument(
"thetaphi_coord_P must have 2 columns");
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]");
469 double phi = thetaphi_coord_P(i, 1);
487 const Eigen::MatrixXd &thetaphi_coord_P) {
490 throw std::out_of_range(
"l_max must be non-negative");
493 if (thetaphi_coord_P.cols() != 2) {
494 throw std::invalid_argument(
"thetaphi_coord_P must have 2 columns");
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");
501 int num_modes = l_max * (l_max + 2) + 1;
502 Eigen::MatrixXd Y(num_points, num_modes);
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]");
510 double phi = thetaphi_coord_P(p, 1);
511 double reduced_theta =
512 std::min(theta, M_PI - theta);
514 for (
auto [l, reduced_P] :
515 enumerate_reduced_spherical_Plm_recursion_three_term_upward_ell(
516 0, l_max, reduced_theta)) {
518 Y(p, n) = ((theta > M_PI_2) && (l & 1)) ? -reduced_P : reduced_P;
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;
528 double Q = ((m & 1) ^ ((theta > M_PI_2) && ((l - m) & 1)))
529 ? -std::sqrt(2) * reduced_P
530 : std::sqrt(2) * reduced_P;
532 Y(p, n_plus) = std::cos(m * phi) * Q;
534 Y(p, n_minus) = std::sin(m * phi) * Q;
541Eigen::MatrixXcd compute_all_Ylm(
int l_max,
542 const Eigen::MatrixXd &thetaphi_coord_P) {
545 throw std::out_of_range(
"l_max must be non-negative");
548 if (thetaphi_coord_P.cols() != 2) {
549 throw std::invalid_argument(
"thetaphi_coord_P must have 2 columns");
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");
556 int num_modes = l_max * (l_max + 2) + 1;
557 Eigen::MatrixXcd Y(num_points, num_modes);
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]");
565 double phi = thetaphi_coord_P(p, 1);
566 double reduced_theta =
567 std::min(theta, M_PI - theta);
569 for (
auto [l, reduced_P] :
570 enumerate_reduced_spherical_Plm_recursion_three_term_upward_ell(
571 0, l_max, reduced_theta)) {
573 Y(p, n) = ((theta > M_PI_2) && (l & 1)) ? -reduced_P : reduced_P;
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;
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));
589 (m & 1) ? -std::conj(Y(p, n_plus)) : std::conj(Y(p, n_plus));
609 std::exp(std::complex<double>(0, m * phi));
622inline std::complex<double>
Ylm(
int l,
int m,
double theta,
double phi) {
625 throw std::out_of_range(
"l must be non-negative");
627 if (m < -l || m > l) {
628 throw std::out_of_range(
"m must be in the range [-l, l]");
630 if (theta < 0 || theta > M_PI) {
631 throw std::out_of_range(
"theta must be in the range [0, pi]");
645Eigen::VectorXcd
Ylm(
int l,
int m,
const Eigen::MatrixXd &thetaphi_coord_P) {
649 throw std::out_of_range(
"l must be non-negative");
651 if (m < -l || m > l) {
652 throw std::out_of_range(
"m must be in the range [-l, l]");
655 if (thetaphi_coord_P.cols() != 2) {
656 throw std::invalid_argument(
"thetaphi_coord_P must have 2 columns");
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]");
664 double phi = thetaphi_coord_P(i, 1);
675inline double series_real_Ylm(
int l,
int m,
double theta,
double phi) {
677 throw std::out_of_range(
"l must be non-negative");
679 if (m < -l || m > l) {
680 throw std::out_of_range(
"m must be in the range [-l, l]");
682 if (theta < 0 || theta > M_PI) {
683 throw std::out_of_range(
"theta must be in the range [0, pi]");
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] =
690 auto [log_abs_sin_theta, arg_over_pi_sin_theta] =
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;
697 -0.5 * std::log(4 * M_PI) - (abs_m + 2 * k) * std::log(2) +
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;
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
715Eigen::VectorXd series_real_Ylm(
int l,
int m,
716 const Eigen::MatrixXd &thetaphi_coord_P) {
719 throw std::out_of_range(
"l must be non-negative");
721 if (m < -l || m > l) {
722 throw std::out_of_range(
"m must be in the range [-l, l]");
724 if (thetaphi_coord_P.cols() != 2) {
725 throw std::invalid_argument(
"thetaphi_coord_P must have 2 columns");
727 int abs_m = std::abs(m);
728 int num_points = thetaphi_coord_P.rows();
729 Eigen::VectorXd Y(num_points);
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]");
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] =
741 auto [log_abs_sin_theta, arg_over_pi_sin_theta] =
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;
748 -0.5 * std::log(4 * M_PI) - (abs_m + 2 * k) * std::log(2) +
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;
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
768inline std::complex<double> series_Ylm(
int l,
int m,
double theta,
double phi) {
770 throw std::out_of_range(
"l must be non-negative");
772 if (m < -l || m > l) {
773 throw std::out_of_range(
"m must be in the range [-l, l]");
775 if (theta < 0 || theta > M_PI) {
776 throw std::out_of_range(
"theta must be in the range [0, pi]");
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] =
783 auto [log_abs_sin_theta, arg_over_pi_sin_theta] =
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) +
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;
801 return (m < 0) ? std::exp(std::complex<double>(0, m * phi)) * Xlm
804 std::exp(std::complex<double>(0, m * phi)) * Xlm
805 :
static_cast<std::complex<double>
>(Xlm);
808Eigen::VectorXcd series_Ylm(
int l,
int m,
809 const Eigen::MatrixXd &thetaphi_coord_P) {
812 throw std::out_of_range(
"l must be non-negative");
814 if (m < -l || m > l) {
815 throw std::out_of_range(
"m must be in the range [-l, l]");
817 if (thetaphi_coord_P.cols() != 2) {
818 throw std::invalid_argument(
"thetaphi_coord_P must have 2 columns");
820 int abs_m = std::abs(m);
821 int num_points = thetaphi_coord_P.rows();
822 Eigen::VectorXcd Y(num_points);
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]");
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] =
834 auto [log_abs_sin_theta, arg_over_pi_sin_theta] =
836 for (
int k{0}; k <= (l - abs_m) / 2; k++) {
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;
842 -0.5 * std::log(4 * M_PI) - (abs_m + 2 * k) * std::log(2) +
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;
855 Y(p) *= std::exp(std::complex<double>(0, m * phi));
873 const Eigen::MatrixXd &thetaphi_coord_P) {
876 throw std::out_of_range(
"l_max must be non-negative");
879 if (thetaphi_coord_P.cols() != 2) {
880 throw std::invalid_argument(
"thetaphi_coord_P must have 2 columns");
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");
887 int num_modes = l_max * (l_max + 2) + 1;
888 Eigen::MatrixXd Y(num_points, num_modes);
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]");
896 double phi = thetaphi_coord_P(p, 1);
897 double cos_theta = std::cos(theta);
898 double sin_theta = std::sin(theta);
900 auto [log_abs_cos_theta, arg_over_pi_cos_theta] =
902 auto [log_abs_sin_theta, arg_over_pi_sin_theta] =
904 for (
int l{0}; l <= l_max; l++) {
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) +
916 int pow_cos = l - 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;
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;
930 -0.5 * std::log(4 * M_PI) - (m + 2 * k) * std::log(2) +
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;
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);
967 throw std::out_of_range(
"l_max must be non-negative");
970 if (thetaphi_coord_P.cols() != 2) {
971 throw std::invalid_argument(
"thetaphi_coord_P must have 2 columns");
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");
978 int num_modes = l_max * (l_max + 2) + 1;
979 Eigen::MatrixXcd Y(num_points, num_modes);
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]");
987 double phi = thetaphi_coord_P(p, 1);
988 double cos_theta = std::cos(theta);
989 double sin_theta = std::sin(theta);
991 auto [log_abs_cos_theta, arg_over_pi_cos_theta] =
993 auto [log_abs_sin_theta, arg_over_pi_sin_theta] =
995 for (
int l{0}; l <= l_max; l++) {
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) +
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;
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;
1022 -0.5 * std::log(4 * M_PI) - (m + 2 * k) * std::log(2) +
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;
1037 Y(p, n_plus) * std::exp(std::complex<double>(0, -m * phi));
1040 std::exp(std::complex<double>(0, m * phi));
1055 double epsilon = 1e-8;
1056 double minus_log4 = std::log(0.25);
1058 int abs_m = std::abs(m);
1060 int ell_minus_m = l - abs_m;
1061 double cos_theta = std::cos(theta);
1062 double abs_cos_theta = std::abs(cos_theta);
1064 double abs_sin_theta = std::sin(theta);
1066 double rlm = 1.0 / (2.0 * std::sqrt(M_PI));
1067 if ((m > 0) && (abs_m % 2 == 1)) {
1076 if ((cos_theta < 0) && (ell_minus_m % 2 == 1)) {
1079 int k_bound = (ell_minus_m) / 2;
1083 double log_qk = 0.5 * std::log(2 * l + 1) + 0.5 *
log_factorial(l + abs_m) -
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) {
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) -
1106 sum_Qk += sk * std::exp(log_qk);
1108 return rlm * sum_Qk;
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) {
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) -
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;
1126 return rlm * sum_Qk;
1130 if (abs_sin_theta < epsilon) {
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) {
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) -
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;
1159 return rlm * sum_Qk;
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) {
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) -
1178 sum_Qk += sk * std::exp(log_qk);
1181 return rlm * sum_Qk;
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]");
1188 return std::exp(std::complex<double>(0, m * phi)) *
1189 phi_independent_Ylm(l, m, theta);
1192inline double old_real_Ylm(
int l,
int m,
double theta,
double phi) {
1194 if (theta > M_PI || theta < 0.0) {
1195 throw std::out_of_range(
"theta must be in the range [0, pi]");
1197 int abs_m = std::abs(m);
1199 if (abs_m % 2 == 1) {
1203 return val * std::sqrt(2) * std::sin(abs_m * phi);
1208 return val * std::sqrt(2) * std::cos(abs_m * phi);
1212old_compute_all_real_Ylm(
int l_max,
const Eigen::MatrixXd &thetaphi_coord_P) {
1215 throw std::out_of_range(
"l_max must be non-negative");
1218 if (thetaphi_coord_P.cols() != 2) {
1219 throw std::invalid_argument(
"thetaphi_coord_P must have 2 columns");
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");
1226 int num_modes = l_max * (l_max + 2) + 1;
1227 Eigen::MatrixXd Y(num_points, num_modes);
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);
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.