ALP User Documentation  0.8.preview
Algebraic Programming User Documentation
Modules
ALP/GraphBLAS

ALP/GraphBLAS enables sparse linear algebraic programming. More...

Modules

 Data Ingestion and Extraction
 Provides functions for putting user data into opaque ALP/GraphBLAS containers, provides functions for extracting data from such containers, and provides query as well resizing functionalities.
 
 Level-0 Primitives
 A collection of functions that let GraphBLAS operators work on zero-dimensional containers, i.e., on scalars.
 
 Level-1 Primitives
 A collection of functions that allow ALP/GraphBLAS operators, monoids, and semirings work on a mix of zero-dimensional and one-dimensional containers; i.e., allows various linear algebra operations on scalars and objects of type grb::Vector.
 
 Level-2 Primitives
 A collection of functions that allow GraphBLAS operators, monoids, and semirings work on a mix of zero-dimensional, one-dimensional, and two-dimensional containers.
 
 Level-3 Primitives
 A collection of functions that allow GraphBLAS semirings to work on one or more two-dimensional sparse containers (i.e, sparse matrices).
 

Detailed Description

ALP/GraphBLAS enables sparse linear algebraic programming.

API introduction

ALP/GraphBLAS is an ANSI C++11 variant of the C GraphBLAS standard with a few different choices and an emphasis on portability and auto-parallelisation. It exposes only two containers: grb::Vector and grb::Matrix. A template argument controls the type of the values contained within a container.

A container may have between \( 0 \) and \( c \) values, and each such value has a coordinate. The value \( c \) is the capacity of a container, and at most equals the size of that container. The size of a matrix is the product of its number of rows and its number of columns. Containers with fewer values than their size are considered sparse, while those with as many values as their size are considered dense. Scalars correspond to the standard C++ plain-old-data types, and, as such, have size, capacity, and number of values equal to one– scalars are always dense.

For matrices, their size can be derived from grb::nrows and grb::ncols, while for vectors their size may be immediately retrieved via grb::size. For both vectors and matrices, their capacity and current number of values may be retrieved via grb::capacity and grb::nnz, respectively. Finally, containers have a unique identifier that may be retrieved via grb::getID. These identifiers are assigned in a deterministic fashion, so that for deterministic programs executed with the same number of processes, the same containers will be assigned the same IDs.

Containers may be populated using grb::set or by using dedicated I/O routines such as grb::buildVectorUnique or grb::buildMatrixUnique. Here, unique refers to the collection of values that should be ingested having no duplicate coordinates; i.e., there are no two values that map to the same coordinate. The first argument to either function is the output container, which is followed by an iterator pair that points to a collection of values to be ingested into the output container.

ALP/GraphBLAS supports multiple user processes \( P \). If \( P > 1 \), there is a difference between grb::SEQUENTIAL and grb::PARALLEL I/O. The default I/O mode is grb::PARALLEL, which may be overridden by supplying grb::SEQUENTIAL as a fourth and final argument to the input routines. In sequential I/O, the iterator pair must point to the exact same collection of input values on each of the \( P \) user processes. In the parallel mode, however, each iterator pair points to disjoint value sets at each of the processes, while their union is what is logically ingested into the output container.

Output iteration is done using the standard STL-style iterators. ALP, however, only supports const_iterators on output. Output iterators default to sequential mode also.

Primitives perform algebraic operations on containers while using explicitly supplied algebraic structures. Primitives may be as simple as the element-wise application of a binary operator to two input vectors, generating values in a third output vector ( \( z = x \odot y \), grb::eWiseApply), or may be as rich as multiplying two matrices together whose result is to be added in-place to a third matrix ( \( C \leftarrow C + AB \), grb::mxm). The latter is typically deemed richer since it requires a semiring structure rather than a more basic binary operator.

Primitives are grouped according to their classical BLAS levels:

The "level-0" primitives operate on scalars, and in terms of arithmetic intensity match those of level-1 primitives– however, since standard BLAS need not define scalar operations this specification groups them separately. All primitives except for grb::set and grb::eWiseApply are in-place, meaning that new output values are "added" to any pre-existing contents in output containers. The operator used for addition is derived from the algebraic structure that the primitive is called with.

ALP requires that every primitive is parallelisable. Every backend that implements primitive for a specific system furthermore must specify performance semantics. Contrary to functional semantics that this reference specifies, performance semantics guarantee certain observable behaviours when it comes to the amount of work, data movement, synchronisation across parallel systems, and/or memory use.

See also
Performance Semantics
Algebraic Structures

