UFO
ObsLocalization.h
Go to the documentation of this file.
1 /*
2  * (C) Copyright 2020-2021 UCAR
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 
8 #ifndef UFO_OBSLOCALIZATION_OBSLOCALIZATION_H_
9 #define UFO_OBSLOCALIZATION_OBSLOCALIZATION_H_
10 
11 #include <algorithm>
12 #include <memory>
13 #include <ostream>
14 #include <string>
15 #include <utility>
16 #include <vector>
17 
18 #include "atlas/util/Earth.h"
19 
20 #include "eckit/config/Configuration.h"
21 #include "eckit/container/KDTree.h"
22 #include "eckit/geometry/Point2.h"
23 #include "eckit/geometry/Point3.h"
24 #include "eckit/geometry/UnitSphere.h"
25 
26 #include "ioda/ObsSpace.h"
27 #include "ioda/ObsVector.h"
28 
29 #include "oops/base/ObsLocalizationBase.h"
30 #include "oops/util/missingValues.h"
31 
33 #include "ufo/ObsTraits.h"
34 
35 namespace ufo {
36 
37 /// Horizontal Box car observation space localization
38 template<class MODEL>
39 class ObsLocalization: public oops::ObsLocalizationBase<MODEL, ObsTraits> {
40  typedef typename MODEL::GeometryIterator GeometryIterator_;
41 
42  public:
43  struct TreeTrait {
44  typedef eckit::geometry::Point3 Point;
45  typedef double Payload;
46  };
47  typedef eckit::KDTreeMemory<TreeTrait> KDTree;
48  ObsLocalization(const eckit::Configuration &, const ioda::ObsSpace &);
49 
50  /// compute localization and save localization values in \p locvector
51  /// (missing values indicate that observation is outside of localization)
53  ioda::ObsVector & locvector) const override;
54 
55  const std::vector<int> & localobs() const {return localobs_;}
56  const std::vector<double> & horizontalObsdist() const {return obsdist_;}
57  const ObsLocParameters & localizationOptions() const {return options_;}
58 
59  private:
61  mutable std::vector<double> obsdist_;
62  mutable std::vector<int> localobs_;
63 
64  void print(std::ostream &) const override;
65 
66  /// KD-tree for searching for local obs
67  std::unique_ptr<KDTree> kd_;
68 
69  std::vector<float> lats_;
70  std::vector<float> lons_;
71 
72  /// TODO(travis) distribution name is needed for temporary fix, should be removed eventually
73  std::string distName_;
74 };
75 
76 // -----------------------------------------------------------------------------
77 
78 /*!
79  * \details Creates a KDTree class member that can be used for searching for local obs
80  */
81 template<typename MODEL>
82 ObsLocalization<MODEL>::ObsLocalization(const eckit::Configuration & config,
83  const ioda::ObsSpace & obsspace)
84  : lats_(obsspace.nlocs()), lons_(obsspace.nlocs())
85 {
86  options_.deserialize(config);
87 
88  // check that this distribution supports local obs space
89  // TODO(travis) this has been moved to computeLocalization as a quick fix for a bug.
90  distName_ = obsspace.distribution()->name();
91 
92  const size_t nlocs = obsspace.nlocs();
93  // Get latitudes and longitudes of all observations.
94  obsspace.get_db("MetaData", "longitude", lons_);
95  obsspace.get_db("MetaData", "latitude", lats_);
96 
98  kd_ = std::unique_ptr<KDTree> ( new KDTree() );
99  // Define points list from lat/lon values
100  typedef typename KDTree::PointType Point;
101  std::vector<typename KDTree::Value> points;
102  for (unsigned int i = 0; i < nlocs; i++) {
103  eckit::geometry::Point2 lonlat(lons_[i], lats_[i]);
104  Point xyz = Point();
105  // FIXME: get geometry from yaml, for now assume spherical Earth radius.
106  atlas::util::Earth::convertSphericalToCartesian(lonlat, xyz);
107  double index = static_cast<double>(i);
108  typename KDTree::Value v(xyz, index);
109  points.push_back(v);
110  }
111  // Create KDTree class member from points list.
112  kd_->build(points.begin(), points.end());
113  }
114 }
115 
116 // -----------------------------------------------------------------------------
117 
118 template<typename MODEL>
120  ioda::ObsVector & locvector) const {
121  oops::Log::trace() << "ObsLocalization::computeLocalization" << std::endl;
122 
123  // check that this distribution supports local obs space
124  // TODO(travis) this should be in the constructor, but currently
125  // breaks LETKF when using a split observer/solver
126  if ( distName_ != "Halo" && distName_ != "InefficientDistribution" ) {
127  std::string message = "Can not use ObsLocalization with distribution=" + distName_;
128  throw eckit::BadParameter(message);
129  }
130 
131  // clear arrays before proceeding
132  localobs_.clear();
133  obsdist_.clear();
134 
135  eckit::geometry::Point2 refPoint = *i;
136  size_t nlocs = lons_.size();
137  if ( options_.searchMethod == SearchMethod::BRUTEFORCE ) {
138  oops::Log::trace() << "Local obs searching via brute force." << std::endl;
139 
140  for (unsigned int jj = 0; jj < nlocs; ++jj) {
141  eckit::geometry::Point2 searchPoint(lons_[jj], lats_[jj]);
142  double localDist = options_.distance(refPoint, searchPoint);
143  if ( localDist < options_.lengthscale ) {
144  localobs_.push_back(jj);
145  obsdist_.push_back(localDist);
146  }
147  }
148  const boost::optional<int> & maxnobs = options_.maxnobs;
149  if ( (maxnobs != boost::none) && (localobs_.size() > *maxnobs ) ) {
150  for (unsigned int jj = 0; jj < localobs_.size(); ++jj) {
151  oops::Log::debug() << "Before sort [i, d]: " << localobs_[jj]
152  << " , " << obsdist_[jj] << std::endl;
153  }
154  // Construct a temporary paired vector to do the sorting
155  std::vector<std::pair<std::size_t, double>> localObsIndDistPair;
156  for (unsigned int jj = 0; jj < obsdist_.size(); ++jj) {
157  localObsIndDistPair.push_back(std::make_pair(localobs_[jj], obsdist_[jj]));
158  }
159 
160  // Use a lambda function to implement an ascending sort.
161  sort(localObsIndDistPair.begin(), localObsIndDistPair.end(),
162  [](const std::pair<std::size_t, double> & p1,
163  const std::pair<std::size_t, double> & p2){
164  return(p1.second < p2.second);
165  });
166 
167  // Unpair the sorted pair vector
168  for (unsigned int jj = 0; jj < obsdist_.size(); ++jj) {
169  localobs_[jj] = localObsIndDistPair[jj].first;
170  obsdist_[jj] = localObsIndDistPair[jj].second;
171  }
172 
173  // Truncate to maxNobs length
174  localobs_.resize(*maxnobs);
175  obsdist_.resize(*maxnobs);
176  }
177  } else if (nlocs > 0) {
178  // Check (nlocs > 0) is needed,
179  // otherwise, it will cause ASERT check fail in kdtree.findInSphere, and hang.
180 
181  oops::Log::trace() << "Local obs searching via KDTree" << std::endl;
182 
183  if ( options_.distanceType == DistanceType::CARTESIAN)
184  ABORT("ObsLocalization:: search method must be 'brute_force' when using 'cartesian' distance");
185 
186  // Using the radius of the earth
187  eckit::geometry::Point3 refPoint3D;
188  atlas::util::Earth::convertSphericalToCartesian(refPoint, refPoint3D);
189  double alpha = (options_.lengthscale / options_.radius_earth)/ 2.0; // angle in radians
190  double chordLength = 2.0*options_.radius_earth * sin(alpha); // search radius in 3D space
191 
192  auto closePoints = kd_->findInSphere(refPoint3D, chordLength);
193 
194  // put closePoints back into localobs_ and obsdist
195  for (unsigned int jloc = 0; jloc < closePoints.size(); ++jloc) {
196  localobs_.push_back(closePoints[jloc].payload()); // observation
197  obsdist_.push_back(closePoints[jloc].distance()); // distance
198  }
199 
200  // The obs are sorted in the kdtree call
201  const boost::optional<int> & maxnobs = options_.maxnobs;
202  if ( (maxnobs != boost::none) && (localobs_.size() > *maxnobs ) ) {
203  // Truncate to maxNobs length
204  localobs_.resize(*maxnobs);
205  obsdist_.resize(*maxnobs);
206  }
207  }
208 
209  // set all to missing (outside of localization distance)
210  const double missing = util::missingValue(double());
211  for (size_t jj = 0; jj < locvector.size(); ++jj) {
212  locvector[jj] = missing;
213  }
214 
215  // set localization for the obs inside localization distance to 1.0
216  const size_t nvars = locvector.nvars();
217  const size_t nlocal = localobs_.size();
218  for (size_t jlocal = 0; jlocal < nlocal; ++jlocal) {
219  // obsdist is calculated at each location; need to update R for each variable
220  for (size_t jvar = 0; jvar < nvars; ++jvar) {
221  locvector[jvar + localobs_[jlocal] * nvars] = 1.0;
222  }
223  }
224 }
225 
226 // -----------------------------------------------------------------------------
227 
228 template<typename MODEL>
229 void ObsLocalization<MODEL>::print(std::ostream & os) const {
230  os << "ObsLocalization (box car) horizontal localization with " << options_.lengthscale
231  << " lengthscale" << std::endl;
232 }
233 
234 } // namespace ufo
235 
236 #endif // UFO_OBSLOCALIZATION_OBSLOCALIZATION_H_
Options controlling local observations subsetting.
oops::Parameter< SearchMethod > searchMethod
Method for searching for nearest points: brute force or KD-tree.
Horizontal Box car observation space localization.
std::unique_ptr< KDTree > kd_
KD-tree for searching for local obs.
void print(std::ostream &) const override
const std::vector< int > & localobs() const
MODEL::GeometryIterator GeometryIterator_
std::vector< float > lons_
void computeLocalization(const GeometryIterator_ &, ioda::ObsVector &locvector) const override
std::string distName_
TODO(travis) distribution name is needed for temporary fix, should be removed eventually.
const ObsLocParameters & localizationOptions() const
ObsLocParameters options_
const std::vector< double > & horizontalObsdist() const
ObsLocalization(const eckit::Configuration &, const ioda::ObsSpace &)
std::vector< double > obsdist_
eckit::KDTreeMemory< TreeTrait > KDTree
std::vector< float > lats_
std::vector< int > localobs_
constexpr int missing
Definition: QCflags.h:20
float distance(const Point &a, const Point &b)
Returns the distance between the two cartesian-mapped Point arguments
std::array< float, 3 > Point
real(kind_real), parameter, public alpha
integer function nlocs(this)
Return the number of observational locations in this Locations object.
Definition: RunCRTM.h:27
eckit::geometry::Point3 Point