Skip to content

Commit fc717c9

Browse files
committed
Merge branch 'hotfix/2.0.2'
2 parents 4deb67e + 23da543 commit fc717c9

File tree

5 files changed

+104
-26
lines changed

5 files changed

+104
-26
lines changed

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.0.1
1+
2.0.2

src/eckit/geo/util/gaussian_latitudes.cc

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ const std::vector<double>& gaussian_latitudes(size_t N, bool increasing) {
3535
return cache[key];
3636
}
3737

38-
std::vector<double> lats(2 * N);
3938

39+
std::vector<double> lats(2 * N);
4040

4141
// Fourier coefficients of series expansion for the ordinary Legendre polynomials
4242
std::vector<double> zzfn(N + 1);
@@ -62,25 +62,29 @@ const std::vector<double>& gaussian_latitudes(size_t N, bool increasing) {
6262
}
6363

6464

65-
// Newton loop (per latitude) to find 0 of Legendre polynomial of degree N (GAWL)
65+
/*
66+
* Newton loop (per latitude) to find 0 of Legendre polynomial of degree N (GAWL)
67+
* The convergence is run more than once, for historical reasons.
68+
*/
6669
constexpr size_t Nmax = 20;
6770
constexpr auto eps = std::numeric_limits<double>::epsilon() * 1000.;
6871

