ARTS 2.5.11 (git: 725533f0)
legendre.cc
Go to the documentation of this file.
1#include "arts_conversions.h"
2#include "legendre.h"
3
4namespace Legendre {
7 static constexpr Numeric dlat_lim = 1e-4;
8
9 const bool high;
10 const bool low;
11 const Numeric theta;
12
13 constexpr ColatitudeConversion(const Numeric latitude) noexcept :
14 high(latitude > (90.0 - dlat_lim)), low(latitude < (dlat_lim - 90.0)),
15 theta(high ? Conversion::deg2rad(dlat_lim) : (low ? Conversion::deg2rad(180.0 - dlat_lim) : Conversion::deg2rad(90.0 - latitude))) {}
16};
17
18
20constexpr Numeric longitude_clamp(const Numeric lon) {
21 if (lon >= -180 and lon < 180)
22 return lon;
23 if (lon < -180)
24 return longitude_clamp(lon + 360);
25 return longitude_clamp(lon - 360);
26}
27
28
29#pragma GCC diagnostic push
30#pragma GCC diagnostic ignored "-Wconversion"
45std::pair<Matrix, Matrix> schmidt(const Numeric theta, const Index nmax) ARTS_NOEXCEPT {
46 ARTS_ASSERT(theta > 0 and theta < Constant::pi)
47 ARTS_ASSERT(nmax > 0)
48
49 using std::sqrt;
50 using Math::pow2;
51
52 Index N = 1 + nmax;
53
54 const Numeric x = std::cos(theta);
55 Matrix P(N, N, 0);
56 Matrix dP(N, N, 0);
57
58 // Start values
59 P(0, 0) = 1.0;
60 P(1, 0) = x;
61 P(1, 1) = - sqrt(1.0 - pow2(x));
62 dP(0, 0) = 0.0;
63 dP(1, 0) = 1.0;
64
65 for (Index n=2; n<N; ++n) {
66 P(n, 0) = ((2 * (n-1) + 1) * x * P(n-1, 0) - (n - 1) * P(n-2, 0)) / n;
67 }
68
69 dP(nmax, 0) = nmax / (pow2(x) - 1) * (x * P(nmax, 0) - P(nmax-1, 0));
70 for (Index n=2; n<nmax; ++n) {
71 dP(n, 0) = (n + 1) / (pow2(x) - 1) * (P(n+1, 0) - x * P(n, 0));
72 }
73
74 Numeric Cm = sqrt(2.0);
75 for (Index m=1; m<N; ++m) {
76 Cm /= sqrt(Numeric(2*m * (2*m - 1)));
77 P(m, m) = std::pow(1 - pow2(x), 0.5*m) * Cm;
78
79 for (Index i=1; i<m; ++i) {
80 P(m, m) *= 2*i + 1;
81 }
82
83 dP(m, m) = -P(m, m) * m * x / sqrt(1 - pow2(x));
84
85 if (nmax > m) {
86 Numeric twoago = 0.0;
87 for (Index n=m+1; n<N; ++n) {
88 P(n, m) = (x * (2*n - 1) * P(n-1, m) - sqrt(Numeric((n+m-1) * (n-m-1))) * twoago) / sqrt(Numeric(pow2(n) - pow2(m)));
89 twoago = P(n-1, m);
90 }
91 }
92 }
93
94 for (Index n=2; n<N; ++n) {
95 for (Index m=1; m<n; ++m) {
96 dP(n, m) = sqrt(Numeric((n-m) * (n+m+1))) * P(n, m+1) - P(n, m) * m * x / sqrt(1 - pow2(x));
97 }
98 }
99
100 return {P, dP};
101}
102
103
104SphericalField schmidt_fieldcalc(const Matrix& g, const Matrix& h, const Numeric r0,
105 const Numeric r, const Numeric lat, const Numeric lon) ARTS_NOEXCEPT {
106 ARTS_ASSERT(lat >= -90 and lat <= 90, "Latitude is ", lat, " should be in [-90, 90]")
107
108 const Index N = h.nrows();
109
110 // Take care of boundary issues
111 const auto [low_lat, high_lat, effective_theta] = ColatitudeConversion(lat);
112 const Numeric sin_theta = std::sin(effective_theta);
113
114 // Compute the legendre polynominal with Schmidt renormalization
115 const auto [P, dP] = schmidt(effective_theta, N - 1);
116
117 // Pre-compute the cosine/sine values
118 std::vector<Numeric> cosm(N);
119 std::vector<Numeric> sinm(N);
120 const Numeric clon = longitude_clamp(lon);
121 if (low_lat or high_lat) {
122 for (Index m=0; m<N; ++m) {
123 cosm[m] = 1.0;
124 sinm[m] = 0.0;
125 }
126 } else {
127 for (Index m=0; m<N; ++m) {
128 cosm[m] = Conversion::cosd(m * clon);
129 sinm[m] = Conversion::sind(m * clon);
130 }
131 }
132
133 // Compute the field
134 SphericalField F; // Auto init to zeroes
135 for (Index n=1; n<N; ++n) {
136 const Numeric ratn = std::pow(r0 / r, n + 2);
137 for (Index m=0; m<n+1; ++m) {
138 F.U += (g(n, m) * cosm[m] + h(n, m) * sinm[m]) * P(n, m) * (n + 1) * ratn;
139 F.S += (g(n, m) * cosm[m] + h(n, m) * sinm[m]) * dP(n, m) * ratn;
140 F.E += (g(n, m) * sinm[m] + h(n, m) * cosm[m]) * P(n, m) * m * ratn;
141 }
142 }
143 F.S *= sin_theta;
144 F.E /= sin_theta;
145
146 // Adjust by theta (nb. can be nan when undefined, but set to zero instead)
147 if (high_lat or low_lat) {
148 F.S = F.total_horizontal();
149 F.E = 0;
150 }
151
152 return F;
153}
154
155
156MatrixOfSphericalField schmidt_fieldcalc(const Matrix& g, const Matrix& h, const Numeric r0, const Vector& rv, const Numeric lat, const Vector& lonv) ARTS_NOEXCEPT {
157 ARTS_ASSERT(lat >= -90 and lat <= 90, "Latitude is ", lat, " should be in [-90, 90]")
158
159 const Index N = h.nrows();
160 const Index nlon = lonv.nelem();
161 const Index nr = rv.nelem();
162
163 // Take care of boundary issues
164 const auto [low_lat, high_lat, effective_theta] = ColatitudeConversion(lat);
165 const Numeric sin_theta = std::sin(effective_theta);
166
167 // Compute the legendre polynominal with Schmidt renormalization
168 const auto [P, dP] = schmidt(effective_theta, N - 1);
169
170 // Pre-compute the radius-ratio-power
171 Matrix ratnm(nr, N);
172 for (Index ir=0; ir<nr; ir++) {
173 ARTS_ASSERT(rv[ir] > 0, "Radius are [", rv, "] must be in (0, inf]")
174 for (Index n=1; n<N; ++n) {
175 ratnm(ir, n) = std::pow(r0 / rv[ir], n + 2);
176 }
177 }
178
179 MatrixOfSphericalField out(nlon, nr); // Auto init to zeroes
180 for (Index ilon=0; ilon<nlon; ilon++) {
181
182 // Pre-compute the cosine/sine values
183 std::vector<Numeric> cosm(N);
184 std::vector<Numeric> sinm(N);
185 const Numeric clon = longitude_clamp(lonv[ilon]);
186 if (low_lat or high_lat) {
187 for (Index m=0; m<N; ++m) {
188 cosm[m] = 1.0;
189 sinm[m] = 0.0;
190 }
191 } else {
192 for (Index m=0; m<N; ++m) {
193 cosm[m] = Conversion::cosd(m * clon);
194 sinm[m] = Conversion::sind(m * clon);
195 }
196 }
197
198 // Compute the field
199 for (Index ir=0; ir<nr; ir++) {
200 SphericalField& F = out(ilon, ir); // Auto init'd to zeroes
201 for (Index n=1; n<N; ++n) {
202 const Numeric ratn = ratnm(ir, n);
203 for (Index m=0; m<n+1; ++m) {
204 const Numeric gnm{g(n, m)};
205 const Numeric hnm{h(n, m)};
206 const Numeric cosmm{cosm[m]};
207 const Numeric sinmm{sinm[m]};
208 const Numeric Pnm{P(n, m)};
209
210 F.U += (gnm * cosmm + hnm * sinmm) * Pnm * (n + 1) * ratn;
211 F.S += (gnm * cosmm + hnm * sinmm) * dP(n, m) * ratn;
212 F.E += (gnm * sinmm + hnm * cosmm) * Pnm * m * ratn;
213 }
214 }
215 F.S *= sin_theta;
216 F.E /= sin_theta;
217
218 // Adjust by theta (nb. can be nan when undefined, but set to zero instead)
219 if (high_lat or low_lat) {
220 F.S = F.total_horizontal();
221 F.E = 0;
222 }
223 }
224 }
225
226 return out;
227}
228#pragma GCC diagnostic pop
229
231std::array<Numeric, 3> to_geodetic(const std::array<Numeric, 3> xyz, const std::array<Numeric, 2> ell) noexcept {
232 using Math::pow2;
233 using Math::pow3;
234 using Math::pow4;
235
236 const Numeric X = std::get<0>(xyz);
237 const Numeric Y = std::get<1>(xyz);
238 const Numeric Z = std::get<2>(xyz);
239 const Numeric a = std::get<0>(ell);
240 const Numeric e = std::get<1>(ell);
241 const Numeric b = a * std::sqrt(1 - pow2(e));
242
243 // Output
244 std::array<Numeric, 3> pos;
245
246 if (std::abs(X) > 1 / a or std::abs(Y) > 1 / a) {
247 const Numeric DZ = std::sqrt(1 - pow2(e)) * Z;
248 const Numeric r = std::hypot(X, Y);
249 const Numeric e2p = (pow2(a) - pow2(b)) / pow2(b);
250 const Numeric F = 54 * pow2(b * Z);
251 const Numeric G = pow2(r) + pow2(DZ) - pow2(e) * (pow2(a) - pow2(b));
252 const Numeric c = pow4(e) * F * pow2(r) / pow3(G);
253 const Numeric s = std::cbrt(1 + c + std::sqrt(pow2(c) + 2 * c));
254 const Numeric fP = F / (3 * pow2(G * (s + 1 / s + 1)));
255 const Numeric Q = std::sqrt(1 + 2 * pow4(e) * fP);
256 const Numeric r0 = (-fP * pow2(e) * r) / (1 + Q) + sqrt(0.5 * pow2(a) * (1 + 1 / Q) - fP * pow2(DZ) / (Q * (1 + Q)) - 0.5 * fP * pow2(r));
257 const Numeric U = std::hypot(r - pow2(e) * r0, Z);
258 const Numeric V = std::hypot(r - pow2(e) * r0, DZ);
259 const Numeric z0 = pow2(b) * Z / (a * V);
260
261 pos[0] = U * (1 - pow2(b) / (a * V));
262 pos[1] = Conversion::atan2d(Z + e2p * z0, r);
263 pos[2] = Conversion::atan2d(Y, X);
264 } else if (std::abs(Z) < 1 / b) {
265 pos[0] = -a;
266 pos[1] = 0;
267 pos[2] = 180;
268 } else {
269 pos[0] = std::abs(Z) - b;
270 pos[1] = Z < 0 ? -90 : 90;
271 pos[2] = 0;
272 }
273
274 return pos;
275}
276} // namespace Legendre
Common ARTS conversions.
Row-major grid creation.
Definition grids.h:52
#define ARTS_NOEXCEPT
Definition debug.h:83
#define ARTS_ASSERT(condition,...)
Definition debug.h:86
constexpr Numeric pi
The following mathematical constants are generated in python Decimal package by the code:
Namespace containing several practical unit conversions, physical and mathematical.
constexpr auto deg2rad(auto x) noexcept
Converts degrees to radians.
auto sind(auto x) noexcept
Returns the sine of the deg2rad of the input.
auto cosd(auto x) noexcept
Returns the cosine of the deg2rad of the input.
auto atan2d(auto y, auto x) noexcept
Returns rad2deg of the arc-tangent of inputs #T1/#T2
std::array< Numeric, 3 > to_geodetic(const std::array< Numeric, 3 > xyz, const std::array< Numeric, 2 > ell) noexcept
Computes the altitude, latitude and longitude in relation to the ellopsiod using non-iterative method...
Definition legendre.cc:231
SphericalField schmidt_fieldcalc(const Matrix &g, const Matrix &h, const Numeric r0, const Numeric r, const Numeric lat, const Numeric lon) ARTS_NOEXCEPT
Computes the spherical field.
Definition legendre.cc:104
std::pair< Matrix, Matrix > schmidt(const Numeric theta, const Index nmax) ARTS_NOEXCEPT
Returns the Schmidt normalized Lagrange polynominal and its derivative.
Definition legendre.cc:45
constexpr Numeric longitude_clamp(const Numeric lon)
Clamps the longitude in the range [-180, 180)
Definition legendre.cc:20
constexpr auto pow3(auto x) noexcept
power of three
constexpr auto pow4(auto x) noexcept
power of four
constexpr auto pow2(auto x) noexcept
power of two
Converts latitude to co-latitude with a small distance from the poles and flags if this was activated...
Definition legendre.cc:6
constexpr ColatitudeConversion(const Numeric latitude) noexcept
Definition legendre.cc:13
static constexpr Numeric dlat_lim
Definition legendre.cc:7
Stores the up (U), south (S), east (E) values of a field relative to the sphere.
Definition legendre.h:8
Numeric total_horizontal() const noexcept
Returns the total strength of the field.
Definition legendre.h:17
#define a
#define c
#define b