diff --git a/.github/actions/setup_linux/action.yml b/.github/actions/setup_linux/action.yml index 7f1aa6dd2..0ded6749c 100644 --- a/.github/actions/setup_linux/action.yml +++ b/.github/actions/setup_linux/action.yml @@ -89,16 +89,17 @@ runs: sudo ln -fs "$(which clang-tidy-22)" "/usr/local/bin/clang-tidy" echo "::endgroup::" - - name: Install and configure gcc-14 + - name: Install and configure gcc-16 shell: bash run: | - echo "::group::Install and configure gcc-14" + echo "::group::Install and configure gcc-16" + sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test sudo apt-get -qqy update - sudo apt-get -qy install gcc-14 g++-14 - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 100 - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 100 - sudo update-alternatives --install /usr/bin/cc cc /usr/bin/gcc-14 100 - sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/g++-14 100 + sudo apt-get -qy install gcc-16 g++-16 + sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-16 100 + sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-16 100 + sudo update-alternatives --install /usr/bin/cc cc /usr/bin/gcc-16 100 + sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/g++-16 100 echo "::endgroup::" - name: install qt diff --git a/cpp/modmesh/buffer/SimpleArray.hpp b/cpp/modmesh/buffer/SimpleArray.hpp index a282e9bfc..89f7d8f0f 100644 --- a/cpp/modmesh/buffer/SimpleArray.hpp +++ b/cpp/modmesh/buffer/SimpleArray.hpp @@ -38,11 +38,14 @@ #include #include +#include #include #include #include #include +#include #include +#include #include #ifdef _MSC_VER @@ -1946,6 +1949,57 @@ class SimpleArray template value_type * vptr(Args... args) { return m_body + buffer_offset(m_stride, args...); } + std::span as_span() + { + if (!is_c_contiguous()) + { + throw std::runtime_error("SimpleArray::as_span: array is not C-contiguous"); + } + return std::span(data(), size()); + } + std::span as_span() const + { + if (!is_c_contiguous()) + { + throw std::runtime_error("SimpleArray::as_span: array is not C-contiguous"); + } + return std::span(data(), size()); + } + + template + std::mdspan> as_mdspan() + { + if (ndim() != N) + { + throw std::out_of_range( + std::format("SimpleArray::as_mdspan: rank {} does not match ndim() {}", N, ndim())); + } + if (!is_c_contiguous()) + { + throw std::runtime_error("SimpleArray::as_mdspan: array is not C-contiguous"); + } + std::array exts; + for (size_t i = 0; i < N; ++i) { exts[i] = shape(i); } + return std::mdspan>(data(), exts); + } + + template + std::mdspan> as_mdspan() const + { + if (ndim() != N) + { + throw std::out_of_range( + std::format("SimpleArray::as_mdspan: rank {} does not match ndim() {}", N, ndim())); + } + if (!is_c_contiguous()) + { + throw std::runtime_error("SimpleArray::as_mdspan: array is not C-contiguous"); + } + std::array exts; + for (size_t i = 0; i < N; ++i) { exts[i] = shape(i); } + return std::mdspan>(data(), exts); + } + /* Backdoor */ value_type const & data(size_t it) const { return data()[it]; } value_type & data(size_t it) { return data()[it]; } @@ -2423,4 +2477,4 @@ class SimpleArrayPlex } /* end namespace modmesh */ -/* vim: set et ts=4 sw=4: */ +// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: diff --git a/gtests/CMakeLists.txt b/gtests/CMakeLists.txt index 4fb7d3696..2ade2370a 100644 --- a/gtests/CMakeLists.txt +++ b/gtests/CMakeLists.txt @@ -29,6 +29,7 @@ add_executable( test_nopython_transform.cpp test_nopython_rtree.cpp test_nopython_formatter.cpp + test_nopython_mdspan.cpp ${MODMESH_TOGGLE_SOURCES} ${MODMESH_BUFFER_SOURCES} ${MODMESH_SERIALIZATION_SOURCES} diff --git a/gtests/test_nopython_mdspan.cpp b/gtests/test_nopython_mdspan.cpp new file mode 100644 index 000000000..bf1b09c07 --- /dev/null +++ b/gtests/test_nopython_mdspan.cpp @@ -0,0 +1,424 @@ +/* + * Copyright (c) 2026, Yung-Yu Chen + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include + +#ifdef Py_PYTHON_H +#error "Python.h should not be included." +#endif + +TEST(SimpleArray, mdspan_1d) +{ + namespace mm = modmesh; + + mm::SimpleArray arr(mm::small_vector{6}); + for (size_t i = 0; i < 6; ++i) { arr(i) = static_cast(i); } + + auto ms = arr.as_mdspan<1>(); + EXPECT_EQ(ms.extent(0), 6u); + for (size_t i = 0; i < 6; ++i) { EXPECT_EQ(ms[i], arr(i)); } + + auto sp = arr.as_span(); + EXPECT_EQ(sp.size(), 6u); + for (size_t i = 0; i < 6; ++i) { EXPECT_EQ(sp[i], arr(i)); } + + // Write through mdspan is visible via span and the underlying SimpleArray. + ms[3] = 99.0; + EXPECT_EQ(arr(3), 99.0); + EXPECT_EQ(sp[3], 99.0); +} + +TEST(SimpleArray, mdspan_1d_const) +{ + namespace mm = modmesh; + + mm::SimpleArray arr(mm::small_vector{6}, 5.0); + const auto & carr = arr; + + auto ms = carr.as_mdspan<1>(); + static_assert(std::is_same_v); + EXPECT_EQ(ms.extent(0), 6u); + for (size_t i = 0; i < 6; ++i) { EXPECT_EQ(ms[i], 5.0); } + + auto sp = carr.as_span(); + static_assert(std::is_same_v); + EXPECT_EQ(sp.size(), 6u); + for (size_t i = 0; i < 6; ++i) { EXPECT_EQ(sp[i], 5.0); } +} + +TEST(SimpleArray, mdspan_1d_ghost) +{ + namespace mm = modmesh; + + // 1D array: 5 total elements, 1 ghost at the front. + mm::SimpleArray arr(mm::small_vector{5}); + arr.set_nghost(1); + for (size_t i = 0; i < 5; ++i) { arr.data(i) = static_cast(i); } + + // Both views span all 5 elements via data(). + auto ms = arr.as_mdspan<1>(); + EXPECT_EQ(ms.extent(0), 5u); + for (size_t i = 0; i < 5; ++i) { EXPECT_EQ(ms[i], arr.data(i)); } + + auto sp = arr.as_span(); + EXPECT_EQ(sp.size(), 5u); + for (size_t i = 0; i < 5; ++i) { EXPECT_EQ(sp[i], arr.data(i)); } +} + +TEST(SimpleArray, mdspan_2d) +{ + namespace mm = modmesh; + + mm::SimpleArray arr(mm::small_vector{3, 4}); + for (size_t i = 0; i < 3; ++i) + { + for (size_t j = 0; j < 4; ++j) + { + arr(i, j) = static_cast(i * 4 + j); + } + } + + auto ms = arr.as_mdspan<2>(); + EXPECT_EQ(ms.extent(0), 3u); + EXPECT_EQ(ms.extent(1), 4u); + for (size_t i = 0; i < 3; ++i) + { + for (size_t j = 0; j < 4; ++j) + { + EXPECT_EQ((ms[i, j]), arr(i, j)); + } + } + + // Linear view over the C-contiguous buffer; sp[i*4+j] == arr(i, j). + auto sp = arr.as_span(); + EXPECT_EQ(sp.size(), 12u); + for (size_t i = 0; i < 3; ++i) + { + for (size_t j = 0; j < 4; ++j) + { + EXPECT_EQ(sp[i * 4 + j], arr(i, j)); + } + } + + // Write through mdspan is visible via span and the underlying SimpleArray. + ms[1, 2] = 99.0; + EXPECT_EQ(arr(1, 2), 99.0); + EXPECT_EQ(sp[1 * 4 + 2], 99.0); +} + +TEST(SimpleArray, mdspan_2d_const) +{ + namespace mm = modmesh; + + mm::SimpleArray arr(mm::small_vector{2, 3}, 7.0); + const auto & carr = arr; + + auto ms = carr.as_mdspan<2>(); + static_assert(std::is_same_v); + for (size_t i = 0; i < 2; ++i) + { + for (size_t j = 0; j < 3; ++j) + { + EXPECT_EQ((ms[i, j]), 7.0); + } + } + + auto sp = carr.as_span(); + static_assert(std::is_same_v); + EXPECT_EQ(sp.size(), 6u); + for (size_t i = 0; i < 6; ++i) { EXPECT_EQ(sp[i], 7.0); } +} + +TEST(SimpleArray, mdspan_2d_ghost) +{ + namespace mm = modmesh; + + // shape {5, 4}: 5 rows (1 ghost + 4 body), 4 columns. + mm::SimpleArray arr(mm::small_vector{5, 4}); + arr.set_nghost(1); + for (size_t idx = 0; idx < arr.size(); ++idx) { arr.data(idx) = static_cast(idx); } + + auto ms = arr.as_mdspan<2>(); + EXPECT_EQ(ms.extent(0), 5u); + EXPECT_EQ(ms.extent(1), 4u); + + // ms origin is data(), so ms[i, j] == data()[i * 4 + j]. + for (size_t i = 0; i < 5; ++i) + { + for (size_t j = 0; j < 4; ++j) + { + EXPECT_EQ((ms[i, j]), arr.data(i * 4 + j)); + } + } + + // Body rows start at ms row 1 (== nghost); ms[i+1, j] == arr(i, j). + for (size_t i = 0; i < 4; ++i) + { + for (size_t j = 0; j < 4; ++j) + { + EXPECT_EQ((ms[i + 1, j]), arr(i, j)); + } + } + + auto sp = arr.as_span(); + EXPECT_EQ(sp.size(), 20u); + for (size_t k = 0; k < sp.size(); ++k) { EXPECT_EQ(sp[k], arr.data(k)); } +} + +TEST(SimpleArray, mdspan_3d) +{ + namespace mm = modmesh; + + mm::SimpleArray arr(mm::small_vector{2, 3, 4}); + for (size_t i = 0; i < 2; ++i) + { + for (size_t j = 0; j < 3; ++j) + { + for (size_t k = 0; k < 4; ++k) + { + arr(i, j, k) = static_cast((i * 3 + j) * 4 + k); + } + } + } + + auto ms = arr.as_mdspan<3>(); + EXPECT_EQ(ms.extent(0), 2u); + EXPECT_EQ(ms.extent(1), 3u); + EXPECT_EQ(ms.extent(2), 4u); + for (size_t i = 0; i < 2; ++i) + { + for (size_t j = 0; j < 3; ++j) + { + for (size_t k = 0; k < 4; ++k) + { + EXPECT_EQ((ms[i, j, k]), arr(i, j, k)); + } + } + } + + auto sp = arr.as_span(); + EXPECT_EQ(sp.size(), 24u); + for (size_t i = 0; i < 2; ++i) + { + for (size_t j = 0; j < 3; ++j) + { + for (size_t k = 0; k < 4; ++k) + { + EXPECT_EQ(sp[(i * 3 + j) * 4 + k], arr(i, j, k)); + } + } + } + + ms[1, 2, 3] = 99.0; + EXPECT_EQ(arr(1, 2, 3), 99.0); + EXPECT_EQ(sp[(1 * 3 + 2) * 4 + 3], 99.0); +} + +TEST(SimpleArray, mdspan_3d_ghost) +{ + namespace mm = modmesh; + + // shape {4, 3, 2}: 4 slices (2 ghost + 2 body), 3 rows, 2 columns. + mm::SimpleArray arr(mm::small_vector{4, 3, 2}); + arr.set_nghost(2); + for (size_t idx = 0; idx < arr.size(); ++idx) { arr.data(idx) = static_cast(idx); } + + auto ms = arr.as_mdspan<3>(); + EXPECT_EQ(ms.extent(0), 4u); + EXPECT_EQ(ms.extent(1), 3u); + EXPECT_EQ(ms.extent(2), 2u); + + // ms origin is data(), so ms[i, j, k] == data()[(i * 3 + j) * 2 + k]. + for (size_t i = 0; i < 4; ++i) + { + for (size_t j = 0; j < 3; ++j) + { + for (size_t k = 0; k < 2; ++k) + { + EXPECT_EQ((ms[i, j, k]), arr.data((i * 3 + j) * 2 + k)); + } + } + } + + // Body slices start at ms index 2 (== nghost); ms[i+2, j, k] == arr(i, j, k). + for (size_t i = 0; i < 2; ++i) + { + for (size_t j = 0; j < 3; ++j) + { + for (size_t k = 0; k < 2; ++k) + { + EXPECT_EQ((ms[i + 2, j, k]), arr(i, j, k)); + } + } + } + + auto sp = arr.as_span(); + EXPECT_EQ(sp.size(), 24u); + for (size_t k = 0; k < sp.size(); ++k) { EXPECT_EQ(sp[k], arr.data(k)); } +} + +TEST(SimpleArray, mdspan_4d) +{ + namespace mm = modmesh; + + mm::SimpleArray arr(mm::small_vector{2, 3, 4, 5}); + for (size_t i = 0; i < 2; ++i) + { + for (size_t j = 0; j < 3; ++j) + { + for (size_t k = 0; k < 4; ++k) + { + for (size_t l = 0; l < 5; ++l) + { + arr(i, j, k, l) = static_cast(((i * 3 + j) * 4 + k) * 5 + l); + } + } + } + } + + auto ms = arr.as_mdspan<4>(); + EXPECT_EQ(ms.extent(0), 2u); + EXPECT_EQ(ms.extent(1), 3u); + EXPECT_EQ(ms.extent(2), 4u); + EXPECT_EQ(ms.extent(3), 5u); + for (size_t i = 0; i < 2; ++i) + { + for (size_t j = 0; j < 3; ++j) + { + for (size_t k = 0; k < 4; ++k) + { + for (size_t l = 0; l < 5; ++l) + { + EXPECT_EQ((ms[i, j, k, l]), arr(i, j, k, l)); + } + } + } + } + + auto sp = arr.as_span(); + EXPECT_EQ(sp.size(), 120u); + for (size_t i = 0; i < 2; ++i) + { + for (size_t j = 0; j < 3; ++j) + { + for (size_t k = 0; k < 4; ++k) + { + for (size_t l = 0; l < 5; ++l) + { + EXPECT_EQ(sp[((i * 3 + j) * 4 + k) * 5 + l], arr(i, j, k, l)); + } + } + } + } + + ms[1, 2, 3, 4] = 99.0; + EXPECT_EQ(arr(1, 2, 3, 4), 99.0); + EXPECT_EQ(sp[((1 * 3 + 2) * 4 + 3) * 5 + 4], 99.0); +} + +TEST(SimpleArray, mdspan_4d_ghost) +{ + namespace mm = modmesh; + + // shape {4, 3, 2, 2}: 4 slices (1 ghost + 3 body), 3 rows, 2 columns, 2 depth. + mm::SimpleArray arr(mm::small_vector{4, 3, 2, 2}); + arr.set_nghost(1); + for (size_t idx = 0; idx < arr.size(); ++idx) { arr.data(idx) = static_cast(idx); } + + auto ms = arr.as_mdspan<4>(); + EXPECT_EQ(ms.extent(0), 4u); + EXPECT_EQ(ms.extent(1), 3u); + EXPECT_EQ(ms.extent(2), 2u); + EXPECT_EQ(ms.extent(3), 2u); + + // ms origin is data(), so ms[i, j, k, l] == data()[((i * 3 + j) * 2 + k) * 2 + l]. + for (size_t i = 0; i < 4; ++i) + { + for (size_t j = 0; j < 3; ++j) + { + for (size_t k = 0; k < 2; ++k) + { + for (size_t l = 0; l < 2; ++l) + { + EXPECT_EQ((ms[i, j, k, l]), arr.data(((i * 3 + j) * 2 + k) * 2 + l)); + } + } + } + } + + // Body slices start at ms index 1 (== nghost); ms[i+1, j, k, l] == arr(i, j, k, l). + for (size_t i = 0; i < 3; ++i) + { + for (size_t j = 0; j < 3; ++j) + { + for (size_t k = 0; k < 2; ++k) + { + for (size_t l = 0; l < 2; ++l) + { + EXPECT_EQ((ms[i + 1, j, k, l]), arr(i, j, k, l)); + } + } + } + } + + auto sp = arr.as_span(); + EXPECT_EQ(sp.size(), 48u); + for (size_t k = 0; k < sp.size(); ++k) { EXPECT_EQ(sp[k], arr.data(k)); } +} + +TEST(SimpleArray, mdspan_rank_mismatch) +{ + namespace mm = modmesh; + + mm::SimpleArray arr(mm::small_vector{3, 4}); + EXPECT_THROW(arr.as_mdspan<3>(), std::out_of_range); +} + +TEST(SimpleArray, mdspan_non_contiguous) +{ + namespace mm = modmesh; + + // Build a 3x4 view whose stride differs from the row-major layout, so the + // array is neither C- nor F-contiguous over the underlying buffer. + mm::small_vector shape{3, 4}; + mm::small_vector stride{8, 1}; + auto buffer = mm::ConcreteBuffer::construct(3 * 8 * sizeof(double)); + mm::SimpleArray arr(shape, stride, buffer); + EXPECT_FALSE(arr.is_c_contiguous()); + + EXPECT_THROW(arr.as_span(), std::runtime_error); + EXPECT_THROW(arr.as_mdspan<2>(), std::runtime_error); + + const auto & carr = arr; + EXPECT_THROW(carr.as_span(), std::runtime_error); + EXPECT_THROW(carr.as_mdspan<2>(), std::runtime_error); +} + +// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: