92 Real normalized_x = direction.
x / magnitude;
93 Real normalized_y = direction.
y / magnitude;
94 Real normalized_z = direction.
z / magnitude;
98 }
else if (degree == 1) {
101 return -normalization_factor * normalized_y;
103 return normalization_factor * normalized_z;
105 return -normalization_factor * normalized_x;
106 }
else if (degree == 2) {
109 return normalization_factor_l2 * normalized_x * normalized_y;
111 return -normalization_factor_l2 * normalized_y * normalized_z;
114 (3.0 * normalized_z * normalized_z - 1.0);
116 return -normalization_factor_l2 * normalized_x * normalized_z;
118 return normalization_factor_l2 / 2.0 *
119 (normalized_x * normalized_x - normalized_y * normalized_y);
120 }
else if (degree == 3) {
122 return -std::sqrt(35.0 / (32.0 *
constants::pi)) * normalized_y *
123 (3.0 * normalized_x * normalized_x - normalized_y * normalized_y);
125 return std::sqrt(105.0 / (4.0 *
constants::pi)) * normalized_x * normalized_y *
128 return -std::sqrt(21.0 / (32.0 *
constants::pi)) * normalized_y *
129 (5.0 * normalized_z * normalized_z - 1.0);
131 return std::sqrt(7.0 / (16.0 *
constants::pi)) * normalized_z *
132 (5.0 * normalized_z * normalized_z - 3.0);
134 return -std::sqrt(21.0 / (32.0 *
constants::pi)) * normalized_x *
135 (5.0 * normalized_z * normalized_z - 1.0);
137 return std::sqrt(105.0 / (16.0 *
constants::pi)) * normalized_z *
138 (normalized_x * normalized_x - normalized_y * normalized_y);
140 return -std::sqrt(35.0 / (32.0 *
constants::pi)) * normalized_x *
141 (normalized_x * normalized_x - 3.0 * normalized_y * normalized_y);
142 }
else if (degree == 4) {
143 Real normalized_x2 = normalized_x * normalized_x,
144 normalized_y2 = normalized_y * normalized_y,
145 normalized_z2 = normalized_z * normalized_z;
147 return (3.0 / 4.0) * std::sqrt(35.0 / (
constants::pi)) * normalized_x * normalized_y *
148 (normalized_x2 - normalized_y2);
150 return -(3.0 / 4.0) * std::sqrt(35.0 / (2.0 *
constants::pi)) * normalized_y *
151 normalized_z * (3.0 * normalized_x2 - normalized_y2);
153 return (3.0 / 4.0) * std::sqrt(5.0 / (
constants::pi)) * normalized_x * normalized_y *
154 (7.0 * normalized_z2 - 1.0);
156 return -(3.0 / 4.0) * std::sqrt(5.0 / (2.0 *
constants::pi)) * normalized_y *
157 normalized_z * (7.0 * normalized_z2 - 3.0);
160 (35.0 * normalized_z2 * normalized_z2 - 30.0 * normalized_z2 + 3.0);
162 return -(3.0 / 4.0) * std::sqrt(5.0 / (2.0 *
constants::pi)) * normalized_x *
163 normalized_z * (7.0 * normalized_z2 - 3.0);
166 (normalized_x2 - normalized_y2) * (7.0 * normalized_z2 - 1.0);
168 return -(3.0 / 4.0) * std::sqrt(35.0 / (2.0 *
constants::pi)) * normalized_x *
169 normalized_z * (normalized_x2 - 3.0 * normalized_y2);
172 (normalized_x2 * normalized_x2 - 6.0 * normalized_x2 * normalized_y2 +
173 normalized_y2 * normalized_y2);
191 throw std::invalid_argument(
192 "compute_real_spherical_harmonic: degree " + std::to_string(degree) +
193 " is above the highest closed form implemented here (4), so no value can be "
194 "returned. An f-channel dataset reaches this through a Q-function expansion of "
196 std::to_string(degree) +
197 "; it needs harmonics up to degree 6, which Q3 does not "
219 int number_of_g_vectors,
220 const std::vector<std::array<Real, 3>>& g_vectors,
221 const std::vector<Real>& g_squared,
222 std::vector<std::vector<Real>>& spherical_harmonics_values) {
223 int maximum_angular_momentum = 0;
224 while ((maximum_angular_momentum + 1) * (maximum_angular_momentum + 1) <
225 number_of_lm_components)
226 maximum_angular_momentum++;
228 spherical_harmonics_values.assign(number_of_g_vectors,
229 std::vector<Real>(number_of_lm_components, 0.0));
231 if (number_of_lm_components == 0 || number_of_g_vectors == 0)
241 constexpr Real degenerate_direction_threshold = 1e-9;
255 const int legendre_size = (maximum_angular_momentum + 1) * (maximum_angular_momentum + 2) / 2;
256 std::vector<Real> legendre(
static_cast<size_t>(legendre_size), 0.0);
257 const auto legendre_index = [](
int degree,
int order) {
258 return degree * (degree + 1) / 2 + order;
261 for (
int g_index = 0; g_index < number_of_g_vectors; g_index++) {
262 const Real g_modulus = std::sqrt(g_squared[g_index]);
263 const bool direction_is_degenerate = g_modulus < degenerate_direction_threshold;
265 const Real cos_theta = direction_is_degenerate ? 0.0 : g_vectors[g_index][2] / g_modulus;
266 const Real sin_theta = std::sqrt(std::max(0.0, 1.0 - cos_theta * cos_theta));
275 const Real azimuthal_angle = direction_is_degenerate
277 : std::atan2(g_vectors[g_index][1], g_vectors[g_index][0]);
279 std::fill(legendre.begin(), legendre.end(), 0.0);
280 legendre[legendre_index(0, 0)] = 1.0;
281 if (maximum_angular_momentum >= 1) {
282 legendre[legendre_index(1, 0)] = cos_theta;
283 legendre[legendre_index(1, 1)] = -sin_theta / std::sqrt(2.0);
286 for (
int degree = 2; degree <= maximum_angular_momentum; degree++) {
287 for (
int order = 0; order <= degree - 2; order++) {
288 const Real denominator =
289 std::sqrt(
static_cast<Real>(degree * degree - order * order));
290 legendre[legendre_index(degree, order)] =
291 cos_theta * (2 * degree - 1) / denominator *
292 legendre[legendre_index(degree - 1, order)] -
293 std::sqrt(
static_cast<Real>((degree - 1) * (degree - 1) - order * order)) /
294 denominator * legendre[legendre_index(degree - 2, order)];
296 const Real diagonal_factor = std::sqrt(
static_cast<Real>(2 * degree - 1));
297 const Real previous_diagonal = legendre[legendre_index(degree - 1, degree - 1)];
298 legendre[legendre_index(degree, degree - 1)] =
299 cos_theta * diagonal_factor * previous_diagonal;
300 legendre[legendre_index(degree, degree)] = -diagonal_factor /
301 std::sqrt(
static_cast<Real>(2 * degree)) *
302 sin_theta * previous_diagonal;
310 std::vector<Real>& values = spherical_harmonics_values[g_index];
314 for (
int degree = 1; degree <= maximum_angular_momentum; degree++) {
315 const Real normalization =
318 normalization * legendre[legendre_index(degree, 0)];
320 const Real off_axis_normalization = normalization * std::sqrt(2.0);
321 for (
int order = 1; order <= degree; order++) {
322 const Real legendre_value = legendre[legendre_index(degree, order)];
324 off_axis_normalization * legendre_value * std::cos(order * azimuthal_angle);
326 off_axis_normalization * legendre_value * std::sin(order * azimuthal_angle);
354 int number_of_lm_components,
355 int number_of_g_vectors,
356 const std::vector<std::array<Real, 3>>& g_vectors,
357 const std::vector<Real>& g_squared,
358 int polarization_index,
359 std::vector<std::vector<Real>>& spherical_harmonic_derivatives) {
361 constexpr Real delta = 1.0e-6;
362 constexpr Real epsilon_threshold = 1.0e-9;
365 std::vector<std::array<Real, 3>> g_vectors_plus(number_of_g_vectors);
366 std::vector<std::array<Real, 3>> g_vectors_minus(number_of_g_vectors);
367 std::vector<Real> g_squared_plus(number_of_g_vectors);
368 std::vector<Real> g_squared_minus(number_of_g_vectors);
369 std::vector<Real> delta_g(number_of_g_vectors);
371 for (
int g_index = 0; g_index < number_of_g_vectors; g_index++) {
372 Real g_modulus = std::sqrt(g_squared[g_index]);
373 delta_g[g_index] = delta * g_modulus;
375 g_vectors_plus[g_index] = g_vectors[g_index];
376 g_vectors_minus[g_index] = g_vectors[g_index];
377 g_vectors_plus[g_index][polarization_index] += delta_g[g_index];
378 g_vectors_minus[g_index][polarization_index] -= delta_g[g_index];
380 g_squared_plus[g_index] = g_vectors_plus[g_index][0] * g_vectors_plus[g_index][0] +
381 g_vectors_plus[g_index][1] * g_vectors_plus[g_index][1] +
382 g_vectors_plus[g_index][2] * g_vectors_plus[g_index][2];
383 g_squared_minus[g_index] = g_vectors_minus[g_index][0] * g_vectors_minus[g_index][0] +
384 g_vectors_minus[g_index][1] * g_vectors_minus[g_index][1] +
385 g_vectors_minus[g_index][2] * g_vectors_minus[g_index][2];
389 std::vector<std::vector<Real>> ylm_plus, ylm_minus;
391 number_of_lm_components, number_of_g_vectors, g_vectors_plus, g_squared_plus, ylm_plus);
393 number_of_lm_components, number_of_g_vectors, g_vectors_minus, g_squared_minus, ylm_minus);
396 spherical_harmonic_derivatives.resize(number_of_g_vectors,
397 std::vector<Real>(number_of_lm_components, 0.0));
398 for (
int g_index = 0; g_index < number_of_g_vectors; g_index++) {
399 if (g_squared[g_index] > epsilon_threshold) {
400 Real inverse_two_delta_g = 0.5 / delta_g[g_index];
401 for (
int lm_index = 0; lm_index < number_of_lm_components; lm_index++) {
402 spherical_harmonic_derivatives[g_index][lm_index] =
403 (ylm_plus[g_index][lm_index] - ylm_minus[g_index][lm_index]) *