IODA
AtlasDistribution.cc
Go to the documentation of this file.
1 /*
2  * (C) Crown copyright 2021 Met Office
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 #include <iostream>
9 #include <unordered_set>
10 
11 #include <boost/make_unique.hpp>
12 
13 #include "atlas/mesh.h"
14 #include "atlas/meshgenerator.h"
15 #include "atlas/util/PolygonLocator.h"
16 #include "atlas/util/PolygonXY.h"
17 
18 #include "eckit/mpi/Comm.h"
19 #include "ioda/distribution/AtlasDistribution.h"
20 #include "ioda/distribution/DistributionFactory.h"
21 #include "oops/util/Logger.h"
22 
23 namespace ioda {
24 
25 namespace {
26 const char DIST_NAME[] = "Atlas";
27 } // namespace
28 
29 // -----------------------------------------------------------------------------
30 
31 /// \brief Assigns records to MPI ranks for the AtlasDistribution.
33  public:
34  /// Constructs an Atlas grid and mesh using settings loaded from the `grid` section
35  /// of `config`; then partitions the mesh across processes making up the `atlas::mpi::Comm()`
36  /// communicator.
37  explicit RecordAssigner(const eckit::Configuration & config);
38 
39  /// If this record hasn't been assigned to any process yet, assigns it to the process
40  /// owning the partition containing `point`.
41  ///
42  /// It is assumed that records will be assigned in consecutive order.
43  void assignRecord(std::size_t recNum, const eckit::geometry::Point2 & point);
44 
45  /// Returns true if record `recNum` has been assigned to the calling process, false otherwise.
46  bool isMyRecord(std::size_t recNum) const;
47 
48  private:
49  bool isInMyDomain(const eckit::geometry::Point2 & point) const;
50 
51  private:
52  atlas::Mesh mesh_;
53  std::unique_ptr<atlas::util::PolygonLocator> locator_;
54 
55  std::unordered_set<std::size_t> myRecords_;
56  std::size_t nextRecordToAssign_ = 0;
57 };
58 
59 AtlasDistribution::RecordAssigner::RecordAssigner(const eckit::Configuration & config)
60 {
61  eckit::LocalConfiguration gridConfig(config, "grid");
62  atlas::util::Config atlasConfig(gridConfig);
63 
64  atlas::Grid grid(atlasConfig);
65 
66  atlasConfig.set("type", grid.meshgenerator().getString("type"));
67  atlas::MeshGenerator generator(atlasConfig);
68 
69  mesh_ = generator.generate(grid);
70  if (mesh_->nb_partitions() != atlas::mpi::comm().size()) {
71  std::stringstream msg;
72  msg << "The number of mesh partitions, " << mesh_->nb_partitions()
73  << ", is different from the number of MPI processes, " << atlas::mpi::comm().size();
74  throw eckit::Exception(msg.str(), Here());
75  }
76 
77  locator_ = boost::make_unique<atlas::util::PolygonLocator>(
78  atlas::util::ListPolygonXY(mesh_.polygons()), mesh_.projection());
79 }
80 
82  const eckit::geometry::Point2 & point) {
83  if (recNum == nextRecordToAssign_) {
84  const bool myRecord = isInMyDomain(point);
85  oops::Log::debug() << "RecordAssigner::assignRecord(): is " << recNum << " my record? "
86  << myRecord << std::endl;
87  if (myRecord)
88  myRecords_.insert(recNum);
89  ++nextRecordToAssign_;
90  } else {
91  // We assume records will be assigned in consecutive order
92  ASSERT(recNum < nextRecordToAssign_);
93  }
94 }
95 
96 bool AtlasDistribution::RecordAssigner::isMyRecord(std::size_t recNum) const {
97  return myRecords_.find(recNum) != myRecords_.end();
98 }
99 
100 bool AtlasDistribution::RecordAssigner::isInMyDomain(const eckit::geometry::Point2 & point) const {
101  const atlas::idx_t partition = (*locator_)(point);
102  oops::Log::debug() << "RecordAssigner::isInMyDomain(): Polygon locator says "
103  << point << " is in domain " << partition << std::endl;
104  return partition == atlas::mpi::comm().rank();
105 }
106 
107 // -----------------------------------------------------------------------------
108 
110 
111 AtlasDistribution::AtlasDistribution(const eckit::mpi::Comm & comm,
112  const eckit::Configuration & config)
114  recordAssigner_(boost::make_unique<RecordAssigner>(config))
115 {
116  oops::Log::trace() << "AtlasDistribution constructed" << std::endl;
117 }
118 
120  oops::Log::trace() << "AtlasDistribution destructed" << std::endl;
121 }
122 
123 void AtlasDistribution::assignRecord(const std::size_t recNum,
124  const std::size_t locNum,
125  const eckit::geometry::Point2 & point) {
126  recordAssigner_->assignRecord(recNum, point);
127  NonoverlappingDistribution::assignRecord(recNum, locNum, point);
128 }
129 
130 bool AtlasDistribution::isMyRecord(std::size_t RecNum) const {
131  return recordAssigner_->isMyRecord(RecNum);
132 }
133 
134 std::string AtlasDistribution::name() const {
135  return DIST_NAME;
136 }
137 // -----------------------------------------------------------------------------
138 
139 } // namespace ioda
Assigns records to MPI ranks for the AtlasDistribution.
void assignRecord(std::size_t recNum, const eckit::geometry::Point2 &point)
std::unordered_set< std::size_t > myRecords_
bool isInMyDomain(const eckit::geometry::Point2 &point) const
std::unique_ptr< atlas::util::PolygonLocator > locator_
RecordAssigner(const eckit::Configuration &config)
bool isMyRecord(std::size_t recNum) const
Returns true if record recNum has been assigned to the calling process, false otherwise.
AtlasDistribution(const eckit::mpi::Comm &comm, const eckit::Configuration &config)
bool isMyRecord(std::size_t recNum) const override
Returns true if record RecNum has been assigned to the calling PE during a previous call to assignRec...
std::string name() const override
void assignRecord(const std::size_t recNum, const std::size_t locNum, const eckit::geometry::Point2 &point) override
If the record RecNum has not yet been assigned to a PE, assigns it to the appropriate PE.
std::unique_ptr< RecordAssigner > recordAssigner_
A class able to instantiate objects of type T, which should be a subclass of Distribution.
Implements some methods of Distribution in a manner suitable for distributions storing each observati...
void assignRecord(const std::size_t RecNum, const std::size_t LocNum, const eckit::geometry::Point2 &point) override
If the record RecNum has not yet been assigned to a PE, assigns it to the appropriate PE.
static DistributionMaker< AtlasDistribution > maker(DIST_NAME)