A C++20 array and expression template library with some J/APL features
lloda 82abfbe1b8 Bring in more stuff std:: to avoid the global double overloads | 6 years ago | |
---|---|---|
bench | 7 years ago | |
doc | 7 years ago | |
examples | 7 years ago | |
ra | 6 years ago | |
test | 7 years ago | |
.gitattributes | 7 years ago | |
.travis.yml | 7 years ago | |
LICENSE | 9 years ago | |
Makefile | 8 years ago | |
README.md | 7 years ago | |
SConstruct | 7 years ago | |
config.guess | 7 years ago | |
ra.py | 7 years ago |
ra-ra is a C++ header-only, expression template / multidimensional array library with an APL/J bent. Some C++17 features are used, see below for compiler support.
ra-ra has a manual (work in progress) maintained at doc/ra-ra.texi. If you are reading this on the web, there should be a version of that manual in the 'Wiki'.
These examples are necessarily terse. Please check the examples/
folder.
ra::Big<char> A({2, 3}, 'a'); // dynamic rank = 2, dynamic shape = {2, 3}
ra::Big<char, 2> B({2, 3}, 'b'); // static rank = 2, dynamic shape = {2, 3}
ra::Small<char, 2, 3> C('c'); // static rank = 2, static shape = {2, 3}
cout << "A: " << A << "\n\n";
cout << "B: " << B << "\n\n";
cout << "C: " << C << "\n\n";
A: 2
2 3
a a a
a a a
B: 2 3
b b b
b b b
C: c c c
c c c
// memory-owning types
ra::Big<char, 2> A({2, 3}, 'a'); // storage is std::vector inside A
ra::Unique<char, 2> B({2, 3}, 'b'); // storage is owned by std::unique_ptr inside B
ra::Small<char, 2, 3> C('c'); // storage is owned by C itself, on the stack
// view types
char cs[] = { 'a', 'b', 'c', 'd', 'e', 'f' };
ra::View<char, 2> D1({2, 3}, cs); // dynamic sizes and strides, C order
ra::View<char, 2> D2({{2, 1}, {3, 2}}, cs); // dynamic sizes and strides, Fortran order.
ra::SmallView<char, mp::int_list<2, 3>, mp::int_list<3, 1>> D3(cs); // static sizes & strides, C order.
ra::SmallView<char, mp::int_list<2, 3>, mp::int_list<1, 2>> D4(cs); // static sizes & strides, Fortran order.
ra::Big<float, 2> A({2, 3}, { 1, 2, 3, 1, 2, 3 });
ra::Big<float, 1> B({-1, +1});
ra::Big<float, 2> C({2, 3}, 99.);
C = A * B; // C(i, j) = A(i, j) * B(i)
ra::Big<float, 1> D({2}, 0.);
D += A * B; // D(i) += A(i, j) * B(i)
C: 2 3
-1 -2 -3
1 2 3
D: 2
-6 6
A TensorIndex object as in Blitz++ (with some differences).
Iterators over cells of arbitrary rank.
ra::TensorIndex<0> i;
ra::TensorIndex<1> j;
ra::TensorIndex<2> k;
ra::Big<float, 3> A({2, 3, 4}, i+j+k);
ra::Big<float, 2> B({2, 3}, 0);
// store the sum of A(i, j, ...) in B(i, j). These are equivalent.
for_each([](auto && b, auto && a) { b = ra::sum(a); }, B, iter<1>(A)); // give cell rank
for_each([](auto && b, auto && a) { b = ra::sum(a); }, B, iter<-2>(A)); // give frame rank
A: 2 3 4
0 1 2 3
1 2 3 4
2 3 4 5
1 2 3 4
2 3 4 5
3 4 5 6
B: 2 3
6 10 14
10 14 18
// This is a direct translation of J: A = (i.3) -"(0 1) i.4, that is: A(i, j) = b(i)-c(j).
ra::Big<float, 2> A = map(ra::wrank<0, 1>(std::minus<float>()), ra::iota(3), ra::iota(4));
cout << "A: " << A << "\n\n";
A: 3 4
0 -1 -2 -3
1 0 -1 -2
2 1 0 -1
ra::Big<char, 3> A({2, 2, 2}, {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'});
A: 2 2 2
a b
c d
e f
g h
// these are all equivalent to e.g. A(:, 0, :) in Octave.
cout << "A1: " << A(ra::all, 0) << "\n\n";
cout << "A2: " << A(ra::all, 0, ra::all) << "\n\n";
cout << "A3: " << A(ra::all, 0, ra::dots<1>) << "\n\n";
A3: 2 2
a b
e f
// an inverted range.
cout << "A4: " << A(ra::iota(2, 1, -1)) << "\n\n";
A4: 2 2 2
e f
g h
a b
c d
// indices can be arrays of any rank.
ra::Big<int, 2> I({2, 2}, {0, 3, 1, 2});
ra::Big<char, 1> B({4}, {'a', 'b', 'c', 'd'});
cout << "B(I): " << B(I) << "\n\n";
B(I): 2 2
a d
b c
// multiple indexing performs an implicit outer product, as in APL.
// this results in a rank 4 array X = A(J, 1, J) -> X(i, j, k, l) = A(J(i, j), 1, J(k, l))
ra::Big<int, 2> J({2, 2}, {1, 0, 0, 1});
cout << "A(J, 1, J): " << A(J, 1, J) << "\n\n";
A(J, 1, J): 2 2 2 2
h g
g h
d c
c d
d c
c d
h g
g h
// explicit indices do not result in a View (= pointer + strides), but the resulting
// expression can still be written on.
B(I) = ra::Big<char, 2>({2, 2}, {'x', 'y', 'z', 'w'});
cout << "B: " << B << endl;
B: 4
x z w y
ra::Big<char, 1> A = {'x', 'z', 'y'};
std::sort(A.begin(), A.end());
ra::Big<float, 2> B({2, 2}, {1, 2, 3, 4});
B += std::vector<float>({10, 20});
A: 3
x y z
B: 2 2
11 12
23 24
Index and size types are all signed.
Index base is always 0.
Default array order is C or row-major (last dimension changes fastest). You can make array views using other orders by transposing or manipulating the strides yourself, but all newly created arrays use C-order.
The selection operator is (). [] is supported for rank-1 arrays only, where it means the same as ().
Array constructors follow a regular format. Single argument constructors always take an 'init-expression' which must provide enough shape information to construct the new array (unless the array type has static shape), and is otherwise subject to the regular argument shape agreement rules. Two-argument constructors always take a shape argument and a content argument.
Indices are checked by default. This can be disabled with a compilation flag.
Should be namespace-clean.
Beatable subscripts are not beaten if mixed with non-beatable subscripts.
Better reduction mechanisms.
Concatenation, search, reshape, and other infinite rank or rank>0 operations are missing.
More clever/faster traversal of arrays, like in Blitz++.
Handling of nested arrays.
GPU / parallelization / calls to external libraries.
Linear algebra, quaternions, etc. Those things belong in other libraries. The library includes a dual number implementation but it's more of a demo of how to adapt user types to the library.
Sparse arrays. You'd still want to mix & match with dense arrays, so maybe at some point.
The library is header-only and has no dependencies other than a C++14/C++17
compiler (-std=c++1z, partial C++17 support) and the standard library. There is
a test suite in test/
. These tests test internal details and are not meant
as demonstrations of how to use the library. There is a directory with
examples/
, some ported from Blitz++.
All tests pass under g++-6.3.
All tests pass under clang++-4.0 except for:
test/bench-pack.C, crashes clang.
test/test-optimize.C, fails to compile to a defect in the implementation of the vector_size attribute.
For clang on OS X you have to remove the -Wa,q option in the SConstruct which is meant for gcc by setting CCFLAGS to something else:
CCFLAGS="-march=native -DRA_OPTIMIZE_SMALLVECTOR=0" CXXFLAGS=-O3
CXX=clang++-3.9 scons -j4
I haven't tested on Windows.
I do numerical work in C++ so I need a library of this kind. Most C++ array libraries seem to support only vectors and matrices, or small objects for low-dimensional vector algebra. Blitz++ was a great early array library and it hasn't really been replaced as far as I can tell.
I've also been inspired by APL and J, which have led the way in exploring how array operations could be generalized.
This is a simple library. I don't want to second-guess the compiler and I don't stress performance as much as Blitz++ did. However, I am wary of adding features that could become an obstacle if I ever tried to make things fast(er). I believe that improvements such as new traversal methods or the optimization of specific expression patterns should be easy to implement without turning the library inside out.