Tpetra parallel linear algebra  Version of the Day
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
Tpetra_Details_makeColMap_def.hpp
Go to the documentation of this file.
1 #ifndef TPETRA_DETAILS_MAKECOLMAP_DEF_HPP
2 #define TPETRA_DETAILS_MAKECOLMAP_DEF_HPP
3 
4 // @HEADER
5 // ***********************************************************************
6 //
7 // Tpetra: Templated Linear Algebra Services Package
8 // Copyright (2008) Sandia Corporation
9 //
10 // Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
11 // the U.S. Government retains certain rights in this software.
12 //
13 // Redistribution and use in source and binary forms, with or without
14 // modification, are permitted provided that the following conditions are
15 // met:
16 //
17 // 1. Redistributions of source code must retain the above copyright
18 // notice, this list of conditions and the following disclaimer.
19 //
20 // 2. Redistributions in binary form must reproduce the above copyright
21 // notice, this list of conditions and the following disclaimer in the
22 // documentation and/or other materials provided with the distribution.
23 //
24 // 3. Neither the name of the Corporation nor the names of the
25 // contributors may be used to endorse or promote products derived from
26 // this software without specific prior written permission.
27 //
28 // THIS SOFTWARE IS PROVIDED BY SANDIA CORPORATION "AS IS" AND ANY
29 // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
31 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SANDIA CORPORATION OR THE
32 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
33 // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
34 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
35 // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
36 // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
37 // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
38 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 //
40 // Questions? Contact Michael A. Heroux (maherou@sandia.gov)
41 //
42 // ************************************************************************
43 // @HEADER
44 
55 
56 #include "Tpetra_RowGraph.hpp"
57 #include "Tpetra_Util.hpp"
58 #include "Teuchos_Array.hpp"
59 #include <set>
60 #include <vector>
61 
62 namespace Tpetra {
63 namespace Details {
64 
65 template <class LO, class GO, class NT>
66 int
67 makeColMap (Teuchos::RCP<const Tpetra::Map<LO, GO, NT> >& colMap,
68  Teuchos::Array<int>& remotePIDs,
69  const Teuchos::RCP<const Tpetra::Map<LO, GO, NT> >& domMap,
70  const RowGraph<LO, GO, NT>& graph,
71  const bool sortEachProcsGids,
72  std::ostream* errStrm)
73 {
74  using Teuchos::Array;
75  using Teuchos::ArrayView;
76  using Teuchos::rcp;
77  using std::endl;
78  typedef ::Tpetra::Map<LO, GO, NT> map_type;
79  const char prefix[] = "Tpetra::Details::makeColMap: ";
80  int errCode = 0;
81 
82  // If the input domain Map or its communicator is null on the
83  // calling process, then the calling process does not participate in
84  // the returned column Map. Thus, we can set the returned column
85  // Map to null on those processes, and return immediately. This is
86  // not an error condition, as long as when domMap and its
87  // communicator are NOT null, the graph's other Maps and
88  // communicator are not also null.
89  if (domMap.is_null () || domMap->getComm ().is_null ()) {
90  colMap = Teuchos::null;
91  return errCode;
92  }
93 
94  // After the calling process is done going through all of the rows
95  // it owns, myColumns will contain the list of indices owned by this
96  // process in the column Map.
97  Array<GO> myColumns;
98 
99  if (graph.isLocallyIndexed ()) {
100  colMap = graph.getColMap ();
101  // If the graph is locally indexed, it had better have a column Map.
102  // The extra check for ! graph.hasColMap() is conservative.
103  if (colMap.is_null () || ! graph.hasColMap ()) {
104  errCode = -1;
105  if (errStrm != NULL) {
106  *errStrm << prefix << "The graph is locally indexed on the calling "
107  "process, but has no column Map (either getColMap() returns null, "
108  "or hasColMap() returns false)." << endl;
109  }
110  // Under this error condition, this process will not fill
111  // myColumns. The resulting new column Map will be incorrect,
112  // but at least we won't hang, and this process will report the
113  // error.
114  }
115  else {
116  // The graph already has a column Map, and is locally indexed on
117  // the calling process. However, it may be globally indexed (or
118  // neither locally nor globally indexed) on other processes.
119  // Assume that we want to recreate the column Map.
120  if (colMap->isContiguous ()) {
121  // The number of indices on each process must fit in LO.
122  const LO numCurGids = static_cast<LO> (colMap->getNodeNumElements ());
123  myColumns.resize (numCurGids);
124  const GO myFirstGblInd = colMap->getMinGlobalIndex ();
125  for (LO k = 0; k < numCurGids; ++k) {
126  myColumns[k] = myFirstGblInd + static_cast<GO> (k);
127  }
128  }
129  else { // the column Map is NOT contiguous
130  ArrayView<const GO> curGids = graph.getColMap ()->getNodeElementList ();
131  // The number of indices on each process must fit in LO.
132  const LO numCurGids = static_cast<LO> (curGids.size ());
133  myColumns.resize (numCurGids);
134  for (LO k = 0; k < numCurGids; ++k) {
135  myColumns[k] = curGids[k];
136  }
137  } // whether the graph's current column Map is contiguous
138  } // does the graph currently have a column Map?
139  }
140  else if (graph.isGloballyIndexed ()) {
141  // Go through all the rows, finding the populated column indices.
142  //
143  // Our final list of indices for the column Map constructor will
144  // have the following properties (all of which are with respect to
145  // the calling process):
146  //
147  // 1. Indices in the domain Map go first.
148  // 2. Indices not in the domain Map follow, ordered first
149  // contiguously by their owning process rank (in the domain
150  // Map), then in increasing order within that.
151  // 3. No duplicate indices.
152  //
153  // This imitates the ordering used by Aztec(OO) and Epetra.
154  // Storing indices owned by the same process (in the domain Map)
155  // contiguously permits the use of contiguous send and receive
156  // buffers.
157  //
158  // We begin by partitioning the column indices into "local" GIDs
159  // (owned by the domain Map) and "remote" GIDs (not owned by the
160  // domain Map). We use the same order for local GIDs as the
161  // domain Map, so we track them in place in their array. We use
162  // an std::set (RemoteGIDSet) to keep track of remote GIDs, so
163  // that we don't have to merge duplicates later.
164  const LO LINV = Tpetra::Details::OrdinalTraits<LO>::invalid ();
165  size_t numLocalColGIDs = 0;
166  size_t numRemoteColGIDs = 0;
167 
168  // GIDisLocal[lid] is false if and only if local index lid in the
169  // domain Map is remote (not local).
170  std::vector<bool> GIDisLocal (domMap->getNodeNumElements (), false);
171  std::set<GO> RemoteGIDSet;
172  // This preserves the not-sorted Epetra order of GIDs.
173  // We only use this if sortEachProcsGids is false.
174  std::vector<GO> RemoteGIDUnorderedVector;
175 
176  if (! graph.getRowMap ().is_null ()) {
177  const Tpetra::Map<LO, GO, NT>& rowMap = * (graph.getRowMap ());
178  const LO lclNumRows = rowMap.getNodeNumElements ();
179  for (LO lclRow = 0; lclRow < lclNumRows; ++lclRow) {
180  const GO gblRow = rowMap.getGlobalElement (lclRow);
181  Teuchos::ArrayView<const GO> rowGids;
182  graph.getGlobalRowView (gblRow, rowGids);
183 
184  const LO numEnt = static_cast<LO> (rowGids.size ());
185  if (numEnt != 0) {
186  for (LO k = 0; k < numEnt; ++k) {
187  const GO gid = rowGids[k];
188  const LO lid = domMap->getLocalElement (gid);
189  if (lid != LINV) {
190  const bool alreadyFound = GIDisLocal[lid];
191  if (! alreadyFound) {
192  GIDisLocal[lid] = true;
193  ++numLocalColGIDs;
194  }
195  }
196  else {
197  const bool notAlreadyFound = RemoteGIDSet.insert (gid).second;
198  if (notAlreadyFound) { // gid did not exist in the set before
199  if (! sortEachProcsGids) {
200  // The user doesn't want to sort remote GIDs (for each
201  // remote process); they want us to keep remote GIDs
202  // in their original order. We do this by stuffing
203  // each remote GID into an array as we encounter it
204  // for the first time. The std::set helpfully tracks
205  // first encounters.
206  RemoteGIDUnorderedVector.push_back (gid);
207  }
208  ++numRemoteColGIDs;
209  }
210  }
211  } // for each entry k in row r
212  } // if row r contains > 0 entries
213  } // for each locally owned row r
214  } // if the graph has a nonnull row Map
215 
216  // Possible short-circuit for serial scenario:
217  //
218  // If all domain GIDs are present as column indices, then set
219  // ColMap=DomainMap. By construction, LocalGIDs is a subset of
220  // DomainGIDs.
221  //
222  // If we have
223  // * Number of remote GIDs is 0, so that ColGIDs == LocalGIDs,
224  // and
225  // * Number of local GIDs is number of domain GIDs
226  // then
227  // * LocalGIDs \subset DomainGIDs && size(LocalGIDs) ==
228  // size(DomainGIDs) => DomainGIDs == LocalGIDs == ColGIDs
229  // on the calling process.
230  //
231  // We will concern ourselves only with the special case of a
232  // serial DomainMap, obviating the need for communication.
233  //
234  // If
235  // * DomainMap has a serial communicator
236  // then we can set the column Map as the domain Map
237  // return. Benefit: this graph won't need an Import object
238  // later.
239  //
240  // Note, for a serial domain map, there can be no RemoteGIDs,
241  // because there are no remote processes. Likely explanations
242  // for this are:
243  // * user submitted erroneous column indices
244  // * user submitted erroneous domain Map
245  if (domMap->getComm ()->getSize () == 1) {
246  if (numRemoteColGIDs != 0) {
247  errCode = -2;
248  if (errStrm != NULL) {
249  *errStrm << prefix << "The domain Map only has one process, but "
250  << numRemoteColGIDs << " column "
251  << (numRemoteColGIDs != 1 ? "indices are" : "index is")
252  << " not in the domain Map. Either these indices are "
253  "invalid or the domain Map is invalid. Remember that nonsquare "
254  "matrices, or matrices where the row and range Maps differ, "
255  "require calling the version of fillComplete that takes the "
256  "domain and range Maps as input." << endl;
257  }
258  }
259  if (numLocalColGIDs == domMap->getNodeNumElements ()) {
260  colMap = domMap; // shallow copy
261  return errCode;
262  }
263  }
264 
265  // Populate myColumns with a list of all column GIDs. Put
266  // locally owned (in the domain Map) GIDs at the front: they
267  // correspond to "same" and "permuted" entries between the
268  // column Map and the domain Map. Put remote GIDs at the back.
269  myColumns.resize (numLocalColGIDs + numRemoteColGIDs);
270  // get pointers into myColumns for each part
271  ArrayView<GO> LocalColGIDs = myColumns (0, numLocalColGIDs);
272  ArrayView<GO> remoteColGIDs = myColumns (numLocalColGIDs, numRemoteColGIDs);
273 
274  // Copy the remote GIDs into myColumns
275  if (sortEachProcsGids) {
276  // The std::set puts GIDs in increasing order.
277  std::copy (RemoteGIDSet.begin(), RemoteGIDSet.end(),
278  remoteColGIDs.begin());
279  }
280  else {
281  // Respect the originally encountered order.
282  std::copy (RemoteGIDUnorderedVector.begin(),
283  RemoteGIDUnorderedVector.end(), remoteColGIDs.begin());
284  }
285 
286  // Make a list of process ranks corresponding to the remote GIDs.
287  // remotePIDs is an output argument of getRemoteIndexList below;
288  // its initial contents don't matter.
289  if (static_cast<size_t> (remotePIDs.size ()) != numRemoteColGIDs) {
290  remotePIDs.resize (numRemoteColGIDs);
291  }
292  // Look up the remote process' ranks in the domain Map.
293  {
294  const LookupStatus stat =
295  domMap->getRemoteIndexList (remoteColGIDs, remotePIDs ());
296 
297  // If any process returns IDNotPresent, then at least one of
298  // the remote indices was not present in the domain Map. This
299  // means that the Import object cannot be constructed, because
300  // of incongruity between the column Map and domain Map.
301  // This has two likely causes:
302  // - The user has made a mistake in the column indices
303  // - The user has made a mistake with respect to the domain Map
304  if (stat == IDNotPresent) {
305  if (errStrm != NULL) {
306  *errStrm << prefix << "Some column indices are not in the domain Map."
307  "Either these column indices are invalid or the domain Map is "
308  "invalid. Likely cause: For a nonsquare matrix, you must give the "
309  "domain and range Maps as input to fillComplete." << endl;
310  }
311  // Don't return yet, because not all processes may have
312  // encountered this error state. This function ends with an
313  // all-reduce, so we have to make sure that everybody gets to
314  // that point. The resulting Map may be wrong, but at least
315  // nothing should crash.
316  errCode = -3;
317  }
318  }
319 
320  // Sort incoming remote column indices by their owning process
321  // rank, so that all columns coming from a given remote process
322  // are contiguous. This means the Import's Distributor doesn't
323  // need to reorder data.
324  //
325  // NOTE (mfh 02 Sep 2014) This needs to be a stable sort, so that
326  // it respects either of the possible orderings of GIDs (sorted,
327  // or original order) specified above.
328  sort2 (remotePIDs.begin (), remotePIDs.end (), remoteColGIDs.begin ());
329 
330  // Copy the local GIDs into myColumns. Two cases:
331  // 1. If the number of Local column GIDs is the same as the number
332  // of Local domain GIDs, we can simply read the domain GIDs
333  // into the front part of ColIndices (see logic above from the
334  // serial short circuit case)
335  // 2. We step through the GIDs of the DomainMap, checking to see
336  // if each domain GID is a column GID. We want to do this to
337  // maintain a consistent ordering of GIDs between the columns
338  // and the domain.
339 
340  const size_t numDomainElts = domMap->getNodeNumElements ();
341  if (numLocalColGIDs == numDomainElts) {
342  // If the number of locally owned GIDs are the same as the
343  // number of local domain Map elements, then the local domain
344  // Map elements are the same as the locally owned GIDs.
345  if (domMap->isContiguous ()) {
346  // NOTE (mfh 03 Mar 2013, 02 Sep 2014) In the common case that
347  // the domain Map is contiguous, it's more efficient to avoid
348  // calling getNodeElementList(), since that permanently
349  // constructs and caches the GID list in the contiguous Map.
350  GO curColMapGid = domMap->getMinGlobalIndex ();
351  for (size_t k = 0; k < numLocalColGIDs; ++k, ++curColMapGid) {
352  LocalColGIDs[k] = curColMapGid;
353  }
354  }
355  else {
356  ArrayView<const GO> domainElts = domMap->getNodeElementList ();
357  std::copy (domainElts.begin(), domainElts.end(), LocalColGIDs.begin());
358  }
359  }
360  else {
361  // Count the number of locally owned GIDs, both to keep track of
362  // the current array index, and as a sanity check.
363  size_t numLocalCount = 0;
364  if (domMap->isContiguous ()) {
365  // NOTE (mfh 03 Mar 2013, 02 Sep 2014) In the common case that
366  // the domain Map is contiguous, it's more efficient to avoid
367  // calling getNodeElementList(), since that permanently
368  // constructs and caches the GID list in the contiguous Map.
369  GO curColMapGid = domMap->getMinGlobalIndex ();
370  for (size_t i = 0; i < numDomainElts; ++i, ++curColMapGid) {
371  if (GIDisLocal[i]) {
372  LocalColGIDs[numLocalCount++] = curColMapGid;
373  }
374  }
375  }
376  else {
377  ArrayView<const GO> domainElts = domMap->getNodeElementList ();
378  for (size_t i = 0; i < numDomainElts; ++i) {
379  if (GIDisLocal[i]) {
380  LocalColGIDs[numLocalCount++] = domainElts[i];
381  }
382  }
383  }
384  if (numLocalCount != numLocalColGIDs) {
385  if (errStrm != NULL) {
386  *errStrm << prefix << "numLocalCount = " << numLocalCount
387  << " != numLocalColGIDs = " << numLocalColGIDs
388  << ". This should never happen. "
389  "Please report this bug to the Tpetra developers." << endl;
390  }
391  // Don't return yet, because not all processes may have
392  // encountered this error state. This function ends with an
393  // all-reduce, so we have to make sure that everybody gets to
394  // that point.
395  errCode = -4;
396  }
397  }
398 
399  // FIXME (mfh 03 Apr 2013) Now would be a good time to use the
400  // information we collected above to construct the Import. In
401  // particular, building an Import requires:
402  //
403  // 1. numSameIDs (length of initial contiguous sequence of GIDs
404  // on this process that are the same in both Maps; this
405  // equals the number of domain Map elements on this process)
406  //
407  // 2. permuteToLIDs and permuteFromLIDs (both empty in this
408  // case, since there's no permutation going on; the column
409  // Map starts with the domain Map's GIDs, and immediately
410  // after them come the remote GIDs)
411  //
412  // 3. remoteGIDs (exactly those GIDs that we found out above
413  // were not in the domain Map) and remoteLIDs (which we could
414  // have gotten above by using the three-argument version of
415  // getRemoteIndexList() that computes local indices as well
416  // as process ranks, instead of the two-argument version that
417  // was used above)
418  //
419  // 4. remotePIDs (which we have from the getRemoteIndexList() call
420  // above) -- NOTE (mfh 14 Sep 2017) Fix for Trilinos GitHub
421  // Issue #628 (https://github.com/trilinos/Trilinos/issues/628)
422  // addresses this. remotePIDs is now an output argument of
423  // both this function and CrsGraph::makeColMap, and
424  // CrsGraph::makeImportExport now has an option to use that
425  // information, if available (that is, if makeColMap was
426  // actually called, which it would not be if the graph already
427  // had a column Map).
428  //
429  // 5. Apply the permutation from sorting remotePIDs to both
430  // remoteGIDs and remoteLIDs (by calling sort3 above instead of
431  // sort2), instead of just to remoteLIDs alone.
432  //
433  // 6. Everything after the sort3 call in Import::setupExport():
434  // a. Create the Distributor via createFromRecvs(), which
435  // computes exportGIDs and exportPIDs
436  // b. Compute exportLIDs from exportGIDs (by asking the
437  // source Map, in this case the domain Map, to convert
438  // global to local)
439  //
440  // Steps 1-5 come for free, since we must do that work anyway in
441  // order to compute the column Map. In particular, Step 3 is
442  // even more expensive than Step 6a, since it involves both
443  // creating and using a new Distributor object.
444  } // if the graph is globally indexed
445  else {
446  // If we reach this point, the graph is neither locally nor
447  // globally indexed. Thus, the graph is empty on this process
448  // (per the usual legacy Petra convention), so myColumns will be
449  // left empty.
450  ; // do nothing
451  }
452 
453  const global_size_t INV =
454  Tpetra::Details::OrdinalTraits<global_size_t>::invalid ();
455  // FIXME (mfh 05 Mar 2014) Doesn't the index base of a Map have to
456  // be the same as the Map's min GID? If the first column is empty
457  // (contains no entries), then the column Map's min GID won't
458  // necessarily be the same as the domain Map's index base.
459  const GO indexBase = domMap->getIndexBase ();
460  colMap = rcp (new map_type (INV, myColumns, indexBase, domMap->getComm ()));
461  return errCode;
462 }
463 
464 } // namespace Details
465 } // namespace Tpetra
466 
467 //
468 // Explicit instantiation macros
469 //
470 // Must be expanded from within the Tpetra::Details namespace!
471 //
472 #define TPETRA_DETAILS_MAKECOLMAP_INSTANT(LO,GO,NT) template int makeColMap (Teuchos::RCP<const Tpetra::Map<LO, GO, NT> >&, Teuchos::Array<int>&, const Teuchos::RCP<const Tpetra::Map<LO, GO, NT> >&, const RowGraph<LO, GO, NT>&, const bool, std::ostream*);
473 
474 #endif // TPETRA_DETAILS_MAKECOLMAP_DEF_HPP
virtual bool hasColMap() const =0
Whether the graph has a well-defined column Map.
An abstract interface for graphs accessed by rows.
virtual void getGlobalRowView(const GlobalOrdinal gblRow, Teuchos::ArrayView< const GlobalOrdinal > &gblColInds) const
Get a const, non-persisting view of the given global row&#39;s global column indices, as a Teuchos::Array...
LookupStatus
Return status of Map remote index lookup (getRemoteIndexList()).
virtual Teuchos::RCP< const Map< LocalOrdinal, GlobalOrdinal, Node > > getColMap() const =0
The Map that describes this graph&#39;s distribution of columns over processes.
size_t global_size_t
Global size_t object.
virtual Teuchos::RCP< const Map< LocalOrdinal, GlobalOrdinal, Node > > getRowMap() const =0
The Map that describes this graph&#39;s distribution of rows over processes.
GlobalOrdinal getGlobalElement(LocalOrdinal localIndex) const
The global index corresponding to the given local index.
virtual bool isGloballyIndexed() const =0
If graph indices are in the global range, this function returns true. Otherwise, this function return...
void sort2(const IT1 &first1, const IT1 &last1, const IT2 &first2)
Sort the first array, and apply the resulting permutation to the second array.
size_t getNodeNumElements() const
The number of elements belonging to the calling process.
A parallel distribution of indices over processes.
int makeColMap(Teuchos::RCP< const Tpetra::Map< LO, GO, NT > > &colMap, Teuchos::Array< int > &remotePIDs, const Teuchos::RCP< const Tpetra::Map< LO, GO, NT > > &domMap, const RowGraph< LO, GO, NT > &graph, const bool sortEachProcsGids=true, std::ostream *errStrm=NULL)
Make the graph&#39;s column Map.
Stand-alone utility functions and macros.
virtual bool isLocallyIndexed() const =0
If graph indices are in the local range, this function returns true. Otherwise, this function returns...