Program Listing for File array.hpp¶
↰ Return to documentation for file (src/array.hpp)
/* This file is part of brille.
Copyright © 2020 Greg Tucker <greg.tucker@stfc.ac.uk>
brille is free software: you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option)
any later version.
brille is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with brille. If not, see <https://www.gnu.org/licenses/>. */
#ifndef BRILLE_ARRAY_HPP_
#define BRILLE_ARRAY_HPP_
#include <functional>
#include <algorithm>
#include <numeric>
#include <vector>
#include <array>
#include <math.h>
#include <cassert>
#include <iostream>
#include <memory>
#include <string>
#include "subscript.hpp"
#include "utilities.hpp"
#include "comparisons.hpp"
#include "approx.hpp"
#include "types.hpp"
#include "array_.hpp"
#include "array2.hpp"
namespace brille {
template<class T> class Array{
public:
using ref_t = std::shared_ptr<void>;
using shape_t = brille::shape_t;
using bItr = BroadcastIt<ind_t>;
using sItr = SubIt<ind_t>;
using aItr = ArrayIt<T>;
protected:
T* _data;
ind_t _num;
ind_t _shift;
bool _own;
ref_t _ref;
bool _mutable;
// shape_t _offset; //! A multidimensional offset different from {0,…,0} if a view
shape_t _shape;
shape_t _stride;
public:
// accessors
T* data() {return _data;}
T* data(const ind_t& idx){ return _data + this->l2l_d(idx);}
T* data(const shape_t& idx){ return _data + this->s2l_d(idx);}
const T* data() const {return _data;}
const T* data(const ind_t& idx) const {return _data + this->l2l_d(idx);}
const T* data(const shape_t& idx) const {return _data + this->s2l_d(idx);}
ind_t raw_size() const {return _num;}
ind_t raw_shift() const {return _shift;}
bool own() const {return _own;}
ref_t ref() const {return _ref;}
bool ismutable(void) const {return _mutable;}
bool isconst(void) const {return !_mutable;}
ind_t ndim(void) const {return _shape.size();}
ind_t numel(void) const {
auto nel = std::accumulate(_shape.begin(), _shape.end(), 1, std::multiplies<>());
return this->ndim() ? static_cast<ind_t>(nel) : 0u;
}
// shape_t offset(void) const {return _offset;}
ind_t size(const ind_t dim) const {
assert(dim < _shape.size());
return _shape[dim];
}
shape_t shape(void) const {return _shape;}
shape_t stride(void) const {return _stride;}
shape_t cstride() const {
shape_t cs(_stride);
for (auto& s: cs) s *= sizeof(T);
return cs;
}
sItr subItr() const { return sItr(_shape); }
sItr subItr(const shape_t& fix) const { return sItr(_shape, fix); }
aItr valItr() const { return aItr(*this); }
bItr broadcastItr(const shape_t& other) const { return bItr(_shape, other); }
template<class R>
bItr broadcastItr(const Array<R>& other) const {return bItr(_shape, other.shape());}
bool is_column_ordered() const { // stride {1,2,4,8} is column ordered
//return std::is_sorted(_stride.begin(), _stride.end(), [](ind_t a, ind_t b){return a <= b;});
for (size_t i=1; i<_stride.size(); ++i) if (_stride[i] < _stride[i-1]) return false;
return true;
}
bool is_row_ordered() const { // stride {8,4,2,1} is row ordered
// is_sorted doesn't like the vector {1,1} with this predicate?!
//return std::is_sorted(_stride.begin(), _stride.end(), [](ind_t a, ind_t b){return a >= b;});
for (size_t i=1; i<_stride.size(); ++i) if (_stride[i] > _stride[i-1]) return false;
return true;
}
bool is_contiguous() const {
shape_t expected(1, 1);
if (this->is_row_ordered()){
for (auto s = _shape.rbegin(); s!=_shape.rend(); ++s)
expected.push_back(expected.back()*(*s));
expected.pop_back();
return std::equal(_stride.begin(), _stride.end(), expected.rbegin());
}
if (this->is_column_ordered()){
for (auto s : _shape) expected.push_back(expected.back()*s);
// expected has one too many elements
return std::equal(_stride.begin(), _stride.end(), expected.begin());
}
return false;
}
// empty initializer
explicit Array()
// : _data(nullptr), _num(0), _own(false), _ref(std::make_shared<char>()),
// _mutable(false), _offset({0}), _shape({0}), _stride({1})
: _data(nullptr), _num(0), _shift(0), _own(false), _ref(std::make_shared<char>()),
_mutable(false), _shape({0}), _stride({1})
{}
// 1D initializer
Array(T* data, const ind_t num, const bool own, const bool mut=true)
// : _data(data), _num(num), _own(own), _ref(std::make_shared<char>()),
// _mutable(mut), _offset({0}), _shape({num}), _stride({1})
: _data(data), _num(num), _shift(0u), _own(own), _ref(std::make_shared<char>()),
_mutable(mut), _shape({num}), _stride({1})
{
this->init_check();
}
// ND initializer
Array(T* data, const ind_t num, const bool own,
const shape_t& shape, const shape_t& stride, const bool mut=true)
// : _data(data), _num(num), _own(own), _ref(std::make_shared<char>()),
// _mutable(mut), _offset(shape.size(),0), _shape(shape), _stride(stride)
: _data(data), _num(num), _shift(0u), _own(own), _ref(std::make_shared<char>()),
_mutable(mut), _shape(shape), _stride(stride)
{
this->init_check();
}
// ND initializer with reference-counter specified (used in pybind11 wrapper)
template<class P>
Array(T* data, const ind_t num, const bool own, std::shared_ptr<P> ref,
const shape_t& shape, const shape_t& stride, const bool mut=true)
// : _data(data), _num(num), _own(own), _ref(ref),
// _mutable(mut), _offset(shape.size(),0), _shape(shape), _stride(stride)
: _data(data), _num(num), _shift(0u), _own(own), _ref(ref),
_mutable(mut), _shape(shape), _stride(stride)
{
this->init_check();
}
// ND view constructor
// Array(T* data, const ind_t num, const bool own, const ref_t& ref,
// const shape_t& offset, const shape_t& shape, const shape_t& stride, const bool mut=true)
// : _data(data), _num(num), _own(own), _ref(ref), _mutable(mut), _offset(offset),
// _shape(shape), _stride(stride)
template<class P>
Array(T* data, const ind_t num, const ind_t shift, const bool own, const std::shared_ptr<P>& ref,
const shape_t& shape, const shape_t& stride, const bool mut=true)
: _data(data), _num(num), _shift(shift), _own(own), _ref(ref), _mutable(mut),
_shape(shape), _stride(stride)
{
this->init_check();
}
// 2D allocate new memory constructor
Array(const ind_t s0, const ind_t s1)
// : _mutable(true), _offset({0,0}), _shape({s0,s1}), _stride({s1,1})
: _shift(0u), _mutable(true), _shape({s0,s1}), _stride({s1,1})
{
this->construct();
this->init_check();
}
// 2D initialize new memory constructor
Array(const ind_t s0, const ind_t s1, const T init)
// : _mutable(true), _offset({0,0}), _shape({s0,s1}), _stride({s1,1})
: _shift(0u), _mutable(true), _shape({s0,s1}), _stride({s1,1})
{
this->construct(init);
this->init_check();
}
// ND allocate new memory constructor
Array(const shape_t& shape)
// : _mutable(true), _offset(shape.size(), 0), _shape(shape)
: _shift(0u), _mutable(true), _shape(shape)
{
this->set_stride();
this->construct();
this->init_check();
}
// ND initialize new memory constructor
Array(const shape_t& shape, const T init)
// : _mutable(true), _offset(shape.size(), 0), _shape(shape)
: _shift(0u), _mutable(true), _shape(shape)
{
this->set_stride();
this->construct(init);
this->init_check();
}
// ND allocate new memory with specified stride constructor
Array(const shape_t& shape, const shape_t& stride)
// : _mutable(true), _offset(shape.size(), 0), _shape(shape), _stride(stride)
: _shift(0u), _mutable(true), _shape(shape), _stride(stride)
{
this->construct();
this->init_check();
}
// ND initialize new memory with specified stride constructor
Array(const shape_t& shape, const shape_t& stride, const T init)
// : _mutable(true), _offset(shape.size(), 0), _shape(shape), _stride(stride)
: _shift(0u), _mutable(true), _shape(shape), _stride(stride)
{
this->construct(init);
this->init_check();
}
// otherwise possibly ambiguous constructor from std::vector
static Array<T> from_std(const std::vector<T>& data){
ind_t num = static_cast<ind_t>(data.size());
T* d = new T[num]();
for (ind_t i=0; i<num; ++i) d[i] = data[i];
// take ownership of d while creating the Array
return Array<T>(d, num, true);
}
template<class R, size_t Nel>
static Array<T> from_std(const std::vector<std::array<R,Nel>>& data){
shape_t shape{static_cast<ind_t>(data.size()), static_cast<ind_t>(Nel)};
shape_t stride{shape[1], 1};
ind_t num = shape[0]*shape[1];
T* d = new T[num]();
ind_t x{0};
for (ind_t i=0; i<shape[0]; ++i) for (ind_t j=0; j<shape[1]; ++j)
d[x++] = static_cast<T>(data[i][j]);
return Array<T>(d, num, true, shape, stride);
}
// Construct an Array from an Array2 and higher-dimensional shape:
Array(const Array2<T>& twod, const shape_t& shape): _shape(shape){
this->set_stride();
Array2<T> c2d = twod.contiguous_row_ordered_copy();
_data = c2d.data();
_num = c2d.raw_size();
_shift = c2d.raw_shift();
_own = c2d.own();
_ref = c2d.ref();
_mutable = c2d.ismutable();
this->init_check();
}
// Rule-of-five definitions since we may not own the associated raw array:
Array(const Array<T>& o)
// : _data(o._data), _num(o._num), _own(o._own), _ref(o._ref),
// _mutable(o._mutable), _offset(o._offset), _shape(o._shape), _stride(o._stride)
: _data(o._data), _num(o._num), _shift(o._shift), _own(o._own), _ref(o._ref),
_mutable(o._mutable), _shape(o._shape), _stride(o._stride)
{}
~Array(){
if (_own && _ref.use_count()==1 && _data != nullptr) delete[] _data;
}
Array<T>& operator=(const Array<T>& other){
if (this != &other){
if (_own){
T* old_data = _data;
ref_t old_ref = _ref;
_data = other._data;
_ref = other._ref;
if (old_ref.use_count()==1 && old_data != nullptr) delete[] old_data;
} else {
_data = other._data;
_ref = other._ref;
}
_own = other.own();
_num = other.raw_size();
_shift = other.raw_shift();
_mutable = other.ismutable();
// _offset = other.offset();
_shape = other.shape();
_stride = other.stride();
}
return *this;
}
// type casting requires a copy
// the reference pointer type does not need to be P since a new raw array is made
// handles also Array<T>(Array<T>&) reference type conversion
template<class R>
Array(const Array<R>& other)
// : _mutable(true), _offset(other.ndim(),0), _shape(other.shape())
: _shift(0u), _mutable(true), _shape(other.shape())
{
this->set_stride();
this->construct();
for (auto x: SubIt(_shape)) _data[s2l_d(x)] = static_cast<T>(other[x]);
}
template<class R>
Array<T>& operator=(const Array<R>& other){
_mutable = true;
_shape = other.shape();
// _offset = shape_t(_shape.size(), 0);
_shift = 0u;
this->set_stride();
this->construct();
for (auto x: SubIt(_shape)) _data[s2l_d(x)] = static_cast<T>(other[x]);
return *this;
}
// modifiers
bool make_mutable() {_mutable = true; return _mutable;}
bool make_immutable() {_mutable = false; return _mutable;}
Array<T> decouple() const {
if (!_own || !_mutable || _ref.use_count() > 1) return this->_decouple();
return *this;
}
// data accessors
T& operator[](ind_t lin) {return _data[this->l2l_d(lin)];}
const T& operator[](ind_t lin) const {return _data[this->l2l_d(lin)];}
T& operator[](shape_t& sub) {return _data[this->s2l_d(sub)];}
const T& operator[](shape_t& sub) const {return _data[this->s2l_d(sub)];}
protected: // so inherited classes can calculate subscript indexes into their data
// ind_t l2l_d(const ind_t l) const {
// shape_t sub = lin2sub(l, _stride);
// return offset_sub2lin(_offset, sub, _stride);
// }
// ind_t s2l_d(const shape_t& s) const {
// assert(s.size() == _offset.size());
// return offset_sub2lin(_offset, s, _stride);
// }
ind_t l2l_d(const ind_t l) const {
return l + _shift;
}
ind_t s2l_d(const shape_t& s) const {
return sub2lin(s, _stride) + _shift;
}
private:
ind_t size_from_shape(const shape_t& s) const {
// std::reduce can perform the same operation in parallel, but isn't implemented
// in the gcc libraries until v9.3. Since the number of dimensions is (probably)
// small a serial algorithm is not a giant speed hit.
size_t sz = std::accumulate(s.begin(), s.end(), ind_t(1), std::multiplies<ind_t>());
return static_cast<ind_t>(sz);
}
ind_t size_from_shape() const {return this->size_from_shape(_shape);}
void construct() {
_num = this->size_from_shape();
if (_num > 0){
_ref = std::make_shared<char>();
_data = new T[_num]();
_own = true;
} else {
_data = nullptr;
_own = false;
}
}
void construct(const T init){
this->construct();
if (_num > 0 && _data != nullptr) std::fill(_data, _data+_num, init);
}
void set_stride(void){
_stride.clear();
_stride.push_back(1);
for (auto s = _shape.rbegin(); s!=_shape.rend(); ++s)
_stride.push_back(_stride.back()*(*s));
_stride.pop_back();
std::reverse(_stride.begin(), _stride.end());
}
void init_check(void){
// if (_shape.size() != _offset.size() || _shape.size() != _stride.size())
if (_shape.size() != _stride.size())
throw std::runtime_error("Attempting to construct Array with inconsistent offset, shape, and strides");
// shape_t sh;
// for (size_t i=0; i<_shape.size(); ++i) sh.push_back(_offset[i]+_shape[i]);
// ind_t offset_size = this->size_from_shape(sh);
ind_t offset_size = _shift + this->size_from_shape(_shape);
if (_num < offset_size) {
// std::string msg = "The offset { ";
// for (auto x: _offset) msg += std::to_string(x) + " ";
std::string msg = "The shift {" + std::to_string(_shift);
msg += "} and size { ";
for (auto x: _shape) msg += std::to_string(x) + " ";
msg += "} of an Array must not exceed the allocated pointer size ";
msg += std::to_string(_num);
throw std::runtime_error(msg);
}
}
shape_t calculate_stride(const shape_t& sh) const {
shape_t st(sh.size(), 1u);
if (_stride.front() < _stride.back()){
for (size_t i=1; i<st.size(); ++i) st[i] = st[i-1]*sh[i-1];
} else {
for (size_t i=st.size()-1; i--;) st[i] = st[i+1]*sh[i+1];
}
return st;
}
void reset_stride(){
if (!this->is_contiguous())
throw std::runtime_error("Re-calculating non-contiguous strides is not yet working");
_stride = this->calculate_stride(_shape);
}
Array<T> _decouple() const {
ind_t nnum = this->size_from_shape(_shape);
T* new_data = new T[nnum]();
if (nnum == _num) {
std::copy(_data, _data+_num, new_data);
} else {
//subscript conversion necessary due to offset
// vvv(no offset)vvvv vvv(offset)vvv
for (auto x: SubIt(_shape)) new_data[sub2lin(x,_stride)] = _data[this->s2l_d(x)];
}
bool new_own = true; // always take ownership of C++ allocated memory
auto new_ref = std::make_shared<char>(); // always use the default with C++ created arrays
bool new_mut = true; // new allocated memory should be mutable
return Array<T>(new_data, nnum, new_own, new_ref, _shape, _stride, new_mut);
}
public:
// sub-array access
Array<T> view() const; // whole array non-owning view
Array<T> view(ind_t i) const;
Array<T> view(ind_t i, ind_t j) const;
Array<T> view(const shape_t&) const;
// duplication of one or more sub-arrays:
Array<T> extract(ind_t i) const;
template<class I> std::enable_if_t<std::is_integral_v<I>, Array<T>> extract(const Array<I>& i) const;
template<class I> std::enable_if_t<std::is_integral_v<I>, Array<T>> extract(const std::vector<I>& i) const;
template<class I, size_t Nel> std::enable_if_t<std::is_integral_v<I>, Array<T>> extract(const std::array<I,Nel>& i) const;
template<class I, size_t Nel> std::enable_if_t<std::is_integral_v<I>, Array<T>> extract(const std::vector<std::array<I,Nel>>& i) const;
Array<T> extract(const Array<bool>& i) const;
Array<T> extract(const std::vector<bool>& i) const;
bool set(const ind_t i, const Array<T>& in);
template<class R>
bool set(const ind_t i, const Array<R>& in);
bool set(const ind_t i, const std::vector<T>& in);
template<size_t Nel> bool set(const ind_t i, const std::array<T,Nel>& in);
T set(const shape_t& sub, T in);
Array<T>& append(const ind_t, const Array<T>&);
std::string to_string() const;
std::string to_string(const ind_t) const;
Array<T>& reshape(const shape_t& ns);
Array<T>& resize(const shape_t&, T init=T(0));
template<class I> Array<T>& resize(const I, T init=T(0));
bool all(ind_t n=0) const;
bool any(ind_t n=0) const;
ind_t count(ind_t n=0) const;
ind_t first(ind_t n=0) const;
ind_t last(ind_t n=0) const;
// bool all() const;
// bool any() const;
// ind_t count() const;
// ind_t first() const;
// ind_t last() const;
bool all(T val, ind_t n=0) const;
bool any(T val, ind_t n=0) const;
ind_t count(T val, ind_t n=0) const;
ind_t first(T val, ind_t n=0) const;
ind_t last(T val, ind_t n=0) const;
Array<int> round() const;
Array<int> floor() const;
Array<int> ceil() const;
Array<T> sum(ind_t dim=0) const;
Array<T> prod(ind_t dim=0) const;
Array<T> min(ind_t dim=0) const;
Array<T> max(ind_t dim=0) const;
T sum() const;
T prod() const;
template<class R, size_t Nel>
bool match(ind_t i, ind_t j, const std::array<R,Nel>& rot, int order=1) const;
bool match(ind_t i, ind_t j, ops op=ops::plus, T val=T{0}) const;
bool all(cmp expr, T val) const;
bool any(cmp expr, T val) const;
ind_t first(cmp expr, T val) const;
ind_t last(cmp expr, T val) const;
ind_t count(cmp expr, T val) const;
Array<bool> is(cmp expr, T val) const;
std::vector<ind_t> find(cmp expr, T val) const;
template<class R> Array<bool> is(cmp expr, const Array<R>& that) const;
template<class R> std::vector<bool> is(cmp expr, const std::vector<R>& val) const;
template<class R> bool is(const Array<R>& that) const;
std::vector<bool> is_unique() const;
std::vector<ind_t> unique_idx() const;
Array<T> unique() const;
Array<T> operator-() const;
Array<T>& operator +=(const T&);
Array<T>& operator -=(const T&);
Array<T>& operator *=(const T&);
Array<T>& operator /=(const T&);
template<class R> Array<T>& operator +=(const Array<R>&);
template<class R> Array<T>& operator -=(const Array<R>&);
template<class R> Array<T>& operator *=(const Array<R>&);
template<class R> Array<T>& operator /=(const Array<R>&);
T dot(ind_t i, ind_t j) const;
T norm(ind_t i) const;
template<typename I, typename=std::enable_if_t<std::is_integral<I>::value>>
void permute(std::vector<I>& p);
bool swap(ind_t a, ind_t b);
bool swap(ind_t i, ind_t a, ind_t b);
std::vector<T> to_std() const;
T* ptr();
T* ptr(const ind_t i0);
template<class ... Subs, class=std::enable_if_t<brille::utils::are_same<ind_t,Subs...>::value, void>>
T* ptr(const ind_t i0, Subs... subscripts);
T* ptr(const shape_t& partial_subscript);
const T* ptr() const;
const T* ptr(const ind_t i0) const;
template<class ... Subs, class=std::enable_if_t<brille::utils::are_same<ind_t,Subs...>::value, void>>
const T* ptr(const ind_t i0, Subs... subscripts) const;
const T* ptr(const shape_t& partial_subscript) const;
T& val(const ind_t i0);
template<class ... Subs, class=std::enable_if_t<brille::utils::are_same<ind_t,Subs...>::value, void>>
T& val(const ind_t i0, Subs... subscripts);
T& val(const shape_t& partial_subscript);
template<typename I> T& val(std::initializer_list<I> l);
const T& val(const ind_t i0) const;
template<class ... Subs, class=std::enable_if_t<brille::utils::are_same<ind_t,Subs...>::value, void>>
const T& val(const ind_t i0, Subs... subscripts) const;
const T& val(const shape_t& partial_subscript) const;
template<typename I> const T& val(std::initializer_list<I> l) const;
Array<T> contiguous_copy() const;
Array<T> contiguous_row_ordered_copy() const;
Array<T> squeeze() const;
Array<T> squeeze(const ind_t dim) const;
Array<T> slice(const ind_t i0) const;
bool shares_with(const Array<T>& other) const {
// compare the base pointer to see if two arrays share heap memory
return other.data() == _data;
}
// ^^^^^^^^^^ IMPLEMENTED ^^^^^^^^^^^vvvvvvvvv TO IMPLEMENT vvvvvvvvvvvvvvvvv
};
template<class T>
class ArrayIt {
public:
Array<T> array;
SubIt<ind_t> subit;
public:
// constructing with array(a) does not copy the underlying data:
explicit ArrayIt()
: array(), subit()
{}
ArrayIt(const Array<T>& a, const SubIt<ind_t>& s)
: array(a), subit(s)
{}
ArrayIt(const Array<T>& a)
: array(a), subit(a.shape()) // initialises to first element, e.g., {0,…,0}
{}
//
ArrayIt<T> begin() const {
return ArrayIt(array);
}
ArrayIt<T> end() const {
return ArrayIt(array, subit.end());
}
ArrayIt<T>& operator++() {
++subit;
return *this;
}
const SubIt<ind_t>& iterator() const {return subit;}
bool operator==(const ArrayIt<T>& other) const {
// add checking to ensure array and other.array point to the same data?
return subit == other.iterator();
}
bool operator!=(const ArrayIt<T>& other) const {
return subit != other.iterator();
}
const T& operator*() const {return array[*subit];}
const T* operator->() const {return &(array[*subit]);}
T& operator*() {return array[*subit];}
T* operator->() {return &(array[*subit]);}
};
#include "array.tpp"
} // end namespace brille
#endif // ARRAY_HPP