ALP/GraphBLAS defines three types of algebra structures, namely, a

  1. binary operator such as grb::operators::add (numerical addition),
  2. grb::Monoid, and
  3. grb::Semiring.

Binary operators are parametrised in two input domains and one output domain, \( D_1 \times D_2 \to D_3 \). The \( D_i \) are given as template arguments to the operator. A grb::Monoid is composed from a binary operator coupled with an identity. For example, the additive monoid is defined as

>

Note that passing a single domain as a template argument to a binary operator is a short-hand for an operator with \( D_{\{1,2,3\}} \) equal to the same domain.

Likewise, a grb::Semiring is composed from two monoids, where the first, the so-called additive monoid, furthermore must be commutative. The classic semiring over integers taught in elementary school, for example, reads

>

Monoids and semirings must comply with their regular axioms– a type system assists users by checking for incorrect operators acting as additive or multiplicative monoids. Errors are reported at compile time, through the use of algebraic type traits such as grb::is_associative.

See also
Algebraic Type Traits

Standard operators and identities are found in their respective namespaces, grb::operators and grb::identities, respectively. The ALP monoids and semirings are generalised from their standard mathematical definitions in that they hold multiple domains. The description of grb::Semiring details the underlying mathematical structure that nevertheless can be identified.

ALP/GraphBLAS by example

An example is provided within examples/sp.cpp. It demonstrates usage of this API. We now follow with some code snippets from that example. First, the example dataset:

static const char * const vertex_ids[ 5 ] = { "Shenzhen", "Hong Kong", "Santa Clara", "London", "Paris" };
static const double distances[ 10 ] = { 8.628, 8.964, 11.148, .334, 9.606, 9.610, .017, .334, .017, .334 };
static const int price[ 10 ] = { 723, 956, 600, 85, 468, 457, 333, 85, 50, 150 };
static const double timeliness[ 10 ] = { 0.9, 0.7, 0.99, 0.9, 0.9, 0.7, 0.99, 0.7, .99, 0.99 };
static const std::string mode[ 10 ] = { "air", "air", "air", "air", "air", "air", "air", "air", "land", "land" };
static const size_t I[ 10 ] = { 3, 4, 2, 3, 3, 4, 1, 4, 1, 4 };
static const size_t J[ 10 ] = { 2, 2, 1, 4, 1, 1, 0, 3, 0, 3 };

Matrix creation (5-by-5 matrix, 10 nonzeroes):

grb::Matrix< double > dist( 5, 5 );
resize( dist, 10 );

Vector creation:

Matrix assignment:

buildMatrixUnique( dist, &( I[ 0 ] ), &( J[ 0 ] ), distances, 10, SEQUENTIAL );

Vector assignment:

grb::set( x, INFINITY );
grb::setElement( x, 0.0, 4 );
grb::set( y, x );

Example semiring definition:

Example semiring use:

grb::vxm( y, x, dist, shortest_path_double );

Example function taking arbitrary semirings:

template< typename Ring >
shortest_path( const grb::Matrix< typename Ring::D2 > & A, const grb::Vector< typename Ring::D1 > & initial_state, const size_t hops = 1, const Ring & ring = Ring() ) {
const size_t size = grb::size( initial_state );
grb::set( ret, initial_state );
vxm( ret, initial_state, A, ring );
for( size_t i = 1; i < hops; ++i ) {
grb::set( new_state, ret );
vxm( ret, new_state, A, ring );
}
return ret;
}

Example use of a function taking arbitrary semirings:

grb::Vector< int > trip_prices = shortest_path< shortest_path_ints >( prices, initial_trip_price, 2 );

Full example use case:

resize( T, 10 );
buildMatrixUnique( T, &( I[ 0 ] ), &( J[ 0 ] ), timeliness, 10, SEQUENTIAL );
grb::Vector< double > initial_timeliness( 5 );
grb::set( initial_timeliness, 0.0 );
grb::setElement( initial_timeliness, 1.0, 4 );
const grb::Vector< double > trip_timeliness2 = shortest_path< mul_max_double >( T, initial_timeliness, 2 );
(void)printf( "If we take a maximum of two separate trips, we can go from Paris "
"to the following cities timeliness as follows:\n" );
for( const std::pair< size_t, double > & pair : trip_timeliness2 ) {
const size_t i = pair.first;
const double val = pair.second;
if( val > 0 ) {
(void)printf( "--> %s with %lf percent probability of arriving on time\n", vertex_ids[ i ], val * 100.0 );
}
}
Author
A. N. Yzelman, Huawei Technologies France (2016-2020)
A. N. Yzelman, Huawei Technologies Switzerland AG (2020-current)