Skip to content

Commit 707c85c

Browse files
committed
Archive standard solutions to exercises
1 parent a1db0c5 commit 707c85c

File tree

3 files changed

+168
-1
lines changed

3 files changed

+168
-1
lines changed

ALP_Tutorial.tex

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ \subsection{Basic Container I/O}
201201
\end{lstlisting}
202202
The type \texttt{grb::RC} is the standard return type; ALP primitives\footnote{that are not simple `getters' like \texttt{grb::nnz}} always return an error code, and, if no error is encountered, return \texttt{grb::SUCCESS}. Iterators in ALP may be either \emph{sequential} or \emph{parallel}. Start-end iterator pairs that are sequential, such as retrieved from the parser in the above snippet, iterate over all elements of the underlying container (in this case, all nonzeroes in the sparse matrix file). A parallel iterator, by contrast, only retrieves some subset of elements $V_s$, where $s$ is the process ID. It assumes that there are a total of $p$ subsets $V_i$, where $p$ is the total number of processes. These subsets are pairwise disjoint (i.e., $V_i\cap V_j=\emptyset$ for all $i\neq j, 0\leq i,j<p$), while $\cup V_i$ corresponds to all elements in the underlying container. Parallel iterators are useful when launching an ALP/GraphBLAS program with multiple processes to benefit from distributed-memory parallelisation; in such cases, it would be wasteful if every process iterates over all data elements on data ingestion-- instead, parallel I/O is preferred. ALP primitives that take iterator pairs as input must be aware of the I/O type, which is passed as the last argument to \texttt{grb::buildMatrixUnique} in the above code snippet.
203203

204-
\textbf{Exercise 5.} Use the \texttt{FileMatrixParser} and its iterators to build $A$ from \texttt{west0497.mtx}. Have it print the number of nonzeroes in $A$ after buildMatrixUnique. Then modify the \texttt{main} function to take as the first program argument a path to a .mtx file, pass that path to the ALP/GraphBLAS program. Find and download the west0497 matrix from the SuiteSparse matrix collection, and run the application with the path to the downloaded matrix. If all went well, its output should be something like:
204+
\noindent \textbf{Exercise 5.} Use the \texttt{FileMatrixParser} and its iterators to build $A$ from \texttt{west0497.mtx}. Have it print the number of nonzeroes in $A$ after buildMatrixUnique. Then modify the \texttt{main} function to take as the first program argument a path to a .mtx file, pass that path to the ALP/GraphBLAS program. Find and download the west0497 matrix from the SuiteSparse matrix collection, and run the application with the path to the downloaded matrix. If all went well, its output should be something like:
205205
\begin{lstlisting}[keywordstyle=\ttfamily]
206206
Info: grb::init (reference) called.
207207
elements in x: 497
@@ -214,6 +214,50 @@ \subsection{Basic Container I/O}
214214
Info: grb::finalize (reference) called.
215215
\end{lstlisting}
216216

217+
\noindent \textbf{Bonus question.} Why is there no \texttt{grb::set(matrix,scalar)} primitive?
218+
219+
\subsection{Copying, Masking, and Standard Matrices}
220+
221+
Continuing from the last exercise, the following code would store a copy of $y$ in $x$ and a copy of $A$ in $B$:
222+
\begin{lstlisting}
223+
grb::Matrix< double > B( 497, 497 );
224+
assert( grb::capacity( B ) == 497 );
225+
grb::RC rc = grb::set( x, y );
226+
rc = rc ? rc : grb::set( B, A, grb::RESIZE );
227+
rc = rc ? rc : grb::set( B, A, grb::EXECUTE );
228+
\end{lstlisting}
229+
\noindent \textbf{Question.} What does the code pattern \texttt{rc = rc ? rc : <function call>;} achieve?
230+
231+
Note that after instantiation of $B$ (line 1 in the above) it will be allocated with a default capacity of $497$ values maximum (line 2). However, $A$ from the preceding exercise has $1\ 727$ values; therefore, simply executing \texttt{grb::set( B, A )} would return \texttt{grb::ILLEGAL}. Rather than manually having to call \texttt{grb::resize( B, 1727 )} to make the copy work, the above snippet instead first requests ALP/GraphBLAS to figure out the required capacity of $B$ and resize it if necessary (line 4), before then executing the copy (line 5). The execute phase is default-- i.e., the last line could equivalently have read \texttt{rc = rc ? rc : grb::set( B, A );}. Similarly, the vector copy (line 3) could have been broken up in resize-execute phases, however, from the previous exercises we already learned that the default vector capacity guarantees are sufficient to store $y$ and so we call the execute phase immediately.
232+
233+
\noindent \textbf{Question.} $A$ contains $1727$ double-precision elements. Are these $1727$ nonzeroes?
234+
235+
The \texttt{grb::set} primitive may also take a mask argument. The mask determines which outputs are computed and which outputs are discarded. For example, recall that $y$ from the previous exercise is a vector of size $497$ that contains only one value $y_{200}=$\texttt{true}. Then
236+
\begin{lstlisting}
237+
grb::RC rc = grb::set( x, y, false );
238+
\end{lstlisting}
239+
results in a vector $x$ that has one entry only: $x_{200}=\texttt{false}$. This is because $y$ has no elements anywhere except at position $200$, and hence the mask evaluates \texttt{false} for any $0\leq i<497, i\neq200$, and no entries are generated by the primitive at those positions. At position $200$, however, the mask $y$ does contain an element whose value is \texttt{true}, and hence the \texttt{grb::set} primitive will generate an output entry there. The value of the entry $x_{200}$ is set to the value given to the primitive, which is \texttt{false}. All GraphBLAS primitives with an output container can take mask arguments.
240+
241+
\noindent \textbf{Question.} What would \texttt{grb::set( y, x, true )} return for $y$, assuming it is computed immediately after the preceding code snippet?
242+
243+
We have shown how matrices may be built from input iterators while similarly, vectors may be built from standard iterators through \texttt{grb::buildVectorUnique} as well. Iterator-based ingestion also allows for the construction of vectors and matrices that have regular structures. ALP/GraphBLAS comes with some standard recipes that exploit this, for example to build an $n\times n$ identity matrix:
244+
\begin{lstlisting}
245+
...
246+
#include <graphblas/algorithms/matrix_factory.hpp>
247+
...
248+
249+
const grb::Matrix< double > identity = grb::algorithms::matrices< double >::identity( n );
250+
251+
...
252+
\end{lstlisting}
253+
\href{http://albert-jan.yzelman.net/alp/v0.8-preview/classgrb_1_1algorithms_1_1matrices.html#a1336accbaf6a61ebd890bef9da4116fc}{Other regular patterns supported} are \emph{eye} (similar to identity but not required to be a square matrix) and \emph{diag} (which takes an optional parameter $k$ to generate offset the diagonal, e.g., to generate a superdiagonal matrix). The class includes a set of constructors that result in dense matrices as well, including \emph{dense}, \emph{full}, \emph{zeros}, and \emph{ones}; note, however, that ALP/GraphBLAS is not optimised to handle dense matrices efficiently and so their use is discouraged\footnote{for similar reasons, actually, there is no primitive \texttt{grb::set(matrix,scalar)} in the GraphBLAS API}. While constructing matrices from standard file formats and through general iterators hence are key features for usability, it is also possible to derive matrices from existing ones via \texttt{grb::set(matrix,mask,value)}.
254+
255+
\noindent \textbf{Exercise 6.} Copy the code from the previous exercise, and modify it to determine whether $A$ holds explicit zeroes; i.e., entries in $A$ that have numerical value zero. \textbf{Hint:} it is possible to complete this exercise using only masking and copiying. For bonus points, consider making use of the \texttt{grb::descriptors::invert\_mask} descriptor described \href{http://albert-jan.yzelman.net/alp/v0.8-preview/namespacegrb_1_1descriptors.html}{here}.
256+
257+
\noindent \textbf{Exercise 7.} Determine how many explicit zeroes exist on the diagonal, superdiagonal, and subdiagonal of $A$; i.e., compute $|\{A_{ij}\ |\ A_{ij}=0, |i-j|\leq1, 0\leq i,j<497\}|$.
258+
259+
\noindent \textbf{Bonus question.} How much memory beyond that which is required to store the $n\times n$ identity matrix will a call to \texttt{matrices< double >::identity( n )} consume? \textbf{Hint:} consider that the iterators passed to \texttt{buildMatrixUnique} iterate over regular index sequences that can easily be systematically enumerated.
260+
217261
\subsection{Semirings and Algebraic Operations}
218262

219263
A key feature of GraphBLAS (and ALP) is that operations are defined over semirings rather than just the conventional arithmetic operations. A semiring consists of a pair of operations (an “addition” and a “multiplication”) along with their identity elements, which generalize the standard arithmetic (+ and $\times$). GraphBLAS allows using different semirings to, for example, perform computations like shortest paths or logical operations by substituting the plus or times operations with min, max, logical OR/AND, etc. In GraphBLAS, matrix multiplication is defined in terms of a semiring: the “add” operation is used to accumulate results, and the “multiply” operation is used when combining elements.

solutions/3-4-5-container-io.cpp

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
2+
#include <cstddef>
3+
#include <cstring>
4+
5+
#include <graphblas.hpp>
6+
#include <graphblas/utils/parser.hpp>
7+
8+
#include <assert.h>
9+
10+
11+
constexpr size_t max_fn_size = 255;
12+
typedef char Filename[ max_fn_size ];
13+
14+
void hello_world( const Filename &in, int &out ) {
15+
grb::Vector< bool > x( 497 ), y( 497, 1 );
16+
grb::Matrix< void > A( 497, 497, 1727 );
17+
18+
grb::RC rc = grb::set( x, false );
19+
rc = rc ? rc : grb::setElement( y, true, 200 );
20+
if( rc != grb::SUCCESS ) {
21+
out = 10;
22+
return;
23+
}
24+
25+
std::cout << "elements in x: " << grb::nnz( x ) << "\n";
26+
std::cout << "elements in y: " << grb::nnz( y ) << "\n";
27+
std::cout << "cacacity of y: " << grb::capacity( y ) << "\n";
28+
29+
for( const auto &pair : y ) {
30+
std::cout << "y[ " << pair.first << " ] = " << pair.second << "\n";
31+
}
32+
33+
size_t x_nnz = 0;
34+
for( const auto &pair : x ) {
35+
(void) ++x_nnz;
36+
if( pair.second ) {
37+
std::cerr << "x[ " << pair.first << " ] reads true "
38+
<< "but false was expected!\n";
39+
out = 20;
40+
return;
41+
}
42+
}
43+
if( x_nnz != 497 ) {
44+
std::cerr << "Output iterator of x retrieved " << x_nnz << " elements, "
45+
<< "expected 497!\n";
46+
out = 30;
47+
return;
48+
}
49+
50+
grb::utils::MatrixFileReader< double > parser( in, true );
51+
if( parser.m() != 497 || parser.n() != 497 ) {
52+
std::cerr << in << " corresponds to a matrix of unexpected size\n";
53+
out = 40;
54+
return;
55+
}
56+
{
57+
const auto iterator = parser.begin();
58+
std::cout << "First parsed entry: ( " << iterator.i() << ", " << iterator.j() << " ) = " << iterator.v() << "\n";
59+
}
60+
61+
rc = grb::buildMatrixUnique(
62+
A,
63+
parser.begin( grb::SEQUENTIAL ), parser.end( grb::SEQUENTIAL ),
64+
grb::SEQUENTIAL
65+
);
66+
if( rc != grb::SUCCESS ) {
67+
std::cerr << "Error encountered during reading " << in << "\n";
68+
out = 50;
69+
return;
70+
}
71+
std::cout << "nonzeroes in A: " << grb::nnz( A ) << "\n";
72+
73+
// all OK
74+
out = 0;
75+
}
76+
77+
int main( int argc, char ** argv ) {
78+
if( argc < 2 || argc > 2 ) {
79+
std::cout << "Usage: " << argv[ 0 ] << " </path/to/west0497.mtx>\n";
80+
return 0;
81+
}
82+
83+
// get input
84+
Filename fn;
85+
(void) std::strncpy( fn, argv[ 1 ], max_fn_size );
86+
87+
// set up output field
88+
int error_code = 100;
89+
90+
// launch hello world program
91+
grb::Launcher< grb::AUTOMATIC > launcher;
92+
assert( launcher.exec( &hello_world, fn, error_code, true )
93+
== grb::SUCCESS );
94+
95+
// return with the hello_world error code
96+
return error_code;
97+
}
98+

solutions/alp_hw.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
#include <cstddef>
3+
#include <cstring>
4+
#include <graphblas.hpp>
5+
#include <assert.h>
6+
constexpr size_t max_fn_size = 255;
7+
typedef char Filename[ max_fn_size ];
8+
void hello_world( const Filename &in, int &out ) {
9+
std::cout << "Hello from " << in << std::endl;
10+
out = 0;
11+
}
12+
int main( int argc, char ** argv ) {
13+
// get input
14+
Filename fn;
15+
(void) std::strncpy( fn, argv[ 0 ], max_fn_size );
16+
// set up output field
17+
int error_code = 100;
18+
// launch hello world program
19+
grb::Launcher< grb::AUTOMATIC > launcher;
20+
assert( launcher.exec( &hello_world, fn, error_code, true )
21+
== grb::SUCCESS );
22+
// return with the hello_world error code
23+
return error_code;
24+
}
25+

0 commit comments

Comments
 (0)