.. _program_listing_file_src_array.hpp: Program Listing for File array.hpp ================================== |exhale_lsh| :ref:`Return to documentation for file ` (``src/array.hpp``) .. |exhale_lsh| unicode:: U+021B0 .. UPWARDS ARROW WITH TIP LEFTWARDS .. code-block:: cpp /* This file is part of brille. Copyright © 2020 Greg Tucker 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 . */ #ifndef BRILLE_ARRAY_HPP_ #define BRILLE_ARRAY_HPP_ #include #include #include #include #include #include #include #include #include #include #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 Array{ public: using ref_t = std::shared_ptr; using shape_t = brille::shape_t; using bItr = BroadcastIt; using sItr = SubIt; using aItr = ArrayIt; 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(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 bItr broadcastItr(const Array& 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()), // _mutable(false), _offset({0}), _shape({0}), _stride({1}) : _data(nullptr), _num(0), _shift(0), _own(false), _ref(std::make_shared()), _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()), // _mutable(mut), _offset({0}), _shape({num}), _stride({1}) : _data(data), _num(num), _shift(0u), _own(own), _ref(std::make_shared()), _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()), // _mutable(mut), _offset(shape.size(),0), _shape(shape), _stride(stride) : _data(data), _num(num), _shift(0u), _own(own), _ref(std::make_shared()), _mutable(mut), _shape(shape), _stride(stride) { this->init_check(); } // ND initializer with reference-counter specified (used in pybind11 wrapper) template Array(T* data, const ind_t num, const bool own, std::shared_ptr

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 Array(T* data, const ind_t num, const ind_t shift, const bool own, const std::shared_ptr

& 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 from_std(const std::vector& data){ ind_t num = static_cast(data.size()); T* d = new T[num](); for (ind_t i=0; i(d, num, true); } template static Array from_std(const std::vector>& data){ shape_t shape{static_cast(data.size()), static_cast(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(data[i][j]); return Array(d, num, true, shape, stride); } // Construct an Array from an Array2 and higher-dimensional shape: Array(const Array2& twod, const shape_t& shape): _shape(shape){ this->set_stride(); Array2 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& 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& operator=(const Array& 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(Array&) reference type conversion template Array(const Array& 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(other[x]); } template Array& operator=(const Array& 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(other[x]); return *this; } // modifiers bool make_mutable() {_mutable = true; return _mutable;} bool make_immutable() {_mutable = false; return _mutable;} Array 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()); return static_cast(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(); _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; iis_contiguous()) throw std::runtime_error("Re-calculating non-contiguous strides is not yet working"); _stride = this->calculate_stride(_shape); } Array _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(); // always use the default with C++ created arrays bool new_mut = true; // new allocated memory should be mutable return Array(new_data, nnum, new_own, new_ref, _shape, _stride, new_mut); } public: // sub-array access Array view() const; // whole array non-owning view Array view(ind_t i) const; Array view(ind_t i, ind_t j) const; Array view(const shape_t&) const; // duplication of one or more sub-arrays: Array extract(ind_t i) const; template std::enable_if_t, Array> extract(const Array& i) const; template std::enable_if_t, Array> extract(const std::vector& i) const; template std::enable_if_t, Array> extract(const std::array& i) const; template std::enable_if_t, Array> extract(const std::vector>& i) const; Array extract(const Array& i) const; Array extract(const std::vector& i) const; bool set(const ind_t i, const Array& in); template bool set(const ind_t i, const Array& in); bool set(const ind_t i, const std::vector& in); template bool set(const ind_t i, const std::array& in); T set(const shape_t& sub, T in); Array& append(const ind_t, const Array&); std::string to_string() const; std::string to_string(const ind_t) const; Array& reshape(const shape_t& ns); Array& resize(const shape_t&, T init=T(0)); template Array& 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 round() const; Array floor() const; Array ceil() const; Array sum(ind_t dim=0) const; Array prod(ind_t dim=0) const; Array min(ind_t dim=0) const; Array max(ind_t dim=0) const; T sum() const; T prod() const; template bool match(ind_t i, ind_t j, const std::array& 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 is(cmp expr, T val) const; std::vector find(cmp expr, T val) const; template Array is(cmp expr, const Array& that) const; template std::vector is(cmp expr, const std::vector& val) const; template bool is(const Array& that) const; std::vector is_unique() const; std::vector unique_idx() const; Array unique() const; Array operator-() const; Array& operator +=(const T&); Array& operator -=(const T&); Array& operator *=(const T&); Array& operator /=(const T&); template Array& operator +=(const Array&); template Array& operator -=(const Array&); template Array& operator *=(const Array&); template Array& operator /=(const Array&); T dot(ind_t i, ind_t j) const; T norm(ind_t i) const; template::value>> void permute(std::vector& p); bool swap(ind_t a, ind_t b); bool swap(ind_t i, ind_t a, ind_t b); std::vector to_std() const; T* ptr(); T* ptr(const ind_t i0); template::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::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::value, void>> T& val(const ind_t i0, Subs... subscripts); T& val(const shape_t& partial_subscript); template T& val(std::initializer_list l); const T& val(const ind_t i0) const; template::value, void>> const T& val(const ind_t i0, Subs... subscripts) const; const T& val(const shape_t& partial_subscript) const; template const T& val(std::initializer_list l) const; Array contiguous_copy() const; Array contiguous_row_ordered_copy() const; Array squeeze() const; Array squeeze(const ind_t dim) const; Array slice(const ind_t i0) const; bool shares_with(const Array& 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 ArrayIt { public: Array array; SubIt subit; public: // constructing with array(a) does not copy the underlying data: explicit ArrayIt() : array(), subit() {} ArrayIt(const Array& a, const SubIt& s) : array(a), subit(s) {} ArrayIt(const Array& a) : array(a), subit(a.shape()) // initialises to first element, e.g., {0,…,0} {} // ArrayIt begin() const { return ArrayIt(array); } ArrayIt end() const { return ArrayIt(array, subit.end()); } ArrayIt& operator++() { ++subit; return *this; } const SubIt& iterator() const {return subit;} bool operator==(const ArrayIt& other) const { // add checking to ensure array and other.array point to the same data? return subit == other.iterator(); } bool operator!=(const ArrayIt& 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