-
Notifications
You must be signed in to change notification settings - Fork 567
Tpetra::CrsGraph creation, from a graph perspective
Tpetra::CrsGraph represents a bipartite graph, with edges leaving the set of rows and going to the set of columns. The graph is distributed over one or more MPI processes in a given MPI communicator. I use the words "rows" and "columns" because the graph will eventually represent the sparsity structure of a sparse matrix. However, you can just think of "rows" and "columns" as two possibly different sets of vertices.
Let’s say you want a graph with 5 rows, where the global indices of those rows are [0, 1, 3, 7, 10]
. I deliberately made the set noncontiguous in this example; this is a perfectly valid graph or matrix in Tpetra. (I won’t say anything about the graph’s columns yet, but see below.) You want this graph to be distributed over exactly two processes, with rows [0, 1, 3]
on Process 0, and rows [7, 10]
on Process 1. Let’s first make a communicator that is guaranteed to have two processes:
// Teuchos::Comm: see Trilinos/packages/teuchos/comm/src/Teuchos_Comm.hpp
auto initialComm = Tpetra::getDefaultComm ();
TEUCHOS_TEST_FOR_EXCEPTION(initialComm.getSize () < 2, std::runtime_error, “This test requires at least two processes”);
// Select processes whose rank is less than 2.
const int color = (initialComm.getRank() < 2) ? 0 : 1;
const int key = 0; // see MPI_Comm_split documentation
auto comm = initialComm->split (color, key);
if (color == 0) {
run_the_actual_test (comm); // exactly 2 processes
}
The rest of the code in this example is happening inside run_the_actual_test(comm)
, with comm
as the communicator.
Now let’s make the row Map. If you look at the Doxygen documentation or at Trilinos/packages/tpetra/core/src/Tpetra_Map_decl.hpp
, you’ll see that Map has three kinds of constructors. The third kind of constructor takes an arbitrary list of global indices on each process. That’s the constructor you want.
using map_type = Tpetra::Map<>;
using GO = map_type::global_ordinal_type;
using LO = map_type::local_ordinal_type;
const GO gblNumInds = 5;
const LO lclNumInds = (myRank == 0) ? 3 : 2;
std::vector<GO> myGIDs;
const int myRank = comm->getRank ();
if (myRank == 0) {
myGIDs = {0, 1, 3};
}
else if (myRank == 1) {
myGIDs = {7, 10};
}
// Make the row Map.
RCP<const map_type> rowMap (new map_type (gblNumInds, myGIDs.data (), lclNumInds, 0, comm));
Now we need to make the graph. You have many options, but they boil down to two:
- Create graph with a row Map only
- Create graph with a row Map and a column Map
CrsGraph is a bipartite graph, but it’s a distributed bipartite graph. If you create the graph with a column Map, you assert that for every edge on the calling process, the target vertex of that edge is in the column Map on the calling process. The column Map can have “extra” entries in it, but you pay for those in terms of communication. Thus, if you don’t know what should go in the column Map on each process, you should not specify the column Map when creating the CrsGraph. CrsGraph’s fillComplete
method will create the column Map for you, based on the column indices you insert into the graph on each process.
Let’s suppose you want to insert the following (row, column) edges into the graph. Note how I arrange them by row index:
(0, 0), (0, 11)
(1, 3), (1, 4)
(3, 2)
(7, 5) (7, 7)
(10, 6)
Here’s how you could create the CrsGraph (this code works with StaticProfile):
Teuchos::ArrayRCP<size> numEntPerRow (lclNumInds); // indexed by local index
If (myRank == 0) {
numEntPerRow[0] = 2; // (0, 0), (0, 11)
numEntPerRow[1] = 2; // (1, 3), (1, 4)
numEntPerRow[2] = 1; // (3, 2) – note that 3 is a global index here
}
else if (myRank == 1) {
numEntPerRow[0] = 2; // (7, 5), (7, 7)
numEntPerRow[1] = 1; // (10, 6)
}
using crs_graph_type = Tpetra::CrsGraph<>;
RCP<crs_graph_type> G (new crs_graph_type (rowMap, numEntPerRow()));
And here’s how you would insert the edges:
std::vector<GO> myGblInds (2);
if (myRank == 0) {
myGblInds[0] = 0; myGblInds[1] = 11;
G->insertGlobalIndices (0, myGblInds.data (), 2); // (0, 0), (0, 11)
myGblInds[0] = 3; myGblInds[1] = 4;
G->insertGlobalIndices (1, myGblInds.data (), 2); // (1, 3), (1, 4)
myGblInds[0] = 2;
G->insertGlobalIndices (3, myGblInds.data (), 1); // (3, 2)
}
else if (myRank == 1) {
myGblInds[0] = 5; myGblInds[1] = 7;
G->insertGlobalIndices (7, myGblInds.data (), 2); // (7, 5), (7, 7)
myGblInds[0] = 6;
G->insertGlobalIndices (10, myGblInds.data (), 1); // (10, 6)
}
At this point, you are ready to call fillComplete on the graph. This does a few things:
- Create a column Map (if the graph doesn’t already have one). The resulting column Map will have global indices
[0, 3, 2, 4, 11]
on Process 0, and[7, 5, 6]
on Process 1. (Don’t worry about the very specific order of those column Map entries for now.) - Take in the domain and range Maps. Domain and range Maps interpret a graph as a matrix in the linear algebra sense. For example, the domain Map (NOT the column Map) defines the matrix’s number of columns.
- Compute communication patterns for sparse matrix-vector multiply, using all four Maps.
For example, suppose I would like to interpret this graph as a non-square matrix with 5 rows (globally), and 12 columns (globally). I’ll create domain and range Maps as follows:
// rowMap works fine here as a range Map, because it has the right number of rows, and because it’s one to one.
RCP<const map_type> rangeMap = rowMap;
// I can’t use the column Map here, because it doesn’t have all the indices that actually would live in the range of the matrix.
// This is the Map constructor that takes nothing but a global number of indices, and an index base (the starting / globally smallest index).
const GO indexBase = 0;
RCP<const map_type> domainMap (new map_type (12, indexBase, comm));
And now I’ll call fillComplete on the graph. This is a collective, in the MPI sense.
G->fillComplete (domainMap, rangeMap);
Copyright © Trilinos a Series of LF Projects, LLC
For web site terms of use, trademark policy and other project policies please see https://lfprojects.org.
Trilinos Developer Home
Trilinos Package Owners
Policies
New Developers
Trilinos PR/CR
Productivity++
Support Policy
Test Dashboard Policy
Testing Policy
Managing Issues
New Issue Quick Ref
Handling Stale Issues and Pull Requests
Release Notes
Software Quality Plan
Proposing a New Package
Guidance on Copyrights and Licenses
Tools
CMake
Doxygen
git
GitHub Notifications
Mail lists
Clang-format
Version Control
Initial git setup
'feature'/'develop'/'master' (cheatsheet)
Simple centralized workflow
Building
SEMS Dev Env
Mac OS X
ATDM Platforms
Containers
Development Tips
Automated Workflows
Testing
Test Harness
Pull Request Testing
Submitting a Pull Request
Pull Request Workflow
Reproducing PR Errors
Addressing Test Failures
Trilinos Status Table Archive
Pre-push (Checkin) Testing
Remote pull/test/push
PR Creation & Approval Guidelines for Tpetra, Ifpack2, and MueLu Developers