From 89fc13c87385edc9e4f74371306a21d56133cba8 Mon Sep 17 00:00:00 2001 From: "KOKO\\Mimi" Date: Sun, 16 Aug 2026 07:49:57 +0900 Subject: [PATCH] feat(cpp-object-oriented-modular-refactoring): step 10 - dense-blas-adapter --- src/fesa/CMakeLists.txt | 3 ++ src/fesa/math/dense_blas_internal.cpp | 34 +++++++++++++++ src/fesa/math/dense_blas_internal.h | 26 +++++++++++ src/fesa/math/matrix.cpp | 45 +++++++++----------- src/fesa/math/vector.cpp | 40 +++++++---------- tests/CMakeLists.txt | 8 ++++ tests/unit/math/dense_blas_internal_test.cpp | 39 +++++++++++++++++ 7 files changed, 146 insertions(+), 49 deletions(-) create mode 100644 src/fesa/math/dense_blas_internal.cpp create mode 100644 src/fesa/math/dense_blas_internal.h create mode 100644 tests/unit/math/dense_blas_internal_test.cpp diff --git a/src/fesa/CMakeLists.txt b/src/fesa/CMakeLists.txt index 1dc761f..258f080 100644 --- a/src/fesa/CMakeLists.txt +++ b/src/fesa/CMakeLists.txt @@ -18,6 +18,7 @@ add_library( io/abaqus/domain_mapper.cpp io/abaqus/input_reader.cpp io/hdf5/hdf5_results_writer.cpp + math/dense_blas_internal.cpp math/matrix.cpp math/sparse_matrix.cpp math/vector.cpp @@ -31,6 +32,8 @@ target_include_directories( fesa_solver PUBLIC "${PROJECT_SOURCE_DIR}/include" + PRIVATE + "${PROJECT_SOURCE_DIR}/src/fesa" ) target_link_libraries( diff --git a/src/fesa/math/dense_blas_internal.cpp b/src/fesa/math/dense_blas_internal.cpp new file mode 100644 index 0000000..e19948c --- /dev/null +++ b/src/fesa/math/dense_blas_internal.cpp @@ -0,0 +1,34 @@ +#include "math/dense_blas_internal.h" + +#include +#include + +namespace fesa::dense_blas_internal { + +Result ToMklSize(const std::size_t size) { + if (size > static_cast((std::numeric_limits::max)())) { + return Result::Failure(Status::Failure( + {{Severity::kError, + "dense-blas-size-overflow", + {}, + "", + "", + "Dense operation size exceeds the MKL integer range."}})); + } + return Result::Success(static_cast(size)); +} + +void CopyValues(const double* source, const std::size_t size, + double* destination) { + if (size == 0U) { + return; + } + + const auto mkl_size = ToMklSize(size); + if (!mkl_size.HasValue()) { + throw std::length_error{"Dense copy size exceeds the MKL integer range."}; + } + cblas_dcopy(mkl_size.Value(), source, 1, destination, 1); +} + +} // namespace fesa::dense_blas_internal diff --git a/src/fesa/math/dense_blas_internal.h b/src/fesa/math/dense_blas_internal.h new file mode 100644 index 0000000..056a20a --- /dev/null +++ b/src/fesa/math/dense_blas_internal.h @@ -0,0 +1,26 @@ +#ifndef FESA_SRC_FESA_MATH_DENSE_BLAS_INTERNAL_H_ +#define FESA_SRC_FESA_MATH_DENSE_BLAS_INTERNAL_H_ + +#include + +#include + +#include "fesa/core/status.h" + +namespace fesa::dense_blas_internal { + +/// @brief Converts a dense storage length to the private MKL integer type. +/// @return The converted length or an uncategorized overflow failure. +Result ToMklSize(std::size_t size); + +/// @brief Copies contiguous double values through the private BLAS backend. +/// @param source Readable storage for size values when size is nonzero. +/// @param size Number of contiguous values to copy. +/// @param destination Writable storage for size values when size is nonzero. +/// @throws std::length_error if size exceeds the MKL integer range. +/// @note A zero-length copy accepts null pointers and does not call BLAS. +void CopyValues(const double* source, std::size_t size, double* destination); + +} // namespace fesa::dense_blas_internal + +#endif // FESA_SRC_FESA_MATH_DENSE_BLAS_INTERNAL_H_ diff --git a/src/fesa/math/matrix.cpp b/src/fesa/math/matrix.cpp index 82b5849..fa9ce89 100644 --- a/src/fesa/math/matrix.cpp +++ b/src/fesa/math/matrix.cpp @@ -6,6 +6,8 @@ #include #include +#include "math/dense_blas_internal.h" + namespace fesa { namespace { @@ -21,25 +23,15 @@ std::size_t CheckedStorageSize(const std::size_t rows, return rows * columns; } -/// @brief Converts a dense matrix dimension to the private MKL integer -/// contract. -MKL_INT ToMklSize(const std::size_t size) { - if (size > static_cast((std::numeric_limits::max)())) { +/// @brief Preserves the Matrix exception contract around private size +/// conversion. +MKL_INT MklSizeOrThrow(const std::size_t size) { + const auto converted = dense_blas_internal::ToMklSize(size); + if (!converted.HasValue()) { throw std::length_error{ "Dense matrix dimension exceeds the MKL integer range."}; } - return static_cast(size); -} - -/// @brief Copies owned values without exposing the dense backend publicly. -void CopyValues(const std::vector& source, - std::vector& destination) { - if (source.empty()) { - return; - } - - cblas_dcopy(ToMklSize(source.size()), source.data(), 1, destination.data(), - 1); + return converted.Value(); } } // namespace @@ -54,7 +46,8 @@ Matrix::Matrix(const Matrix& other) : rows_(other.rows_), columns_(other.columns_), values_(other.values_.size()) { - CopyValues(other.values_, values_); + dense_blas_internal::CopyValues(other.values_.data(), other.values_.size(), + values_.data()); } Matrix::Matrix(Matrix&& other) noexcept @@ -69,7 +62,8 @@ Matrix::Matrix(Matrix&& other) noexcept Matrix& Matrix::operator=(const Matrix& other) { if (this != &other) { std::vector copied(other.values_.size()); - CopyValues(other.values_, copied); + dense_blas_internal::CopyValues(other.values_.data(), other.values_.size(), + copied.data()); rows_ = other.rows_; columns_ = other.columns_; values_.swap(copied); @@ -121,9 +115,9 @@ Vector Matrix::Multiply(const Vector& rhs) const { // The owned layout is row-major, so the leading dimension is the column // count for the adapter call and remains invisible to public consumers. - cblas_dgemv(CblasRowMajor, CblasNoTrans, ToMklSize(rows_), - ToMklSize(columns_), 1.0, values_.data(), ToMklSize(columns_), - rhs.Data(), 1, 0.0, result.Data(), 1); + cblas_dgemv(CblasRowMajor, CblasNoTrans, MklSizeOrThrow(rows_), + MklSizeOrThrow(columns_), 1.0, values_.data(), + MklSizeOrThrow(columns_), rhs.Data(), 1, 0.0, result.Data(), 1); return result; } @@ -138,10 +132,11 @@ Matrix Matrix::Multiply(const Matrix& rhs) const { return result; } - cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, ToMklSize(rows_), - ToMklSize(rhs.columns_), ToMklSize(columns_), 1.0, values_.data(), - ToMklSize(columns_), rhs.values_.data(), ToMklSize(rhs.columns_), - 0.0, result.values_.data(), ToMklSize(rhs.columns_)); + cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, MklSizeOrThrow(rows_), + MklSizeOrThrow(rhs.columns_), MklSizeOrThrow(columns_), 1.0, + values_.data(), MklSizeOrThrow(columns_), rhs.values_.data(), + MklSizeOrThrow(rhs.columns_), 0.0, result.values_.data(), + MklSizeOrThrow(rhs.columns_)); return result; } diff --git a/src/fesa/math/vector.cpp b/src/fesa/math/vector.cpp index a7e5f5f..feadd38 100644 --- a/src/fesa/math/vector.cpp +++ b/src/fesa/math/vector.cpp @@ -2,32 +2,22 @@ #include -#include #include #include +#include "math/dense_blas_internal.h" + namespace fesa { namespace { -/// @brief Converts a dense vector size to the private MKL integer contract. -MKL_INT ToMklSize(const std::size_t size) { - if (size > static_cast((std::numeric_limits::max)())) { +/// @brief Preserves the Vector exception contract around private size +/// conversion. +MKL_INT MklSizeOrThrow(const std::size_t size) { + const auto converted = dense_blas_internal::ToMklSize(size); + if (!converted.HasValue()) { throw std::length_error{"Dense vector size exceeds the MKL integer range."}; } - return static_cast(size); -} - -/// @brief Copies owned values without exposing the dense backend publicly. -void CopyValues(const std::vector& source, - std::vector& destination) { - if (source.empty()) { - return; - } - - // Keep the backend operation in this translation unit so public ownership - // remains independent of MKL headers and integer types. - cblas_dcopy(ToMklSize(source.size()), source.data(), 1, destination.data(), - 1); + return converted.Value(); } } // namespace @@ -36,7 +26,8 @@ Vector::Vector(const std::size_t size, const double value) : values_(size, value) {} Vector::Vector(const Vector& other) : values_(other.Size()) { - CopyValues(other.values_, values_); + dense_blas_internal::CopyValues(other.values_.data(), other.values_.size(), + values_.data()); } Vector::Vector(Vector&& other) noexcept : values_(std::move(other.values_)) { @@ -46,7 +37,8 @@ Vector::Vector(Vector&& other) noexcept : values_(std::move(other.values_)) { Vector& Vector::operator=(const Vector& other) { if (this != &other) { std::vector copied(other.Size()); - CopyValues(other.values_, copied); + dense_blas_internal::CopyValues(other.values_.data(), other.values_.size(), + copied.data()); values_.swap(copied); } return *this; @@ -83,7 +75,7 @@ double Vector::Dot(const Vector& rhs) const { return 0.0; } - return cblas_ddot(ToMklSize(Size()), Data(), 1, rhs.Data(), 1); + return cblas_ddot(MklSizeOrThrow(Size()), Data(), 1, rhs.Data(), 1); } double Vector::Norm() const { @@ -91,7 +83,7 @@ double Vector::Norm() const { return 0.0; } - return cblas_dnrm2(ToMklSize(Size()), Data(), 1); + return cblas_dnrm2(MklSizeOrThrow(Size()), Data(), 1); } void Vector::Scale(const double alpha) { @@ -99,7 +91,7 @@ void Vector::Scale(const double alpha) { return; } - cblas_dscal(ToMklSize(Size()), alpha, Data(), 1); + cblas_dscal(MklSizeOrThrow(Size()), alpha, Data(), 1); } void Vector::Axpy(const double alpha, const Vector& x) { @@ -110,7 +102,7 @@ void Vector::Axpy(const double alpha, const Vector& x) { return; } - cblas_daxpy(ToMklSize(Size()), alpha, x.Data(), 1, Data(), 1); + cblas_daxpy(MklSizeOrThrow(Size()), alpha, x.Data(), 1, Data(), 1); } } // namespace fesa diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index dfe32a4..866d78d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -15,6 +15,7 @@ add_executable( unit/elements/euler_beam_3d_test.cpp unit/elements/mitc4_shell_test.cpp unit/fem/dof_manager_test.cpp + unit/math/dense_blas_internal_test.cpp unit/math/matrix_test.cpp unit/math/sparse_matrix_test.cpp unit/math/vector3_test.cpp @@ -33,11 +34,18 @@ add_executable( unit/solvers/linear/mkl_pardiso_solver_test.cpp ) +target_include_directories( + fesa_unit_tests + PRIVATE + "${PROJECT_SOURCE_DIR}/src/fesa" +) + target_link_libraries( fesa_unit_tests PRIVATE fesa_solver Fesa::HDF5 + Fesa::MKL GTest::gtest_main ) diff --git a/tests/unit/math/dense_blas_internal_test.cpp b/tests/unit/math/dense_blas_internal_test.cpp new file mode 100644 index 0000000..d036800 --- /dev/null +++ b/tests/unit/math/dense_blas_internal_test.cpp @@ -0,0 +1,39 @@ +#include "math/dense_blas_internal.h" + +#include + +#include +#include +#include + +namespace fesa::dense_blas_internal { +namespace { + +TEST(DenseBlasInternal, CDup003ConvertsZeroNormalAndOverflowLengths) { + const auto zero = ToMklSize(0U); + ASSERT_TRUE(zero.HasValue()); + EXPECT_EQ(zero.Value(), 0); + + const auto normal = ToMklSize(3U); + ASSERT_TRUE(normal.HasValue()); + EXPECT_EQ(normal.Value(), 3); + + const std::size_t overflow = + static_cast((std::numeric_limits::max)()) + 1U; + const auto rejected = ToMklSize(overflow); + EXPECT_FALSE(rejected.HasValue()); + EXPECT_FALSE(rejected.GetStatus().IsOk()); +} + +TEST(DenseBlasInternal, CDup003CopiesZeroAndNonzeroContiguousValues) { + EXPECT_NO_THROW(CopyValues(nullptr, 0U, nullptr)); + + const std::array source{1.25, -2.5, 4.0}; + std::array destination{}; + CopyValues(source.data(), source.size(), destination.data()); + + EXPECT_EQ(destination, source); +} + +} // namespace +} // namespace fesa::dense_blas_internal