Program Listing for File array2.hpp¶
↰ Return to documentation for file (src/array2.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_ARRAY2_HPP
#define BRILLE_ARRAY2_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 "array.hpp"
namespace brille {
template<class T> class Array2{
public:
using ref_t = std::shared_ptr<void>;
using shape_t = std::array<ind_t,2>;
using bItr = BroadcastIt2<ind_t>;
using sItr = SubIt2<ind_t>;
using aItr = Array2It<T>;
protected:
T* _data;
ind_t _num;
ind_t _shift;
bool _own;
ref_t _ref;
bool _mutable;
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 ind_t& i0, const ind_t& i1){ return _data + this->s2l_d(i0,i1);}
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 ind_t& i0, const ind_t& i1) const { return _data + this->s2l_d(i0,i1);}
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 2;}
ind_t numel(void) const {
return _shape[0]*_shape[1];
}
ind_t size(const ind_t dim) const {
assert(dim < 2);
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 Array2<R>& other) const {return bItr(_shape, other.shape());}
bool is_column_ordered() const { // stride {1,2,4,8} is column ordered
return _stride[0] <= _stride[1];
}
bool is_row_ordered() const { // stride {8,4,2,1} is row ordered
return _stride[1] <= _stride[0];
}
bool is_contiguous() const {
shape_t expected({1, 1});
if (this->is_row_ordered()) expected[0] = _shape[1];
if (this->is_column_ordered()) expected[1] = _shape[0];
// if a dimension is 1 (or 0?) then its stride does not impact here
return (_shape[0]<2||expected[0]==_stride[0]) && (_shape[1]<2||expected[1]==_stride[1]);
}
// empty initializer
explicit Array2()
: _data(nullptr), _num(0), _shift(0u), _own(false), _ref(std::make_shared<char>()),
_mutable(false), _shape({0,0}), _stride({0,1})
{}
// 1D initializer
Array2(T* data, const ind_t num, const bool own, const bool mut=true)
: _data(data), _num(num), _shift(0u), _own(own), _ref(std::make_shared<char>()),
_mutable(mut), _shape({num,1}), _stride({1,1})
{
this->init_check();
}
// 2D initializer
Array2(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), _shift(0u), _own(own), _ref(std::make_shared<char>()),
_mutable(mut), _shape(shape), _stride(stride)
{
this->init_check();
}
// 2D initializer with reference-counter specified (used in pybind11 wrapper)
template<class P>
Array2(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), _shift(0u), _own(own), _ref(ref),
_mutable(mut), _shape(shape), _stride(stride)
{
this->init_check();
}
// 2D view constructor
template<class P>
Array2(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
Array2(const ind_t s0, const ind_t s1)
: _shift(0u), _mutable(true), _shape({s0,s1}), _stride({s1,1})
{
this->construct();
this->init_check();
}
// 2D initialize new memory constructor
Array2(const ind_t s0, const ind_t s1, const T init)
: _shift(0u), _mutable(true), _shape({s0,s1}), _stride({s1,1})
{
this->construct(init);
this->init_check();
}
// 2D allocate new memory constructor
Array2(const shape_t& shape)
: _shift(0u), _mutable(true), _shape(shape)
{
this->set_stride();
this->construct();
this->init_check();
}
// ND initialize new memory constructor
Array2(const shape_t& shape, const T init)
: _shift(0u), _mutable(true), _shape(shape)
{
this->set_stride();
this->construct(init);
this->init_check();
}
// ND allocate new memory with specified stride constructor
Array2(const shape_t& shape, const shape_t& stride)
: _shift(0u), _mutable(true), _shape(shape), _stride(stride)
{
this->construct();
this->init_check();
}
// ND initialize new memory with specified stride constructor
Array2(const shape_t& shape, const shape_t& stride, const T init)
: _shift(0u), _mutable(true), _shape(shape), _stride(stride)
{
this->construct(init);
this->init_check();
}
// otherwise possibly ambiguous constructor from std::vector
static Array2<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 Array2<T>(d, num, true);
}
template<class R, size_t Nel>
static Array2<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 Array2<T>(d, num, true, shape, stride);
}
// Construct an Array2 from an Array, unravelling higher dimensions
Array2(const Array<T>& nd)
: _num(0u), _shift(0u), _shape({0,1}), _stride({1,1})
{
// we always want to construct contiguous row-ordered arrays
if (nd.ndim()>0){
_shape[0] = nd.size(0);
if (nd.ndim()>1){
for (ind_t i=1; i<nd.ndim(); ++i) _shape[1] *= nd.size(i);
_stride[0] = _shape[1];
}
// the following is a very-light (metadata) copy unless the underlying
// data is not row-ordered contiguous.
auto cnd = nd.contiguous_row_ordered_copy();
_data = cnd.data();
_num = cnd.raw_size();
_shift = cnd.raw_shift();
_own = cnd.own();
_ref = cnd.ref();
_mutable = cnd.ismutable();
}
}
// Rule-of-five definitions since we may not own the associated raw array:
Array2(const Array2<T>& o)
: _data(o._data), _num(o._num), _shift(o._shift), _own(o._own), _ref(o._ref),
_mutable(o._mutable), _shape(o._shape), _stride(o._stride)
{}
~Array2(){
if (_own && _ref.use_count()==1 && _data != nullptr) delete[] _data;
}
Array2<T>& operator=(const Array2<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();
_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 Array2<T>(Array2<T>&) reference type conversion
template<class R>
Array2(const Array2<R>& other)
: _shift(0u), _mutable(true), _shape(other.shape())
{
this->set_stride();
this->construct();
for (auto x: this->subItr()) _data[s2l_d(x)] = static_cast<T>(other[x]);
}
template<class R>
Array2<T>& operator=(const Array2<R>& other){
_mutable = true;
_shape = other.shape();
_shift = 0;
this->set_stride();
this->construct();
for (auto x: this->subItr()) _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;}
Array2<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 {
return l + _shift;
}
ind_t ij2l_d(const ind_t x, const ind_t y) const {
return sub2lin(x, y, _stride) + _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 {
return s[0]*s[1];
}
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[1] = 1;
_stride[0] = _shape[1];
}
void init_check(void){
ind_t offset_size = _shift + this->size_from_shape(_shape);
if (_num < offset_size) {
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& shape) const {
shape_t stride{{1,1}};
if (_stride[0] < _stride[1]){
stride[1] = shape[0];
} else {
stride[0] = shape[1];
}
return stride;
}
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);
}
Array2<T> _decouple() const {
ind_t nnum = this->size_from_shape(_shape);
T* new_data = new T[nnum]();
shape_t new_st{_stride};
if (this->is_contiguous() && nnum == _num) {
std::copy(_data, _data+_num, new_data);
} else {
//subscript conversion necessary due to offset or strided array
new_st = this->calculate_stride(_shape);
// vv(no offset)vvv vvv(offset)vvv
for (auto x: this->subItr()) new_data[sub2lin(x,new_st)] = _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 Array2<T>(new_data, nnum, new_own, new_ref, _shape, new_st, new_mut);
}
public:
// sub-array access
Array2<T> view() const; // whole array non-owning view
Array2<T> view(ind_t i) const;
Array2<T> view(ind_t i, ind_t j) const;
Array2<T> view(const shape_t&) const;
// duplication of one or more sub-arrays:
Array2<T>
extract(ind_t i) const;
template<class I> std::enable_if_t<std::is_integral_v<I>, Array2<T>> extract(const Array2<I>& i) const;
template<class I> std::enable_if_t<std::is_integral_v<I>, Array2<T>> extract(const std::vector<I>& i) const;
template<class I, size_t Nel> std::enable_if_t<std::is_integral_v<I>, Array2<T>> extract(const std::array<I,Nel>& i) const;
template<class I, size_t Nel> std::enable_if_t<std::is_integral_v<I>, Array2<T>> extract(const std::vector<std::array<I,Nel>>& i) const;
Array2<T> extract(const Array2<bool>& i) const;
Array2<T> extract(const std::vector<bool>& i) const;
bool set(const ind_t i, const Array2<T>& in);
template<class R>
bool set(const ind_t i, const Array2<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);
Array2<T>& append(const ind_t, const Array2<T>&);
std::string to_string() const;
std::string to_string(const ind_t) const;
Array2<T>& reshape(const shape_t& ns);
Array2<T>& resize(const shape_t&, T init=T(0));
template<class I> Array2<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;
Array2<int> round() const;
Array2<int> floor() const;
Array2<int> ceil() const;
Array2<T> sum(ind_t dim=0) const;
Array2<T> prod(ind_t dim=0) const;
Array2<T> min(ind_t dim=0) const;
Array2<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;
Array2<bool> is(cmp expr, T val) const;
std::vector<ind_t> find(cmp expr, T val) const;
template<class R> Array2<bool> is(cmp expr, const Array2<R>& that) const;
template<class R> std::vector<bool> is(cmp expr, const std::vector<R>& val) const;
template<class R> bool is(const Array2<R>& that) const;
std::vector<bool> is_unique() const;
std::vector<ind_t> unique_idx() const;
Array2<T> unique() const;
Array2<T> operator-() const;
Array2<T>& operator +=(const T&);
Array2<T>& operator -=(const T&);
Array2<T>& operator *=(const T&);
Array2<T>& operator /=(const T&);
template<class R> Array2<T>& operator +=(const Array2<R>&);
template<class R> Array2<T>& operator -=(const Array2<R>&);
template<class R> Array2<T>& operator *=(const Array2<R>&);
template<class R> Array2<T>& operator /=(const Array2<R>&);
T dot(ind_t i, ind_t j) const;
T norm(ind_t i) const;
template<class 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(const ind_t i0);
T* ptr(const ind_t i0, const ind_t j0);
T* ptr(const shape_t& partial_subscript);
const T* ptr(const ind_t i0) const;
const T* ptr(const ind_t i0, const ind_t j0) const;
const T* ptr(const shape_t& partial_subscript) const;
T& val(const ind_t i0);
T& val(const ind_t i0, const ind_t j0);
T& val(const shape_t& partial_subscript);
template<class I> T& val(std::initializer_list<I> l);
const T& val(const ind_t i0) const;
const T& val(const ind_t i0, const ind_t j0) const;
const T& val(const shape_t& partial_subscript) const;
template<class I> const T& val(std::initializer_list<I> l) const;
Array2<T> contiguous_copy() const;
Array2<T> contiguous_row_ordered_copy() const;
// ^^^^^^^^^^ IMPLEMENTED ^^^^^^^^^^^vvvvvvvvv TO IMPLEMENT vvvvvvvvvvvvvvvvv
};
template<class T>
class Array2It {
public:
Array2<T> array;
SubIt2<ind_t> subit;
public:
// constructing with array(a) does not copy the underlying data:
explicit Array2It()
: array(), subit()
{}
Array2It(const Array2<T>& a, const SubIt2<ind_t>& s)
: array(a), subit(s)
{}
Array2It(const Array2<T>& a)
: array(a), subit(a.shape()) // initialises to first element, e.g., {0,…,0}
{}
//
Array2It<T> begin() const {
return Array2It(array);
}
Array2It<T> end() const {
return Array2It(array, subit.end());
}
Array2It<T>& operator++() {
++subit;
return *this;
}
const SubIt2<ind_t>& iterator() const {return subit;}
bool operator==(const Array2It<T>& other) const {
// add checking to ensure array and other.array point to the same data?
return subit == other.iterator();
}
bool operator!=(const Array2It<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 "array2.tpp"
} // end namespace brille
#endif // ARRAY_HPP