Skip to content

Commit

Permalink
Better support for ranges
Browse files Browse the repository at this point in the history
* ra/ply.hh (begin, end, range): New functions.
* test/stl-compat.cc: Some tests/examples.
  • Loading branch information
lloda committed Nov 29, 2023
1 parent 1413fd4 commit 7397c85
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 53 deletions.
25 changes: 8 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,30 +42,21 @@ Please check the manual online at [lloda.github.io/ra-ra](https://lloda.github.i
**ra-ra** offers:
* Array types with arbitrary compile time or runtime rank, and compile time or runtime shape.
* Array types with arbitrary compile time or runtime rank and shape.
* Memory owning types as well as views over any piece of memory.
* Support for builtin arrays and for the standard library, including `<ranges>`.
* Interoperability with other libraries and/or languages through transparent memory layout.
* Slicing with indices of arbitrary rank, linear range indices, axis skipping and elision, and contextual `len`.
* Compatibility with builtin arrays and with the standard library, including ranges.
* Interoperability with other libraries and languages through transparent memory layout.
* Slicing with indices of arbitrary rank, axis skipping and insertion (e.g. for broadcasting), and contextual `len`.
* Rank extension by prefix matching, as in APL/J, for functions of any number of arguments.
* Iterators over subarrays of any rank.
* Axis insertion, e.g. for broadcasting.
* Outer product operation.
* Tensor index object.
* Iterators over subarrays (cells) of any rank.
* Rank conjunction as in J (compile time rank only), outer product operation.
* Short-circuiting logical operators.
* Argument list selection operators (`where` with bool selector, or `pick` with integer selector).
* Argument list selection operators (`where` with bool selector, `pick` with integer selector).
* Reshape, transpose, reverse, collapse/explode, stencils.
* Arbitrary types as array elements, or as scalar operands.
* Many predefined array operations. Adding yours is trivial.
* Rank conjunction as in J (compile time ranks only).
* Configurable error checking.
`constexpr` is suported as much as possible. For example:
```
constexpr ra::Small<int, 3> a = { 1, 2, 3 };
static_assert(6==ra::sum(a));
```
* Mostly `constexpr`.
Performance is competitive with hand written scalar (element by element) loops, but probably not with cache-tuned code such as your platform BLAS, or with code using SIMD. Please have a look at the benchmarks in [bench/](bench/).
Expand Down
53 changes: 27 additions & 26 deletions examples/read-me.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ using ra::mp::int_list;
int main()
{
TestRecorder tr(std::cout);
tr.section("first example");
tr.section("First example");
{
// run time rank
ra::Big<float> A = { {1, 2, 3, 4}, {5, 6, 7, 8} };
Expand All @@ -35,8 +35,12 @@ int main()
std::cout << "B: " << B << std::endl;
tr.test_eq(ra::Small<float, 2, 4> { {103, 106, -109, -112}, {215, 218, -221, -224} }, B);
}
tr.section("dynamic/static shape");
// Dynamic or static array rank. Dynamic or static array shape (all dimensions or none).
tr.section("Most things are constexpr");
{
constexpr ra::Small<int, 3> a = { 1, 2, 3 };
static_assert(6==ra::sum(a));
}
tr.section("Dynamic or static array rank. Dynamic or static array shape (all dimensions or none)");
{
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}
Expand All @@ -45,8 +49,7 @@ int main()
cout << "B: " << B << "\n\n";
cout << "C: " << C << "\n\n";
}
tr.section("storage");
// Memory-owning types and views. You can make array views over any piece of memory.
tr.section("Memory-owning types and views. You can make array views over any piece of memory");
{
// memory-owning types
ra::Big<char, 2> A({2, 3}, 'a'); // storage is std::vector inside A
Expand All @@ -69,7 +72,7 @@ int main()
cout << "D3: " << D3 << "\n\n";
cout << "D4: " << D4 << "\n\n";
}
tr.section("shape agreement");
tr.section("Shape agreement");
// Shape agreement rules and rank extension (broadcasting) for rank-0 operations of any arity
// and operands of any rank, any of which can a reference (so you can write on them). These
// rules are taken from the array language, J.
Expand All @@ -86,8 +89,7 @@ int main()
D += A * B; // D(i) += A(i, j) * C(i)
cout << "D: " << D << "\n\n";
}
tr.section("rank iterators");
// Iterators over cells of arbitrary rank.
tr.section("Iterators over cells of arbitrary rank");
{
constexpr auto i = ra::iota<0>();
constexpr auto j = ra::iota<1>();
Expand All @@ -107,16 +109,14 @@ int main()
for_each([](auto && b, auto && a) { b = ra::sum(a); }, B, A.iter<2>()); // give cell rank
cout << "B: " << B << "\n\n";
}
// A rank conjunction (only for static rank and somewhat fragile).
tr.section("rank conjuction");
tr.section("A rank conjunction (only for static rank and somewhat fragile)");
{
// This is a 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 proper selection operator with 'beating' of range or scalar subscripts.
// See examples/slicing.cc for more examples.
tr.section("selector");
tr.section("A proper selection operator with 'beating' of range or scalar subscripts.");
{
// TODO do implicit reshape in constructors?? so I can accept any 1-array and not only an initializer_list.
ra::Big<char, 3> A({2, 2, 2}, {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'});
Expand Down Expand Up @@ -145,7 +145,8 @@ int main()
B(I) = ra::Big<char, 2> {{'x', 'y'}, {'z', 'w'}};
cout << "B: " << B << endl;
}
tr.section("STL compat");
// FIXME bring in some examples from test/stl-compat.cc. Show examples both ways.
tr.section("STL compatibility");
{
ra::Big<char, 1> A = {'x', 'z', 'y'};
std::sort(A.begin(), A.end());
Expand All @@ -155,13 +156,13 @@ int main()
B += std::vector<float> {10, 20};
cout << "B: " << B << "\n\n";
}
tr.section("example from the manual [ma100]");
tr.section("Example from the manual [ma100]");
{
ra::Small<int, 3> s {2, 1, 0};
ra::Small<double, 3> z = pick(s, s*s, s+s, sqrt(s));
cout << "z: " << z << endl;
}
tr.section("example from the manual [ma101]");
tr.section("Example from the manual [ma101]");
{
ra::Big<char, 2> A({2, 5}, "helloworld");
std::cout << ra::noshape << format_array(transpose<1, 0>(A), "|") << std::endl;
Expand All @@ -170,13 +171,13 @@ int main()
ra::Big<char const *, 1> A = {"hello", "array", "world"};
std::cout << ra::noshape << format_array(A, "|") << std::endl;
}
tr.section("example from the manual [ma102]");
tr.section("Example from the manual [ma102]");
{
// ra::Big<char const *, 1> A({3}, "hello"); // ERROR bc of pointer constructor
ra::Big<char const *, 1> A({3}, ra::scalar("hello"));
std::cout << ra::noshape << format_array(A, "|") << std::endl;
}
tr.section("example from the manual [ma103]");
tr.section("Example from the manual [ma103]");
{
ra::Big<int, 2> A {{1, 2}, {3, 4}, {5, 6}};
ra::Big<int, 2> B {{7, 8, 9}, {10, 11, 12}};
Expand All @@ -188,59 +189,59 @@ int main()
95 106 117 */
cout << C << endl;
}
tr.section("example from the manual [ma104] - dynamic size");
tr.section("Example from the manual [ma104] - dynamic size");
{
ra::Big<int, 3> c({3, 2, 2}, ra::_0 - ra::_1 - 2*ra::_2);
cout << "c: " << c << endl;
cout << "s: " << map([](auto && a) { return sum(diag(a)); }, iter<-1>(c)) << endl;
}
tr.section("example from the manual [ma104] - static size");
tr.section("Example from the manual [ma104] - static size");
{
ra::Small<int, 3, 2, 2> c = ra::_0 - ra::_1 - 2*ra::_2;
cout << "c: " << c << endl;
cout << "s: " << map([](auto && a) { return sum(diag(a)); }, iter<-1>(c)) << endl;
}
tr.section("example from the manual [ma105]");
tr.section("Example from the manual [ma105]");
{
ra::Big<double, 2> a {{1, 2, 3}, {4, 5, 6}};
ra::Big<double, 1> b {10, 20, 30};
ra::Big<double, 2> c({2, 3}, 0);
iter<1>(c) = iter<1>(a) * iter<1>(b); // multiply each item of a by b
cout << c << endl;
}
// example from the manual [ma109]. This is a rare case where I need explicit ply.
tr.section("Example from the manual [ma109]. Contrived to need explicit ply");
{
ra::Big<int, 1> o = {};
ra::Big<int, 1> e = {};
ra::Big<int, 1> n = {1, 2, 7, 9, 12};
ply(where(odd(n), map([&o](auto && x) { o.push_back(x); }, n), map([&e](auto && x) { e.push_back(x); }, n)));
cout << "o: " << ra::noshape << o << ", e: " << ra::noshape << e << endl;
}
tr.section("example from manual [ma110]");
tr.section("Example from manual [ma110]");
{
std::cout << exp(ra::Small<double, 3> {4, 5, 6}) << std::endl;
}
tr.section("example from manual [ma111]");
tr.section("Example from manual [ma111]");
{
ra::Small<int, 2, 2> a = {{1, 2}, {3, 4}}; // explicit contents
ra::Small<int, 2, 2> b = {1, 2, 3, 4}; // ravel of content
cout << "a: " << a << ", b: " << b << endl;
}
tr.section("example from manual [ma112]");
tr.section("Example from manual [ma112]");
{
double bx[6] = {1, 2, 3, 4, 5, 6};
ra::Big<double, 2> b({3, 2}, bx); // {{1, 2}, {3, 4}, {5, 6}}
cout << "b: " << b << endl;
}
tr.section("example from manual [ma114]");
tr.section("Example from manual [ma114]");
{
using sizes = int_list<2, 3>;
using steps = int_list<1, 2>;
ra::SmallArray<int, sizes, steps> a {{1, 2, 3}, {4, 5, 6}}; // stored column-major
cout << "a: " << a << endl;
cout << ra::Small<int, 6>(ra::ptr(a.data())) << endl;
}
tr.section("example from manual [ma116]");
tr.section("Example from manual [ma116]");
{
ra::Big<int, 2> a({3, 2}, {1, 2, 3, 4, 5, 6});
ra::Big<int, 1> x = {1, 10};
Expand Down
2 changes: 1 addition & 1 deletion ra/expr.hh
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,7 @@ map(auto && op, auto && ... a) { return expr(RA_FWD(op), start(RA_FWD(a)) ...);


// ---------------------------
// pick
// pick expression
// ---------------------------

template <class T, class J> struct pick_at_type;
Expand Down
13 changes: 8 additions & 5 deletions ra/ply.hh
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ early(IteratorConcept auto && a, auto && def) { return ply(RA_FWD(a), Default {


// --------------------
// iterator adapter for the standard library
// iterator adapter for the standard library. FIXME maybe random for rank 1?
// --------------------

template <IteratorConcept A>
Expand All @@ -347,10 +347,10 @@ struct STLIterator
bool over;

STLIterator(A a_): a(a_), ind(ra::shape(a_)), over(0==ra::size(a)) {}
constexpr STLIterator(STLIterator && it) = default;
constexpr STLIterator(STLIterator const & it) = delete;
constexpr STLIterator & operator=(STLIterator && it) = default;
constexpr STLIterator & operator=(STLIterator const & it) = delete;
constexpr STLIterator(STLIterator &&) = default;
constexpr STLIterator(STLIterator const &) = delete;
constexpr STLIterator & operator=(STLIterator &&) = default;
constexpr STLIterator & operator=(STLIterator const &) = delete;
bool operator==(std::default_sentinel_t end) const { return over; }
decltype(auto) operator*() const { return *a; }

Expand Down Expand Up @@ -398,6 +398,9 @@ struct STLIterator
};

template <class A> STLIterator(A &&) -> STLIterator<A>;
constexpr auto begin(is_ra auto && a) { return STLIterator(ra::start(RA_FWD(a))); }
constexpr auto end(is_ra auto && a) { return std::default_sentinel; }
constexpr auto range(is_ra auto && a) { return std::ranges::subrange(ra::begin(RA_FWD(a)), std::default_sentinel); }


// ---------------------------
Expand Down
2 changes: 0 additions & 2 deletions test/big-0.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ int main(int argc, char * * argv)
{
ra::View<int, 2> a;
static_assert(ra::rank_s<decltype(a().iter<0>())>()==ra::rank_s<decltype(a().iter())>());
static_assert(std::input_iterator<decltype(a.begin())>);
// static_assert(std::weak_output_iterator<decltype(a.begin()), int>); // p2550 when ready c++
}
tr.section("constructors");
{
Expand Down
22 changes: 20 additions & 2 deletions test/stl-compat.cc
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,24 @@ int main()
.test_eq(ra::ANY, size_s(ra::start(std::ranges::iota_view(-5, 10))));
tr.test_eq(ra::iota(15, -5), std::ranges::iota_view(-5, 10));
}
tr.section("STL predicates");
{
ra::View<int, 2> a;
tr.test(std::input_iterator<decltype(a.begin())>);
// tr.test(std::weak_output_iterator<decltype(a.begin()), int>); // p2550 when ready c++
tr.test(std::input_iterator<decltype(begin(a+1))>);
tr.test(std::sentinel_for<decltype(end(a+1)), decltype(begin(a+1))>);
}
tr.section("STLIterator works with arbitrary expr not just views");
// FIXME give begin/end to exprs so subrange(expr) works by itself.
{
ra::Big<int, 3> a({4, 2, 3}, ra::_0 - ra::_1 + ra::_2);
ra::Big<int, 1> b(4*2*3, 0);
std::ranges::copy(std::ranges::subrange(ra::STLIterator(a+1), std::default_sentinel), b.begin());
std::ranges::copy(std::ranges::subrange(ra::STLIterator(a+1), std::default_sentinel), begin(b));
tr.test_eq(ra::ravel_free(a) + 1, b);
b = 0;
// FIXME broken bc of hairy ADL issues (https://stackoverflow.com/a/33576098). Use ra::range instead.
// static_assert(std::ranges::input_range<decltype(a+1)>);
std::ranges::copy(range(a+1), begin(b));
tr.test_eq(ra::ravel_free(a) + 1, b);
}
tr.section("STLIterator as output");
Expand All @@ -113,6 +125,12 @@ int main()
ra::Big<double, 1> b(4*2*3, real_part(ra::ravel_free(a)));
std::ranges::copy(std::ranges::subrange(b), ra::STLIterator(imag_part(a)));
tr.test_eq((ra::_0 - ra::_1 + ra::_2)*1.*complex(1, 1), a);
a = 0;
std::ranges::copy(std::ranges::subrange(b), begin(imag_part(a)));
tr.test_eq((ra::_0 - ra::_1 + ra::_2)*1.*complex(0, 1), a);
a = 0;
std::ranges::copy(range(b*1.) | std::views::transform([](auto x) { return -x; }), begin(a));
tr.test_eq((ra::_0 - ra::_1 + ra::_2)*(-1.), a);
}
#if __cpp_lib_span >= 202002L
tr.section("std::span");
Expand Down

0 comments on commit 7397c85

Please sign in to comment.