UFO
MetOfficeBuddyPairFinder.cc
Go to the documentation of this file.
1 /*
2  * (C) Copyright 2020 Met Office UK
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 
9 
10 #include <algorithm>
11 
12 #include "oops/util/Logger.h"
17 
18 #include <boost/make_unique.hpp>
19 
20 namespace ufo {
21 
22 namespace {
23 
24 float zonalBandWidth(int numBands) {
25  return 180.0f / numBands;
26 }
27 
28 } // namespace
29 
31  const std::vector<float> &latitudes,
32  const std::vector<float> &longitudes,
33  const std::vector<util::DateTime> &datetimes,
34  const std::vector<float> *pressures,
35  const std::vector<int> &stationIds)
36  : options_(options), latitudes_(latitudes), longitudes_(longitudes),
37  datetimes_(datetimes), pressures_(pressures), stationIds_(stationIds)
38 {}
39 
40 std::vector<MetOfficeBuddyPair> MetOfficeBuddyPairFinder::findBuddyPairs(
41  const std::vector<size_t> & validObsIds) {
42  std::vector<int> validObsIdsInSortOrder;
43  std::vector<int> bandLbounds;
44  sortObservations(validObsIds, validObsIdsInSortOrder, bandLbounds);
45  return pairObservations(validObsIdsInSortOrder, bandLbounds);
46 }
47 
48 void MetOfficeBuddyPairFinder::sortObservations(const std::vector<size_t> & validObsIds,
49  std::vector<int> &validObsIdsInSortOrder,
50  std::vector<int> &bandLbounds)
51 {
52  // Initialize output parameters
53 
54  validObsIdsInSortOrder.clear();
55  validObsIdsInSortOrder.reserve(validObsIds.size());
56  bandLbounds.assign(options_.numZonalBands + 1, 0);
57 
58  // Identify the band containing each valid observation
59 
60  const float bandWidth = zonalBandWidth(options_.numZonalBands);
61  std::vector<int> bandIndices(validObsIds.size());
62  for (size_t validObsIndex = 0; validObsIndex < validObsIds.size(); ++validObsIndex) {
63  const size_t obsId = validObsIds[validObsIndex];
64  // Use 89.999 to round up values, e.g. ranges are 70.0 to 74.999
65  // (There may be a more elegant way to do this, but use this one for compatibility with OPS.)
66  bandIndices[validObsIndex] =
67  std::max(0, static_cast<int>((89.999f - latitudes_[obsId]) / bandWidth));
68  }
69 
70  // Sort observations
71 
72  RecursiveSplitter splitter(validObsIds.size());
73  splitter.groupBy(bandIndices);
74  splitter.sortGroupsBy(
75  [this, &validObsIds](size_t obsIndexA, size_t obsIndexB)
76  {
77  size_t obsIdA = validObsIds[obsIndexA];
78  size_t obsIdB = validObsIds[obsIndexB];
79  if (pressures_ != nullptr)
80  return std::make_tuple(longitudes_[obsIdA], -latitudes_[obsIdA],
81  (*pressures_)[obsIdA], datetimes_[obsIdA]) <
82  std::make_tuple(longitudes_[obsIdB], -latitudes_[obsIdB],
83  (*pressures_)[obsIdB], datetimes_[obsIdB]);
84  else
85  return std::make_tuple(longitudes_[obsIdA], -latitudes_[obsIdA], datetimes_[obsIdA]) <
86  std::make_tuple(longitudes_[obsIdB], -latitudes_[obsIdB], datetimes_[obsIdB]);
87  });
88 
89  // Fill the validObsIdsInSortOrder and bandLbounds vectors
90 
91  int currentBandIndex = -1;
92  for (auto group : splitter.groups()) {
93  for (size_t validObsIndex : group) {
94  const size_t obsId = validObsIds[validObsIndex];
95  for (int band = currentBandIndex + 1; band <= bandIndices[validObsIndex]; ++band) {
96  bandLbounds[band] = validObsIdsInSortOrder.size();
97  }
98  currentBandIndex = bandIndices[validObsIndex];
99  validObsIdsInSortOrder.push_back(obsId);
100  }
101  }
102  for (int band = currentBandIndex + 1; band < bandLbounds.size(); ++band) {
103  bandLbounds[band] = validObsIds.size();
104  }
105 
106  oops::Log::trace() << "Buddy check: " << validObsIds.size() << " input observations" << std::endl;
107 }
108 
109 std::vector<MetOfficeBuddyPair> MetOfficeBuddyPairFinder::pairObservations(
110  const std::vector<int> &validObsIdsInSortOrder,
111  const std::vector<int> &bandLbounds) {
112 
113  std::vector<MetOfficeBuddyPair> pairs;
114 
115  // Initialise variables
116  const float bandWidth = zonalBandWidth(options_.numZonalBands);
117  // eqn 3.1
119  const float searchDLatB = searchDLat + 0.5f * bandWidth;
120  const int numSearchBands = static_cast<int>(searchDLat / bandWidth) + 1;
121 
122  typedef std::vector<int>::const_iterator ObsIdIt;
123  typedef std::vector<int>::const_reverse_iterator ObsIdRevIt;
124 
125  std::vector<ObsIdIt> bandBegins(options_.numZonalBands);
126  std::vector<ObsIdIt> bandEnds(options_.numZonalBands);
127 
128  for (int bandIndex = 0; bandIndex < options_.numZonalBands; ++bandIndex) {
129  bandBegins[bandIndex] = validObsIdsInSortOrder.begin() + bandLbounds[bandIndex];
130  bandEnds[bandIndex] = validObsIdsInSortOrder.begin() + bandLbounds[bandIndex + 1];
131  }
132 
133  std::vector<ObsIdIt> firstObsToCheckInBands(options_.numZonalBands);
134 
135  // Collects buddies of a single observation. When we're done with that observation, the collected
136  // list of buddies is extracted into 'pairs' and the collector is reset.
137  std::unique_ptr<MetOfficeBuddyCollector> buddyCollector = makeBuddyCollector();
138 
139  // Iterate over all bands
140  for (int jBandA = 0; jBandA < options_.numZonalBands; ++jBandA) {
141  const float lonSearchRangeHalfWidth = getLongitudeSearchRangeHalfWidth(jBandA, bandWidth);
142 
143  const int firstBandToSearch = jBandA;
144  const int lastBandToSearch = std::min(options_.numZonalBands.value() - 1,
145  jBandA + numSearchBands);
146 
147  firstObsToCheckInBands = bandBegins;
148 
149  // Iterate over observations in (jBandA)th band
150  for (ObsIdIt obsIdItA = bandBegins[jBandA]; obsIdItA != bandEnds[jBandA]; ++obsIdItA) {
151  const int obsIdA = *obsIdItA;
152 
153  const float minLonToCheck = longitudes_[obsIdA] - lonSearchRangeHalfWidth;
154  const float maxLonToCheck = longitudes_[obsIdA] + lonSearchRangeHalfWidth;
155 
156  buddyCollector->reset(obsIdA);
157 
158  // Iterate over bands that may contain buddies
159  for (int jBandB = firstBandToSearch; jBandB <= lastBandToSearch; ++jBandB) {
160  float midBandLatB = 90.0f - bandWidth * (jBandB + 0.5f);
161  if (std::abs(latitudes_[obsIdA] - midBandLatB) > searchDLatB)
162  continue;
163 
164  ObsIdIt firstObsIdItB = firstObsToCheckInBands[jBandB];
165  if (jBandA == jBandB)
166  firstObsIdItB = obsIdItA + 1; // Iterate only over observations following observation A.
167 
168  // First loop: look for buddies at longitudes [minLonToCheck, maxLonToCheck]
169  bool firstObsInSearchRangeFound = false;
170  for (ObsIdIt obsIdItB = firstObsIdItB; obsIdItB != bandEnds[jBandB]; ++obsIdItB) {
171  const int obsIdB = *obsIdItB;
172  if (longitudes_[obsIdB] < minLonToCheck)
173  continue;
174  if (longitudes_[obsIdB] > maxLonToCheck)
175  break;
176  if (!firstObsInSearchRangeFound) {
177  firstObsToCheckInBands[jBandB] = obsIdItB;
178  firstObsInSearchRangeFound = true;
179  }
180 
181  buddyCollector->examinePotentialBuddy(obsIdB);
182  if (buddyCollector->foundEnoughBuddies())
183  goto FinishProcessingObsA;
184  if (buddyCollector->foundEnoughBuddiesInCurrentBand())
185  goto FinishProcessingBandB;
186  }
187 
188  // Optional second loop: look for buddies at longitudes [-180, maxLonToCheck - 360]
189  if (maxLonToCheck > 180 && (jBandA != jBandB || lonSearchRangeHalfWidth < 180)) {
190  // Observation A is near band end (+180); wrap around and check the band start too.
191  float wrappedMaxLonToCheck = maxLonToCheck - 360;
192  for (ObsIdIt obsIdItB = bandBegins[jBandB]; obsIdItB != bandEnds[jBandB]; ++obsIdItB) {
193  const int obsIdB = *obsIdItB;
194  if (longitudes_[obsIdB] > wrappedMaxLonToCheck ||
195  longitudes_[obsIdB] >= minLonToCheck /* visited already in the first loop */)
196  break;
197 
198  buddyCollector->examinePotentialBuddy(obsIdB);
199  if (buddyCollector->foundEnoughBuddies())
200  goto FinishProcessingObsA;
201  if (buddyCollector->foundEnoughBuddiesInCurrentBand())
202  goto FinishProcessingBandB;
203  }
204  }
205 
206  // Optional third loop: look for buddies at longitudes [minLonToCheck + 360, 180]
207  if (minLonToCheck < -180 && jBandA != jBandB) {
208  // Observation A is near band start (-180); wrap around and check the band end too.
209  float wrappedMinLonToCheck = minLonToCheck + 360;
210  for (ObsIdRevIt obsIdItB(bandEnds[jBandB]), reverseBandEnd(bandBegins[jBandB]);
211  obsIdItB != reverseBandEnd; ++obsIdItB) {
212  const int obsIdB = *obsIdItB;
213  if (longitudes_[obsIdB] < wrappedMinLonToCheck ||
214  longitudes_[obsIdB] <= maxLonToCheck /* visited already in the first loop */)
215  break;
216 
217  buddyCollector->examinePotentialBuddy(obsIdB);
218  if (buddyCollector->foundEnoughBuddies())
219  goto FinishProcessingObsA;
220  if (buddyCollector->foundEnoughBuddiesInCurrentBand())
221  goto FinishProcessingBandB;
222  }
223  }
224 
225 FinishProcessingBandB:
226  buddyCollector->startProcessingNextBand();
227  } // end of secondary loop over bands (jBandB)
228 FinishProcessingObsA:
229  buddyCollector->appendBuddyPairsTo(pairs);
230  } // end of main loop over observations (obsIdItA)
231  } // end of main loop over bands (jBandA)
232 
233  oops::Log::trace() << "Found " << pairs.size() << " buddy pairs.\n";
234 
235  return pairs;
236 }
237 
238 std::unique_ptr<MetOfficeBuddyCollector> MetOfficeBuddyPairFinder::makeBuddyCollector() const {
240  return boost::make_unique<MetOfficeBuddyCollectorV1>(options_, latitudes_,
242  else
243  return boost::make_unique<MetOfficeBuddyCollectorV2>(options_, latitudes_,
245 }
246 
248  float bandWidth) const {
249  const float earthRadius = Constants::mean_earth_rad;
250  const float deg2rad = static_cast<float>(Constants::deg2rad);
251  const float rad2deg = static_cast<float>(Constants::rad2deg);
252 
253  const float midBandLatA = 90.0f - bandWidth * (bandIndex + 0.5f);
254  // eqn 3.2a
255  const float rad = earthRadius * std::cos((std::abs(midBandLatA) + bandWidth * 0.5f) * deg2rad);
256  if (rad <= 10.0f)
257  return 360.0f; // Adjacent to pole
258  else
259  return rad2deg * options_.searchRadius / rad; // eqn 3.2b
260 }
261 
262 } // namespace ufo
263 
MetOfficeBuddyCollectorV2.h
ufo::MetOfficeBuddyPairFinder::MetOfficeBuddyPairFinder
MetOfficeBuddyPairFinder(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)
Constructor.
Definition: MetOfficeBuddyPairFinder.cc:30
ufo::MetOfficeBuddyCheckParameters::numZonalBands
oops::Parameter< int > numZonalBands
Definition: MetOfficeBuddyCheckParameters.h:68
ufo::RecursiveSplitter::sortGroupsBy
void sortGroupsBy(Compare comp)
Sort the elements in each equivalence class in ascending order.
Definition: src/ufo/utils/RecursiveSplitter.h:329
ufo::MetOfficeBuddyPairFinder::makeBuddyCollector
std::unique_ptr< MetOfficeBuddyCollector > makeBuddyCollector() const
Definition: MetOfficeBuddyPairFinder.cc:238
ufo::MetOfficeBuddyPairFinder::stationIds_
const std::vector< int > & stationIds_
Definition: src/ufo/filters/MetOfficeBuddyPairFinder.h:82
MetOfficeBuddyCollectorV1.h
ufo::RecursiveSplitter
Partitions an array into groups of elements equivalent according to certain criteria.
Definition: src/ufo/utils/RecursiveSplitter.h:63
ufo::anonymous_namespace{MetOfficeBuddyPairFinder.cc}::zonalBandWidth
float zonalBandWidth(int numBands)
Definition: MetOfficeBuddyPairFinder.cc:24
ufo
Definition: RunCRTM.h:27
MetOfficeBuddyPairFinder.h
ufo::Constants::rad2deg
static constexpr double rad2deg
Definition: Constants.h:22
ufo::Constants::mean_earth_rad
static constexpr double mean_earth_rad
Definition: Constants.h:39
ufo::MetOfficeBuddyCheckParameters
Options controlling the operation of the MetOfficeBuddyCheck filter.
Definition: MetOfficeBuddyCheckParameters.h:41
ufo::MetOfficeBuddyCheckParameters::searchRadius
oops::Parameter< float > searchRadius
Maximum distance between two observations that may be classified as buddies, in km.
Definition: MetOfficeBuddyCheckParameters.h:49
ufo::RecursiveSplitter::groupBy
void groupBy(const std::vector< size_t > &categories)
Split existing equivalence classes according to a new criterion.
Definition: src/ufo/utils/RecursiveSplitter.h:275
ufo::MetOfficeBuddyPairFinder::sortObservations
void sortObservations(const std::vector< size_t > &validObsIds, std::vector< int > &validObsIdsInSortOrder, std::vector< int > &bandLbounds)
Sorts observations in an order facilitating rapid search for buddies.
Definition: MetOfficeBuddyPairFinder.cc:48
ufo::RecursiveSplitter::groups
GroupRange groups() const
Return the range of equivalence classes.
Definition: src/ufo/utils/RecursiveSplitter.h:290
ufo::MetOfficeBuddyPairFinder::longitudes_
const std::vector< float > & longitudes_
Definition: src/ufo/filters/MetOfficeBuddyPairFinder.h:79
ufo::MetOfficeBuddyPairFinder::pressures_
const std::vector< float > * pressures_
Definition: src/ufo/filters/MetOfficeBuddyPairFinder.h:81
ufo::MetOfficeBuddyCheckParameters::useLegacyBuddyCollector
oops::Parameter< bool > useLegacyBuddyCollector
Definition: MetOfficeBuddyCheckParameters.h:101
ufo::abs
util::Duration abs(const util::Duration &duration)
Definition: TrackCheckUtils.h:31
ufo::MetOfficeBuddyPairFinder::options_
const MetOfficeBuddyCheckParameters & options_
Definition: src/ufo/filters/MetOfficeBuddyPairFinder.h:77
ufo::MetOfficeBuddyPairFinder::getLongitudeSearchRangeHalfWidth
float getLongitudeSearchRangeHalfWidth(int bandIndex, float bandWidth) const
Definition: MetOfficeBuddyPairFinder.cc:247
ufo::Constants::deg2rad
static constexpr double deg2rad
Definition: Constants.h:21
ufo::MetOfficeBuddyPairFinder::pairObservations
std::vector< MetOfficeBuddyPair > pairObservations(const std::vector< int > &validObsIdsInSortOrder, const std::vector< int > &bandLbounds)
Finds pairs of observations to be considered as buddies. Calculates the distance and mutual orientati...
Definition: MetOfficeBuddyPairFinder.cc:109
ufo_constants_mod::rad2deg
real(kind_real), parameter, public rad2deg
Definition: ufo_constants_mod.F90:51
ufo::MetOfficeBuddyPairFinder::datetimes_
const std::vector< util::DateTime > & datetimes_
Definition: src/ufo/filters/MetOfficeBuddyPairFinder.h:80
ufo_constants_mod::deg2rad
real(kind_real), parameter, public deg2rad
Definition: ufo_constants_mod.F90:50
MetOfficeBuddyCheckParameters.h
ufo::MetOfficeBuddyPairFinder::findBuddyPairs
std::vector< MetOfficeBuddyPair > findBuddyPairs(const std::vector< size_t > &validObsIds)
Returns a list of MetOfficeBuddyPair objects representing pairs of "buddy" observations that should b...
Definition: MetOfficeBuddyPairFinder.cc:40
ufo::MetOfficeBuddyPairFinder::latitudes_
const std::vector< float > & latitudes_
Definition: src/ufo/filters/MetOfficeBuddyPairFinder.h:78
RecursiveSplitter.h