// Copyright (C) 2026 Kiyotsugu Arai // SPDX-License-Identifier: LGPL-3.0-or-later // // static_dims.hpp // // Compile-time dimension check helpers for LinAlg algorithms. // // Background // ---------- // BaseMatrix and BaseVector expose dimension traits: // // static constexpr std::ptrdiff_t static_rows = -1; // BaseMatrix // static constexpr std::ptrdiff_t static_cols = -1; // BaseMatrix // static constexpr std::ptrdiff_t static_size = -1; // BaseVector // // where -1 means "dynamic (only known at runtime)". Derived classes that carry // statically known dimensions (StaticMatrix, StaticVector) shadow // these with their template parameters, e.g.: // // StaticMatrix::static_rows == 3 // StaticMatrix::static_cols == 4 // StaticVector::static_size == 5 // // LinAlg algorithms templated on the derived matrix/vector type can use the // helpers in this header to assert dimension agreement at build time when both // operands carry static dimensions, while remaining a no-op for dynamic Matrix/ // Vector arguments. // // Usage // ----- // template // requires BaseMatrixLike && BaseVectorLike // Vector> lu_solve(const MA& A, const VB& b, ...) { // SANGI_STATIC_ASSERT_SQUARE(MA); // SANGI_STATIC_ASSERT_MATRIX_ROWS_EQ_VECTOR_SIZE(MA, VB); // // ... existing runtime body unchanged ... // } // // The macros expand to `if constexpr (...) static_assert(...);` blocks that // fire only when both operands have known static dimensions. #ifndef SANGI_LINALG_STATIC_DIMS_HPP #define SANGI_LINALG_STATIC_DIMS_HPP #include #include #include #include "../core/BaseMatrix.hpp" #include "../core/BaseVector.hpp" namespace sangi { // ============================================================================ // Concepts: identify types that derive from BaseMatrix / BaseVector // ============================================================================ namespace detail { template inline constexpr bool is_base_matrix_of_v = std::is_base_of_v, std::remove_cvref_t>; template inline constexpr bool is_base_vector_of_v = std::is_base_of_v, std::remove_cvref_t>; } // namespace detail // True if M derives from BaseMatrix for some T. template concept BaseMatrixLike = requires { typename std::remove_cvref_t::value_type; requires detail::is_base_matrix_of_v::value_type>; }; // True if V derives from BaseVector for some T. template concept BaseVectorLike = requires { typename std::remove_cvref_t::value_type; requires detail::is_base_vector_of_v::value_type>; }; // Element type extractor. template using element_t = typename std::remove_cvref_t::value_type; // ============================================================================ // Compile-time dimension predicates (all -1-aware: -1 = dynamic, never fails) // ============================================================================ // True iff both dimensions are statically known and equal, or at least one is dynamic. constexpr bool dims_compatible(std::ptrdiff_t l, std::ptrdiff_t r) { return l < 0 || r < 0 || l == r; } // True iff both dimensions are statically known. constexpr bool dims_both_static(std::ptrdiff_t l, std::ptrdiff_t r) { return l >= 0 && r >= 0; } } // namespace sangi // ============================================================================ // Convenience macros for use inside templated LinAlg function bodies // ============================================================================ // Static-assert that matrix type M is square (M::static_rows == M::static_cols). // No-op if either dimension is dynamic. #define SANGI_STATIC_ASSERT_SQUARE(MatType) \ do { \ using M_ = std::remove_cvref_t; \ if constexpr (::sangi::dims_both_static(M_::static_rows, M_::static_cols)) { \ static_assert(M_::static_rows == M_::static_cols, \ "sangi LinAlg: matrix argument must be square (static check)"); \ } \ } while (0) // Static-assert that matrix MA and vector VB satisfy MA.rows == VB.size. // No-op if either dimension is dynamic. #define SANGI_STATIC_ASSERT_MATRIX_ROWS_EQ_VECTOR_SIZE(MatType, VecType) \ do { \ using M_ = std::remove_cvref_t; \ using V_ = std::remove_cvref_t; \ if constexpr (::sangi::dims_both_static(M_::static_rows, V_::static_size)) { \ static_assert(M_::static_rows == V_::static_size, \ "sangi LinAlg: matrix rows must equal rhs vector size (static check)"); \ } \ } while (0) // Static-assert that matrix MA and matrix MB share the inner dimension (MA.cols == MB.rows). // No-op if either dimension is dynamic. #define SANGI_STATIC_ASSERT_INNER_DIM_EQ(MatTypeA, MatTypeB) \ do { \ using MA_ = std::remove_cvref_t; \ using MB_ = std::remove_cvref_t; \ if constexpr (::sangi::dims_both_static(MA_::static_cols, MB_::static_rows)) { \ static_assert(MA_::static_cols == MB_::static_rows, \ "sangi LinAlg: inner dimension mismatch A.cols != B.rows (static check)"); \ } \ } while (0) // Static-assert that two vector types VA, VB satisfy VA::static_size == VB::static_size. // No-op if either size is dynamic. #define SANGI_STATIC_ASSERT_VECTOR_SIZES_EQ(VecTypeA, VecTypeB) \ do { \ using VA_ = std::remove_cvref_t; \ using VB_ = std::remove_cvref_t; \ if constexpr (::sangi::dims_both_static(VA_::static_size, VB_::static_size)) { \ static_assert(VA_::static_size == VB_::static_size, \ "sangi LinAlg: vector sizes must match (static check)"); \ } \ } while (0) // Static-assert that VA::static_size == VB::static_size - 1 (off-diagonal vs main diagonal). // Used by tridiagonal / pentadiagonal solvers. No-op if either size is dynamic. #define SANGI_STATIC_ASSERT_VECTOR_SIZE_EQ_OTHER_MINUS_ONE(VecTypeA, VecTypeOtherDiag) \ do { \ using VA_ = std::remove_cvref_t; \ using VB_ = std::remove_cvref_t; \ if constexpr (VA_::static_size >= 0 && VB_::static_size > 0) { \ static_assert(VA_::static_size == VB_::static_size - 1, \ "sangi LinAlg: off-diagonal vector size must equal main_diag - 1 (static check)"); \ } \ } while (0) #endif // SANGI_LINALG_STATIC_DIMS_HPP