From 1270ef055af430d254bad11b89a0c3f9a7b4cce7 Mon Sep 17 00:00:00 2001 From: David Dickinson Date: Wed, 16 Jan 2019 10:11:44 +0000 Subject: [PATCH 1/9] Initial example of a general ND array that supports a specific form of slicing --- include/bout/arraynd.hxx | 195 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 include/bout/arraynd.hxx diff --git a/include/bout/arraynd.hxx b/include/bout/arraynd.hxx new file mode 100644 index 0000000000..530b387fb5 --- /dev/null +++ b/include/bout/arraynd.hxx @@ -0,0 +1,195 @@ +#ifndef __ARRAYND_HXX__ +#define __ARRAYND_HXX__ + +#include "bout/array.hxx" + +template +class ArrayND { +public: + using data_type = T; + using slice_type = ArrayND; + using size_type = int; + using shape_type = decltype( + std::tuple_cat(std::tuple{}, typename slice_type::shape_type{})); + + constexpr static int ndims = ndim; + + ArrayND() : n1(0){}; + + template + ArrayND(size_type n1, allSizes... sizes) : n1(n1) { + // Note we compare against ndim rather than ndim-1 here. + // Not clear exactly why this is the case currently but perhaps + // is including n1 as well? + static_assert(sizeof...(sizes) != ndim, + "Incorrect number of dimension sizes passed as arguments to ArrayND."); + + data = Array(n1); + for (auto& i : data) { + i = slice_type(sizes...); + } + } + ArrayND(const ArrayND& other) : n1(other.n1), data(other.data) { + // Prevent copy on write for ArrayND + data.ensureUnique(); + } + + ArrayND& operator=(const ArrayND& other) { + n1 = other.n1; + data = other.data; + // Prevent copy on write for ArrayND + data.ensureUnique(); + return *this; + } + template + inline T& operator()(size_type i1, allSizes... sizes) { + ASSERT2(0 <= i1 && i1 < n1); + return data[i1](sizes...); + } + inline slice_type& operator[](size_type i1) { + ASSERT2(0 <= i1 && i1 < n1); + return data[i1]; + } + inline slice_type& operator()(size_type i1) { + ASSERT2(0 <= i1 && i1 < n1); + return data[i1]; + } + template + inline const T& operator()(size_type i1, allSizes... sizes) const { + ASSERT2(0 <= i1 && i1 < n1); + return data[i1](sizes...); + } + inline const slice_type& operator[](size_type i1) const { + ASSERT2(0 <= i1 && i1 < n1); + return data[i1]; + } + inline const slice_type& operator()(size_type i1) const { + ASSERT2(0 <= i1 && i1 < n1); + return data[i1]; + } + + ArrayND& operator=(const T& val) { + for (auto& i : data) { + i = val; + }; + return *this; + }; + + slice_type* begin() { return std::begin(data); }; + const slice_type* begin() const { return std::begin(data); }; + slice_type* end() { return std::end(data); }; + const slice_type* end() const { return std::end(data); }; + + auto shape() const -> shape_type { + return std::tuple_cat(std::make_tuple(n1), data[0].shape()); + } + + size_type size() const { return n1 * data[0].size(); } + + bool empty() const { return size() == 0; } + + /*! + * Ensures that this ArrayND does not share data with another + * This should be called before performing any write operations + * on the data. + */ + void ensureUnique() { data.ensureUnique(); } + +private: + size_type n1; + Array data; +}; + +template +class ArrayND { +public: + using data_type = T; + using size_type = int; + using shape_type = std::tuple; + constexpr static int ndim = 1; + + constexpr static int ndims = ndim; + + ArrayND() : n1(0){}; + ArrayND(size_type n1) : n1(n1) { data = Array(n1); } + // Should only end up calling this if we pass too many dimension sizes + template + ArrayND(size_type n1, allSizes... sizes) : n1(n1) { + // Note we compare against ndim rather than ndim-1 here. + // Not clear exactly why this is the case currently but perhaps + // is including n1 as well? + static_assert( + sizeof...(sizes) != ndim, + "Incorrect number of dimension sizes passed as arguments to ArrayND."); + } + + ArrayND(const ArrayND& other) : n1(other.n1), data(other.data) { + // Prevent copy on write for ArrayND + data.ensureUnique(); + } + + ArrayND& operator=(const ArrayND& other) { + n1 = other.n1; + data = other.data; + // Prevent copy on write for ArrayND + data.ensureUnique(); + return *this; + } + + inline T& operator()(size_type i1) { + ASSERT2(0 <= i1 && i1 < n1); + return data[i1]; + } + inline T& operator[](size_type i1) { + ASSERT2(0 <= i1 && i1 < n1); + return data[i1]; + } + inline const T& operator()(size_type i1) const { + ASSERT2(0 <= i1 && i1 < n1); + return data[i1]; + } + inline const T& operator[](size_type i1) const { + ASSERT2(0 <= i1 && i1 < n1); + return data[i1]; + } + + ArrayND& operator=(const T& val) { + for (auto& i : data) { + i = val; + }; + return *this; + }; + + T* begin() { return std::begin(data); }; + const T* begin() const { return std::begin(data); }; + T* end() { return std::end(data); }; + const T* end() const { return std::end(data); }; + + std::tuple shape() const { return std::make_tuple(n1); } + + size_type size() const { return n1; } + + bool empty() const { return size() == 0; } + + /*! + * Ensures that this ArrayND does not share data with another + * This should be called before performing any write operations + * on the data. + */ + void ensureUnique() { data.ensureUnique(); } + +private: + size_type n1; + Array data; +}; + +template +using Array1D = ArrayND; + +template +using Array2D = ArrayND; + +template +using Array3D = ArrayND; + +#endif From 2a91f28694c97b6e4bdfc7dfbfbc18ba947ebfd7 Mon Sep 17 00:00:00 2001 From: David Dickinson Date: Wed, 16 Jan 2019 10:44:27 +0000 Subject: [PATCH 2/9] Logic fix for static_assert --- include/bout/arraynd.hxx | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/include/bout/arraynd.hxx b/include/bout/arraynd.hxx index 530b387fb5..038a60b4da 100644 --- a/include/bout/arraynd.hxx +++ b/include/bout/arraynd.hxx @@ -1,6 +1,7 @@ #ifndef __ARRAYND_HXX__ #define __ARRAYND_HXX__ +#include "output.hxx" #include "bout/array.hxx" template @@ -18,10 +19,7 @@ public: template ArrayND(size_type n1, allSizes... sizes) : n1(n1) { - // Note we compare against ndim rather than ndim-1 here. - // Not clear exactly why this is the case currently but perhaps - // is including n1 as well? - static_assert(sizeof...(sizes) != ndim, + static_assert(sizeof...(sizes) == ndim - 1, "Incorrect number of dimension sizes passed as arguments to ArrayND."); data = Array(n1); @@ -115,11 +113,8 @@ public: // Should only end up calling this if we pass too many dimension sizes template ArrayND(size_type n1, allSizes... sizes) : n1(n1) { - // Note we compare against ndim rather than ndim-1 here. - // Not clear exactly why this is the case currently but perhaps - // is including n1 as well? static_assert( - sizeof...(sizes) != ndim, + sizeof...(sizes) == ndim - 1, "Incorrect number of dimension sizes passed as arguments to ArrayND."); } From b86663b88bcaf0528502996e01cc79ba2f6dc670 Mon Sep 17 00:00:00 2001 From: David Dickinson Date: Wed, 16 Jan 2019 10:49:58 +0000 Subject: [PATCH 3/9] Rename n1 to len --- include/bout/arraynd.hxx | 52 ++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/include/bout/arraynd.hxx b/include/bout/arraynd.hxx index 038a60b4da..9e9dc582be 100644 --- a/include/bout/arraynd.hxx +++ b/include/bout/arraynd.hxx @@ -15,25 +15,25 @@ public: constexpr static int ndims = ndim; - ArrayND() : n1(0){}; + ArrayND() : len(0){}; template - ArrayND(size_type n1, allSizes... sizes) : n1(n1) { + ArrayND(size_type len, allSizes... sizes) : len(len) { static_assert(sizeof...(sizes) == ndim - 1, "Incorrect number of dimension sizes passed as arguments to ArrayND."); - data = Array(n1); + data = Array(len); for (auto& i : data) { i = slice_type(sizes...); } } - ArrayND(const ArrayND& other) : n1(other.n1), data(other.data) { + ArrayND(const ArrayND& other) : len(other.len), data(other.data) { // Prevent copy on write for ArrayND data.ensureUnique(); } ArrayND& operator=(const ArrayND& other) { - n1 = other.n1; + len = other.len; data = other.data; // Prevent copy on write for ArrayND data.ensureUnique(); @@ -41,28 +41,28 @@ public: } template inline T& operator()(size_type i1, allSizes... sizes) { - ASSERT2(0 <= i1 && i1 < n1); + ASSERT2(0 <= i1 && i1 < len); return data[i1](sizes...); } inline slice_type& operator[](size_type i1) { - ASSERT2(0 <= i1 && i1 < n1); + ASSERT2(0 <= i1 && i1 < len); return data[i1]; } inline slice_type& operator()(size_type i1) { - ASSERT2(0 <= i1 && i1 < n1); + ASSERT2(0 <= i1 && i1 < len); return data[i1]; } template inline const T& operator()(size_type i1, allSizes... sizes) const { - ASSERT2(0 <= i1 && i1 < n1); + ASSERT2(0 <= i1 && i1 < len); return data[i1](sizes...); } inline const slice_type& operator[](size_type i1) const { - ASSERT2(0 <= i1 && i1 < n1); + ASSERT2(0 <= i1 && i1 < len); return data[i1]; } inline const slice_type& operator()(size_type i1) const { - ASSERT2(0 <= i1 && i1 < n1); + ASSERT2(0 <= i1 && i1 < len); return data[i1]; } @@ -79,10 +79,10 @@ public: const slice_type* end() const { return std::end(data); }; auto shape() const -> shape_type { - return std::tuple_cat(std::make_tuple(n1), data[0].shape()); + return std::tuple_cat(std::make_tuple(len), data[0].shape()); } - size_type size() const { return n1 * data[0].size(); } + size_type size() const { return len * data[0].size(); } bool empty() const { return size() == 0; } @@ -94,7 +94,7 @@ public: void ensureUnique() { data.ensureUnique(); } private: - size_type n1; + size_type len; Array data; }; @@ -108,23 +108,23 @@ public: constexpr static int ndims = ndim; - ArrayND() : n1(0){}; - ArrayND(size_type n1) : n1(n1) { data = Array(n1); } + ArrayND() : len(0){}; + ArrayND(size_type len) : len(len) { data = Array(len); } // Should only end up calling this if we pass too many dimension sizes template - ArrayND(size_type n1, allSizes... sizes) : n1(n1) { + ArrayND(size_type len, allSizes... sizes) : len(len) { static_assert( sizeof...(sizes) == ndim - 1, "Incorrect number of dimension sizes passed as arguments to ArrayND."); } - ArrayND(const ArrayND& other) : n1(other.n1), data(other.data) { + ArrayND(const ArrayND& other) : len(other.len), data(other.data) { // Prevent copy on write for ArrayND data.ensureUnique(); } ArrayND& operator=(const ArrayND& other) { - n1 = other.n1; + len = other.len; data = other.data; // Prevent copy on write for ArrayND data.ensureUnique(); @@ -132,19 +132,19 @@ public: } inline T& operator()(size_type i1) { - ASSERT2(0 <= i1 && i1 < n1); + ASSERT2(0 <= i1 && i1 < len); return data[i1]; } inline T& operator[](size_type i1) { - ASSERT2(0 <= i1 && i1 < n1); + ASSERT2(0 <= i1 && i1 < len); return data[i1]; } inline const T& operator()(size_type i1) const { - ASSERT2(0 <= i1 && i1 < n1); + ASSERT2(0 <= i1 && i1 < len); return data[i1]; } inline const T& operator[](size_type i1) const { - ASSERT2(0 <= i1 && i1 < n1); + ASSERT2(0 <= i1 && i1 < len); return data[i1]; } @@ -160,9 +160,9 @@ public: T* end() { return std::end(data); }; const T* end() const { return std::end(data); }; - std::tuple shape() const { return std::make_tuple(n1); } + std::tuple shape() const { return std::make_tuple(len); } - size_type size() const { return n1; } + size_type size() const { return len; } bool empty() const { return size() == 0; } @@ -174,7 +174,7 @@ public: void ensureUnique() { data.ensureUnique(); } private: - size_type n1; + size_type len; Array data; }; From a941176e35f749fcff6d383d0e9d1ef70022d21b Mon Sep 17 00:00:00 2001 From: David Dickinson Date: Wed, 16 Jan 2019 10:50:25 +0000 Subject: [PATCH 4/9] Expose internal data item. Could be dangerous but mirrors standard containers --- include/bout/arraynd.hxx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/bout/arraynd.hxx b/include/bout/arraynd.hxx index 9e9dc582be..1c7e0d5557 100644 --- a/include/bout/arraynd.hxx +++ b/include/bout/arraynd.hxx @@ -93,9 +93,10 @@ public: */ void ensureUnique() { data.ensureUnique(); } + Array data; + private: size_type len; - Array data; }; template @@ -173,9 +174,10 @@ public: */ void ensureUnique() { data.ensureUnique(); } + Array data; + private: size_type len; - Array data; }; template From dca817aa09eecfbe784c24ec90af09ef23f94065 Mon Sep 17 00:00:00 2001 From: David Dickinson Date: Wed, 16 Jan 2019 12:27:59 +0000 Subject: [PATCH 5/9] Add flatten/pack methods to arrayND for converting between ND and 1D arrays --- include/bout/arraynd.hxx | 92 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 2 deletions(-) diff --git a/include/bout/arraynd.hxx b/include/bout/arraynd.hxx index 1c7e0d5557..a8804d068b 100644 --- a/include/bout/arraynd.hxx +++ b/include/bout/arraynd.hxx @@ -78,7 +78,7 @@ public: slice_type* end() { return std::end(data); }; const slice_type* end() const { return std::end(data); }; - auto shape() const -> shape_type { + constexpr auto shape() const -> shape_type { return std::tuple_cat(std::make_tuple(len), data[0].shape()); } @@ -86,6 +86,55 @@ public: bool empty() const { return size() == 0; } + void pack(ArrayND& flat) { + ASSERT1(size() == flat.size()); + size_type currentPosition = 0; + currentPosition = setFromFlatArray(flat, currentPosition); + ASSERT1(currentPosition == size()); + }; + + void pack(Array& flat) { + ASSERT1(size() == flat.size()); + size_type currentPosition = 0; + currentPosition = setFromFlatArray(flat, currentPosition); + ASSERT1(currentPosition == size()); + }; + + template + void pack(ArrayND& out) { + ASSERT1(size() == out.size()); + size_type currentPosition = 0; + currentPosition = out.setFromFlatArray(data, currentPosition); + ASSERT1(currentPosition == size()); + }; + + size_type setFromFlatArray(Array& flat, size_type currentPos = 0) { + ArrayND tmp(flat.size()); + tmp.data = flat; + return this->setFromFlatArray(tmp, currentPos); + } + + size_type setFromFlatArray(ArrayND& flat, size_type currentPos = 0) { + ASSERT1(flat.size() >= size()); + + for (auto& i : data) { + currentPos = i.setFromFlatArray(flat, currentPos); + } + + return currentPos; + }; + + ArrayND flatten() { + ArrayND result(size()); + size_type start = 0; + for (auto& i : data) { + result.setSpan(start, i.size(), i.flatten()); + start += i.size(); + } + + return result; + }; + /*! * Ensures that this ArrayND does not share data with another * This should be called before performing any write operations @@ -161,7 +210,7 @@ public: T* end() { return std::end(data); }; const T* end() const { return std::end(data); }; - std::tuple shape() const { return std::make_tuple(len); } + constexpr std::tuple shape() const { return std::make_tuple(len); } size_type size() const { return len; } @@ -174,6 +223,45 @@ public: */ void ensureUnique() { data.ensureUnique(); } + size_type setFromFlatArray(Array& flat, size_type currentPos = 0) { + ArrayND tmp(flat.size()); + tmp.data = flat; + return this->setFromFlatArray(tmp, currentPos); + } + + size_type setFromFlatArray(ArrayND& flat, size_type currentPos = 0) { + ASSERT1(flat.size() >= size()); + setSpan(0, size(), flat.getSpan(currentPos, size())); + return currentPos + size(); + }; + + ArrayND flatten() { return *this; } + + void setSpan(size_type start, size_type count, ArrayND source) { + this->setSpan(start, count, source.data); + } + + void setSpan(size_type start, size_type count, Array source) { + ASSERT1(start >= 0); + ASSERT1(start < len); + ASSERT1(start + count - 1 <= len); + ASSERT1(source.size() >= count); + for (size_type i = 0; i < count; i++) { + data[start + i] = source[i]; + } + }; + + Array getSpan(size_type start, size_type count) { + ASSERT1(start >= 0); + ASSERT1(start < len); + ASSERT1(start + count - 1 <= len); + Array result{count}; + for (size_type i = 0; i < count; i++) { + result[i] = data[start + i]; + } + return result; + }; + Array data; private: From 9e693895c1484d16fc757e62e78f5db7bb03a282 Mon Sep 17 00:00:00 2001 From: David Dickinson Date: Wed, 16 Jan 2019 15:34:20 +0000 Subject: [PATCH 6/9] Make ArrayND use COW by default. Adds/expands on mechanisms to check and enforce unique nature --- include/bout/arraynd.hxx | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/include/bout/arraynd.hxx b/include/bout/arraynd.hxx index a8804d068b..47fd62c157 100644 --- a/include/bout/arraynd.hxx +++ b/include/bout/arraynd.hxx @@ -28,17 +28,14 @@ public: } } ArrayND(const ArrayND& other) : len(other.len), data(other.data) { - // Prevent copy on write for ArrayND - data.ensureUnique(); } ArrayND& operator=(const ArrayND& other) { len = other.len; data = other.data; - // Prevent copy on write for ArrayND - data.ensureUnique(); return *this; } + template inline T& operator()(size_type i1, allSizes... sizes) { ASSERT2(0 <= i1 && i1 < len); @@ -52,6 +49,7 @@ public: ASSERT2(0 <= i1 && i1 < len); return data[i1]; } + template inline const T& operator()(size_type i1, allSizes... sizes) const { ASSERT2(0 <= i1 && i1 < len); @@ -86,6 +84,23 @@ public: bool empty() const { return size() == 0; } + bool unique() const { + bool res = data.unique(); + for (const auto& i : data) { + res = res && i.unique(); + if (!res) + return res; + }; + return res; + } + + void ensureUnique() { + data.ensureUnique(); + for (auto& i : data) { + i.ensureUnique(); + }; + } + void pack(ArrayND& flat) { ASSERT1(size() == flat.size()); size_type currentPosition = 0; @@ -135,13 +150,6 @@ public: return result; }; - /*! - * Ensures that this ArrayND does not share data with another - * This should be called before performing any write operations - * on the data. - */ - void ensureUnique() { data.ensureUnique(); } - Array data; private: @@ -169,15 +177,11 @@ public: } ArrayND(const ArrayND& other) : len(other.len), data(other.data) { - // Prevent copy on write for ArrayND - data.ensureUnique(); } ArrayND& operator=(const ArrayND& other) { len = other.len; data = other.data; - // Prevent copy on write for ArrayND - data.ensureUnique(); return *this; } @@ -216,6 +220,8 @@ public: bool empty() const { return size() == 0; } + bool unique() const { return data.unique(); } + /*! * Ensures that this ArrayND does not share data with another * This should be called before performing any write operations From 7ceb4fd06af64086fd9535b7a7f89d8f4337ff8d Mon Sep 17 00:00:00 2001 From: David Dickinson Date: Wed, 16 Jan 2019 15:35:56 +0000 Subject: [PATCH 7/9] First few basic unit tests for ArrayND --- tests/unit/include/bout/test_arraynd.cxx | 47 ++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 tests/unit/include/bout/test_arraynd.cxx diff --git a/tests/unit/include/bout/test_arraynd.cxx b/tests/unit/include/bout/test_arraynd.cxx new file mode 100644 index 0000000000..37b8248015 --- /dev/null +++ b/tests/unit/include/bout/test_arraynd.cxx @@ -0,0 +1,47 @@ +#include "gtest/gtest.h" + +#include "bout/arraynd.hxx" + +class ArrayNDTest : public ::testing::Test { +public: + ArrayNDTest() {} + ~ArrayNDTest() {} +}; + +TEST_F(ArrayNDTest, ArrayNDSize1D) { + ArrayND a(5); + + ASSERT_FALSE(a.empty()); + EXPECT_EQ(a.size(), 5); + EXPECT_EQ(std::tuple_size::value, 1); + EXPECT_EQ(std::get<0>(a.shape()), 5); + EXPECT_TRUE(a.unique()); +} + +TEST_F(ArrayNDTest, ArrayNDSize2D) { + ArrayND a(5, 10); + + ASSERT_FALSE(a.empty()); + EXPECT_EQ(a.size(), 5 * 10); + EXPECT_EQ(std::tuple_size::value, 2); + EXPECT_EQ(std::get<0>(a.shape()), 5); + EXPECT_EQ(std::get<1>(a.shape()), 10); + EXPECT_TRUE(a.unique()); +} + +TEST_F(ArrayNDTest, ArrayNDUnique2D) { + ArrayND a(5, 10); + EXPECT_TRUE(a.unique()); + auto b = a[0]; + EXPECT_FALSE(a.unique()); + EXPECT_FALSE(b.unique()); + b.ensureUnique(); + EXPECT_TRUE(b.unique()); + EXPECT_TRUE(a.unique()); + auto c = a.data[0]; + EXPECT_FALSE(c.unique()); + EXPECT_FALSE(a.unique()); + a.ensureUnique(); + EXPECT_TRUE(c.unique()); + EXPECT_TRUE(a.unique()); +} From 4f955a0baf591f5a27e168fc6a3c96ede5a06758 Mon Sep 17 00:00:00 2001 From: David Dickinson Date: Wed, 16 Jan 2019 15:44:44 +0000 Subject: [PATCH 8/9] Add some in-code documentation relating to assumptions present in shape/size methods --- include/bout/arraynd.hxx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/bout/arraynd.hxx b/include/bout/arraynd.hxx index 47fd62c157..4eb235bf3a 100644 --- a/include/bout/arraynd.hxx +++ b/include/bout/arraynd.hxx @@ -76,10 +76,14 @@ public: slice_type* end() { return std::end(data); }; const slice_type* end() const { return std::end(data); }; + // Note we assume a non-jagged array, i.e. we assume + // data[0].shape() == data[j].shape() for 0 <= j < len constexpr auto shape() const -> shape_type { return std::tuple_cat(std::make_tuple(len), data[0].shape()); } + // Note we assume a non-jagged array, i.e. we assume + // data[0].size() == data[j].size() for 0 <= j < len size_type size() const { return len * data[0].size(); } bool empty() const { return size() == 0; } From 0079c1b115d622e0f66c50ce623d9f5b52b7167d Mon Sep 17 00:00:00 2001 From: David Dickinson Date: Wed, 16 Jan 2019 15:58:45 +0000 Subject: [PATCH 9/9] Remove deprecated pack routine --- include/bout/arraynd.hxx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/include/bout/arraynd.hxx b/include/bout/arraynd.hxx index 4eb235bf3a..87a965d11c 100644 --- a/include/bout/arraynd.hxx +++ b/include/bout/arraynd.hxx @@ -92,6 +92,7 @@ public: bool res = data.unique(); for (const auto& i : data) { res = res && i.unique(); + // Try to return early if we can. if (!res) return res; }; @@ -119,14 +120,6 @@ public: ASSERT1(currentPosition == size()); }; - template - void pack(ArrayND& out) { - ASSERT1(size() == out.size()); - size_type currentPosition = 0; - currentPosition = out.setFromFlatArray(data, currentPosition); - ASSERT1(currentPosition == size()); - }; - size_type setFromFlatArray(Array& flat, size_type currentPos = 0) { ArrayND tmp(flat.size()); tmp.data = flat;