UFO
TrackCheck.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 #include <cmath>
12 #include <map>
13 #include <string>
14 #include <utility>
15 #include <vector>
16 
17 #include "eckit/config/Configuration.h"
18 #include "ioda/ObsDataVector.h"
19 #include "ioda/ObsSpace.h"
20 #include "oops/base/Variables.h"
21 #include "oops/util/DateTime.h"
22 #include "oops/util/Duration.h"
23 #include "oops/util/Logger.h"
24 #include "oops/util/sqr.h"
28 #include "ufo/utils/Constants.h"
31 
32 namespace ufo {
33 
34 TrackCheck::TrackObservation::TrackObservation(float latitude, float longitude,
35  const util::DateTime &time, float pressure)
36  : obsLocationTime_(latitude, longitude, time), pressure_(pressure),
37  rejectedInPreviousSweep_(false), rejectedBeforePreviousSweep_(false),
38  numNeighborsVisitedInPreviousSweep_{NO_PREVIOUS_SWEEP, NO_PREVIOUS_SWEEP}
39 {}
40 
42  const TrackObservation &buddyObs,
43  const TrackCheckParameters &options,
44  const PiecewiseLinearInterpolation &maxValidSpeedAtPressure,
45  float referencePressure) const {
46  CheckResults results;
47  util::Duration temporalDistance = abs(buddyObs.obsLocationTime_.time() -
48  this->obsLocationTime_.time());
49  const float spatialDistance = TrackCheckUtils::distance(this->obsLocationTime_.location(),
50  buddyObs.obsLocationTime_.location());
51 
52  // Estimate the speed and check if it is within the allowed range
53  const float conservativeSpeedEstimate =
54  (spatialDistance - options.spatialResolution) /
55  (temporalDistance + options.temporalResolution).toSeconds();
56  const float maxSpeed = maxValidSpeedAtPressure(referencePressure);
57  results.speedCheckResult = TrackCheckUtils::CheckResult(conservativeSpeedEstimate <= maxSpeed);
58 
59  // Estimate the climb rate and check if it is within the allowed range
60  if (options.maxClimbRate.value() != boost::none) {
61  const float pressureDiff = std::abs(pressure_ - buddyObs.pressure_);
62  const float conservativeClimbRateEstimate =
63  pressureDiff / (temporalDistance + options.temporalResolution).toSeconds();
64  results.climbRateCheckResult =
66  (conservativeClimbRateEstimate <= *options.maxClimbRate.value());
67  }
68 
69  const int resolutionMultiplier = options.distinctBuddyResolutionMultiplier;
70  results.isBuddyDistinct =
71  temporalDistance > resolutionMultiplier * options.temporalResolution &&
72  spatialDistance > resolutionMultiplier * options.spatialResolution;
73 
74  return results;
75 }
76 
78  checkCounter_.registerCheckResult(results.speedCheckResult);
79  checkCounter_.registerCheckResult(results.climbRateCheckResult);
80 }
81 
83  checkCounter_.unregisterCheckResult(results.speedCheckResult);
84  checkCounter_.unregisterCheckResult(results.climbRateCheckResult);
85 }
86 
88  rejectedBeforePreviousSweep_ = rejected();
89  rejectedInPreviousSweep_ = rejectedInSweep;
90 }
91 
93  return this->checkCounter_.failedChecksFraction();
94 }
95 
96 
97 TrackCheck::TrackCheck(ioda::ObsSpace & obsdb, const Parameters_ & parameters,
98  std::shared_ptr<ioda::ObsDataVector<int> > flags,
99  std::shared_ptr<ioda::ObsDataVector<float> > obserr)
100  : FilterBase(obsdb, parameters, flags, obserr), options_(parameters)
101 {
102  oops::Log::debug() << "TrackCheck: config = " << options_ << std::endl;
103 }
104 
105 // Required for the correct destruction of options_.
107 {}
108 
109 void TrackCheck::applyFilter(const std::vector<bool> & apply,
110  const Variables & filtervars,
111  std::vector<std::vector<bool>> & flagged) const {
113 
114  const std::vector<size_t> validObsIds
115  = obsAccessor.getValidObservationIds(apply, *flags_, filtervars);
116 
117  RecursiveSplitter splitter = obsAccessor.splitObservationsIntoIndependentGroups(validObsIds);
118  TrackCheckUtils::sortTracksChronologically(validObsIds, obsAccessor, splitter);
119 
122 
123  std::vector<bool> isRejected(obsPressureLoc.pressures.size(), false);
124  for (auto track : splitter.multiElementGroups()) {
125  identifyRejectedObservationsInTrack(track.begin(), track.end(), validObsIds,
126  obsPressureLoc, maxSpeedByPressure, isRejected);
127  }
128  obsAccessor.flagRejectedObservations(isRejected, flagged);
129 }
130 
132  const ObsAccessor &obsAccessor) const {
133  ObsGroupPressureLocationTime obsPressureLoc;
134  obsPressureLoc.locationTimes = TrackCheckUtils::collectObservationsLocations(obsAccessor);
135  obsPressureLoc.pressures = obsAccessor.getFloatVariableFromObsSpace("MetaData", "air_pressure");
136  return obsPressureLoc;
137 }
138 
140  const std::map<float, float> &maxSpeedInterpolationPoints =
142 
143  std::vector<double> pressures, maxSpeeds;
144  pressures.reserve(maxSpeedInterpolationPoints.size());
145  maxSpeeds.reserve(maxSpeedInterpolationPoints.size());
146 
147  // The interpolator needs to produce speeds in km/s rather than m/s because observation
148  // locations are expressed in kilometers.
149  const int metersPerKm = 1000;
150  for (const auto &pressureAndMaxSpeed : maxSpeedInterpolationPoints) {
151  pressures.push_back(pressureAndMaxSpeed.first);
152  maxSpeeds.push_back(pressureAndMaxSpeed.second / metersPerKm);
153  }
154 
155  return PiecewiseLinearInterpolation(std::move(pressures), std::move(maxSpeeds));
156 }
157 
159  std::vector<size_t>::const_iterator trackObsIndicesBegin,
160  std::vector<size_t>::const_iterator trackObsIndicesEnd,
161  const std::vector<size_t> &validObsIds,
162  const ObsGroupPressureLocationTime &obsPressureLoc,
163  const PiecewiseLinearInterpolation &maxValidSpeedAtPressure,
164  std::vector<bool> &isRejected) const {
165 
166  std::vector<TrackObservation> trackObservations = collectTrackObservations(
167  trackObsIndicesBegin, trackObsIndicesEnd, validObsIds, obsPressureLoc);
168  std::vector<float> workspace;
169 
170  while (sweepOverObservations(trackObservations, maxValidSpeedAtPressure, workspace) ==
172  // can't exit the loop yet
173  }
174 
175  flagRejectedTrackObservations(trackObsIndicesBegin, trackObsIndicesEnd,
176  validObsIds, trackObservations, isRejected);
177 }
178 
179 std::vector<TrackCheck::TrackObservation> TrackCheck::collectTrackObservations(
180  std::vector<size_t>::const_iterator trackObsIndicesBegin,
181  std::vector<size_t>::const_iterator trackObsIndicesEnd,
182  const std::vector<size_t> &validObsIds,
183  const ObsGroupPressureLocationTime &obsPressureLoc) const {
184  std::vector<TrackObservation> trackObservations;
185  trackObservations.reserve(trackObsIndicesEnd - trackObsIndicesBegin);
186  for (std::vector<size_t>::const_iterator it = trackObsIndicesBegin;
187  it != trackObsIndicesEnd; ++it) {
188  const size_t obsId = validObsIds[*it];
189  trackObservations.push_back(TrackObservation(obsPressureLoc.locationTimes.latitudes[obsId],
190  obsPressureLoc.locationTimes.longitudes[obsId],
191  obsPressureLoc.locationTimes.datetimes[obsId],
192  obsPressureLoc.pressures[obsId]));
193  }
194  return trackObservations;
195 }
196 
198  std::vector<TrackObservation> &trackObservations,
199  const PiecewiseLinearInterpolation &maxValidSpeedAtPressure,
200  std::vector<float> &workspace) const {
201 
202  std::vector<float> &failedChecksFraction = workspace;
203  failedChecksFraction.assign(trackObservations.size(), 0.0f);
204 
205  for (int obsIdx = 0; obsIdx < trackObservations.size(); ++obsIdx) {
206  TrackObservation &obs = trackObservations[obsIdx];
207  if (obs.rejected())
208  continue;
209 
210  for (Direction dir : { FORWARD, BACKWARD}) {
211  const bool firstSweep = obs.numNeighborsVisitedInPreviousSweep(dir) == NO_PREVIOUS_SWEEP;
212  const int numNeighborsVisitedInPreviousSweep =
213  firstSweep ? 0 : obs.numNeighborsVisitedInPreviousSweep(dir);
214  int numNewDistinctBuddiesToVisit = firstSweep ? options_.numDistinctBuddiesPerDirection : 0;
215 
216  auto getNthNeighbor = [&trackObservations, obsIdx, dir](int n) -> const TrackObservation* {
217  const int neighborObsIdx = obsIdx + (dir == FORWARD ? n : -n);
218  if (neighborObsIdx < 0 || neighborObsIdx >= trackObservations.size())
219  return nullptr; // We've reached the end of the track
220  else
221  return &trackObservations[neighborObsIdx];
222  };
223 
224  float minPressureBetween = obs.pressure();
225  int neighborIdx = 1;
226  const TrackObservation *neighborObs = getNthNeighbor(neighborIdx);
227  for (; neighborIdx <= numNeighborsVisitedInPreviousSweep && neighborObs != nullptr;
228  neighborObs = getNthNeighbor(++neighborIdx)) {
229  // Strictly speaking, neighborObs->pressure should be disregarded if neighborObs has already
230  // been rejected. However, that would force us to check each pair of observations anew
231  // whenever an observation between them is rejected, whereas as things stand, we only
232  // need to "undo" checks against rejected observations.
233  minPressureBetween = std::min(minPressureBetween, neighborObs->pressure());
234  if (neighborObs->rejectedInPreviousSweep()) {
235  CheckResults results = obs.checkAgainstBuddy(*neighborObs, options_,
236  maxValidSpeedAtPressure, minPressureBetween);
237  obs.unregisterCheckResults(results);
238  if (results.isBuddyDistinct) {
239  // The rejected distinct buddy needs to be replaced with another
240  ++numNewDistinctBuddiesToVisit;
241  }
242  }
243  }
244 
245  for (; numNewDistinctBuddiesToVisit > 0 && neighborObs != nullptr;
246  neighborObs = getNthNeighbor(++neighborIdx)) {
247  minPressureBetween = std::min(minPressureBetween, neighborObs->pressure());
248  if (!neighborObs->rejected()) {
249  CheckResults results = obs.checkAgainstBuddy(*neighborObs, options_,
250  maxValidSpeedAtPressure, minPressureBetween);
251  obs.registerCheckResults(results);
252  if (results.isBuddyDistinct)
253  --numNewDistinctBuddiesToVisit;
254  }
255  }
256 
257  const int numNeighborsVisitedInThisSweep = neighborIdx - 1;
258  assert(numNeighborsVisitedInThisSweep >= numNeighborsVisitedInPreviousSweep);
259  obs.setNumNeighborsVisitedInPreviousSweep(dir, numNeighborsVisitedInThisSweep);
260  } // end of loop over directions
261 
262  failedChecksFraction[obsIdx] = obs.getFailedChecksFraction();
263  }
264 
265  const float maxFailedChecksFraction = *std::max_element(failedChecksFraction.begin(),
266  failedChecksFraction.end());
267  const float failedChecksThreshold = options_.rejectionThreshold * maxFailedChecksFraction;
268  if (failedChecksThreshold <= 0)
270 
271  for (int obsIdx = 0; obsIdx < trackObservations.size(); ++obsIdx) {
272  const bool rejected = failedChecksFraction[obsIdx] > failedChecksThreshold;
273  trackObservations[obsIdx].registerSweepOutcome(rejected);
274  }
275 
277 }
278 
280  std::vector<size_t>::const_iterator trackObsIndicesBegin,
281  std::vector<size_t>::const_iterator trackObsIndicesEnd,
282  const std::vector<size_t> &validObsIds,
283  const std::vector<TrackObservation> &trackObservations,
284  std::vector<bool> &isRejected) const {
285  auto trackObsIndexIt = trackObsIndicesBegin;
286  auto trackObsIt = trackObservations.begin();
287  for (; trackObsIndexIt != trackObsIndicesEnd; ++trackObsIndexIt, ++trackObsIt)
288  if (trackObsIt->rejected())
289  isRejected[validObsIds[*trackObsIndexIt]] = true;
290 }
291 
292 void TrackCheck::print(std::ostream & os) const {
293  os << "TrackCheck: config = " << options_ << std::endl;
294 }
295 
296 } // namespace ufo
Base class for UFO QC filters.
Definition: FilterBase.h:45
This class provides access to observations that may be held on multiple MPI ranks.
Definition: ObsAccessor.h:56
RecursiveSplitter splitObservationsIntoIndependentGroups(const std::vector< size_t > &validObsIds, bool opsCompatibilityMode=false) const
Definition: ObsAccessor.cc:197
std::vector< size_t > getValidObservationIds(const std::vector< bool > &apply, const ioda::ObsDataVector< int > &flags, const Variables &filtervars, bool validIfAnyFilterVariablePassedQC=true) const
Return the IDs of observation locations that should be treated as valid by a filter.
Definition: ObsAccessor.cc:99
std::vector< float > getFloatVariableFromObsSpace(const std::string &group, const std::string &variable) const
Definition: ObsAccessor.cc:144
void flagRejectedObservations(const std::vector< bool > &isRejected, std::vector< std::vector< bool > > &flagged) const
Update flags of observations held on the current MPI rank.
Definition: ObsAccessor.cc:243
ioda::ObsSpace & obsdb_
std::shared_ptr< ioda::ObsDataVector< int > > flags_
Represents a piecewise linear interpolation of a set of data points.
Partitions an array into groups of elements equivalent according to certain criteria.
MultiElementGroupRange multiElementGroups() const
Return the range of equivalence classes consisting of more than one element.
Attributes of an observation belonging to a track.
void registerCheckResults(const CheckResults &result)
Definition: TrackCheck.cc:77
TrackObservation(float latitude, float longitude, const util::DateTime &time, float pressure)
Definition: TrackCheck.cc:34
CheckResults checkAgainstBuddy(const TrackObservation &buddyObs, const TrackCheckParameters &options, const PiecewiseLinearInterpolation &maxValidSpeedAtPressure, float referencePressure) const
Definition: TrackCheck.cc:41
void setNumNeighborsVisitedInPreviousSweep(Direction dir, int num)
void unregisterCheckResults(const CheckResults &result)
Definition: TrackCheck.cc:82
TrackCheckUtils::ObsLocationTime obsLocationTime_
void registerSweepOutcome(bool rejectedInSweep)
Definition: TrackCheck.cc:87
int numNeighborsVisitedInPreviousSweep(Direction dir) const
TrackCheck(ioda::ObsSpace &obsdb, const Parameters_ &parameters, std::shared_ptr< ioda::ObsDataVector< int > > flags, std::shared_ptr< ioda::ObsDataVector< float > > obserr)
Definition: TrackCheck.cc:97
void applyFilter(const std::vector< bool > &, const Variables &, std::vector< std::vector< bool >> &) const override
Definition: TrackCheck.cc:109
TrackCheckUtils::SweepResult sweepOverObservations(std::vector< TrackObservation > &trackObservations, const PiecewiseLinearInterpolation &maxValidSpeedAtPressure, std::vector< float > &workspace) const
Definition: TrackCheck.cc:197
ObsGroupPressureLocationTime collectObsPressuresLocationsTimes(const ObsAccessor &obsAccessor) const
Definition: TrackCheck.cc:131
std::vector< TrackObservation > collectTrackObservations(std::vector< size_t >::const_iterator trackObsIndicesBegin, std::vector< size_t >::const_iterator trackObsIndicesEnd, const std::vector< size_t > &validObsIds, const ObsGroupPressureLocationTime &obsPressureLoc) const
Definition: TrackCheck.cc:179
void print(std::ostream &) const override
Definition: TrackCheck.cc:292
~TrackCheck() override
Definition: TrackCheck.cc:106
PiecewiseLinearInterpolation makeMaxSpeedByPressureInterpolation() const
Returns an interpolator mapping pressures (in Pa) to maximum accepted speeds (in km/s).
Definition: TrackCheck.cc:139
void flagRejectedTrackObservations(std::vector< size_t >::const_iterator trackObsIndicesBegin, std::vector< size_t >::const_iterator trackObsIndicesEnd, const std::vector< size_t > &validObsIds, const std::vector< TrackObservation > &trackObservations, std::vector< bool > &isRejected) const
Definition: TrackCheck.cc:279
static const int NO_PREVIOUS_SWEEP
void identifyRejectedObservationsInTrack(std::vector< size_t >::const_iterator trackObsIndicesBegin, std::vector< size_t >::const_iterator trackObsIndicesEnd, const std::vector< size_t > &validObsIds, const ObsGroupPressureLocationTime &obsPressureLoc, const PiecewiseLinearInterpolation &maxSpeedByPressure, std::vector< bool > &isRejected) const
Definition: TrackCheck.cc:158
Options controlling the operation of the track check filter.
oops::Parameter< std::map< float, float > > maxSpeedInterpolationPoints
oops::OptionalParameter< float > maxClimbRate
Maximum allowed rate of ascent and descent (Pa/s). If not set, climb rate checks are disabled.
oops::Parameter< float > rejectionThreshold
oops::Parameter< util::Duration > temporalResolution
oops::Parameter< float > spatialResolution
oops::Parameter< int > distinctBuddyResolutionMultiplier
oops::Parameter< int > numDistinctBuddiesPerDirection
std::vector< util::DateTime > datetimes
const util::DateTime & time() const
oops::OptionalParameter< Variable > stationIdVariable
constexpr int track
Definition: QCflags.h:31
ObsGroupLocationTimes collectObservationsLocations(const ObsAccessor &obsAccessor)
ObsAccessor createObsAccessor(const boost::optional< Variable > &stationIdVariable, const ioda::ObsSpace &obsdb)
Create an ObsAccessor object providing access to observations that need to be checked by the current ...
void sortTracksChronologically(const std::vector< size_t > &validObsIds, const ObsAccessor &obsAccessor, RecursiveSplitter &splitter)
float distance(const Point &a, const Point &b)
Returns the distance between the two cartesian-mapped Point arguments
Definition: RunCRTM.h:27
util::Duration abs(const util::Duration &duration)
Results of cross-checking an observation with another (a "buddy").
TrackCheckUtils::CheckResult climbRateCheckResult
TrackCheckUtils::CheckResult speedCheckResult
TrackCheckUtils::ObsGroupLocationTimes locationTimes