6972
for (size_t i = 0; i < N; ++i) {
7073
// First guess for colatitude [rad]
71-
double z = static_cast<double>(4 * (i + 1) - 1) * M_PI / static_cast<double>(4 * 2 * N + 2);
72-
double x = (z + 1. / (std::tan(z) * static_cast<double>(8 * (2 * N) * (2 * N))));
74+
auto z = static_cast<double>(4 * (i + 1) - 1) * M_PI / static_cast<double>(4 * 2 * N + 2);
75+
auto x = z + 1. / (std::tan(z) * static_cast<double>(8 * (2 * N) * (2 * N)));
7376

7477
auto converged = false;
7578

7679
for (size_t n = 0; n < Nmax; ++n) {
77-
auto f = 0.5 * zzfn[0]; // normalised ordinary Legendre polynomial == \overbar{P_n}^0
78-
auto fp = 0.; // normalised derivative == d/d\theta(\overbar{P_n}^0)
79-
80-
for (size_t i = 1; i <= N; ++i) {
81-
const auto i2 = static_cast<double>(i * 2);
82-
f += zzfn[i] * std::cos(i2 * x);
83-
fp -= zzfn[i] * std::sin(i2 * x) * i2;
80+
auto f = zzfn[0] * 0.5 +
81+
zzfn[1] * std::cos(2. * x); // normalised ordinary Legendre polynomial == \overbar{P_n}^0
82+
auto fp = zzfn[1] * std::sin(2. * x) * -2.; // normalised derivative == d/d\theta(\overbar{P_n}^0)
83+
84+
for (size_t j = 2; j <= N; ++j) {
85+
const auto j2 = static_cast<double>(j * 2);
86+
f += zzfn[j] * std::cos(j2 * x);
87+
fp -= zzfn[j] * std::sin(j2 * x) * j2;
8488
}
8589

8690
auto dx = -f / fp;

src/eckit/geo/util/gaussian_quadrature_weights.cc

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const std::vector<double>& gaussian_quadrature_weights(size_t N) {
4949
zfn[i] *= std::sqrt(1. - 0.25 / (static_cast<double>(j * j)));
5050
}
5151

52-
for (size_t j = 2; j <= i - i % 2; j += 2) {
52+
for (size_t j = 2; j <= i - (i % 2); j += 2) {
5353
zfn[i - j] = zfn[i - j + 2] * static_cast<double>((j - 1) * (2 * i - j + 2)) /
5454
static_cast<double>(j * (2 * i - j + 1));
5555
}
@@ -61,34 +61,37 @@ const std::vector<double>& gaussian_quadrature_weights(size_t N) {
6161
}
6262

6363

64-
// Newton loop (per latitude) to find 0 of Legendre polynomial of degree N (GAWL)
64+
/*
65+
* Newton loop (per latitude) to find 0 of Legendre polynomial of degree N (GAWL)
66+
* The convergence is run more than once, for historical reasons.
67+
*/
6568
constexpr size_t Nmax = 20;
66-
constexpr double eps = std::numeric_limits<double>::epsilon() * 1000.;
69+
constexpr auto eps = std::numeric_limits<double>::epsilon() * 1000.;
6770

6871
for (size_t i = 0; i < N; ++i) {
69-
// First guess
72+
// First guess for colatitude [rad]
7073
auto z = static_cast<double>(4 * (i + 1) - 1) * M_PI / static_cast<double>(4 * 2 * N + 2);
7174
auto x = z + 1. / (std::tan(z) * static_cast<double>(8 * (2 * N) * (2 * N)));
7275

7376
double w = 0.;
74-
bool converged = false;
77+
auto converged = false;
7578

7679
for (size_t n = 1; n < Nmax; ++n) {
77-
auto f = 0.5 * zzfn[0]; // normalised ordinary Legendre polynomial == \overbar{P_n}^0
78-
auto fp = 0.; // normalised derivative == d/d\theta(\overbar{P_n}^0)
79-
80-
for (size_t j = 1; j <= N; ++j) {
81-
const auto i2 = static_cast<double>(j * 2);
82-
f += zzfn[j] * std::cos(i2 * x);
83-
fp -= zzfn[j] * std::sin(i2 * x) * i2;
80+
auto f = zzfn[0] * 0.5 +
81+
zzfn[1] * std::cos(2. * x); // normalised ordinary Legendre polynomial == \overbar{P_n}^0
82+
auto fp = zzfn[1] * std::sin(2. * x) * -2.; // normalised derivative == d/d\theta(\overbar{P_n}^0)
83+
84+
for (size_t j = 2; j <= N; ++j) {
85+
const auto j2 = static_cast<double>(j * 2);
86+
f += zzfn[j] * std::cos(j2 * x);
87+
fp -= zzfn[j] * std::sin(j2 * x) * j2;
8488
}
8589

8690
auto dx = -f / fp;
8791
x += dx;
8892

8993
if (converged) {
90-
const auto j = 2 * N - 1 - i;
91-
w = weights[i] = static_cast<double>(2 * 2 * N + 1) / (fp * fp);
94+
w = static_cast<double>(2 * 2 * N + 1) / (fp * fp);
9295
break;
9396
}
9497

tests/geo/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ foreach(_test
44
cache
55
figure
66
figure_earth
7+
gaussian
78
great_circle
89
grid
910
grid_healpix

tests/geo/gaussian.cc

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* (C) Copyright 1996- ECMWF.
3+
*
4+
* This software is licensed under the terms of the Apache Licence Version 2.0
5+
* which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
6+
*
7+
* In applying this licence, ECMWF does not waive the privileges and immunities
8+
* granted to it by virtue of its status as an intergovernmental organisation nor
9+
* does it submit to any jurisdiction.
10+
*/
11+
12+
13+
#include "eckit/geo/util.h"
14+
#include "eckit/maths/FloatingPointExceptions.h"
15+
#include "eckit/testing/Test.h"
16+
#include "eckit/types/FloatCompare.h"
17+
18+
19+
#define EXPECT_APPROX(a, b) EXPECT(::eckit::types::is_approximately_equal<double>((a), (b), 1e-6))
20+
21+
22+
namespace eckit::geo::test {
23+
24+
25+
/**
26+
* This case verifies that Floating Point Exceptions (FPEs) don't trigger for the functions:
27+
* - eckit::geo::util::gaussian_latitudes
28+
* - eckit::geo::util::gaussian_quadrature_weights
29+
*
30+
* FPE trapping is enabled to ensure any FPE-triggering operations are caught.
31+
* @see ECKIT-674
32+
*/
33+
CASE("gaussian_latitudes") {
34+
const auto& lats_1 = geo::util::gaussian_latitudes(1, true);
35+
36+
EXPECT(lats_1.size() == 2);
37+
EXPECT_APPROX(lats_1.front(), -35.264390);
38+
EXPECT_APPROX(lats_1.front(), -lats_1.back());
39+
40+
const auto& lats_2 = geo::util::gaussian_latitudes(80, false);
41+
42+
EXPECT(lats_2.size() == 2 * 80);
43+
EXPECT_APPROX(lats_2.front(), 89.141519);
44+
EXPECT_APPROX(lats_2.front(), -lats_2.back());
45+
}
46+
47+
48+
CASE("gaussian_quadrature_weights") {
49+
const auto& quad_1 = geo::util::gaussian_quadrature_weights(1);
50+
51+
EXPECT(quad_1.size() == 2);
52+
EXPECT_APPROX(quad_1.front(), 0.5);
53+
EXPECT_APPROX(quad_1.front(), quad_1.back());
54+
55+
const auto& quad_2 = geo::util::gaussian_quadrature_weights(80);
56+
57+
EXPECT(quad_2.size() == 2 * 80);
58+
EXPECT_APPROX(quad_2.front(), 0.000144);
59+
EXPECT_APPROX(quad_2.front(), quad_2.back());
60+
}
61+
62+
63+
} // namespace eckit::geo::test
64+
65+
66+
int main(int argc, char** argv) {
67+
eckit::maths::FloatingPointExceptions::enable_floating_point_exceptions();
68+
69+
return eckit::testing::run_tests(argc, argv);
70+
}

0 commit comments

Comments
 (0)