// Copyright (c) 2010-2023, Lawrence Livermore National Security, LLC. Produced // at the Lawrence Livermore National Laboratory. All Rights reserved. See files // LICENSE and NOTICE for details. LLNL-CODE-806117. // // This file is part of the MFEM library. For more information and source code // availability visit https://mfem.org. // // MFEM is free software; you can redistribute it and/or modify it under the // terms of the BSD-3 license. We welcome feedback and contributions, see file // CONTRIBUTING.md for details. #ifndef MFEM_ARRAY #define MFEM_ARRAY #include "../config/config.hpp" #include "mem_manager.hpp" #include "device.hpp" #include "error.hpp" #include "globals.hpp" #include #include #include #include #include namespace mfem { template class Array; template void Swap(Array &, Array &); /** Abstract data type Array. Array is an automatically increasing array containing elements of the generic type T, which must be a trivial type, see `std::is_trivial`. The allocated size may be larger then the logical size of the array. The elements can be accessed by the [] operator, the range is 0 to size-1. */ template class Array { protected: /// Pointer to data Memory data; /// Size of the array int size; inline void GrowSize(int minsize); static inline void TypeAssert() { static_assert(std::is_trivial::value, "type T must be trivial"); } public: friend void Swap(Array &, Array &); /// Creates an empty array inline Array() : size(0) { data.Reset(); } /// Creates an empty array with a given MemoryType inline Array(MemoryType mt) : size(0) { data.Reset(mt); } /// Creates array of @a asize elements explicit inline Array(int asize) : size(asize) { asize > 0 ? data.New(asize) : data.Reset(); } /// Creates array of @a asize elements with a given MemoryType inline Array(int asize, MemoryType mt) : size(asize) { asize > 0 ? data.New(asize, mt) : data.Reset(mt); } /** @brief Creates array using an existing c-array of asize elements; allocsize is set to -asize to indicate that the data will not be deleted. */ inline Array(T *data_, int asize) { data.Wrap(data_, asize, false); size = asize; } /// Copy constructor: deep copy from @a src /** This method supports source arrays using any MemoryType. */ inline Array(const Array &src); /// Copy constructor (deep copy) from 'src', an Array of convertible type. template inline Array(const Array &src); /// Deep copy from a braced init-list of convertible type template explicit inline Array(const CT (&values)[N]); /// Move constructor ("steals" data from 'src') inline Array(Array &&src) { Swap(src, *this); } /// Destructor inline ~Array() { TypeAssert(); data.Delete(); } /// Assignment operator: deep copy from 'src'. Array &operator=(const Array &src) { src.Copy(*this); return *this; } /// Assignment operator (deep copy) from @a src, an Array of convertible type. template inline Array &operator=(const Array &src); /// Return the data as 'T *' inline operator T *() { return data; } /// Return the data as 'const T *' inline operator const T *() const { return data; } /// Returns the data inline T *GetData() { return data; } /// Returns the data inline const T *GetData() const { return data; } /// Return a reference to the Memory object used by the Array. Memory &GetMemory() { return data; } /// Return a reference to the Memory object used by the Array, const version. const Memory &GetMemory() const { return data; } /// Return the device flag of the Memory object used by the Array bool UseDevice() const { return data.UseDevice(); } /// Return true if the data will be deleted by the array inline bool OwnsData() const { return data.OwnsHostPtr(); } /// Changes the ownership of the data inline void StealData(T **p) { *p = data; data.Reset(); size = 0; } /// NULL-ifies the data inline void LoseData() { data.Reset(); size = 0; } /// Make the Array own the data void MakeDataOwner() const { data.SetHostPtrOwner(true); } /// Return the logical size of the array. inline int Size() const { return size; } /// Change the logical size of the array, keep existing entries. inline void SetSize(int nsize); /// Same as SetSize(int) plus initialize new entries with 'initval'. inline void SetSize(int nsize, const T &initval); /** @brief Resize the array to size @a nsize using MemoryType @a mt. Note that unlike the other versions of SetSize(), the current content of the array is not preserved. */ inline void SetSize(int nsize, MemoryType mt); /** Maximum number of entries the array can store without allocating more memory. */ inline int Capacity() const { return data.Capacity(); } /// Ensures that the allocated size is at least the given size. inline void Reserve(int capacity) { if (capacity > Capacity()) { GrowSize(capacity); } } /// Reference access to the ith element. inline T & operator[](int i); /// Const reference access to the ith element. inline const T &operator[](int i) const; /// Append element 'el' to array, resize if necessary. inline int Append(const T & el); /// Append another array to this array, resize if necessary. inline int Append(const T *els, int nels); /// Append another array to this array, resize if necessary. inline int Append(const Array &els) { return Append(els, els.Size()); } /// Prepend an 'el' to the array, resize if necessary. inline int Prepend(const T &el); /// Return the last element in the array. inline T &Last(); /// Return the last element in the array. inline const T &Last() const; /// Append element when it is not yet in the array, return index. inline int Union(const T & el); /// Return the first index where 'el' is found; return -1 if not found. inline int Find(const T &el) const; /// Do bisection search for 'el' in a sorted array; return -1 if not found. inline int FindSorted(const T &el) const; /// Delete the last entry of the array. inline void DeleteLast() { if (size > 0) { size--; } } /// Delete the first entry with value == 'el'. inline void DeleteFirst(const T &el); /// Delete the whole array. inline void DeleteAll(); /// Create a copy of the internal array to the provided @a copy. inline void Copy(Array ©) const; /// Make this Array a reference to a pointer. inline void MakeRef(T *, int); /// Make this Array a reference to 'master'. inline void MakeRef(const Array &master); /// Copy sub array starting from @a offset out to the provided @a sa. inline void GetSubArray(int offset, int sa_size, Array &sa) const; /// Prints array to stream with width elements per row. void Print(std::ostream &out = mfem::out, int width = 4) const; /** @brief Save the Array to the stream @a out using the format @a fmt. The format @a fmt can be: 0 - write the size followed by all entries 1 - write only the entries */ void Save(std::ostream &out, int fmt = 0) const; /** @brief Read an Array from the stream @a in using format @a fmt. The format @a fmt can be: 0 - read the size then the entries 1 - read Size() entries */ void Load(std::istream &in, int fmt = 0); /** @brief Set the Array size to @a new_size and read that many entries from the stream @a in. */ void Load(int new_size, std::istream &in) { SetSize(new_size); Load(in, 1); } /** @brief Find the maximal element in the array, using the comparison operator `<` for class T. */ T Max() const; /** @brief Find the minimal element in the array, using the comparison operator `<` for class T. */ T Min() const; /// Sorts the array in ascending order. This requires operator< to be defined for T. void Sort() { std::sort((T*)data, data + size); } /// Sorts the array in ascending order using the supplied comparison function object. template void Sort(Compare cmp) { std::sort((T*)data, data + size, cmp); } /** @brief Removes duplicities from a sorted array. This requires operator== to be defined for T. */ void Unique() { T* end = std::unique((T*)data, data + size); SetSize(end - data); } /// Return 1 if the array is sorted from lowest to highest. Otherwise return 0. int IsSorted(); /// Fill the entries of the array with the cumulative sum of the entries. void PartialSum(); /// Return the sum of all the array entries using the '+'' operator for class 'T'. T Sum(); /// Set all entries of the array to the provided constant. inline void operator=(const T &a); /// Copy data from a pointer. 'Size()' elements are copied. inline void Assign(const T *); /// STL-like copyTo @a dest from begin to end. template inline void CopyTo(U *dest) { std::copy(begin(), end(), dest); } /** @brief Copy from @a src into this array. Copies enough entries to fill the Capacity size of this array. Careful this does not update the Size to match this Capacity after this.*/ template inline void CopyFrom(const U *src) { std::memcpy(begin(), src, MemoryUsage()); } /// STL-like begin. Returns pointer to the first element of the array. inline T* begin() { return data; } /// STL-like end. Returns pointer after the last element of the array. inline T* end() { return data + size; } /// STL-like begin. Returns const pointer to the first element of the array. inline const T* begin() const { return data; } /// STL-like end. Returns const pointer after the last element of the array. inline const T* end() const { return data + size; } /// Returns the number of bytes allocated for the array including any reserve. std::size_t MemoryUsage() const { return Capacity() * sizeof(T); } /// Shortcut for mfem::Read(a.GetMemory(), a.Size(), on_dev). const T *Read(bool on_dev = true) const { return mfem::Read(data, size, on_dev); } /// Shortcut for mfem::Read(a.GetMemory(), a.Size(), false). const T *HostRead() const { return mfem::Read(data, size, false); } /// Shortcut for mfem::Write(a.GetMemory(), a.Size(), on_dev). T *Write(bool on_dev = true) { return mfem::Write(data, size, on_dev); } /// Shortcut for mfem::Write(a.GetMemory(), a.Size(), false). T *HostWrite() { return mfem::Write(data, size, false); } /// Shortcut for mfem::ReadWrite(a.GetMemory(), a.Size(), on_dev). T *ReadWrite(bool on_dev = true) { return mfem::ReadWrite(data, size, on_dev); } /// Shortcut for mfem::ReadWrite(a.GetMemory(), a.Size(), false). T *HostReadWrite() { return mfem::ReadWrite(data, size, false); } }; template inline bool operator==(const Array &LHS, const Array &RHS) { if ( LHS.Size() != RHS.Size() ) { return false; } for (int i=0; i inline bool operator!=(const Array &LHS, const Array &RHS) { return !( LHS == RHS ); } /// Utility function similar to std::as_const in c++17. template const T &AsConst(const T &a) { return a; } template class Array2D; template void Swap(Array2D &, Array2D &); /// Dynamic 2D array using row-major layout template class Array2D { private: friend void Swap(Array2D &, Array2D &); Array array1d; int M, N; // number of rows and columns public: Array2D() { M = N = 0; } Array2D(int m, int n) : array1d(m*n) { M = m; N = n; } Array2D(const Array2D &) = default; void SetSize(int m, int n) { array1d.SetSize(m*n); M = m; N = n; } int NumRows() const { return M; } int NumCols() const { return N; } inline const T &operator()(int i, int j) const; inline T &operator()(int i, int j); inline const T *operator[](int i) const; inline T *operator[](int i); const T *operator()(int i) const { return (*this)[i]; } T *operator()(int i) { return (*this)[i]; } const T *GetRow(int i) const { return (*this)[i]; } T *GetRow(int i) { return (*this)[i]; } /// Extract a copy of the @a i-th row into the Array @a sa. void GetRow(int i, Array &sa) const { sa.SetSize(N); sa.Assign(GetRow(i)); } /** @brief Save the Array2D to the stream @a out using the format @a fmt. The format @a fmt can be: 0 - write the number of rows and columns, followed by all entries 1 - write only the entries, using row-major layout */ void Save(std::ostream &os, int fmt = 0) const { if (fmt == 0) { os << NumRows() << ' ' << NumCols() << '\n'; } array1d.Save(os, 1); } /** @brief Read an Array2D from the stream @a in using format @a fmt. The format @a fmt can be: 0 - read the number of rows and columns, then the entries 1 - read NumRows() x NumCols() entries, using row-major layout */ void Load(std::istream &in, int fmt = 0) { if (fmt == 0) { in >> M >> N; array1d.SetSize(M*N); } array1d.Load(in, 1); } /// Read an Array2D from a file void Load(const char *filename, int fmt = 0); /** @brief Set the Array2D dimensions to @a new_size0 x @a new_size1 and read that many entries from the stream @a in. */ void Load(int new_size0,int new_size1, std::istream &in) { SetSize(new_size0,new_size1); Load(in, 1); } void Copy(Array2D ©) const { copy.M = M; copy.N = N; array1d.Copy(copy.array1d); } inline void operator=(const T &a) { array1d = a; } inline Array2D& operator=(const Array2D &a) = default; /// Make this Array a reference to 'master' inline void MakeRef(const Array2D &master) { M = master.M; N = master.N; array1d.MakeRef(master.array1d); } /// Delete all dynamically allocated memory, resetting all dimensions to zero. inline void DeleteAll() { M = 0; N = 0; array1d.DeleteAll(); } /// Prints array to stream with width elements per row void Print(std::ostream &out = mfem::out, int width = 4); }; template class Array3D { private: Array array1d; int N2, N3; public: Array3D() { N2 = N3 = 0; } Array3D(int n1, int n2, int n3) : array1d(n1*n2*n3) { N2 = n2; N3 = n3; } void SetSize(int n1, int n2, int n3) { array1d.SetSize(n1*n2*n3); N2 = n2; N3 = n3; } inline const T &operator()(int i, int j, int k) const; inline T &operator()(int i, int j, int k); inline void operator=(const T &a) { array1d = a; } }; /** A container for items of type T. Dynamically grows as items are added. * Each item is accessible by its index. Items are allocated in larger chunks * (blocks), so the 'Append' method is very fast on average. */ template class BlockArray { public: BlockArray(int block_size = 16*1024); BlockArray(const BlockArray &other); // deep copy BlockArray& operator=(const BlockArray&) = delete; // not supported ~BlockArray() { Destroy(); } /// Allocate and construct a new item in the array, return its index. int Append(); /// Allocate and copy-construct a new item in the array, return its index. int Append(const T &item); /// Access item of the array. inline T& At(int index) { CheckIndex(index); return blocks[index >> shift][index & mask]; } inline const T& At(int index) const { CheckIndex(index); return blocks[index >> shift][index & mask]; } /// Access item of the array. inline T& operator[](int index) { return At(index); } inline const T& operator[](int index) const { return At(index); } /// Return the number of items actually stored. int Size() const { return size; } /// Return the current capacity of the BlockArray. int Capacity() const { return blocks.Size()*(mask+1); } /// Destroy all items, set size to zero. void DeleteAll() { Destroy(); blocks.DeleteAll(); size = 0; } void Swap(BlockArray &other); std::size_t MemoryUsage() const; protected: template class iterator_base { public: cT& operator*() const { return *ptr; } cT* operator->() const { return ptr; } bool good() const { return !stop; } int index() const { return (ptr - ref); } protected: cA *array; cT *ptr, *b_end, *ref; int b_end_idx; bool stop; iterator_base() { } iterator_base(bool stop) : stop(stop) { } iterator_base(cA *a) : array(a), ptr(a->blocks[0]), ref(ptr), stop(false) { b_end_idx = std::min(a->size, a->mask+1); b_end = ptr + b_end_idx; } void next() { MFEM_ASSERT(!stop, "invalid use"); if (++ptr == b_end) { if (b_end_idx < array->size) { ptr = &array->At(b_end_idx); ref = ptr - b_end_idx; b_end_idx = std::min(array->size, (b_end_idx|array->mask) + 1); b_end = &array->At(b_end_idx-1) + 1; } else { MFEM_ASSERT(b_end_idx == array->size, "invalid use"); stop = true; } } } }; public: class iterator : public iterator_base { protected: friend class BlockArray; typedef iterator_base base; iterator() { } iterator(bool stop) : base(stop) { } iterator(BlockArray *a) : base(a) { } public: iterator &operator++() { base::next(); return *this; } bool operator==(const iterator &other) const { return base::stop; } bool operator!=(const iterator &other) const { return !base::stop; } }; class const_iterator : public iterator_base { protected: friend class BlockArray; typedef iterator_base base; const_iterator() { } const_iterator(bool stop) : base(stop) { } const_iterator(const BlockArray *a) : base(a) { } public: const_iterator &operator++() { base::next(); return *this; } bool operator==(const const_iterator &other) const { return base::stop; } bool operator!=(const const_iterator &other) const { return !base::stop; } }; iterator begin() { return size ? iterator(this) : iterator(true); } iterator end() { return iterator(); } const_iterator cbegin() const { return size ? const_iterator(this) : const_iterator(true); } const_iterator cend() const { return const_iterator(); } protected: Array blocks; int size, shift, mask; int Alloc(); inline void CheckIndex(int index) const { MFEM_ASSERT(index >= 0 && index < size, "Out of bounds access: " << index << ", size = " << size); } void Destroy(); }; /// inlines /// template inline void Swap(T &a, T &b) { T c = a; a = b; b = c; } template inline void Swap(Array &a, Array &b) { Swap(a.data, b.data); Swap(a.size, b.size); } template inline Array::Array(const Array &src) : size(src.Size()) { size > 0 ? data.New(size, src.data.GetMemoryType()) : data.Reset(); data.CopyFrom(src.data, size); data.UseDevice(src.data.UseDevice()); } template template inline Array::Array(const Array &src) : size(src.Size()) { size > 0 ? data.New(size) : data.Reset(); for (int i = 0; i < size; i++) { (*this)[i] = T(src[i]); } } template template inline Array::Array(const CT (&values)[N]) : Array(N) { for (int i = 0; i < size; i++) { (*this)[i] = T(values[i]); } } template inline void Array::GrowSize(int minsize) { const int nsize = std::max(minsize, 2 * data.Capacity()); Memory p(nsize, data.GetMemoryType()); p.CopyFrom(data, size); p.UseDevice(data.UseDevice()); data.Delete(); data = p; } template template inline Array &Array::operator=(const Array &src) { SetSize(src.Size()); for (int i = 0; i < size; i++) { (*this)[i] = T(src[i]); } return *this; } template inline void Array::SetSize(int nsize) { MFEM_ASSERT( nsize>=0, "Size must be non-negative. It is " << nsize ); if (nsize > Capacity()) { GrowSize(nsize); } size = nsize; } template inline void Array::SetSize(int nsize, const T &initval) { MFEM_ASSERT( nsize>=0, "Size must be non-negative. It is " << nsize ); if (nsize > size) { if (nsize > Capacity()) { GrowSize(nsize); } for (int i = size; i < nsize; i++) { data[i] = initval; } } size = nsize; } template inline void Array::SetSize(int nsize, MemoryType mt) { MFEM_ASSERT(nsize >= 0, "invalid new size: " << nsize); if (mt == data.GetMemoryType()) { if (nsize <= Capacity()) { size = nsize; return; } } const bool use_dev = data.UseDevice(); data.Delete(); if (nsize > 0) { data.New(nsize, mt); size = nsize; } else { data.Reset(); size = 0; } data.UseDevice(use_dev); } template inline T &Array::operator[](int i) { MFEM_ASSERT( i>=0 && i inline const T &Array::Last() const { MFEM_ASSERT(size > 0, "Array size is zero: " << size); return data[size-1]; } template inline int Array::Union(const T &el) { int i = 0; while ((i < size) && (data[i] != el)) { i++; } if (i == size) { Append(el); } return i; } template inline int Array::Find(const T &el) const { for (int i = 0; i < size; i++) { if (data[i] == el) { return i; } } return -1; } template inline int Array::FindSorted(const T &el) const { const T *begin = data, *end = begin + size; const T* first = std::lower_bound(begin, end, el); if (first == end || !(*first == el)) { return -1; } return first - begin; } template inline void Array::DeleteFirst(const T &el) { for (int i = 0; i < size; i++) { if (data[i] == el) { for (i++; i < size; i++) { data[i-1] = data[i]; } size--; return; } } } template inline void Array::DeleteAll() { const bool use_dev = data.UseDevice(); data.Delete(); data.Reset(); size = 0; data.UseDevice(use_dev); } template inline void Array::Copy(Array ©) const { copy.SetSize(Size(), data.GetMemoryType()); data.CopyTo(copy.data, Size()); copy.data.UseDevice(data.UseDevice()); } template inline void Array::MakeRef(T *p, int s) { data.Delete(); data.Wrap(p, s, false); size = s; } template inline void Array::MakeRef(const Array &master) { data.Delete(); size = master.size; data.MakeAlias(master.GetMemory(), 0, size); } template inline void Array::GetSubArray(int offset, int sa_size, Array &sa) const { sa.SetSize(sa_size); for (int i = 0; i < sa_size; i++) { sa[i] = (*this)[offset+i]; } } template inline void Array::operator=(const T &a) { for (int i = 0; i < size; i++) { data[i] = a; } } template inline void Array::Assign(const T *p) { data.CopyFromHost(p, Size()); } template inline const T &Array2D::operator()(int i, int j) const { MFEM_ASSERT( i>=0 && i< array1d.Size()/N && j>=0 && j inline T &Array2D::operator()(int i, int j) { MFEM_ASSERT( i>=0 && i< array1d.Size()/N && j>=0 && j inline const T *Array2D::operator[](int i) const { MFEM_ASSERT( i>=0 && i< array1d.Size()/N, "Array2D: invalid access of row " << i << " in array with " << array1d.Size()/N << " rows."); return &array1d[i*N]; } template inline T *Array2D::operator[](int i) { MFEM_ASSERT( i>=0 && i< array1d.Size()/N, "Array2D: invalid access of row " << i << " in array with " << array1d.Size()/N << " rows."); return &array1d[i*N]; } template inline void Swap(Array2D &a, Array2D &b) { Swap(a.array1d, b.array1d); Swap(a.N, b.N); } template inline const T &Array3D::operator()(int i, int j, int k) const { MFEM_ASSERT(i >= 0 && i < array1d.Size() / N2 / N3 && j >= 0 && j < N2 && k >= 0 && k < N3, "Array3D: invalid access of element (" << i << ',' << j << ',' << k << ") in array of size (" << array1d.Size() / N2 / N3 << ',' << N2 << ',' << N3 << ")."); return array1d[(i*N2+j)*N3+k]; } template inline T &Array3D::operator()(int i, int j, int k) { MFEM_ASSERT(i >= 0 && i < array1d.Size() / N2 / N3 && j >= 0 && j < N2 && k >= 0 && k < N3, "Array3D: invalid access of element (" << i << ',' << j << ',' << k << ") in array of size (" << array1d.Size() / N2 / N3 << ',' << N2 << ',' << N3 << ")."); return array1d[(i*N2+j)*N3+k]; } template BlockArray::BlockArray(int block_size) { mask = block_size-1; MFEM_VERIFY(!(block_size & mask), "block_size must be a power of two."); size = shift = 0; while ((1 << shift) < block_size) { shift++; } } template BlockArray::BlockArray(const BlockArray &other) { blocks.SetSize(other.blocks.Size()); size = other.size; shift = other.shift; mask = other.mask; int bsize = mask+1; for (int i = 0; i < blocks.Size(); i++) { blocks[i] = (T*) new char[bsize * sizeof(T)]; } // copy all items for (int i = 0; i < size; i++) { new (&At(i)) T(other[i]); } } template int BlockArray::Alloc() { int bsize = mask+1; if (size >= blocks.Size() * bsize) { T* new_block = (T*) new char[bsize * sizeof(T)]; blocks.Append(new_block); } return size++; } template int BlockArray::Append() { int index = Alloc(); new (&At(index)) T(); return index; } template int BlockArray::Append(const T &item) { int index = Alloc(); new (&At(index)) T(item); return index; } template void BlockArray::Swap(BlockArray &other) { mfem::Swap(blocks, other.blocks); std::swap(size, other.size); std::swap(shift, other.shift); std::swap(mask, other.mask); } template std::size_t BlockArray::MemoryUsage() const { return (mask+1)*sizeof(T)*blocks.Size() + blocks.MemoryUsage(); } template void BlockArray::Destroy() { int bsize = size & mask; for (int i = blocks.Size(); i != 0; ) { T *block = blocks[--i]; for (int j = bsize; j != 0; ) { block[--j].~T(); } delete [] (char*) block; bsize = mask+1; } } } // namespace mfem #endif