8 #ifndef TEST_UFO_METOFFICEBUDDYPAIRFINDER_H_
9 #define TEST_UFO_METOFFICEBUDDYPAIRFINDER_H_
18 #include <boost/optional.hpp>
20 #include "eckit/config/LocalConfiguration.h"
21 #include "eckit/testing/Test.h"
22 #include "ioda/ObsSpace.h"
23 #include "ioda/ObsVector.h"
24 #include "oops/mpi/mpi.h"
25 #include "oops/runs/Test.h"
26 #include "oops/util/Expect.h"
27 #include "test/TestEnvironment.h"
38 bool duplicatesFound =
false;
39 std::set<ObsPair> pairSet;
42 for (
const ObsPair &pair : pairs) {
43 bool inserted = pairSet.insert(pair).second;
45 duplicatesFound =
true;
46 oops::Log::trace() <<
"Duplicate pair found: (" << pair.first <<
", " << pair.second <<
")\n";
51 for (
const ObsPair &pair : pairs) {
52 ObsPair reversePair(pair.second, pair.first);
53 bool inserted = pairSet.insert(reversePair).second;
55 duplicatesFound =
true;
56 oops::Log::trace() <<
"Pair (" << pair.first <<
", " << pair.second
57 <<
") present also in reverse order\n";
61 oops::Log::trace().flush();
63 return duplicatesFound;
66 template <
typename Key,
typename Value>
67 Value
maxValue(
const std::map<Key, Value> &map)
72 typedef typename std::map<Key, Value>::value_type ValueType;
73 return std::max_element(map.begin(), map.end(),
74 [](
const ValueType &a,
const ValueType &b)
75 { return a.second < b.second; })->second;
79 std::map<int, int> numBuddiesByObsId;
80 for (
const ObsPair & pair : pairs)
81 ++numBuddiesByObsId[pair.first];
86 const std::vector<int> &stationIds) {
87 std::map<int, int> numBuddiesWithSameStationIdByObsId;
88 for (
const ObsPair & pair : pairs)
89 if (stationIds[pair.first] == stationIds[pair.second])
90 ++numBuddiesWithSameStationIdByObsId[pair.first];
91 return maxValue(numBuddiesWithSameStationIdByObsId);
95 util::DateTime bgn(conf.getString(
"window begin"));
96 util::DateTime end(conf.getString(
"window end"));
98 const eckit::LocalConfiguration obsSpaceConf(conf,
"obs space");
99 ioda::ObsTopLevelParameters obsParams;
100 obsParams.validateAndDeserialize(obsSpaceConf);
101 ioda::ObsSpace obsSpace(obsParams, oops::mpi::world(), bgn, end, oops::mpi::myself());
103 boost::optional<std::vector<float>> airPressures;
104 if (obsSpace.has(
"MetaData",
"air_pressure")) {
105 airPressures = std::vector<float>(obsSpace.nlocs());
106 obsSpace.get_db(
"MetaData",
"air_pressure", *airPressures);
109 std::vector<float> latitudes(obsSpace.nlocs());
110 obsSpace.get_db(
"MetaData",
"latitude", latitudes);
112 std::vector<float> longitudes(obsSpace.nlocs());
113 obsSpace.get_db(
"MetaData",
"longitude", longitudes);
115 std::vector<util::DateTime> datetimes(obsSpace.nlocs());
116 obsSpace.get_db(
"MetaData",
"datetime", datetimes);
118 std::vector<int> stationIds(obsSpace.recnum().begin(), obsSpace.recnum().end());
120 std::vector<size_t> validObsIds;
121 if (conf.has(
"valid_obs_ids")) {
122 validObsIds = conf.getUnsignedVector(
"valid_obs_ids");
124 validObsIds.resize(obsSpace.nlocs());
125 std::iota(validObsIds.begin(), validObsIds.end(), 0);
128 const eckit::LocalConfiguration modernFilterConf(conf,
"Met Office Buddy Check, modern");
130 modernOptions.deserialize(modernFilterConf);
132 std::vector<ObsPair> modernPairs;
135 airPressures.get_ptr(), stationIds);
136 const std::vector<MetOfficeBuddyPair> buddyPairs = finder.findBuddyPairs(validObsIds);
139 modernPairs.push_back(
ObsPair(pair.obsIdA, pair.obsIdB));
148 const eckit::LocalConfiguration legacyFilterConf(conf,
"Met Office Buddy Check, legacy");
150 legacyOptions.deserialize(legacyFilterConf);
152 std::vector<ObsPair> legacyPairs;
155 airPressures.get_ptr(), stationIds);
156 const std::vector<MetOfficeBuddyPair> buddyPairs = finder.findBuddyPairs(validObsIds);
159 legacyPairs.push_back(
ObsPair(pair.obsIdA, pair.obsIdB));
170 EXPECT(modernPairs.size() > legacyPairs.size());
173 CASE(
"ufo/MetOfficeBuddyPairFinder/"
174 "Duplicates, constraints on buddy counts, legacy pair collector") {
176 ::test::TestEnvironment::config(),
177 "Duplicates, constraints on buddy counts, "
178 "legacy pair collector"));
182 const std::vector<float> &latitudes,
183 const std::vector<float> &longitudes,
184 const std::vector<util::DateTime> &datetimes,
185 const std::vector<float> *pressures,
186 const std::vector<int> &stationIds,
187 const std::vector<size_t> &validObsIds) {
189 pressures, stationIds);
190 return finder.findBuddyPairs(validObsIds);
194 const float searchRadius = 100;
196 std::vector<float> referenceLatitudes = conf.getFloatVector(
"reference.latitudes");
197 std::vector<float> referenceLongitudes = conf.getFloatVector(
"reference.longitudes");
199 std::vector<util::DateTime> datetimes(2, util::DateTime());
200 std::vector<int> stationIds{1, 2};
202 std::vector<size_t> validObsIds{0, 1};
204 const eckit::LocalConfiguration filterConf(conf,
"Met Office Buddy Check");
206 options.deserialize(filterConf);
208 const std::vector<MetOfficeBuddyPair> referenceBuddyPairs =
findBuddyPairs(
209 options, referenceLatitudes, referenceLongitudes,
210 datetimes,
nullptr, stationIds, validObsIds);
211 EXPECT_EQUAL(referenceBuddyPairs.size(), 1);
214 for (
const eckit::LocalConfiguration &testConf : conf.getSubConfigurations(
"test")) {
215 std::vector<float> latitudes = testConf.getFloatVector(
"latitudes");
216 std::vector<float> longitudes = testConf.getFloatVector(
"longitudes");
217 const std::vector<MetOfficeBuddyPair> buddyPairs =
findBuddyPairs(
218 options, latitudes, longitudes, datetimes,
nullptr, stationIds, validObsIds);
219 EXPECT_EQUAL(buddyPairs.size(), 1);
226 referenceLatitudes[refPair.
obsIdB] - referenceLatitudes[refPair.
obsIdA] &&
228 referenceLongitudes[refPair.
obsIdB] - referenceLongitudes[refPair.
obsIdA]) {
237 referenceLatitudes[refPair.
obsIdA] - referenceLatitudes[refPair.
obsIdB] &&
239 referenceLongitudes[refPair.
obsIdA] - referenceLongitudes[refPair.
obsIdB]) {
247 CASE(
"ufo/TemporalThinning/Invariance to longitude, different zonal bands") {
249 ::test::TestEnvironment::config(),
250 "Invariance to longitude, different zonal bands"));
253 CASE(
"ufo/TemporalThinning/Invariance to longitude, same zonal band") {
255 ::test::TestEnvironment::config(),
256 "Invariance to longitude, same zonal band"));
260 double &endLat,
double &endLon)
266 endLat = std::asin(std::sin(startLat) * std::cos(
distance) +
267 std::cos(startLat) * std::sin(
distance) * std::cos(bearing));
268 endLon = startLon + std::atan2(std::sin(bearing) * std::sin(
distance) * std::cos(startLat),
269 std::cos(
distance) - std::sin(startLat) * std::sin(endLat));
275 float &endLat,
float &endLon)
277 double dEndLat, dEndLon;
283 template <
typename T>
284 bool contains(
const std::set<T> &set,
const T &element) {
285 return set.find(element) != set.end();
289 return ObsPair(pair.second, pair.first);
293 const eckit::LocalConfiguration filterConf(conf,
"Met Office Buddy Check");
295 options.deserialize(filterConf);
299 for (
const eckit::LocalConfiguration &testConf : conf.getSubConfigurations(
"test")) {
300 float centralLatitude = testConf.getFloat(
"central_latitude");
301 float centralLongitude = testConf.getFloat(
"central_longitude");
303 std::vector<float> latitudes{centralLatitude};
304 std::vector<float> longitudes{centralLongitude};
305 std::vector<ObsPair> pairsExpectedToBePresent, pairsExpectedToBeAbsent;
307 const int numBearings = 8;
308 for (
float i = 0; i < numBearings; ++i) {
309 float latitude, longitude;
312 0.99f * searchRadius, i * 360.0f / numBearings, latitude, longitude);
313 latitudes.push_back(latitude);
314 longitudes.push_back(longitude);
315 pairsExpectedToBePresent.push_back(
ObsPair(0, latitudes.size() - 1));
318 1.01f * searchRadius, i * 360.0f / numBearings, latitude, longitude);
319 latitudes.push_back(latitude);
320 longitudes.push_back(longitude);
321 pairsExpectedToBeAbsent.push_back(
ObsPair(0, latitudes.size() - 1));
324 const std::vector<util::DateTime> datetimes(latitudes.size(), util::DateTime());
325 const std::vector<int> stationIds(latitudes.size(), 0);
327 std::vector<size_t> validObsIds(latitudes.size());
328 std::iota(validObsIds.begin(), validObsIds.end(), 0);
330 const std::vector<MetOfficeBuddyPair> buddyPairs =
findBuddyPairs(
331 options, latitudes, longitudes, datetimes,
nullptr, stationIds, validObsIds);
333 std::set<ObsPair> pairs;
335 pairs.insert(
ObsPair(pair.obsIdA, pair.obsIdB));
338 for (
const ObsPair &pair : pairsExpectedToBePresent)
341 for (
const ObsPair &pair : pairsExpectedToBeAbsent)
346 CASE(
"ufo/TemporalThinning/Search radius") {
347 testSearchRadius(eckit::LocalConfiguration(::test::TestEnvironment::config(),
"Search radius"));
352 std::string
testid()
const override {
return "ufo::test::MetOfficeBuddyPairFinder";}
Options controlling the operation of the MetOfficeBuddyCheck filter.
oops::Parameter< int > maxNumBuddiesWithSameStationId
oops::Parameter< int > maxTotalNumBuddies
oops::Parameter< float > searchRadius
Maximum distance between two observations that may be classified as buddies, in km.
void clear() const override
void register_tests() const override
std::string testid() const override
float distance(const Point &a, const Point &b)
Returns the distance between the two cartesian-mapped Point arguments
void testSearchRadius(const eckit::LocalConfiguration &conf)
std::pair< int, int > ObsPair
Value maxValue(const std::map< Key, Value > &map)
int maxTotalNumBuddies(const std::vector< ObsPair > &pairs)
int maxNumBuddiesWithSameStationId(const std::vector< ObsPair > &pairs, const std::vector< int > &stationIds)
bool duplicatePairsPresent(const std::vector< ObsPair > &pairs)
ObsPair reverse(const ObsPair &pair)
CASE("ufo/DataExtractor/bilinearinterp/float_linear")
void findEndpoint(double startLat, double startLon, double distance, double bearing, double &endLat, double &endLon)
void testInvarianceToLongitude(const eckit::LocalConfiguration &conf)
void testDuplicatesAndBuddyCountConstraints(const eckit::LocalConfiguration &conf)
std::vector< MetOfficeBuddyPair > findBuddyPairs(const MetOfficeBuddyCheckParameters &options, const std::vector< float > &latitudes, const std::vector< float > &longitudes, const std::vector< util::DateTime > &datetimes, const std::vector< float > *pressures, const std::vector< int > &stationIds, const std::vector< size_t > &validObsIds)
bool contains(const std::set< T > &set, const T &element)
static constexpr double deg2rad
static constexpr double rad2deg
static constexpr double mean_earth_rad
Properties of a pair of observations checked against each other during buddy check.