// Copyright (C) 2026 Kiyotsugu Arai // SPDX-License-Identifier: LGPL-3.0-or-later // common.hpp // Common definitions, exceptions, and configuration // // This file provides the common definitions, exception classes, global // configuration, and utility functions used throughout the MKL algebra // library. // // Main features: // - Namespace definitions // - Version information // - MKL detection and configuration // - Exception class hierarchy // - Common utility functions // - Compile-time configuration // - Thread management // - Exceptions and configuration related to state management #ifndef SANGI_COMMON_HPP #define SANGI_COMMON_HPP // Compiler compatibility macros #ifdef _MSC_VER #define SANGI_FORCEINLINE __forceinline #else #define SANGI_FORCEINLINE __attribute__((always_inline)) inline #endif // ============================================================================== // DLL export/import macros // ============================================================================== // SANGI_SHARED_LIB: defined for DLL builds (set by CMake) // SANGI_BUILDING_CORE: defined while building the sangi_core library itself // // Usage: // class SANGI_API Int { ... }; -> subject to DLL export/import // SANGI_API int someFunction(); -> exports the function // Header-only templates need not be marked -> templates do not need SANGI_API // #ifdef SANGI_SHARED_LIB #ifdef _MSC_VER #ifdef SANGI_BUILDING_CORE #define SANGI_API __declspec(dllexport) #else #define SANGI_API __declspec(dllimport) #endif #else #define SANGI_API __attribute__((visibility("default"))) #endif #else #define SANGI_API #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Library namespace namespace sangi { // Version information struct struct Version { static constexpr int major = VERSION_MAJOR; static constexpr int minor = VERSION_MINOR; static constexpr int patch = VERSION_PATCH; // Get the version string static constexpr std::string_view string() { return "2.0.0"; // Should ideally be generated dynamically, but the constexpr constraint forces a fixed value } }; // Platform detection #if defined(_WIN32) || defined(_WIN64) #define SANGI_PLATFORM_WINDOWS #elif defined(__APPLE__) #define SANGI_PLATFORM_MACOS #elif defined(__linux__) #define SANGI_PLATFORM_LINUX #else #define SANGI_PLATFORM_UNKNOWN #endif // Compiler detection #if defined(_MSC_VER) #define SANGI_COMPILER_MSVC #elif defined(__clang__) #define SANGI_COMPILER_CLANG #elif defined(__GNUC__) #define SANGI_COMPILER_GCC #else #define SANGI_COMPILER_UNKNOWN #endif // C++ version detection #if defined(_MSC_VER) // For Visual Studio, detect the /std:c++XX option #if defined(_MSVC_LANG) #if _MSVC_LANG >= 202302L #define SANGI_CPP23 #define SANGI_CPP20 #elif _MSVC_LANG >= 202002L #define SANGI_CPP20 #else // Error is disabled so it still compiles under pre-C++20 // #error "C++20 or later is required" #endif #else // The /std:c++XX option was not specified // #error "Please specify /std:c++latest or /std:c++20 or later" #endif #else // Other compilers #if __cplusplus >= 202302L #define SANGI_CPP23 #define SANGI_CPP20 #elif __cplusplus >= 202002L #define SANGI_CPP20 #else // Error is disabled so it still compiles under pre-C++20 // #error "C++20 or later is required" #endif #endif // MKL / OpenBLAS BLAS backend detection // Priority: MKL > OpenBLAS > none (self-implemented gemm_blocked fallback) // // - SANGI_HAS_MKL: MKL is available; mkl.h + cblas_* + VML (vsExp, etc.) are usable // - SANGI_HAS_OPENBLAS: OpenBLAS is available; only cblas_* is usable (no VML) // - SANGI_HAS_CBLAS: the cblas_* API is available (either MKL or OpenBLAS) // - SANGI_HAS_VML: MKL VML (vsExp / vsFmax / vsLn, etc.) is available (MKL only) #if defined(SANGI_USE_MKL) #include #define SANGI_HAS_MKL 1 #define SANGI_HAS_OPENBLAS 0 #define SANGI_HAS_CBLAS 1 #define SANGI_HAS_VML 1 #elif defined(SANGI_USE_OPENBLAS) #include #define SANGI_HAS_MKL 0 #define SANGI_HAS_OPENBLAS 1 #define SANGI_HAS_CBLAS 1 #define SANGI_HAS_VML 0 #else #define SANGI_HAS_MKL 0 #define SANGI_HAS_OPENBLAS 0 #define SANGI_HAS_CBLAS 0 #define SANGI_HAS_VML 0 #endif // Abstraction for the BLAS integer type // MKL (ilp64): MKL_INT = 64-bit // OpenBLAS (default): blasint = 32-bit int (64-bit when built with OPENBLAS_USE64BITINT) // Neither: int // User code should consistently use sangi::blas_int_t. cblas_* calls cast through this typedef. #if SANGI_HAS_MKL using blas_int_t = MKL_INT; #elif SANGI_HAS_OPENBLAS using blas_int_t = blasint; #else using blas_int_t = int; #endif // Detect the available SIMD instruction set constexpr SimdInstructionSet detect_simd_level() { #if defined(__AVX512F__) return SimdInstructionSet::AVX512; #elif defined(__AVX2__) return SimdInstructionSet::AVX2; #elif defined(__AVX__) return SimdInstructionSet::AVX; #elif defined(__SSE4_2__) return SimdInstructionSet::SSE4_2; #elif defined(__SSE4_1__) return SimdInstructionSet::SSE4_1; #elif defined(__SSSE3__) return SimdInstructionSet::SSSE3; #elif defined(__SSE3__) return SimdInstructionSet::SSE3; #elif defined(__SSE2__) || defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2) return SimdInstructionSet::SSE2; #elif defined(__ARM_NEON) || defined(__ARM_NEON__) return SimdInstructionSet::NEON; #else return SimdInstructionSet::None; #endif } // The available SIMD instruction set constexpr SimdInstructionSet available_simd_level = detect_simd_level(); // Mathematical constants namespace constants { template inline constexpr T pi = static_cast(3.14159265358979323846); template inline constexpr T e = static_cast(2.71828182845904523536); template inline constexpr T sqrt2 = static_cast(1.41421356237309504880); template inline constexpr T log2e = static_cast(1.44269504088896340736); template inline constexpr T log10e = static_cast(0.43429448190325182765); } // Exception class hierarchy // Base exception class for the MKL algebra library class MklAlgebraException : public std::exception { public: explicit MklAlgebraException(const std::string& message) : message_(message) {} explicit MklAlgebraException(std::string&& message) : message_(std::move(message)) {} const char* what() const noexcept override { return message_.c_str(); } private: std::string message_; }; // Mathematical errors (invalid operations, domain errors, etc.) class MathError : public MklAlgebraException { public: using MklAlgebraException::MklAlgebraException; }; // Dimension errors (mismatched matrix or vector dimensions) class DimensionError : public MklAlgebraException { public: using MklAlgebraException::MklAlgebraException; }; // Index errors (out-of-range index access) class IndexError : public MklAlgebraException { public: using MklAlgebraException::MklAlgebraException; }; // Linear algebra errors (singular matrix, etc.) class LinearAlgebraError : public MklAlgebraException { public: using MklAlgebraException::MklAlgebraException; }; // Convergence errors (numerical algorithm fails to converge) class ConvergenceError : public MklAlgebraException { public: using MklAlgebraException::MklAlgebraException; }; // MKL errors (an MKL function failed) class MklError : public MklAlgebraException { public: explicit MklError(const std::string& message, int error_code) : MklAlgebraException(message + " (error code: " + std::to_string(error_code) + ")"), error_code_(error_code) { } int error_code() const noexcept { return error_code_; } private: int error_code_; }; // Errors related to numeric state class NumericStateError : public MathError { public: explicit NumericStateError(const std::string& message) : MathError(message), state_(NumericState::Normal), error_(NumericError::None) { } explicit NumericStateError(const std::string& message, NumericState state, NumericError error = NumericError::None) : MathError(message), state_(state), error_(error) { } NumericState getState() const noexcept { return state_; } NumericError getError() const noexcept { return error_; } private: NumericState state_; NumericError error_; }; // NaN error class NaNError : public NumericStateError { public: explicit NaNError(const std::string& message = "Operation resulted in NaN") : NumericStateError(message, NumericState::NaN, NumericError::ExplicitNaN) { } }; // Infinity error class InfinityError : public NumericStateError { public: explicit InfinityError(const std::string& message = "Operation resulted in infinity", bool is_positive = true) : NumericStateError(message, is_positive ? NumericState::PositiveInfinity : NumericState::NegativeInfinity, NumericError::None), is_positive_(is_positive) { } bool isPositive() const noexcept { return is_positive_; } private: bool is_positive_; }; // Overflow error class OverflowError : public NumericStateError { public: explicit OverflowError(const std::string& message = "Numeric overflow occurred") : NumericStateError(message, NumericState::Overflow, NumericError::OutOfRangeInput) { } }; // Underflow error class UnderflowError : public NumericStateError { public: explicit UnderflowError(const std::string& message = "Numeric underflow occurred") : NumericStateError(message, NumericState::Underflow, NumericError::OutOfRangeInput) { } }; // Division-by-zero error class DivideByZeroError : public NumericStateError { public: explicit DivideByZeroError(const std::string& message = "Division by zero") : NumericStateError(message, NumericState::NaN, NumericError::DivideByZero) { } }; // Divergence error class DivergenceStateError : public NumericStateError { public: explicit DivergenceStateError(const std::string& message = "Algorithm diverged", DivergenceDetail detail = DivergenceDetail::Diverging) : NumericStateError(message, NumericState::Divergent, NumericError::DivergenceError), detail_(detail) { } DivergenceDetail getDetail() const noexcept { return detail_; } private: DivergenceDetail detail_; }; // State-error handling configuration struct StateErrorOptions { // Handling mode per error type StateErrorMode nan_error_mode = StateErrorMode::ReturnSpecial; // NaN-related errors StateErrorMode infinity_error_mode = StateErrorMode::ReturnSpecial; // Infinity-related errors StateErrorMode overflow_error_mode = StateErrorMode::ReturnSpecial; // Overflow-related errors StateErrorMode underflow_error_mode = StateErrorMode::ReturnSpecial; // Underflow-related errors StateErrorMode divide_by_zero_error_mode = StateErrorMode::ReturnSpecial; // Division-by-zero errors StateErrorMode divergence_error_mode = StateErrorMode::Exception; // Divergence-related errors // Default settings static StateErrorOptions defaults() { return {}; } // Exception-only mode static StateErrorOptions exceptions_only() { StateErrorOptions options; options.nan_error_mode = StateErrorMode::Exception; options.infinity_error_mode = StateErrorMode::Exception; options.overflow_error_mode = StateErrorMode::Exception; options.underflow_error_mode = StateErrorMode::Exception; options.divide_by_zero_error_mode = StateErrorMode::Exception; options.divergence_error_mode = StateErrorMode::Exception; return options; } // Special-values-only mode static StateErrorOptions special_values_only() { StateErrorOptions options; options.nan_error_mode = StateErrorMode::ReturnSpecial; options.infinity_error_mode = StateErrorMode::ReturnSpecial; options.overflow_error_mode = StateErrorMode::ReturnSpecial; options.underflow_error_mode = StateErrorMode::ReturnSpecial; options.divide_by_zero_error_mode = StateErrorMode::ReturnSpecial; options.divergence_error_mode = StateErrorMode::ReturnSpecial; return options; } // Silent mode static StateErrorOptions silent() { StateErrorOptions options; options.nan_error_mode = StateErrorMode::Silent; options.infinity_error_mode = StateErrorMode::Silent; options.overflow_error_mode = StateErrorMode::Silent; options.underflow_error_mode = StateErrorMode::Silent; options.divide_by_zero_error_mode = StateErrorMode::Silent; options.divergence_error_mode = StateErrorMode::Silent; return options; } // Get the mode based on a NumericError StateErrorMode getModeForError(NumericError error) const { switch (error) { case NumericError::DivideByZero: return divide_by_zero_error_mode; case NumericError::NaNPropagation: case NumericError::ExplicitNaN: return nan_error_mode; case NumericError::OutOfRangeInput: case NumericError::ConversionError: case NumericError::IntegerConversionError: return overflow_error_mode; case NumericError::DivergenceError: return divergence_error_mode; default: return StateErrorMode::ReturnSpecial; } } // Get the mode based on a NumericState StateErrorMode getModeForState(NumericState state) const { if (NumericStateTraits::isNaN(state)) { return nan_error_mode; } if (NumericStateTraits::isInfinite(state)) { return infinity_error_mode; } if (state == NumericState::Overflow) { return overflow_error_mode; } if (state == NumericState::Underflow) { return underflow_error_mode; } if (NumericStateTraits::isDivergent(state)) { return divergence_error_mode; } return StateErrorMode::ReturnSpecial; } }; // Global state-error handling configuration (not thread-safe) inline StateErrorOptions& global_state_error_options() { static StateErrorOptions options = StateErrorOptions::defaults(); return options; } // Guard class that temporarily modifies StateErrorOptions and restores them at scope exit class StateErrorOptionsGuard { private: StateErrorOptions& options_; StateErrorMode original_nan_mode_; StateErrorMode original_infinity_mode_; StateErrorMode original_overflow_mode_; StateErrorMode original_underflow_mode_; StateErrorMode original_divide_by_zero_mode_; StateErrorMode original_divergence_mode_; public: explicit StateErrorOptionsGuard(StateErrorOptions& options) : options_(options), original_nan_mode_(options.nan_error_mode), original_infinity_mode_(options.infinity_error_mode), original_overflow_mode_(options.overflow_error_mode), original_underflow_mode_(options.underflow_error_mode), original_divide_by_zero_mode_(options.divide_by_zero_error_mode), original_divergence_mode_(options.divergence_error_mode) { } ~StateErrorOptionsGuard() { // Restore the settings in the destructor options_.nan_error_mode = original_nan_mode_; options_.infinity_error_mode = original_infinity_mode_; options_.overflow_error_mode = original_overflow_mode_; options_.underflow_error_mode = original_underflow_mode_; options_.divide_by_zero_error_mode = original_divide_by_zero_mode_; options_.divergence_error_mode = original_divergence_mode_; } // Non-copyable StateErrorOptionsGuard(const StateErrorOptionsGuard&) = delete; StateErrorOptionsGuard& operator=(const StateErrorOptionsGuard&) = delete; }; // Result type (utility for error handling) template class Result { public: // Create a success result static Result success(T value) { return Result(std::move(value), std::nullopt); } // Create an error result static Result error(std::string error_message) { return Result(std::nullopt, std::move(error_message)); } // Whether the result is a success bool is_success() const { return value_.has_value(); } // Whether the result is an error bool is_error() const { return error_message_.has_value(); } // Get the value (throws on error) const T& value() const { if (is_error()) { throw MklAlgebraException(error_message_.value()); } return value_.value(); } // Get the value (throws on error) T& value() { if (is_error()) { throw MklAlgebraException(error_message_.value()); } return value_.value(); } // Get the error message (empty string on success) std::string error_message() const { return error_message_.value_or(""); } private: Result(std::optional value, std::optional error_message) : value_(std::move(value)), error_message_(std::move(error_message)) { } std::optional value_; std::optional error_message_; }; // Utility functions // Dimension check (throws on mismatch) inline void check_dimensions(std::size_t expected, std::size_t actual, const std::string& message) { if (expected != actual) { throw DimensionError(message + ": expected " + std::to_string(expected) + ", got " + std::to_string(actual)); } } // Index check (throws on out-of-range) inline void check_index(std::size_t index, std::size_t size, const std::string& message) { if (index >= size) { throw IndexError(message + ": index " + std::to_string(index) + " out of range for size " + std::to_string(size)); } } // Square-matrix check (throws if not square) inline void check_square(std::size_t rows, std::size_t cols, const std::string& message) { if (rows != cols) { throw DimensionError(message + ": matrix is not square (" + std::to_string(rows) + "x" + std::to_string(cols) + ")"); } } // Numeric-state check function template void check_numeric_state(T value, const std::string& operation = "Operation") { const auto& options = global_state_error_options(); // NaN check if (std::isnan(value)) { switch (options.nan_error_mode) { case StateErrorMode::Exception: throw NaNError(operation + " resulted in NaN"); case StateErrorMode::ReturnSpecial: // Nothing to do when returning special values (value is already NaN) break; case StateErrorMode::Silent: // Nothing to do in silent mode break; } } // Infinity check if (std::isinf(value)) { switch (options.infinity_error_mode) { case StateErrorMode::Exception: throw InfinityError(operation + " resulted in infinity"); case StateErrorMode::ReturnSpecial: // Nothing to do when returning special values (value is already infinity) break; case StateErrorMode::Silent: // Nothing to do in silent mode break; } } // Overflow and underflow checks are difficult to perform on // floating-point arithmetic, so they are not implemented here. } // Zero check before division inline void check_divide_by_zero(double divisor, const std::string& message = "Division") { if (divisor == 0.0) { const auto& options = global_state_error_options(); switch (options.divide_by_zero_error_mode) { case StateErrorMode::Exception: throw DivideByZeroError(message + " by zero"); case StateErrorMode::ReturnSpecial: case StateErrorMode::Silent: // Nothing to do in these modes (the caller produces infinity or a special value) break; } } } // Numeric comparison function (floating-point comparison) template constexpr bool approximately_equal(T a, T b, T epsilon = std::numeric_limits::epsilon() * T(100)) { if (a == b) return true; auto safe_abs = [](const T& x) -> T { return (x < T(0)) ? -x : x; }; const T diff = safe_abs(a - b); const T norm = std::min((safe_abs(a) + safe_abs(b)), std::numeric_limits::max()); return diff < std::max(std::numeric_limits::min(), epsilon * norm); } // Numeric-state to string conversion inline std::string numeric_state_to_string(NumericState state) { return NumericStateTraits::toString(state); } // Divergence-detail to string conversion inline std::string divergence_detail_to_string(DivergenceDetail detail) { return NumericStateTraits::toString(detail); } // Error-cause to string conversion inline std::string numeric_error_to_string(NumericError error) { return NumericStateTraits::toString(error); } // Convert both state and error to a string at once inline std::string state_and_error_to_string(NumericState state, NumericError error) { if (error == NumericError::None) { return NumericStateTraits::toString(state); } else { return NumericStateTraits::toString(state) + " (" + NumericStateTraits::toString(error) + ")"; } } // Thread management (MKL thread-count setting, etc.) #if SANGI_HAS_MKL class ThreadManager { public: // Get the number of available threads static int get_max_threads() { return mkl_get_max_threads(); } // Get the current number of threads static int get_num_threads() { return mkl_get_max_threads(); } // Set the number of threads static void set_num_threads(int num_threads) { mkl_set_num_threads(num_threads); } // Temporarily change the number of threads (restored on scope exit) class ScopedThreads { public: explicit ScopedThreads(int num_threads) : previous_threads_(mkl_get_max_threads()) { mkl_set_num_threads(num_threads); } ~ScopedThreads() { mkl_set_num_threads(previous_threads_); } // Non-copyable ScopedThreads(const ScopedThreads&) = delete; ScopedThreads& operator=(const ScopedThreads&) = delete; private: const int previous_threads_; }; }; #else class ThreadManager { public: // Stub implementation when MKL is unavailable static int get_max_threads() { return std::thread::hardware_concurrency(); } static int get_num_threads() { return 1; } static void set_num_threads(int /* num_threads */) { // Stub implementation does nothing } class ScopedThreads { public: explicit ScopedThreads(int /* num_threads */) {} // Non-copyable ScopedThreads(const ScopedThreads&) = delete; ScopedThreads& operator=(const ScopedThreads&) = delete; }; }; #endif // Memory alignment related namespace memory { // Compute the required alignment constexpr std::size_t simd_alignment() { switch (available_simd_level) { case SimdInstructionSet::AVX512: return 64; case SimdInstructionSet::AVX2: case SimdInstructionSet::AVX: return 32; case SimdInstructionSet::SSE4_2: case SimdInstructionSet::SSE4_1: case SimdInstructionSet::SSSE3: case SimdInstructionSet::SSE3: case SimdInstructionSet::SSE2: case SimdInstructionSet::NEON: return 16; default: return 8; } } // Align a value up to the given alignment inline std::size_t align_to(std::size_t size, std::size_t alignment) { return (size + alignment - 1) & ~(alignment - 1); } // Allocate aligned memory template T* aligned_alloc(std::size_t size, std::size_t alignment = simd_alignment()) { // Use C++17 std::aligned_alloc or an equivalent function #if defined(SANGI_COMPILER_MSVC) return static_cast(_aligned_malloc(size * sizeof(T), alignment)); #elif defined(_POSIX_VERSION) void* ptr = nullptr; if (posix_memalign(&ptr, alignment, size * sizeof(T)) != 0) { return nullptr; } return static_cast(ptr); #else // Fallback alignment-allocation implementation usable on common machines void* ptr = std::malloc(size * sizeof(T) + alignment); if (!ptr) return nullptr; std::size_t offset = alignment - (reinterpret_cast(ptr) % alignment); T* aligned = reinterpret_cast(reinterpret_cast(ptr) + offset); // Store the information needed to recover the original pointer *(reinterpret_cast(aligned) - 1) = ptr; return aligned; #endif } // Free aligned memory template void aligned_free(T* ptr) { #if defined(SANGI_COMPILER_MSVC) _aligned_free(ptr); #elif defined(_POSIX_VERSION) free(ptr); #else // Fallback alignment-free implementation usable on common machines if (!ptr) return; // Retrieve the original pointer and free it void* original = *(reinterpret_cast(ptr) - 1); std::free(original); #endif } } // General-purpose numeric-state check function (updated version) inline void check_numeric_state(NumericState state, NumericError error, const std::string& message) { const auto& options = global_state_error_options(); // Check based on the error if (error != NumericError::None) { switch (error) { case NumericError::DivideByZero: if (options.divide_by_zero_error_mode == StateErrorMode::Exception) { throw DivideByZeroError(message); } break; case NumericError::NaNPropagation: case NumericError::ExplicitNaN: case NumericError::InvalidBitPattern: if (options.nan_error_mode == StateErrorMode::Exception) { throw NaNError(message); } break; case NumericError::OutOfRangeInput: case NumericError::ConversionError: case NumericError::IntegerConversionError: if (options.overflow_error_mode == StateErrorMode::Exception) { throw OverflowError(message); } break; case NumericError::DivergenceError: if (options.divergence_error_mode == StateErrorMode::Exception) { throw DivergenceStateError(message); } break; default: // No special handling for other errors break; } } // Check based on the state if (NumericStateTraits::isNaN(state)) { if (options.nan_error_mode == StateErrorMode::Exception) { throw NaNError(message); } return; } // Infinity check if (NumericStateTraits::isInfinite(state)) { if (options.infinity_error_mode == StateErrorMode::Exception) { throw InfinityError(message, state == NumericState::PositiveInfinity); } return; } // Overflow check if (state == NumericState::Overflow) { if (options.overflow_error_mode == StateErrorMode::Exception) { throw OverflowError(message); } return; } // Underflow check if (state == NumericState::Underflow) { if (options.underflow_error_mode == StateErrorMode::Exception) { throw UnderflowError(message); } return; } // Divergence check if (NumericStateTraits::isDivergent(state)) { if (options.divergence_error_mode == StateErrorMode::Exception) { throw DivergenceStateError(message); } return; } } } // namespace sangi #endif // SANGI_COMMON_HPP