From fbf486b715c710c0bfcdd367f91b3fcfd8aa0119 Mon Sep 17 00:00:00 2001 From: Zachary Sexton Date: Wed, 8 Apr 2026 18:54:54 -0700 Subject: [PATCH 01/15] adding basic core exception classes, types, and configuration --- Code/Source/solver/FE/Core/FEConfig.h | 404 ++++++++++++++++ Code/Source/solver/FE/Core/FEException.h | 566 +++++++++++++++++++++++ Code/Source/solver/FE/Core/Types.h | 476 +++++++++++++++++++ 3 files changed, 1446 insertions(+) create mode 100644 Code/Source/solver/FE/Core/FEConfig.h create mode 100644 Code/Source/solver/FE/Core/FEException.h create mode 100644 Code/Source/solver/FE/Core/Types.h diff --git a/Code/Source/solver/FE/Core/FEConfig.h b/Code/Source/solver/FE/Core/FEConfig.h new file mode 100644 index 000000000..82e2fb6fc --- /dev/null +++ b/Code/Source/solver/FE/Core/FEConfig.h @@ -0,0 +1,404 @@ +// SPDX-FileCopyrightText: Copyright (c) Stanford University, The Regents of the University of California, and others. +// SPDX-License-Identifier: BSD-3-Clause + +#ifndef SVMP_FE_CONFIG_H +#define SVMP_FE_CONFIG_H + +/** + * @file FEConfig.h + * @brief Compile-time configuration and feature settings for the FE library + * + * This header centralizes all compile-time configuration options, + * feature flags, and platform-specific settings for the finite element + * library. Settings can be overridden via CMake or compiler flags. + */ + +#include "Types.h" +#include + +// ============================================================================ +// Build Configuration Detection +// ============================================================================ + +// Debug/Release mode detection +#if !defined(NDEBUG) || defined(DEBUG) || defined(_DEBUG) + #define FE_DEBUG_MODE 1 +#else + #define FE_DEBUG_MODE 0 +#endif + +#if FE_DEBUG_MODE +#include +#endif + +// MPI support detection. +// +// FE can be built in two modes: +// - Integrated with Mesh: Mesh defines `MESH_HAS_MPI` when compiled with MPI. +// - Standalone FE with MPI: FE's CMake sets `MESH_ENABLE_MPI` for compatibility. +// +// Normalize to a numeric macro suitable for `#if FE_HAS_MPI` use. +#ifdef FE_HAS_MPI +# undef FE_HAS_MPI +#endif +#if defined(MESH_HAS_MPI) || defined(MESH_ENABLE_MPI) +# define FE_HAS_MPI 1 +#else +# define FE_HAS_MPI 0 +#endif + +// OpenMP support detection +#ifdef _OPENMP + #define FE_HAS_OPENMP 1 + #include +#else + #define FE_HAS_OPENMP 0 +#endif + +namespace svmp { +namespace FE { +namespace config { + +// ============================================================================ +// Dimension Configuration +// ============================================================================ + +/** + * @brief Maximum spatial dimension supported + * + * Controls template instantiations and static array sizes. + * Can be overridden at compile time with -DFE_MAX_DIM=N + */ +#ifndef FE_MAX_DIM + constexpr int MAX_SPATIAL_DIM = 3; +#else + constexpr int MAX_SPATIAL_DIM = FE_MAX_DIM; +#endif + +/** + * @brief Enable specific dimensions + * + * Controls which template instantiations are generated. + * Disabling unused dimensions reduces binary size. + */ +#ifndef FE_ENABLE_1D + #define FE_ENABLE_1D 1 +#endif + +#ifndef FE_ENABLE_2D + #define FE_ENABLE_2D 1 +#endif + +#ifndef FE_ENABLE_3D + #define FE_ENABLE_3D 1 +#endif + +// ============================================================================ +// Precision Configuration +// ============================================================================ + +/** + * @brief Floating point precision settings + */ +enum class Precision { + Single, // 32-bit float + Double, // 64-bit double (default) + Quad // 128-bit quad precision (if available) +}; + +/** + * @brief Default precision for FE computations + * + * Can be overridden with -DFE_USE_SINGLE_PRECISION + */ +#ifdef FE_USE_SINGLE_PRECISION + constexpr Precision DEFAULT_PRECISION = Precision::Single; + using DefaultReal = float; +#elif defined(FE_USE_QUAD_PRECISION) + constexpr Precision DEFAULT_PRECISION = Precision::Quad; + using DefaultReal = long double; +#else + constexpr Precision DEFAULT_PRECISION = Precision::Double; + using DefaultReal = double; +#endif + +// ============================================================================ +// Backend Configuration +// ============================================================================ + +/** + * @brief Linear algebra backend selection + */ +enum class Backend { + None, // No backend (testing only) + Trilinos, // Trilinos (Epetra/Tpetra) + PETSc, // PETSc + Eigen, // Eigen (for small/local problems) + Custom // User-provided backend +}; + +/** + * @brief Default backend selection + * + * Set via CMake based on available libraries + */ +#if defined(FE_USE_TRILINOS) + constexpr Backend DEFAULT_BACKEND = Backend::Trilinos; + #define FE_HAS_TRILINOS 1 +#elif defined(FE_USE_PETSC) + constexpr Backend DEFAULT_BACKEND = Backend::PETSc; + #define FE_HAS_PETSC 1 +#elif defined(FE_USE_EIGEN) + constexpr Backend DEFAULT_BACKEND = Backend::Eigen; + #define FE_HAS_EIGEN 1 +#else + constexpr Backend DEFAULT_BACKEND = Backend::None; +#endif + +// ============================================================================ +// Performance Configuration +// ============================================================================ + +/** + * @brief Cache line size in bytes + * + * Used for memory alignment and padding to avoid false sharing + */ +#ifndef FE_CACHE_LINE_SIZE + constexpr std::size_t CACHE_LINE_SIZE = 64; +#else + constexpr std::size_t CACHE_LINE_SIZE = FE_CACHE_LINE_SIZE; +#endif + +/** + * @brief Enable SIMD vectorization + * + * Controls whether vectorized implementations are used + */ +#ifndef FE_ENABLE_VECTORIZATION + #if defined(__AVX512F__) + #define FE_VECTORIZATION_LEVEL 512 + #define FE_ENABLE_VECTORIZATION 1 + #elif defined(__AVX2__) + #define FE_VECTORIZATION_LEVEL 256 + #define FE_ENABLE_VECTORIZATION 1 + #elif defined(__SSE4_2__) + #define FE_VECTORIZATION_LEVEL 128 + #define FE_ENABLE_VECTORIZATION 1 + #else + #define FE_VECTORIZATION_LEVEL 0 + #define FE_ENABLE_VECTORIZATION 0 + #endif +#endif + +/** + * @brief Enable aggressive loop unrolling + */ +#ifndef FE_ENABLE_LOOP_UNROLL + #define FE_ENABLE_LOOP_UNROLL 1 +#endif + +/** + * @brief Assembly kernel caching + * + * Cache element matrices/vectors to reduce recomputation + */ +#ifndef FE_ENABLE_ASSEMBLY_CACHE + #define FE_ENABLE_ASSEMBLY_CACHE 1 +#endif + +/** + * @brief Maximum elements to process in vectorized batches + */ +constexpr std::size_t VECTOR_BATCH_SIZE = 32; + +// ============================================================================ +// Memory Configuration +// ============================================================================ + +/** + * @brief Memory allocation strategies + */ +enum class AllocStrategy { + Standard, // Standard new/delete + Pooled, // Memory pool allocator + Aligned, // Aligned allocation for SIMD + Custom // User-provided allocator +}; + +/** + * @brief Default memory allocation strategy + */ +#ifdef FE_USE_MEMORY_POOL + constexpr AllocStrategy DEFAULT_ALLOC_STRATEGY = AllocStrategy::Pooled; +#elif FE_ENABLE_VECTORIZATION + constexpr AllocStrategy DEFAULT_ALLOC_STRATEGY = AllocStrategy::Aligned; +#else + constexpr AllocStrategy DEFAULT_ALLOC_STRATEGY = AllocStrategy::Standard; +#endif + +/** + * @brief Stack array size threshold + * + * Arrays smaller than this use stack allocation + */ +constexpr std::size_t STACK_ARRAY_THRESHOLD = 256; + +// ============================================================================ +// Debug/Diagnostic Configuration +// ============================================================================ + +/** + * @brief Bounds checking level + */ +enum class BoundsCheckLevel { + None, // No bounds checking (release mode) + Basic, // Basic index validation + Full // Full bounds and consistency checking +}; + +#if FE_DEBUG_MODE + constexpr BoundsCheckLevel BOUNDS_CHECK_LEVEL = BoundsCheckLevel::Full; + constexpr bool ENABLE_ASSERTIONS = true; + constexpr bool ENABLE_NAN_CHECK = true; +#else + constexpr BoundsCheckLevel BOUNDS_CHECK_LEVEL = BoundsCheckLevel::None; + constexpr bool ENABLE_ASSERTIONS = false; + constexpr bool ENABLE_NAN_CHECK = false; +#endif + +/** + * @brief Performance profiling + */ +#ifndef FE_ENABLE_PROFILING + #define FE_ENABLE_PROFILING FE_DEBUG_MODE +#endif + +/** + * @brief Detailed timing of assembly operations + */ +#ifndef FE_ENABLE_TIMING + #define FE_ENABLE_TIMING 0 +#endif + +// ============================================================================ +// Feature Toggles +// ============================================================================ + + +/** + * @brief Maximum polynomial order supported + */ +#ifndef FE_MAX_POLYNOMIAL_ORDER + constexpr int MAX_POLYNOMIAL_ORDER = 10; +#else + constexpr int MAX_POLYNOMIAL_ORDER = FE_MAX_POLYNOMIAL_ORDER; +#endif + +// ============================================================================ +// Quadrature Configuration +// ============================================================================ + + +/** + * @brief Cache quadrature rules + */ +#ifndef FE_CACHE_QUADRATURE + #define FE_CACHE_QUADRATURE 1 +#endif + +// ============================================================================ +// Compiler Hints and Attributes +// ============================================================================ + +// Likely/unlikely branch hints +#if defined(__GNUC__) || defined(__clang__) + #define FE_LIKELY(x) __builtin_expect(!!(x), 1) + #define FE_UNLIKELY(x) __builtin_expect(!!(x), 0) +#else + #define FE_LIKELY(x) (x) + #define FE_UNLIKELY(x) (x) +#endif + +// Force inline +#if defined(_MSC_VER) + #define FE_ALWAYS_INLINE __forceinline +#elif defined(__GNUC__) || defined(__clang__) + #define FE_ALWAYS_INLINE __attribute__((always_inline)) inline +#else + #define FE_ALWAYS_INLINE inline +#endif + +// Restrict pointer aliasing +#if defined(__GNUC__) || defined(__clang__) + #define FE_RESTRICT __restrict__ +#elif defined(_MSC_VER) + #define FE_RESTRICT __restrict +#else + #define FE_RESTRICT +#endif + +// Memory alignment +#if defined(__GNUC__) || defined(__clang__) + #define FE_ALIGN(x) __attribute__((aligned(x))) +#elif defined(_MSC_VER) + #define FE_ALIGN(x) __declspec(align(x)) +#else + #define FE_ALIGN(x) +#endif + +// ============================================================================ +// Assertion Macros +// ============================================================================ + +#if FE_DEBUG_MODE + #define FE_ASSERT(cond) assert(cond) + #define FE_ASSERT_MSG(cond, msg) assert((cond) && (msg)) +#else + #define FE_ASSERT(cond) ((void)0) + #define FE_ASSERT_MSG(cond, msg) ((void)0) +#endif + +// Bounds checking macro +#if BOUNDS_CHECK_LEVEL == 2 // Full + #define FE_CHECK_BOUNDS(index, size) \ + FE_ASSERT_MSG((index) >= 0 && (index) < (size), "Index out of bounds") +#elif BOUNDS_CHECK_LEVEL == 1 // Basic + #define FE_CHECK_BOUNDS(index, size) \ + FE_ASSERT((index) < (size)) +#else + #define FE_CHECK_BOUNDS(index, size) ((void)0) +#endif + +// ============================================================================ +// Configuration Summary +// ============================================================================ + +/** + * @brief Print configuration summary + * + * Useful for debugging and verifying build configuration + */ +inline void print_config() { + #define PRINT_BOOL(x) ((x) ? "ON" : "OFF") + + printf("FE Library Configuration:\n"); + printf(" Debug Mode: %s\n", PRINT_BOOL(FE_DEBUG_MODE)); + printf(" MPI Support: %s\n", PRINT_BOOL(FE_HAS_MPI)); + printf(" OpenMP Support: %s\n", PRINT_BOOL(FE_HAS_OPENMP)); + printf(" Max Dimension: %d\n", MAX_SPATIAL_DIM); + printf(" Vectorization: %s (Level: %d)\n", + PRINT_BOOL(FE_ENABLE_VECTORIZATION), FE_VECTORIZATION_LEVEL); + printf(" Cache Line Size: %zu bytes\n", CACHE_LINE_SIZE); + printf(" Profiling: %s\n", PRINT_BOOL(FE_ENABLE_PROFILING)); + printf(" Matrix-Free: %s\n", PRINT_BOOL(FE_ENABLE_MATRIX_FREE)); + printf(" Max Polynomial Order: %d\n", MAX_POLYNOMIAL_ORDER); + + #undef PRINT_BOOL +} + +} // namespace config +} // namespace FE +} // namespace svmp + +#endif // SVMP_FE_CONFIG_H diff --git a/Code/Source/solver/FE/Core/FEException.h b/Code/Source/solver/FE/Core/FEException.h new file mode 100644 index 000000000..3b6b2a92e --- /dev/null +++ b/Code/Source/solver/FE/Core/FEException.h @@ -0,0 +1,566 @@ +// SPDX-FileCopyrightText: Copyright (c) Stanford University, The Regents of the University of California, and others. +// SPDX-License-Identifier: BSD-3-Clause + +#ifndef SVMP_FE_EXCEPTION_H +#define SVMP_FE_EXCEPTION_H + +/** + * @file FEException.h + * @brief Exception hierarchy for error handling in the FE library + * + * This header defines a comprehensive exception system for the FE library + * with support for detailed error messages, stack traces, MPI-aware error + * handling, and structured error codes. + */ + +#include "Types.h" +#include "FEConfig.h" +#include +#include +#include +#include +#include + +#if FE_HAS_MPI +#include +#include +#endif + +// Platform-specific includes for stack traces +#if defined(__GNUC__) && !defined(_WIN32) +#include +#include +#endif + +namespace svmp { +namespace FE { + +// ============================================================================ +// Base Exception Class +// ============================================================================ + +/** + * @brief Base exception class for all FE library exceptions + * + * Provides rich error information including: + * - Detailed error messages + * - Source file and line information + * - Optional stack traces + * - MPI rank information in parallel runs + * - Nested exception support + */ +class FEException : public std::exception { +public: + /** + * @brief Construct exception with message + */ + FEException(const std::string& message, + FEStatus status = FEStatus::Unknown) + : message_(message), + status_(status), + file_(""), + line_(0), + function_(""), + mpi_rank_(-1) { + capture_context(); + build_what(); + } + + /** + * @brief Construct exception with source location + */ + FEException(const std::string& message, + const char* file, + int line, + const char* function = "", + FEStatus status = FEStatus::Unknown) + : message_(message), + status_(status), + file_(file), + line_(line), + function_(function), + mpi_rank_(-1) { + capture_context(); + build_what(); + } + + /** + * @brief Copy constructor + */ + FEException(const FEException&) = default; + + /** + * @brief Virtual destructor + */ + virtual ~FEException() noexcept = default; + + /** + * @brief Get exception message + */ + virtual const char* what() const noexcept override { + return what_.c_str(); + } + + /** + * @brief Get error status code + */ + FEStatus status() const noexcept { + return status_; + } + + /** + * @brief Get source file where exception was thrown + */ + const std::string& file() const noexcept { + return file_; + } + + /** + * @brief Get line number where exception was thrown + */ + int line() const noexcept { + return line_; + } + + /** + * @brief Get function name where exception was thrown + */ + const std::string& function() const noexcept { + return function_; + } + + /** + * @brief Get MPI rank (if applicable) + */ + int mpi_rank() const noexcept { + return mpi_rank_; + } + + /** + * @brief Get stack trace (if available) + */ + const std::vector& stack_trace() const noexcept { + return stack_trace_; + } + + /** + * @brief Add nested exception context + */ + void add_context(const std::string& context) { + message_ = context + "\n -> " + message_; + build_what(); + } + +protected: + std::string message_; + FEStatus status_; + std::string file_; + int line_; + std::string function_; + int mpi_rank_; + std::vector stack_trace_; + std::string what_; + + /** + * @brief Capture MPI rank and stack trace + */ + void capture_context() { + // Get MPI rank if available + #if FE_HAS_MPI + int initialized = 0; + MPI_Initialized(&initialized); + if (initialized) { + MPI_Comm_rank(MPI_COMM_WORLD, &mpi_rank_); + } + #endif + + // Capture stack trace if in debug mode + #if FE_DEBUG_MODE + capture_stack_trace(); + #endif + } + + /** + * @brief Capture stack trace (platform-specific) + */ + void capture_stack_trace() { + #if defined(__GNUC__) && !defined(_WIN32) + constexpr int MAX_FRAMES = 32; + void* frames[MAX_FRAMES]; + int n_frames = backtrace(frames, MAX_FRAMES); + + char** symbols = backtrace_symbols(frames, n_frames); + if (symbols) { + if (n_frames > 0) { + stack_trace_.reserve(static_cast(n_frames)); + } + for (int i = 1; i < n_frames; ++i) { // Skip this function + std::string symbol(symbols[i]); + + // Try to demangle C++ names + size_t start = symbol.find('('); + size_t end = symbol.find('+', start); + if (start != std::string::npos && end != std::string::npos) { + std::string mangled = symbol.substr(start + 1, end - start - 1); + int status; + char* demangled = abi::__cxa_demangle(mangled.c_str(), nullptr, nullptr, &status); + if (status == 0 && demangled) { + symbol.replace(start + 1, end - start - 1, demangled); + free(demangled); + } + } + + stack_trace_.push_back(symbol); + } + free(symbols); + } + #endif + } + + /** + * @brief Build the complete error message + */ + void build_what() { + std::ostringstream oss; + + // Error type and status + oss << "[FE Exception] " << status_to_string(status_); + + // MPI rank if applicable + if (mpi_rank_ >= 0) { + oss << " (Rank " << mpi_rank_ << ")"; + } + + oss << "\n"; + + // Location information + if (!file_.empty()) { + oss << " Location: " << file_ << ":" << line_; + if (!function_.empty()) { + oss << " in " << function_ << "()"; + } + oss << "\n"; + } + + // Error message + oss << " Message: " << message_ << "\n"; + + // Stack trace in debug mode + #if FE_DEBUG_MODE + if (!stack_trace_.empty()) { + oss << " Stack trace:\n"; + for (size_t i = 0; i < stack_trace_.size() && i < 10; ++i) { + oss << " #" << i << " " << stack_trace_[i] << "\n"; + } + } + #endif + + what_ = oss.str(); + } +}; + +// ============================================================================ +// Specific Exception Types +// ============================================================================ + +/** + * @brief Exception for invalid arguments + */ +class InvalidArgumentException : public FEException { +public: + InvalidArgumentException(const std::string& message, + const char* file = "", + int line = 0, + const char* function = "") + : FEException(message, file, line, function, FEStatus::InvalidArgument) {} +}; + +/** + * @brief Exception for invalid elements + */ +class InvalidElementException : public FEException { +public: + InvalidElementException(const std::string& message, + ElementType elem_type, + const char* file = "", + int line = 0) + : FEException(build_message(message, elem_type), file, line, "", FEStatus::InvalidElement), + element_type_(elem_type) {} + + ElementType element_type() const { return element_type_; } + +private: + ElementType element_type_; + + static std::string build_message(const std::string& msg, ElementType elem) { + return msg + " (Element type: " + std::to_string(static_cast(elem)) + ")"; + } +}; + +/** + * @brief Exception for DOF-related errors + */ +class DofException : public FEException { +public: + DofException(const std::string& message, + GlobalIndex dof_index = INVALID_GLOBAL_INDEX, + const char* file = "", + int line = 0) + : FEException(build_message(message, dof_index), file, line), + dof_index_(dof_index) {} + + GlobalIndex dof_index() const { return dof_index_; } + +private: + GlobalIndex dof_index_; + + static std::string build_message(const std::string& msg, GlobalIndex dof) { + if (dof != INVALID_GLOBAL_INDEX) { + return msg + " (DOF index: " + std::to_string(dof) + ")"; + } + return msg; + } +}; + +/** + * @brief Exception for assembly errors + */ +class AssemblyException : public FEException { +public: + AssemblyException(const std::string& message, + const char* file = "", + int line = 0) + : FEException(message, file, line, "", FEStatus::AssemblyError) {} +}; + +/** + * @brief Exception for backend-specific errors + */ +class BackendException : public FEException { +public: + BackendException(const std::string& message, + config::Backend backend, + int error_code = 0, + const char* file = "", + int line = 0) + : FEException(build_message(message, backend, error_code), file, line, "", FEStatus::BackendError), + backend_(backend), + error_code_(error_code) {} + + config::Backend backend() const { return backend_; } + int error_code() const { return error_code_; } + +private: + config::Backend backend_; + int error_code_; + + static std::string build_message(const std::string& msg, config::Backend backend, int code) { + std::ostringstream oss; + oss << msg << " (Backend: "; + switch(backend) { + case config::Backend::Trilinos: oss << "Trilinos"; break; + case config::Backend::PETSc: oss << "PETSc"; break; + case config::Backend::Eigen: oss << "Eigen"; break; + default: oss << "Unknown"; + } + if (code != 0) { + oss << ", Error code: " << code; + } + oss << ")"; + return oss.str(); + } +}; + +/** + * @brief Exception for not implemented features + */ +class NotImplementedException : public FEException { +public: + NotImplementedException(const std::string& feature, + const char* file = "", + int line = 0, + const char* function = "") + : FEException("Feature not implemented: " + feature, file, line, function, FEStatus::NotImplemented) {} +}; + +/** + * @brief Exception for convergence failures + */ +class ConvergenceException : public FEException { +public: + ConvergenceException(const std::string& message, + int iteration = -1, + Real residual = 0.0, + const char* file = "", + int line = 0) + : FEException(build_message(message, iteration, residual), file, line, "", FEStatus::ConvergenceError), + iteration_(iteration), + residual_(residual) {} + + int iteration() const { return iteration_; } + Real residual() const { return residual_; } + +private: + int iteration_; + Real residual_; + + static std::string build_message(const std::string& msg, int iter, Real res) { + std::ostringstream oss; + oss << msg; + if (iter >= 0) { + oss << " (Iteration: " << iter; + if (res > 0) { + oss << ", Residual: " << res; + } + oss << ")"; + } + return oss.str(); + } +}; + +/** + * @brief Exception for singular mappings + */ +class SingularMappingException : public FEException { +public: + SingularMappingException(const std::string& message, + Real jacobian_det = 0.0, + const char* file = "", + int line = 0) + : FEException(build_message(message, jacobian_det), file, line, "", FEStatus::SingularMapping), + jacobian_det_(jacobian_det) {} + + Real jacobian_det() const { return jacobian_det_; } + +private: + Real jacobian_det_; + + static std::string build_message(const std::string& msg, Real det) { + return msg + " (Jacobian determinant: " + std::to_string(det) + ")"; + } +}; + +// ============================================================================ +// Exception Throwing Macros +// ============================================================================ + +/** + * @brief Throw exception with automatic source location + */ +#define FE_THROW(ExceptionType, message) \ + throw ExceptionType(message, __FILE__, __LINE__, __FUNCTION__) + +/** + * @brief Conditional throw with source location (3 args: condition, ExceptionType, message) + */ +#define FE_THROW_IF_3(condition, ExceptionType, message) \ + do { \ + if (FE_UNLIKELY(condition)) { \ + FE_THROW(ExceptionType, message); \ + } \ + } while(0) + +/** + * @brief Conditional throw with FEException (2 args: condition, message) + */ +#define FE_THROW_IF_2(condition, message) \ + do { \ + if (FE_UNLIKELY(condition)) { \ + FE_THROW(FEException, message); \ + } \ + } while(0) + +/** + * @brief Helper macro to select between 2 and 3 argument versions + */ +#define FE_THROW_IF_SELECT(_1, _2, _3, NAME, ...) NAME + +/** + * @brief Conditional throw with source location + * + * Can be called with 2 arguments (condition, message) using FEException, + * or 3 arguments (condition, ExceptionType, message) for specific exception types. + */ +#define FE_THROW_IF(...) \ + FE_THROW_IF_SELECT(__VA_ARGS__, FE_THROW_IF_3, FE_THROW_IF_2)(__VA_ARGS__) + +/** + * @brief Check and throw InvalidArgumentException + */ +#define FE_CHECK_ARG(condition, message) \ + FE_THROW_IF(!(condition), InvalidArgumentException, message) + +/** + * @brief Check for null pointers + */ +#define FE_CHECK_NOT_NULL(ptr, name) \ + FE_THROW_IF((ptr) == nullptr, InvalidArgumentException, \ + std::string(name) + " is null") + +/** + * @brief Check index bounds + */ +#define FE_CHECK_INDEX(index, size) \ + FE_THROW_IF((index) < 0 || (index) >= (size), \ + InvalidArgumentException, \ + "Index " + std::to_string(index) + " out of bounds [0, " + \ + std::to_string(size) + ")") + +/** + * @brief Throw NotImplementedException + */ +#define FE_NOT_IMPLEMENTED(feature) \ + FE_THROW(NotImplementedException, feature) + +// ============================================================================ +// MPI Error Handling +// ============================================================================ + +#if FE_HAS_MPI + +/** + * @brief MPI-aware error handler + * + * Ensures all ranks abort cleanly when an exception occurs + */ +class MPIErrorHandler { +public: + static void abort_all_ranks(const FEException& e) { + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + // Print error on rank that threw + if (rank == e.mpi_rank() || e.mpi_rank() < 0) { + std::cerr << e.what() << std::endl; + } + + // Ensure all ranks abort + MPI_Abort(MPI_COMM_WORLD, static_cast(e.status())); + } + + /** + * @brief Install MPI error handler + */ + static void install() { + std::set_terminate([]() { + try { + std::rethrow_exception(std::current_exception()); + } catch (const FEException& e) { + abort_all_ranks(e); + } catch (const std::exception& e) { + std::cerr << "Unhandled exception: " << e.what() << std::endl; + MPI_Abort(MPI_COMM_WORLD, 1); + } catch (...) { + std::cerr << "Unknown exception" << std::endl; + MPI_Abort(MPI_COMM_WORLD, 1); + } + }); + } +}; + +#endif // FE_HAS_MPI + +} // namespace FE +} // namespace svmp + +#endif // SVMP_FE_EXCEPTION_H diff --git a/Code/Source/solver/FE/Core/Types.h b/Code/Source/solver/FE/Core/Types.h new file mode 100644 index 000000000..23c9581d6 --- /dev/null +++ b/Code/Source/solver/FE/Core/Types.h @@ -0,0 +1,476 @@ +// SPDX-FileCopyrightText: Copyright (c) Stanford University, The Regents of the University of California, and others. +// SPDX-License-Identifier: BSD-3-Clause + +#ifndef SVMP_FE_TYPES_H +#define SVMP_FE_TYPES_H + +/** + * @file Types.h + * @brief Fundamental type definitions for the finite element library + * + * This header provides core type aliases, enumerations, and strong type + * definitions used throughout the FE library. It establishes a consistent + * type system that integrates with the Mesh library while maintaining + * independence from backend-specific types. + */ + +namespace svmp { +enum class CellFamily { + Point, + Line, + Triangle, + Quad, + Tetra, + Hex, + Wedge, + Pyramid, + Polygon, + Polyhedron +}; +} // namespace svmp + +#include +#include +#include +#include +#include + +namespace svmp { +namespace FE { + +// ============================================================================ +// Index Types +// ============================================================================ + +/** + * @brief Local index type for element-level operations + * + * Used for local node numbering within elements, local DOF indices, + * and other element-local indexing. Unsigned for safety. + */ +using LocalIndex = std::uint32_t; + +/** + * @brief Global index type for distributed DOF numbering + * + * Signed 64-bit for compatibility with PETSc and Trilinos. + * Negative values can indicate special conditions or invalid indices. + */ +using GlobalIndex = std::int64_t; + +/** + * @brief DOF-specific index type + * + * Strong type alias to prevent mixing DOF indices with other indices. + * Provides type safety at compile time. + */ +struct DofIndex { + GlobalIndex value; + + constexpr explicit DofIndex(GlobalIndex v = -1) noexcept : value(v) {} + constexpr operator GlobalIndex() const noexcept { return value; } + constexpr bool is_valid() const noexcept { return value >= 0; } +}; + +/** + * @brief Field identifier type + * + * Used to distinguish between different physical fields in multi-field problems. + */ +using FieldId = std::uint16_t; + +/** + * @brief Block identifier for block-structured systems + */ +using BlockId = std::uint16_t; + +// Import mesh library scalar/index types when available. +using MeshIndex = std::int32_t; +using MeshOffset = std::int64_t; +using MeshGlobalId = std::int64_t; +using Real = double; + +// ============================================================================ +// Constants +// ============================================================================ + +constexpr LocalIndex INVALID_LOCAL_INDEX = std::numeric_limits::max(); +constexpr GlobalIndex INVALID_GLOBAL_INDEX = -1; +constexpr FieldId INVALID_FIELD_ID = std::numeric_limits::max(); +/// Sentinel FieldId for geometry-only quantities (no DOF dependence). +/// Uses first registered field's space for quadrature, but logically decoupled +/// from any specific field's DOFs. +constexpr FieldId GEOMETRY_FIELD_ID = std::numeric_limits::max() - 1; +constexpr BlockId INVALID_BLOCK_ID = std::numeric_limits::max(); + +/** + * @brief Sentinel FieldId representing "the current solution state" in tangent forms. + * + * When differentiating a residual form to obtain the tangent (Jacobian), undifferentiated + * TrialFunction occurrences are rewritten to StateField nodes. Those that represent the + * block's own primary unknown (rather than a named external field) use this sentinel + * FieldId. The assembler maps it to the current solution coefficients at each quadrature + * point, regardless of which physics or field variables are involved. + * + * This is distinct from INVALID_FIELD_ID, which means "uninitialized / no field." + * CURRENT_SOLUTION_FIELD_ID uses the same numeric value for backward compatibility + * with existing KernelIR encodings, but carries explicit semantic intent. + */ +constexpr FieldId CURRENT_SOLUTION_FIELD_ID = std::numeric_limits::max(); + +// ============================================================================ +// Field Value Entry (for point evaluation of field-dependent expressions) +// ============================================================================ + +/// Maximum number of components in a FieldValueEntry (3x3 tensor). +constexpr int MAX_FIELD_VALUE_COMPONENTS = 9; + +/** + * @brief Field value at an evaluation point — scalar, vector, or tensor. + * + * Used by PointEvaluator and the auxiliary assembly path to supply FE + * field values at entity locations (e.g., nodal DOF values for + * Node-scoped auxiliary models with Lagrange Kronecker delta). + */ +struct FieldValueEntry { + FieldId field{INVALID_FIELD_ID}; + int n_components{0}; + Real components[MAX_FIELD_VALUE_COMPONENTS]{}; +}; + +// ============================================================================ +// Element Type Enumerations +// ============================================================================ + +/** + * @brief Reference element types supported by the FE library + * + * Maps to svmp::CellFamily from the Mesh library but provides + * FE-specific categorization including higher-order variants. + */ +enum class ElementType : std::uint8_t { + // Linear elements + Line2 = 0, // 2-node line + Triangle3 = 1, // 3-node triangle + Quad4 = 2, // 4-node quadrilateral + Tetra4 = 3, // 4-node tetrahedron + Hex8 = 4, // 8-node hexahedron + Wedge6 = 5, // 6-node wedge/prism + Pyramid5 = 6, // 5-node pyramid + + // Quadratic elements + Line3 = 10, // 3-node line + Triangle6 = 11, // 6-node triangle + Quad9 = 12, // 9-node quadrilateral (bi-quadratic) + Quad8 = 13, // 8-node quadrilateral (serendipity) + Tetra10 = 14, // 10-node tetrahedron + Hex27 = 15, // 27-node hexahedron (tri-quadratic) + Hex20 = 16, // 20-node hexahedron (serendipity) + Wedge15 = 17, // 15-node wedge + Wedge18 = 18, // 18-node wedge (complete quadratic) + Pyramid13 = 19, // 13-node pyramid + Pyramid14 = 20, // 14-node pyramid + + // Special elements + Point1 = 30, // 1-node point element + + Unknown = 255 +}; + +/** + * @brief Quadrature rule types + */ +enum class QuadratureType : std::uint8_t { + GaussLegendre, // Standard Gaussian quadrature + GaussLobatto, // Includes endpoints (for spectral elements) + Newton, // Newton-Cotes rules + Reduced, // Order-based reduced integration for locking + PositionBased, // Position-based reduced integration (legacy compatible) + Composite, // Composite rules for adaptivity + Custom // User-defined quadrature points +}; + +/** + * @brief Basis function families + */ +enum class BasisType : std::uint8_t { + Lagrange, // Standard nodal Lagrange basis + Hierarchical, // Hierarchical/modal basis + Bernstein, // Bernstein polynomials + NURBS, // Non-uniform rational B-splines + BSpline, // Non-rational B-spline basis + Spectral, // Spectral element basis + Serendipity, // Serendipity elements + Hermite, // Hermite C1 continuity basis + RaviartThomas, // H(div) Raviart-Thomas family + Nedelec, // H(curl) Nedelec edge elements + BDM, // H(div) Brezzi-Douglas-Marini family + Bubble, // Interior bubble functions for enrichment + Custom // User-defined basis +}; + +/** + * @brief Field types for function spaces + */ +enum class FieldType : std::uint8_t { + Scalar, // Scalar field + Vector, // Vector field + Tensor, // Tensor field + SymmetricTensor, // Symmetric tensor field + Mixed // Mixed/composite field +}; + +/** + * @brief Continuity requirements for function spaces + */ +enum class Continuity : std::uint8_t { + C0, // Continuous (standard FEM) + C1, // C1 continuous (for plates/shells) + L2, // L2 (discontinuous) + H_div, // H(div) conforming + H_curl, // H(curl) conforming + Custom +}; + +/** + * @brief Assembly strategies + */ +enum class AssemblyStrategy : std::uint8_t { + ElementByElement, // Traditional element loop + Vectorized, // SIMD vectorized assembly + MatrixFree, // Matrix-free operators + Hybrid // Mixed strategy +}; + +/** + * @brief Status codes for FE operations + */ +enum class FEStatus : std::uint8_t { + Success = 0, + InvalidArgument = 1, + InvalidElement = 2, + SingularMapping = 3, + QuadratureError = 4, + AssemblyError = 5, + BackendError = 6, + NotImplemented = 7, + ConvergenceError = 8, + AllocationError = 9, + MPIError = 10, + IOError = 11, + Unknown = 255 +}; + +// ============================================================================ +// Geometric Types +// ============================================================================ + +/** + * @brief Point in reference element coordinates + */ +template +using ReferencePoint = std::array(Dim)>; + +/** + * @brief Point in physical coordinates + */ +using PhysicalPoint = std::array; + +/** + * @brief Jacobian matrix type + */ +template +using Jacobian = std::array(ReferenceDim)>, static_cast(SpatialDim)>; + +// ============================================================================ +// Strong Type Wrappers +// ============================================================================ + +/** + * @brief Strong type wrapper template for type-safe programming + * + * Prevents accidental mixing of conceptually different types that have + * the same underlying representation. + */ +template +class StrongType { +public: + using ValueType = T; + + constexpr StrongType() noexcept(std::is_nothrow_default_constructible_v) + : value_{} {} + + constexpr explicit StrongType(T value) noexcept(std::is_nothrow_move_constructible_v) + : value_(std::move(value)) {} + + constexpr T& get() noexcept { return value_; } + constexpr const T& get() const noexcept { return value_; } + + // Explicit conversion + constexpr explicit operator T() const noexcept { return value_; } + + // Comparison operators + constexpr bool operator==(const StrongType& other) const noexcept { + return value_ == other.value_; + } + constexpr bool operator!=(const StrongType& other) const noexcept { + return value_ != other.value_; + } + constexpr bool operator<(const StrongType& other) const noexcept { + return value_ < other.value_; + } + +private: + T value_; +}; + +// Specific strong types for common use cases +struct QuadraturePointTag {}; +struct QuadratureWeightTag {}; +struct BasisValueTag {}; +struct BasisGradientTag {}; + +using QuadraturePointIndex = StrongType; +using QuadratureWeight = StrongType; + +// ============================================================================ +// Type Traits +// ============================================================================ + +/** + * @brief Check if a type is a valid index type + */ +template +struct is_index_type : std::false_type {}; + +template<> +struct is_index_type : std::true_type {}; + +template<> +struct is_index_type : std::true_type {}; + +template<> +struct is_index_type : std::true_type {}; + +template +inline constexpr bool is_index_type_v = is_index_type::value; + +/** + * @brief Check if a type represents a field type + */ +template +struct is_field_type : std::false_type {}; + +template<> +struct is_field_type : std::true_type {}; + +template +inline constexpr bool is_field_type_v = is_field_type::value; + +// ============================================================================ +// Utility Functions +// ============================================================================ + +/** + * @brief Convert FE ElementType to Mesh CellFamily + */ +constexpr svmp::CellFamily to_mesh_family(ElementType elem) noexcept { + switch(elem) { + case ElementType::Line2: + case ElementType::Line3: + return svmp::CellFamily::Line; + + case ElementType::Triangle3: + case ElementType::Triangle6: + return svmp::CellFamily::Triangle; + + case ElementType::Quad4: + case ElementType::Quad8: + case ElementType::Quad9: + return svmp::CellFamily::Quad; + + case ElementType::Tetra4: + case ElementType::Tetra10: + return svmp::CellFamily::Tetra; + + case ElementType::Hex8: + case ElementType::Hex20: + case ElementType::Hex27: + return svmp::CellFamily::Hex; + + case ElementType::Wedge6: + case ElementType::Wedge15: + case ElementType::Wedge18: + return svmp::CellFamily::Wedge; + + case ElementType::Pyramid5: + case ElementType::Pyramid13: + case ElementType::Pyramid14: + return svmp::CellFamily::Pyramid; + + case ElementType::Point1: + return svmp::CellFamily::Point; + + default: + return svmp::CellFamily::Point; // Fallback + } +} + +/** + * @brief Get spatial dimension of element type + */ +constexpr int element_dimension(ElementType elem) noexcept { + switch(elem) { + case ElementType::Point1: + return 0; + case ElementType::Line2: + case ElementType::Line3: + return 1; + case ElementType::Triangle3: + case ElementType::Triangle6: + case ElementType::Quad4: + case ElementType::Quad8: + case ElementType::Quad9: + return 2; + case ElementType::Tetra4: + case ElementType::Tetra10: + case ElementType::Hex8: + case ElementType::Hex20: + case ElementType::Hex27: + case ElementType::Wedge6: + case ElementType::Wedge15: + case ElementType::Wedge18: + case ElementType::Pyramid5: + case ElementType::Pyramid13: + case ElementType::Pyramid14: + return 3; + default: + return -1; + } +} + +/** + * @brief Convert status code to string for error reporting + */ +inline const char* status_to_string(FEStatus status) noexcept { + switch(status) { + case FEStatus::Success: return "Success"; + case FEStatus::InvalidArgument: return "Invalid argument"; + case FEStatus::InvalidElement: return "Invalid element"; + case FEStatus::SingularMapping: return "Singular mapping"; + case FEStatus::QuadratureError: return "Quadrature error"; + case FEStatus::AssemblyError: return "Assembly error"; + case FEStatus::BackendError: return "Backend error"; + case FEStatus::NotImplemented: return "Not implemented"; + case FEStatus::ConvergenceError: return "Convergence error"; + case FEStatus::AllocationError: return "Allocation error"; + case FEStatus::MPIError: return "MPI error"; + case FEStatus::IOError: return "I/O error"; + default: return "Unknown error"; + } +} + +} // namespace FE +} // namespace svmp + +#endif // SVMP_FE_TYPES_H From 8fa1a06169ccc8123e009ae9fb8d400cb9f2ac50 Mon Sep 17 00:00:00 2001 From: Zachary Sexton Date: Mon, 20 Apr 2026 19:02:39 -0700 Subject: [PATCH 02/15] Add shared solver and FE exception headers --- Code/Source/solver/Core/Exception.h | 400 +++++++++++++++++++++ Code/Source/solver/Core/PlatformSupport.h | 163 +++++++++ Code/Source/solver/FE/Common/FEException.h | 347 ++++++++++++++++++ 3 files changed, 910 insertions(+) create mode 100644 Code/Source/solver/Core/Exception.h create mode 100644 Code/Source/solver/Core/PlatformSupport.h create mode 100644 Code/Source/solver/FE/Common/FEException.h diff --git a/Code/Source/solver/Core/Exception.h b/Code/Source/solver/Core/Exception.h new file mode 100644 index 000000000..86fbf05b4 --- /dev/null +++ b/Code/Source/solver/Core/Exception.h @@ -0,0 +1,400 @@ +// SPDX-FileCopyrightText: Copyright (c) Stanford University, The Regents of the University of California, and others. +// SPDX-License-Identifier: BSD-3-Clause + +#ifndef SVMP_CORE_EXCEPTION_H +#define SVMP_CORE_EXCEPTION_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#if !defined(NDEBUG) || defined(DEBUG) || defined(_DEBUG) +# define SVMP_EXCEPTION_DEBUG_MODE 1 +#else +# define SVMP_EXCEPTION_DEBUG_MODE 0 +#endif + +namespace svmp { + +enum class StatusCode : std::uint8_t { + Success = 0, + InvalidArgument, + InvalidState, + ParseError, + IOError, + ResourceExhausted, + DependencyError, + MPIError, + NotImplemented, + UnsupportedOperation, + InternalError, + Unknown = 255 +}; + +inline const char* status_code_to_string(StatusCode status) noexcept +{ + switch (status) { + case StatusCode::Success: + return "Success"; + case StatusCode::InvalidArgument: + return "Invalid argument"; + case StatusCode::InvalidState: + return "Invalid state"; + case StatusCode::ParseError: + return "Parse error"; + case StatusCode::IOError: + return "I/O error"; + case StatusCode::ResourceExhausted: + return "Resource exhausted"; + case StatusCode::DependencyError: + return "Dependency error"; + case StatusCode::MPIError: + return "MPI error"; + case StatusCode::NotImplemented: + return "Not implemented"; + case StatusCode::UnsupportedOperation: + return "Unsupported operation"; + case StatusCode::InternalError: + return "Internal error"; + default: + return "Unknown error"; + } +} + +struct SourceLocation { + const char* file; + int line; + const char* function; +}; + +class StackTraceFrame final { +public: + StackTraceFrame() = default; + + StackTraceFrame(std::string symbol, std::string module, std::string file, + int line, std::uintptr_t address) noexcept + : symbol_(std::move(symbol)), + module_(std::move(module)), + file_(std::move(file)), + line_(line), + address_(address) + { + } + + const std::string& symbol() const noexcept { return symbol_; } + const std::string& module() const noexcept { return module_; } + const std::string& file() const noexcept { return file_; } + int line() const noexcept { return line_; } + std::uintptr_t address() const noexcept { return address_; } + +private: + std::string symbol_; + std::string module_; + std::string file_; + int line_ = 0; + std::uintptr_t address_ = 0; +}; + +class StackTrace final { +public: + bool empty() const noexcept { return frames_.empty(); } + std::size_t size() const noexcept { return frames_.size(); } + const std::vector& frames() const noexcept { return frames_; } + void add_frame(StackTraceFrame frame) { frames_.push_back(std::move(frame)); } + +private: + std::vector frames_; +}; + +struct PlatformCapabilities final { + bool has_stack_trace; + bool has_symbol_resolution; + bool has_demangling; +}; + +class PlatformSupport final { +public: + static PlatformCapabilities capabilities() noexcept; + static int query_mpi_rank() noexcept; + static StackTrace capture_stack_trace(); + static std::string demangle_symbol(const char* symbol); + static void finalize_mpi_if_needed() noexcept; + static void abort_mpi_if_needed(int exit_code) noexcept; +}; +} // namespace svmp + +#include "Core/PlatformSupport.h" + +namespace svmp { + +class ExceptionContext final { +public: + StatusCode status_code() const noexcept { return status_code_; } + const std::string& file() const noexcept { return file_; } + int line() const noexcept { return line_; } + const std::string& function() const noexcept { return function_; } + int mpi_rank() const noexcept { return mpi_rank_; } + const StackTrace& stack_trace() const noexcept { return stack_trace_; } + + void set_status_code(StatusCode status_code) noexcept + { + status_code_ = status_code; + } + + void set_source_location(const char* file, int line, const char* function) + { + file_ = (file == nullptr) ? std::string() : std::string(file); + line_ = line; + function_ = + (function == nullptr) ? std::string() : std::string(function); + } + + void set_mpi_rank(int mpi_rank) noexcept { mpi_rank_ = mpi_rank; } + void set_stack_trace(StackTrace stack_trace) + { + stack_trace_ = std::move(stack_trace); + } + +private: + StatusCode status_code_ = StatusCode::Unknown; + std::string file_; + int line_ = 0; + std::string function_; + int mpi_rank_ = -1; + StackTrace stack_trace_; +}; + +class ExceptionFormatter final { +public: + static std::string format(const ExceptionContext& context, + const std::string& message, + const char* subsystem_label) + { + std::ostringstream oss; + + oss << "[" << ((subsystem_label == nullptr) ? "Exception" : subsystem_label) + << "] " << status_code_to_string(context.status_code()); + if (context.mpi_rank() >= 0) { + oss << " (Rank " << context.mpi_rank() << ")"; + } + oss << "\n"; + + if (!context.file().empty()) { + oss << " Location: " << context.file() << ":" << context.line(); + if (!context.function().empty()) { + oss << " in " << context.function() << "()"; + } + oss << "\n"; + } + + oss << " Message: " << message << "\n"; + + if (!context.stack_trace().empty()) { + oss << " Stack trace:\n"; + std::size_t frame_index = 0; + for (const auto& frame : context.stack_trace().frames()) { + oss << " #" << frame_index++ << " "; + if (!frame.symbol().empty()) { + oss << frame.symbol(); + } else { + std::ostringstream address; + address << "0x" << std::hex << frame.address(); + oss << address.str(); + } + + if (!frame.module().empty()) { + oss << " [" << frame.module() << "]"; + } + + if (!frame.file().empty()) { + oss << " (" << frame.file(); + if (frame.line() > 0) { + oss << ":" << frame.line(); + } + oss << ")"; + } + + oss << "\n"; + } + } + + return oss.str(); + } +}; + +class ExceptionBase; + +class ExceptionRuntime final { +public: + static int query_mpi_rank() noexcept + { + return PlatformSupport::query_mpi_rank(); + } + + static StackTrace capture_stack_trace() + { + return PlatformSupport::capture_stack_trace(); + } + + static void finalize_mpi_if_needed() noexcept + { + PlatformSupport::finalize_mpi_if_needed(); + } + + static void install_terminate_handler(); + + static void report_unhandled_exception(const std::exception& exception) + { + std::cerr << exception.what() << std::endl; + } + + static void abort_mpi_if_needed(int exit_code) noexcept + { + PlatformSupport::abort_mpi_if_needed(exit_code); + } +}; + +class ExceptionBase : public std::exception { +public: + const char* what() const noexcept override { return what_.c_str(); } + StatusCode status_code() const noexcept { return context_.status_code(); } + const std::string& message() const noexcept { return message_; } + const ExceptionContext& context() const noexcept { return context_; } + + void add_context(const std::string& context) + { + message_ = context + "\n -> " + message_; + rebuild_what(subsystem_label()); + } + + virtual ~ExceptionBase() noexcept = default; + +protected: + ExceptionBase(std::string message, StatusCode status, const char* file, + int line, const char* function) + : message_(std::move(message)) + { + context_.set_status_code(status); + context_.set_source_location(file, line, function); + context_.set_mpi_rank(ExceptionRuntime::query_mpi_rank()); +#if SVMP_EXCEPTION_DEBUG_MODE + context_.set_stack_trace(ExceptionRuntime::capture_stack_trace()); +#endif + } + + virtual const char* subsystem_label() const noexcept = 0; + + void rebuild_what(const char* subsystem_label) + { + what_ = ExceptionFormatter::format(context_, message_, subsystem_label); + } + + std::string message_; + ExceptionContext context_; + std::string what_; +}; + +class CoreException : public ExceptionBase { +public: + CoreException(const std::string& message, + StatusCode status = StatusCode::Unknown, + const char* file = "", + int line = 0, + const char* function = "") + : ExceptionBase(message, status, file, line, function) + { + rebuild_what(subsystem_label()); + } + +protected: + const char* subsystem_label() const noexcept override + { + return "Core Exception"; + } +}; + +class ParseException : public CoreException { +public: + ParseException(const std::string& message, + const char* file = "", + int line = 0, + const char* function = "") + : CoreException(message, StatusCode::ParseError, file, line, function) + { + } +}; + +class DependencyException : public CoreException { +public: + DependencyException(const std::string& message, + const char* file = "", + int line = 0, + const char* function = "") + : CoreException(message, StatusCode::DependencyError, file, line, + function) + { + } +}; + +inline void ExceptionRuntime::install_terminate_handler() +{ + std::set_terminate([]() { + try { + const std::exception_ptr current = std::current_exception(); + if (current != nullptr) { + std::rethrow_exception(current); + } + } catch (const std::exception& exception) { + ExceptionRuntime::report_unhandled_exception(exception); + } catch (...) { + std::cerr << "[Unhandled Exception] Unknown non-std exception" + << std::endl; + } + + ExceptionRuntime::abort_mpi_if_needed(EXIT_FAILURE); + std::abort(); + }); +} + +template +[[noreturn]] void raise(SourceLocation location, Args&&... args) +{ + throw ExceptionT(std::forward(args)..., location.file, location.line, + location.function); +} + +template +void check(bool condition, SourceLocation location, Args&&... args) +{ + if (!condition) { + raise(location, std::forward(args)...); + } +} + +template +void check_arg(bool condition, SourceLocation location, Args&&... args) +{ + if (!condition) { + raise(location, std::forward(args)...); + } +} + +template +PointerT* check_not_null(PointerT* ptr, SourceLocation location, Args&&... args) +{ + if (ptr == nullptr) { + raise(location, std::forward(args)...); + } + return ptr; +} + +} // namespace svmp + +#define SVMP_HERE ::svmp::SourceLocation{__FILE__, __LINE__, __func__} + +#endif // SVMP_CORE_EXCEPTION_H diff --git a/Code/Source/solver/Core/PlatformSupport.h b/Code/Source/solver/Core/PlatformSupport.h new file mode 100644 index 000000000..7117dae5b --- /dev/null +++ b/Code/Source/solver/Core/PlatformSupport.h @@ -0,0 +1,163 @@ +// SPDX-FileCopyrightText: Copyright (c) Stanford University, The Regents of the University of California, and others. +// SPDX-License-Identifier: BSD-3-Clause + +#ifndef SVMP_CORE_PLATFORM_SUPPORT_H +#define SVMP_CORE_PLATFORM_SUPPORT_H + +#include + +#include +#include +#include +#include +#include + +#if defined(_WIN32) +# include +#elif defined(__GNUC__) || defined(__clang__) +# include +# include +#endif + +namespace svmp { + +inline PlatformCapabilities PlatformSupport::capabilities() noexcept +{ +#if defined(_WIN32) + return PlatformCapabilities{true, false, false}; +#elif defined(__GNUC__) || defined(__clang__) + return PlatformCapabilities{true, true, true}; +#else + return PlatformCapabilities{false, false, false}; +#endif +} + +inline int PlatformSupport::query_mpi_rank() noexcept +{ + int initialized = 0; + MPI_Initialized(&initialized); + if (!initialized) { + return -1; + } + + int finalized = 0; + MPI_Finalized(&finalized); + if (finalized) { + return -1; + } + + int rank = -1; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + return rank; +} + +inline std::string PlatformSupport::demangle_symbol(const char* symbol) +{ + if (symbol == nullptr) { + return std::string(); + } + +#if defined(__GNUC__) || defined(__clang__) + int status = 0; + char* demangled = abi::__cxa_demangle(symbol, nullptr, nullptr, &status); + if (status == 0 && demangled != nullptr) { + std::string result(demangled); + std::free(demangled); + return result; + } +#endif + + return std::string(symbol); +} + +inline StackTrace PlatformSupport::capture_stack_trace() +{ + StackTrace trace; + +#if defined(_WIN32) + constexpr unsigned long max_frames = 32; + void* frames[max_frames] = {}; + const USHORT count = CaptureStackBackTrace(0, max_frames, frames, nullptr); + + for (USHORT i = 1; i < count; ++i) { + const std::uintptr_t address = + reinterpret_cast(frames[i]); + std::ostringstream symbol; + symbol << "0x" << std::hex << address; + trace.add_frame(StackTraceFrame(symbol.str(), std::string(), + std::string(), 0, address)); + } +#elif defined(__GNUC__) || defined(__clang__) + constexpr int max_frames = 32; + void* frames[max_frames] = {}; + const int count = backtrace(frames, max_frames); + char** symbols = backtrace_symbols(frames, count); + + if (symbols != nullptr) { + for (int i = 2; i < count; ++i) { + const std::uintptr_t address = + reinterpret_cast(frames[i]); + std::string symbol(symbols[i]); + + const std::size_t start = symbol.find('('); + const std::size_t end = symbol.find('+', start); + if (start != std::string::npos && end != std::string::npos && + end > start + 1) { + const std::string mangled = + symbol.substr(start + 1, end - start - 1); + const std::string demangled = + PlatformSupport::demangle_symbol(mangled.c_str()); + if (!demangled.empty()) { + symbol.replace(start + 1, end - start - 1, demangled); + } + } + + trace.add_frame( + StackTraceFrame(symbol, std::string(), std::string(), 0, address)); + } + std::free(symbols); + } +#endif + + return trace; +} + +inline void PlatformSupport::finalize_mpi_if_needed() noexcept +{ + int initialized = 0; + MPI_Initialized(&initialized); + if (!initialized) { + return; + } + + int finalized = 0; + MPI_Finalized(&finalized); + if (!finalized) { + MPI_Finalize(); + } +} + +inline void PlatformSupport::abort_mpi_if_needed(int exit_code) noexcept +{ + int initialized = 0; + MPI_Initialized(&initialized); + if (!initialized) { + return; + } + + int finalized = 0; + MPI_Finalized(&finalized); + if (finalized) { + return; + } + + int size = 1; + MPI_Comm_size(MPI_COMM_WORLD, &size); + if (size > 1) { + MPI_Abort(MPI_COMM_WORLD, exit_code); + } +} + +} // namespace svmp + +#endif // SVMP_CORE_PLATFORM_SUPPORT_H diff --git a/Code/Source/solver/FE/Common/FEException.h b/Code/Source/solver/FE/Common/FEException.h new file mode 100644 index 000000000..20ee0eef6 --- /dev/null +++ b/Code/Source/solver/FE/Common/FEException.h @@ -0,0 +1,347 @@ +// SPDX-FileCopyrightText: Copyright (c) Stanford University, The Regents of the University of California, and others. +// SPDX-License-Identifier: BSD-3-Clause + +#ifndef SVMP_FE_EXCEPTION_H +#define SVMP_FE_EXCEPTION_H + +/** + * @file FEException.h + * @brief Exception hierarchy for error handling in the FE library + * + * This header defines FE-specific exception types that derive from the shared + * solver exception infrastructure in Core/Exception.h. + */ + +#include "Core/Exception.h" + +#include +#include +#include + +namespace svmp { +namespace FE { + +class FEException : public ExceptionBase { +public: + FEException(const std::string& message, + StatusCode status = StatusCode::Unknown, + const char* file = "", + int line = 0, + const char* function = "") + : ExceptionBase(prefix_status_message(message, status), + status, + file, + line, + function), + status_(status) + { + rebuild_what(subsystem_label()); + } + + FEException(const std::string& message, + const char* file, + int line, + const char* function = "") + : FEException(message, StatusCode::Unknown, file, line, function) + { + } + + StatusCode status() const noexcept { return status_; } + +protected: + const char* subsystem_label() const noexcept override + { + return "FE Exception"; + } + +private: + static std::string prefix_status_message(const std::string& message, + StatusCode status) + { + if (status == StatusCode::Success || status == StatusCode::Unknown) { + return message; + } + + return std::string("[") + status_code_to_string(status) + "] " + + message; + } + + StatusCode status_ = StatusCode::Unknown; +}; + +class InvalidArgumentException : public FEException { +public: + InvalidArgumentException(const std::string& message, + const char* file = "", + int line = 0, + const char* function = "") + : FEException(message, StatusCode::InvalidArgument, file, line, + function) + { + } +}; + +class InvalidElementException : public FEException { +public: + InvalidElementException(const std::string& message, + std::string element_type = "", + const char* file = "", + int line = 0, + const char* function = "") + : FEException(build_message(message, element_type), + StatusCode::InvalidArgument, + file, + line, + function), + element_type_(std::move(element_type)) + { + } + + const std::string& element_type() const noexcept { return element_type_; } + +private: + static std::string build_message(const std::string& message, + const std::string& element_type) + { + if (element_type.empty()) { + return message; + } + + return message + " (Element type: " + element_type + ")"; + } + + std::string element_type_; +}; + +class DofException : public FEException { +public: + DofException(const std::string& message, + long long dof_index = invalid_dof_index(), + const char* file = "", + int line = 0, + const char* function = "") + : FEException(build_message(message, dof_index), + StatusCode::InvalidArgument, + file, + line, + function), + dof_index_(dof_index) + { + } + + long long dof_index() const noexcept { return dof_index_; } + static constexpr long long invalid_dof_index() noexcept { return -1; } + +private: + static std::string build_message(const std::string& message, + long long dof_index) + { + if (dof_index == invalid_dof_index()) { + return message; + } + + return message + " (DOF index: " + std::to_string(dof_index) + ")"; + } + + long long dof_index_ = invalid_dof_index(); +}; + +class AssemblyException : public FEException { +public: + AssemblyException(const std::string& message, + const char* file = "", + int line = 0, + const char* function = "") + : FEException(message, StatusCode::InvalidState, file, line, function) + { + } +}; + +class BackendException : public FEException { +public: + BackendException(const std::string& message, + std::string backend_name = "", + int error_code = 0, + const char* file = "", + int line = 0, + const char* function = "") + : FEException(build_message(message, backend_name, error_code), + StatusCode::DependencyError, + file, + line, + function), + backend_name_(std::move(backend_name)), + error_code_(error_code) + { + } + + const std::string& backend_name() const noexcept { return backend_name_; } + int error_code() const noexcept { return error_code_; } + +private: + static std::string build_message(const std::string& message, + const std::string& backend_name, + int error_code) + { + std::ostringstream oss; + oss << message; + if (!backend_name.empty() || error_code != 0) { + oss << " ("; + if (!backend_name.empty()) { + oss << "Backend: " << backend_name; + } + if (error_code != 0) { + if (!backend_name.empty()) { + oss << ", "; + } + oss << "Error code: " << error_code; + } + oss << ")"; + } + return oss.str(); + } + + std::string backend_name_; + int error_code_ = 0; +}; + +class NotImplementedException : public FEException { +public: + NotImplementedException(const std::string& feature, + const char* file = "", + int line = 0, + const char* function = "") + : FEException("Feature not implemented: " + feature, + StatusCode::NotImplemented, + file, + line, + function) + { + } +}; + +class ConvergenceException : public FEException { +public: + ConvergenceException(const std::string& message, + int iteration = -1, + double residual = 0.0, + const char* file = "", + int line = 0, + const char* function = "") + : FEException(build_message(message, iteration, residual), + StatusCode::InvalidState, + file, + line, + function), + iteration_(iteration), + residual_(residual) + { + } + + int iteration() const noexcept { return iteration_; } + double residual() const noexcept { return residual_; } + +private: + static std::string build_message(const std::string& message, + int iteration, + double residual) + { + std::ostringstream oss; + oss << message; + if (iteration >= 0) { + oss << " (Iteration: " << iteration; + if (residual > 0.0) { + oss << ", Residual: " << residual; + } + oss << ")"; + } + return oss.str(); + } + + int iteration_ = -1; + double residual_ = 0.0; +}; + +class SingularMappingException : public FEException { +public: + SingularMappingException(const std::string& message, + double jacobian_det = 0.0, + const char* file = "", + int line = 0, + const char* function = "") + : FEException(build_message(message, jacobian_det), + StatusCode::InvalidState, + file, + line, + function), + jacobian_det_(jacobian_det) + { + } + + double jacobian_det() const noexcept { return jacobian_det_; } + +private: + static std::string build_message(const std::string& message, double det) + { + return message + " (Jacobian determinant: " + std::to_string(det) + + ")"; + } + + double jacobian_det_ = 0.0; +}; + +template +[[noreturn]] inline void raise(SourceLocation location, Args&&... args) +{ + ::svmp::raise(location, std::forward(args)...); +} + +template +inline void throw_if(bool condition, SourceLocation location, Args&&... args) +{ + if (condition) { + ::svmp::FE::raise(location, std::forward(args)...); + } +} + +template +inline void check_arg(bool condition, SourceLocation location, Args&&... args) +{ + ::svmp::check_arg(condition, location, + std::forward(args)...); +} + +template +inline PointerT* check_not_null(PointerT* ptr, SourceLocation location, + Args&&... args) +{ + return ::svmp::check_not_null(ptr, location, + std::forward(args)...); +} + +template +inline void check_index(IndexT index, SizeT size, SourceLocation location) +{ + const long long fe_check_index_value = static_cast(index); + const long long fe_check_size_value = static_cast(size); + + ::svmp::FE::check_arg( + fe_check_index_value >= 0 && + fe_check_index_value < fe_check_size_value, + location, + "Index " + std::to_string(fe_check_index_value) + + " out of bounds [0, " + std::to_string(fe_check_size_value) + ")"); +} + +[[noreturn]] inline void not_implemented(const std::string& feature, + SourceLocation location) +{ + ::svmp::FE::raise(location, feature); +} + +} // namespace FE +} // namespace svmp + +#endif // SVMP_FE_EXCEPTION_H From a873af508815ac65fdb9849ef8569e5b1c0f6401 Mon Sep 17 00:00:00 2001 From: Zachary Sexton Date: Tue, 21 Apr 2026 01:04:37 -0700 Subject: [PATCH 03/15] deleting the old Core folder from the FE library --- Code/Source/solver/FE/Core/FEConfig.h | 404 ---------------- Code/Source/solver/FE/Core/FEException.h | 566 ----------------------- Code/Source/solver/FE/Core/Types.h | 476 ------------------- 3 files changed, 1446 deletions(-) delete mode 100644 Code/Source/solver/FE/Core/FEConfig.h delete mode 100644 Code/Source/solver/FE/Core/FEException.h delete mode 100644 Code/Source/solver/FE/Core/Types.h diff --git a/Code/Source/solver/FE/Core/FEConfig.h b/Code/Source/solver/FE/Core/FEConfig.h deleted file mode 100644 index 82e2fb6fc..000000000 --- a/Code/Source/solver/FE/Core/FEConfig.h +++ /dev/null @@ -1,404 +0,0 @@ -// SPDX-FileCopyrightText: Copyright (c) Stanford University, The Regents of the University of California, and others. -// SPDX-License-Identifier: BSD-3-Clause - -#ifndef SVMP_FE_CONFIG_H -#define SVMP_FE_CONFIG_H - -/** - * @file FEConfig.h - * @brief Compile-time configuration and feature settings for the FE library - * - * This header centralizes all compile-time configuration options, - * feature flags, and platform-specific settings for the finite element - * library. Settings can be overridden via CMake or compiler flags. - */ - -#include "Types.h" -#include - -// ============================================================================ -// Build Configuration Detection -// ============================================================================ - -// Debug/Release mode detection -#if !defined(NDEBUG) || defined(DEBUG) || defined(_DEBUG) - #define FE_DEBUG_MODE 1 -#else - #define FE_DEBUG_MODE 0 -#endif - -#if FE_DEBUG_MODE -#include -#endif - -// MPI support detection. -// -// FE can be built in two modes: -// - Integrated with Mesh: Mesh defines `MESH_HAS_MPI` when compiled with MPI. -// - Standalone FE with MPI: FE's CMake sets `MESH_ENABLE_MPI` for compatibility. -// -// Normalize to a numeric macro suitable for `#if FE_HAS_MPI` use. -#ifdef FE_HAS_MPI -# undef FE_HAS_MPI -#endif -#if defined(MESH_HAS_MPI) || defined(MESH_ENABLE_MPI) -# define FE_HAS_MPI 1 -#else -# define FE_HAS_MPI 0 -#endif - -// OpenMP support detection -#ifdef _OPENMP - #define FE_HAS_OPENMP 1 - #include -#else - #define FE_HAS_OPENMP 0 -#endif - -namespace svmp { -namespace FE { -namespace config { - -// ============================================================================ -// Dimension Configuration -// ============================================================================ - -/** - * @brief Maximum spatial dimension supported - * - * Controls template instantiations and static array sizes. - * Can be overridden at compile time with -DFE_MAX_DIM=N - */ -#ifndef FE_MAX_DIM - constexpr int MAX_SPATIAL_DIM = 3; -#else - constexpr int MAX_SPATIAL_DIM = FE_MAX_DIM; -#endif - -/** - * @brief Enable specific dimensions - * - * Controls which template instantiations are generated. - * Disabling unused dimensions reduces binary size. - */ -#ifndef FE_ENABLE_1D - #define FE_ENABLE_1D 1 -#endif - -#ifndef FE_ENABLE_2D - #define FE_ENABLE_2D 1 -#endif - -#ifndef FE_ENABLE_3D - #define FE_ENABLE_3D 1 -#endif - -// ============================================================================ -// Precision Configuration -// ============================================================================ - -/** - * @brief Floating point precision settings - */ -enum class Precision { - Single, // 32-bit float - Double, // 64-bit double (default) - Quad // 128-bit quad precision (if available) -}; - -/** - * @brief Default precision for FE computations - * - * Can be overridden with -DFE_USE_SINGLE_PRECISION - */ -#ifdef FE_USE_SINGLE_PRECISION - constexpr Precision DEFAULT_PRECISION = Precision::Single; - using DefaultReal = float; -#elif defined(FE_USE_QUAD_PRECISION) - constexpr Precision DEFAULT_PRECISION = Precision::Quad; - using DefaultReal = long double; -#else - constexpr Precision DEFAULT_PRECISION = Precision::Double; - using DefaultReal = double; -#endif - -// ============================================================================ -// Backend Configuration -// ============================================================================ - -/** - * @brief Linear algebra backend selection - */ -enum class Backend { - None, // No backend (testing only) - Trilinos, // Trilinos (Epetra/Tpetra) - PETSc, // PETSc - Eigen, // Eigen (for small/local problems) - Custom // User-provided backend -}; - -/** - * @brief Default backend selection - * - * Set via CMake based on available libraries - */ -#if defined(FE_USE_TRILINOS) - constexpr Backend DEFAULT_BACKEND = Backend::Trilinos; - #define FE_HAS_TRILINOS 1 -#elif defined(FE_USE_PETSC) - constexpr Backend DEFAULT_BACKEND = Backend::PETSc; - #define FE_HAS_PETSC 1 -#elif defined(FE_USE_EIGEN) - constexpr Backend DEFAULT_BACKEND = Backend::Eigen; - #define FE_HAS_EIGEN 1 -#else - constexpr Backend DEFAULT_BACKEND = Backend::None; -#endif - -// ============================================================================ -// Performance Configuration -// ============================================================================ - -/** - * @brief Cache line size in bytes - * - * Used for memory alignment and padding to avoid false sharing - */ -#ifndef FE_CACHE_LINE_SIZE - constexpr std::size_t CACHE_LINE_SIZE = 64; -#else - constexpr std::size_t CACHE_LINE_SIZE = FE_CACHE_LINE_SIZE; -#endif - -/** - * @brief Enable SIMD vectorization - * - * Controls whether vectorized implementations are used - */ -#ifndef FE_ENABLE_VECTORIZATION - #if defined(__AVX512F__) - #define FE_VECTORIZATION_LEVEL 512 - #define FE_ENABLE_VECTORIZATION 1 - #elif defined(__AVX2__) - #define FE_VECTORIZATION_LEVEL 256 - #define FE_ENABLE_VECTORIZATION 1 - #elif defined(__SSE4_2__) - #define FE_VECTORIZATION_LEVEL 128 - #define FE_ENABLE_VECTORIZATION 1 - #else - #define FE_VECTORIZATION_LEVEL 0 - #define FE_ENABLE_VECTORIZATION 0 - #endif -#endif - -/** - * @brief Enable aggressive loop unrolling - */ -#ifndef FE_ENABLE_LOOP_UNROLL - #define FE_ENABLE_LOOP_UNROLL 1 -#endif - -/** - * @brief Assembly kernel caching - * - * Cache element matrices/vectors to reduce recomputation - */ -#ifndef FE_ENABLE_ASSEMBLY_CACHE - #define FE_ENABLE_ASSEMBLY_CACHE 1 -#endif - -/** - * @brief Maximum elements to process in vectorized batches - */ -constexpr std::size_t VECTOR_BATCH_SIZE = 32; - -// ============================================================================ -// Memory Configuration -// ============================================================================ - -/** - * @brief Memory allocation strategies - */ -enum class AllocStrategy { - Standard, // Standard new/delete - Pooled, // Memory pool allocator - Aligned, // Aligned allocation for SIMD - Custom // User-provided allocator -}; - -/** - * @brief Default memory allocation strategy - */ -#ifdef FE_USE_MEMORY_POOL - constexpr AllocStrategy DEFAULT_ALLOC_STRATEGY = AllocStrategy::Pooled; -#elif FE_ENABLE_VECTORIZATION - constexpr AllocStrategy DEFAULT_ALLOC_STRATEGY = AllocStrategy::Aligned; -#else - constexpr AllocStrategy DEFAULT_ALLOC_STRATEGY = AllocStrategy::Standard; -#endif - -/** - * @brief Stack array size threshold - * - * Arrays smaller than this use stack allocation - */ -constexpr std::size_t STACK_ARRAY_THRESHOLD = 256; - -// ============================================================================ -// Debug/Diagnostic Configuration -// ============================================================================ - -/** - * @brief Bounds checking level - */ -enum class BoundsCheckLevel { - None, // No bounds checking (release mode) - Basic, // Basic index validation - Full // Full bounds and consistency checking -}; - -#if FE_DEBUG_MODE - constexpr BoundsCheckLevel BOUNDS_CHECK_LEVEL = BoundsCheckLevel::Full; - constexpr bool ENABLE_ASSERTIONS = true; - constexpr bool ENABLE_NAN_CHECK = true; -#else - constexpr BoundsCheckLevel BOUNDS_CHECK_LEVEL = BoundsCheckLevel::None; - constexpr bool ENABLE_ASSERTIONS = false; - constexpr bool ENABLE_NAN_CHECK = false; -#endif - -/** - * @brief Performance profiling - */ -#ifndef FE_ENABLE_PROFILING - #define FE_ENABLE_PROFILING FE_DEBUG_MODE -#endif - -/** - * @brief Detailed timing of assembly operations - */ -#ifndef FE_ENABLE_TIMING - #define FE_ENABLE_TIMING 0 -#endif - -// ============================================================================ -// Feature Toggles -// ============================================================================ - - -/** - * @brief Maximum polynomial order supported - */ -#ifndef FE_MAX_POLYNOMIAL_ORDER - constexpr int MAX_POLYNOMIAL_ORDER = 10; -#else - constexpr int MAX_POLYNOMIAL_ORDER = FE_MAX_POLYNOMIAL_ORDER; -#endif - -// ============================================================================ -// Quadrature Configuration -// ============================================================================ - - -/** - * @brief Cache quadrature rules - */ -#ifndef FE_CACHE_QUADRATURE - #define FE_CACHE_QUADRATURE 1 -#endif - -// ============================================================================ -// Compiler Hints and Attributes -// ============================================================================ - -// Likely/unlikely branch hints -#if defined(__GNUC__) || defined(__clang__) - #define FE_LIKELY(x) __builtin_expect(!!(x), 1) - #define FE_UNLIKELY(x) __builtin_expect(!!(x), 0) -#else - #define FE_LIKELY(x) (x) - #define FE_UNLIKELY(x) (x) -#endif - -// Force inline -#if defined(_MSC_VER) - #define FE_ALWAYS_INLINE __forceinline -#elif defined(__GNUC__) || defined(__clang__) - #define FE_ALWAYS_INLINE __attribute__((always_inline)) inline -#else - #define FE_ALWAYS_INLINE inline -#endif - -// Restrict pointer aliasing -#if defined(__GNUC__) || defined(__clang__) - #define FE_RESTRICT __restrict__ -#elif defined(_MSC_VER) - #define FE_RESTRICT __restrict -#else - #define FE_RESTRICT -#endif - -// Memory alignment -#if defined(__GNUC__) || defined(__clang__) - #define FE_ALIGN(x) __attribute__((aligned(x))) -#elif defined(_MSC_VER) - #define FE_ALIGN(x) __declspec(align(x)) -#else - #define FE_ALIGN(x) -#endif - -// ============================================================================ -// Assertion Macros -// ============================================================================ - -#if FE_DEBUG_MODE - #define FE_ASSERT(cond) assert(cond) - #define FE_ASSERT_MSG(cond, msg) assert((cond) && (msg)) -#else - #define FE_ASSERT(cond) ((void)0) - #define FE_ASSERT_MSG(cond, msg) ((void)0) -#endif - -// Bounds checking macro -#if BOUNDS_CHECK_LEVEL == 2 // Full - #define FE_CHECK_BOUNDS(index, size) \ - FE_ASSERT_MSG((index) >= 0 && (index) < (size), "Index out of bounds") -#elif BOUNDS_CHECK_LEVEL == 1 // Basic - #define FE_CHECK_BOUNDS(index, size) \ - FE_ASSERT((index) < (size)) -#else - #define FE_CHECK_BOUNDS(index, size) ((void)0) -#endif - -// ============================================================================ -// Configuration Summary -// ============================================================================ - -/** - * @brief Print configuration summary - * - * Useful for debugging and verifying build configuration - */ -inline void print_config() { - #define PRINT_BOOL(x) ((x) ? "ON" : "OFF") - - printf("FE Library Configuration:\n"); - printf(" Debug Mode: %s\n", PRINT_BOOL(FE_DEBUG_MODE)); - printf(" MPI Support: %s\n", PRINT_BOOL(FE_HAS_MPI)); - printf(" OpenMP Support: %s\n", PRINT_BOOL(FE_HAS_OPENMP)); - printf(" Max Dimension: %d\n", MAX_SPATIAL_DIM); - printf(" Vectorization: %s (Level: %d)\n", - PRINT_BOOL(FE_ENABLE_VECTORIZATION), FE_VECTORIZATION_LEVEL); - printf(" Cache Line Size: %zu bytes\n", CACHE_LINE_SIZE); - printf(" Profiling: %s\n", PRINT_BOOL(FE_ENABLE_PROFILING)); - printf(" Matrix-Free: %s\n", PRINT_BOOL(FE_ENABLE_MATRIX_FREE)); - printf(" Max Polynomial Order: %d\n", MAX_POLYNOMIAL_ORDER); - - #undef PRINT_BOOL -} - -} // namespace config -} // namespace FE -} // namespace svmp - -#endif // SVMP_FE_CONFIG_H diff --git a/Code/Source/solver/FE/Core/FEException.h b/Code/Source/solver/FE/Core/FEException.h deleted file mode 100644 index 3b6b2a92e..000000000 --- a/Code/Source/solver/FE/Core/FEException.h +++ /dev/null @@ -1,566 +0,0 @@ -// SPDX-FileCopyrightText: Copyright (c) Stanford University, The Regents of the University of California, and others. -// SPDX-License-Identifier: BSD-3-Clause - -#ifndef SVMP_FE_EXCEPTION_H -#define SVMP_FE_EXCEPTION_H - -/** - * @file FEException.h - * @brief Exception hierarchy for error handling in the FE library - * - * This header defines a comprehensive exception system for the FE library - * with support for detailed error messages, stack traces, MPI-aware error - * handling, and structured error codes. - */ - -#include "Types.h" -#include "FEConfig.h" -#include -#include -#include -#include -#include - -#if FE_HAS_MPI -#include -#include -#endif - -// Platform-specific includes for stack traces -#if defined(__GNUC__) && !defined(_WIN32) -#include -#include -#endif - -namespace svmp { -namespace FE { - -// ============================================================================ -// Base Exception Class -// ============================================================================ - -/** - * @brief Base exception class for all FE library exceptions - * - * Provides rich error information including: - * - Detailed error messages - * - Source file and line information - * - Optional stack traces - * - MPI rank information in parallel runs - * - Nested exception support - */ -class FEException : public std::exception { -public: - /** - * @brief Construct exception with message - */ - FEException(const std::string& message, - FEStatus status = FEStatus::Unknown) - : message_(message), - status_(status), - file_(""), - line_(0), - function_(""), - mpi_rank_(-1) { - capture_context(); - build_what(); - } - - /** - * @brief Construct exception with source location - */ - FEException(const std::string& message, - const char* file, - int line, - const char* function = "", - FEStatus status = FEStatus::Unknown) - : message_(message), - status_(status), - file_(file), - line_(line), - function_(function), - mpi_rank_(-1) { - capture_context(); - build_what(); - } - - /** - * @brief Copy constructor - */ - FEException(const FEException&) = default; - - /** - * @brief Virtual destructor - */ - virtual ~FEException() noexcept = default; - - /** - * @brief Get exception message - */ - virtual const char* what() const noexcept override { - return what_.c_str(); - } - - /** - * @brief Get error status code - */ - FEStatus status() const noexcept { - return status_; - } - - /** - * @brief Get source file where exception was thrown - */ - const std::string& file() const noexcept { - return file_; - } - - /** - * @brief Get line number where exception was thrown - */ - int line() const noexcept { - return line_; - } - - /** - * @brief Get function name where exception was thrown - */ - const std::string& function() const noexcept { - return function_; - } - - /** - * @brief Get MPI rank (if applicable) - */ - int mpi_rank() const noexcept { - return mpi_rank_; - } - - /** - * @brief Get stack trace (if available) - */ - const std::vector& stack_trace() const noexcept { - return stack_trace_; - } - - /** - * @brief Add nested exception context - */ - void add_context(const std::string& context) { - message_ = context + "\n -> " + message_; - build_what(); - } - -protected: - std::string message_; - FEStatus status_; - std::string file_; - int line_; - std::string function_; - int mpi_rank_; - std::vector stack_trace_; - std::string what_; - - /** - * @brief Capture MPI rank and stack trace - */ - void capture_context() { - // Get MPI rank if available - #if FE_HAS_MPI - int initialized = 0; - MPI_Initialized(&initialized); - if (initialized) { - MPI_Comm_rank(MPI_COMM_WORLD, &mpi_rank_); - } - #endif - - // Capture stack trace if in debug mode - #if FE_DEBUG_MODE - capture_stack_trace(); - #endif - } - - /** - * @brief Capture stack trace (platform-specific) - */ - void capture_stack_trace() { - #if defined(__GNUC__) && !defined(_WIN32) - constexpr int MAX_FRAMES = 32; - void* frames[MAX_FRAMES]; - int n_frames = backtrace(frames, MAX_FRAMES); - - char** symbols = backtrace_symbols(frames, n_frames); - if (symbols) { - if (n_frames > 0) { - stack_trace_.reserve(static_cast(n_frames)); - } - for (int i = 1; i < n_frames; ++i) { // Skip this function - std::string symbol(symbols[i]); - - // Try to demangle C++ names - size_t start = symbol.find('('); - size_t end = symbol.find('+', start); - if (start != std::string::npos && end != std::string::npos) { - std::string mangled = symbol.substr(start + 1, end - start - 1); - int status; - char* demangled = abi::__cxa_demangle(mangled.c_str(), nullptr, nullptr, &status); - if (status == 0 && demangled) { - symbol.replace(start + 1, end - start - 1, demangled); - free(demangled); - } - } - - stack_trace_.push_back(symbol); - } - free(symbols); - } - #endif - } - - /** - * @brief Build the complete error message - */ - void build_what() { - std::ostringstream oss; - - // Error type and status - oss << "[FE Exception] " << status_to_string(status_); - - // MPI rank if applicable - if (mpi_rank_ >= 0) { - oss << " (Rank " << mpi_rank_ << ")"; - } - - oss << "\n"; - - // Location information - if (!file_.empty()) { - oss << " Location: " << file_ << ":" << line_; - if (!function_.empty()) { - oss << " in " << function_ << "()"; - } - oss << "\n"; - } - - // Error message - oss << " Message: " << message_ << "\n"; - - // Stack trace in debug mode - #if FE_DEBUG_MODE - if (!stack_trace_.empty()) { - oss << " Stack trace:\n"; - for (size_t i = 0; i < stack_trace_.size() && i < 10; ++i) { - oss << " #" << i << " " << stack_trace_[i] << "\n"; - } - } - #endif - - what_ = oss.str(); - } -}; - -// ============================================================================ -// Specific Exception Types -// ============================================================================ - -/** - * @brief Exception for invalid arguments - */ -class InvalidArgumentException : public FEException { -public: - InvalidArgumentException(const std::string& message, - const char* file = "", - int line = 0, - const char* function = "") - : FEException(message, file, line, function, FEStatus::InvalidArgument) {} -}; - -/** - * @brief Exception for invalid elements - */ -class InvalidElementException : public FEException { -public: - InvalidElementException(const std::string& message, - ElementType elem_type, - const char* file = "", - int line = 0) - : FEException(build_message(message, elem_type), file, line, "", FEStatus::InvalidElement), - element_type_(elem_type) {} - - ElementType element_type() const { return element_type_; } - -private: - ElementType element_type_; - - static std::string build_message(const std::string& msg, ElementType elem) { - return msg + " (Element type: " + std::to_string(static_cast(elem)) + ")"; - } -}; - -/** - * @brief Exception for DOF-related errors - */ -class DofException : public FEException { -public: - DofException(const std::string& message, - GlobalIndex dof_index = INVALID_GLOBAL_INDEX, - const char* file = "", - int line = 0) - : FEException(build_message(message, dof_index), file, line), - dof_index_(dof_index) {} - - GlobalIndex dof_index() const { return dof_index_; } - -private: - GlobalIndex dof_index_; - - static std::string build_message(const std::string& msg, GlobalIndex dof) { - if (dof != INVALID_GLOBAL_INDEX) { - return msg + " (DOF index: " + std::to_string(dof) + ")"; - } - return msg; - } -}; - -/** - * @brief Exception for assembly errors - */ -class AssemblyException : public FEException { -public: - AssemblyException(const std::string& message, - const char* file = "", - int line = 0) - : FEException(message, file, line, "", FEStatus::AssemblyError) {} -}; - -/** - * @brief Exception for backend-specific errors - */ -class BackendException : public FEException { -public: - BackendException(const std::string& message, - config::Backend backend, - int error_code = 0, - const char* file = "", - int line = 0) - : FEException(build_message(message, backend, error_code), file, line, "", FEStatus::BackendError), - backend_(backend), - error_code_(error_code) {} - - config::Backend backend() const { return backend_; } - int error_code() const { return error_code_; } - -private: - config::Backend backend_; - int error_code_; - - static std::string build_message(const std::string& msg, config::Backend backend, int code) { - std::ostringstream oss; - oss << msg << " (Backend: "; - switch(backend) { - case config::Backend::Trilinos: oss << "Trilinos"; break; - case config::Backend::PETSc: oss << "PETSc"; break; - case config::Backend::Eigen: oss << "Eigen"; break; - default: oss << "Unknown"; - } - if (code != 0) { - oss << ", Error code: " << code; - } - oss << ")"; - return oss.str(); - } -}; - -/** - * @brief Exception for not implemented features - */ -class NotImplementedException : public FEException { -public: - NotImplementedException(const std::string& feature, - const char* file = "", - int line = 0, - const char* function = "") - : FEException("Feature not implemented: " + feature, file, line, function, FEStatus::NotImplemented) {} -}; - -/** - * @brief Exception for convergence failures - */ -class ConvergenceException : public FEException { -public: - ConvergenceException(const std::string& message, - int iteration = -1, - Real residual = 0.0, - const char* file = "", - int line = 0) - : FEException(build_message(message, iteration, residual), file, line, "", FEStatus::ConvergenceError), - iteration_(iteration), - residual_(residual) {} - - int iteration() const { return iteration_; } - Real residual() const { return residual_; } - -private: - int iteration_; - Real residual_; - - static std::string build_message(const std::string& msg, int iter, Real res) { - std::ostringstream oss; - oss << msg; - if (iter >= 0) { - oss << " (Iteration: " << iter; - if (res > 0) { - oss << ", Residual: " << res; - } - oss << ")"; - } - return oss.str(); - } -}; - -/** - * @brief Exception for singular mappings - */ -class SingularMappingException : public FEException { -public: - SingularMappingException(const std::string& message, - Real jacobian_det = 0.0, - const char* file = "", - int line = 0) - : FEException(build_message(message, jacobian_det), file, line, "", FEStatus::SingularMapping), - jacobian_det_(jacobian_det) {} - - Real jacobian_det() const { return jacobian_det_; } - -private: - Real jacobian_det_; - - static std::string build_message(const std::string& msg, Real det) { - return msg + " (Jacobian determinant: " + std::to_string(det) + ")"; - } -}; - -// ============================================================================ -// Exception Throwing Macros -// ============================================================================ - -/** - * @brief Throw exception with automatic source location - */ -#define FE_THROW(ExceptionType, message) \ - throw ExceptionType(message, __FILE__, __LINE__, __FUNCTION__) - -/** - * @brief Conditional throw with source location (3 args: condition, ExceptionType, message) - */ -#define FE_THROW_IF_3(condition, ExceptionType, message) \ - do { \ - if (FE_UNLIKELY(condition)) { \ - FE_THROW(ExceptionType, message); \ - } \ - } while(0) - -/** - * @brief Conditional throw with FEException (2 args: condition, message) - */ -#define FE_THROW_IF_2(condition, message) \ - do { \ - if (FE_UNLIKELY(condition)) { \ - FE_THROW(FEException, message); \ - } \ - } while(0) - -/** - * @brief Helper macro to select between 2 and 3 argument versions - */ -#define FE_THROW_IF_SELECT(_1, _2, _3, NAME, ...) NAME - -/** - * @brief Conditional throw with source location - * - * Can be called with 2 arguments (condition, message) using FEException, - * or 3 arguments (condition, ExceptionType, message) for specific exception types. - */ -#define FE_THROW_IF(...) \ - FE_THROW_IF_SELECT(__VA_ARGS__, FE_THROW_IF_3, FE_THROW_IF_2)(__VA_ARGS__) - -/** - * @brief Check and throw InvalidArgumentException - */ -#define FE_CHECK_ARG(condition, message) \ - FE_THROW_IF(!(condition), InvalidArgumentException, message) - -/** - * @brief Check for null pointers - */ -#define FE_CHECK_NOT_NULL(ptr, name) \ - FE_THROW_IF((ptr) == nullptr, InvalidArgumentException, \ - std::string(name) + " is null") - -/** - * @brief Check index bounds - */ -#define FE_CHECK_INDEX(index, size) \ - FE_THROW_IF((index) < 0 || (index) >= (size), \ - InvalidArgumentException, \ - "Index " + std::to_string(index) + " out of bounds [0, " + \ - std::to_string(size) + ")") - -/** - * @brief Throw NotImplementedException - */ -#define FE_NOT_IMPLEMENTED(feature) \ - FE_THROW(NotImplementedException, feature) - -// ============================================================================ -// MPI Error Handling -// ============================================================================ - -#if FE_HAS_MPI - -/** - * @brief MPI-aware error handler - * - * Ensures all ranks abort cleanly when an exception occurs - */ -class MPIErrorHandler { -public: - static void abort_all_ranks(const FEException& e) { - int rank = 0; - MPI_Comm_rank(MPI_COMM_WORLD, &rank); - - // Print error on rank that threw - if (rank == e.mpi_rank() || e.mpi_rank() < 0) { - std::cerr << e.what() << std::endl; - } - - // Ensure all ranks abort - MPI_Abort(MPI_COMM_WORLD, static_cast(e.status())); - } - - /** - * @brief Install MPI error handler - */ - static void install() { - std::set_terminate([]() { - try { - std::rethrow_exception(std::current_exception()); - } catch (const FEException& e) { - abort_all_ranks(e); - } catch (const std::exception& e) { - std::cerr << "Unhandled exception: " << e.what() << std::endl; - MPI_Abort(MPI_COMM_WORLD, 1); - } catch (...) { - std::cerr << "Unknown exception" << std::endl; - MPI_Abort(MPI_COMM_WORLD, 1); - } - }); - } -}; - -#endif // FE_HAS_MPI - -} // namespace FE -} // namespace svmp - -#endif // SVMP_FE_EXCEPTION_H diff --git a/Code/Source/solver/FE/Core/Types.h b/Code/Source/solver/FE/Core/Types.h deleted file mode 100644 index 23c9581d6..000000000 --- a/Code/Source/solver/FE/Core/Types.h +++ /dev/null @@ -1,476 +0,0 @@ -// SPDX-FileCopyrightText: Copyright (c) Stanford University, The Regents of the University of California, and others. -// SPDX-License-Identifier: BSD-3-Clause - -#ifndef SVMP_FE_TYPES_H -#define SVMP_FE_TYPES_H - -/** - * @file Types.h - * @brief Fundamental type definitions for the finite element library - * - * This header provides core type aliases, enumerations, and strong type - * definitions used throughout the FE library. It establishes a consistent - * type system that integrates with the Mesh library while maintaining - * independence from backend-specific types. - */ - -namespace svmp { -enum class CellFamily { - Point, - Line, - Triangle, - Quad, - Tetra, - Hex, - Wedge, - Pyramid, - Polygon, - Polyhedron -}; -} // namespace svmp - -#include -#include -#include -#include -#include - -namespace svmp { -namespace FE { - -// ============================================================================ -// Index Types -// ============================================================================ - -/** - * @brief Local index type for element-level operations - * - * Used for local node numbering within elements, local DOF indices, - * and other element-local indexing. Unsigned for safety. - */ -using LocalIndex = std::uint32_t; - -/** - * @brief Global index type for distributed DOF numbering - * - * Signed 64-bit for compatibility with PETSc and Trilinos. - * Negative values can indicate special conditions or invalid indices. - */ -using GlobalIndex = std::int64_t; - -/** - * @brief DOF-specific index type - * - * Strong type alias to prevent mixing DOF indices with other indices. - * Provides type safety at compile time. - */ -struct DofIndex { - GlobalIndex value; - - constexpr explicit DofIndex(GlobalIndex v = -1) noexcept : value(v) {} - constexpr operator GlobalIndex() const noexcept { return value; } - constexpr bool is_valid() const noexcept { return value >= 0; } -}; - -/** - * @brief Field identifier type - * - * Used to distinguish between different physical fields in multi-field problems. - */ -using FieldId = std::uint16_t; - -/** - * @brief Block identifier for block-structured systems - */ -using BlockId = std::uint16_t; - -// Import mesh library scalar/index types when available. -using MeshIndex = std::int32_t; -using MeshOffset = std::int64_t; -using MeshGlobalId = std::int64_t; -using Real = double; - -// ============================================================================ -// Constants -// ============================================================================ - -constexpr LocalIndex INVALID_LOCAL_INDEX = std::numeric_limits::max(); -constexpr GlobalIndex INVALID_GLOBAL_INDEX = -1; -constexpr FieldId INVALID_FIELD_ID = std::numeric_limits::max(); -/// Sentinel FieldId for geometry-only quantities (no DOF dependence). -/// Uses first registered field's space for quadrature, but logically decoupled -/// from any specific field's DOFs. -constexpr FieldId GEOMETRY_FIELD_ID = std::numeric_limits::max() - 1; -constexpr BlockId INVALID_BLOCK_ID = std::numeric_limits::max(); - -/** - * @brief Sentinel FieldId representing "the current solution state" in tangent forms. - * - * When differentiating a residual form to obtain the tangent (Jacobian), undifferentiated - * TrialFunction occurrences are rewritten to StateField nodes. Those that represent the - * block's own primary unknown (rather than a named external field) use this sentinel - * FieldId. The assembler maps it to the current solution coefficients at each quadrature - * point, regardless of which physics or field variables are involved. - * - * This is distinct from INVALID_FIELD_ID, which means "uninitialized / no field." - * CURRENT_SOLUTION_FIELD_ID uses the same numeric value for backward compatibility - * with existing KernelIR encodings, but carries explicit semantic intent. - */ -constexpr FieldId CURRENT_SOLUTION_FIELD_ID = std::numeric_limits::max(); - -// ============================================================================ -// Field Value Entry (for point evaluation of field-dependent expressions) -// ============================================================================ - -/// Maximum number of components in a FieldValueEntry (3x3 tensor). -constexpr int MAX_FIELD_VALUE_COMPONENTS = 9; - -/** - * @brief Field value at an evaluation point — scalar, vector, or tensor. - * - * Used by PointEvaluator and the auxiliary assembly path to supply FE - * field values at entity locations (e.g., nodal DOF values for - * Node-scoped auxiliary models with Lagrange Kronecker delta). - */ -struct FieldValueEntry { - FieldId field{INVALID_FIELD_ID}; - int n_components{0}; - Real components[MAX_FIELD_VALUE_COMPONENTS]{}; -}; - -// ============================================================================ -// Element Type Enumerations -// ============================================================================ - -/** - * @brief Reference element types supported by the FE library - * - * Maps to svmp::CellFamily from the Mesh library but provides - * FE-specific categorization including higher-order variants. - */ -enum class ElementType : std::uint8_t { - // Linear elements - Line2 = 0, // 2-node line - Triangle3 = 1, // 3-node triangle - Quad4 = 2, // 4-node quadrilateral - Tetra4 = 3, // 4-node tetrahedron - Hex8 = 4, // 8-node hexahedron - Wedge6 = 5, // 6-node wedge/prism - Pyramid5 = 6, // 5-node pyramid - - // Quadratic elements - Line3 = 10, // 3-node line - Triangle6 = 11, // 6-node triangle - Quad9 = 12, // 9-node quadrilateral (bi-quadratic) - Quad8 = 13, // 8-node quadrilateral (serendipity) - Tetra10 = 14, // 10-node tetrahedron - Hex27 = 15, // 27-node hexahedron (tri-quadratic) - Hex20 = 16, // 20-node hexahedron (serendipity) - Wedge15 = 17, // 15-node wedge - Wedge18 = 18, // 18-node wedge (complete quadratic) - Pyramid13 = 19, // 13-node pyramid - Pyramid14 = 20, // 14-node pyramid - - // Special elements - Point1 = 30, // 1-node point element - - Unknown = 255 -}; - -/** - * @brief Quadrature rule types - */ -enum class QuadratureType : std::uint8_t { - GaussLegendre, // Standard Gaussian quadrature - GaussLobatto, // Includes endpoints (for spectral elements) - Newton, // Newton-Cotes rules - Reduced, // Order-based reduced integration for locking - PositionBased, // Position-based reduced integration (legacy compatible) - Composite, // Composite rules for adaptivity - Custom // User-defined quadrature points -}; - -/** - * @brief Basis function families - */ -enum class BasisType : std::uint8_t { - Lagrange, // Standard nodal Lagrange basis - Hierarchical, // Hierarchical/modal basis - Bernstein, // Bernstein polynomials - NURBS, // Non-uniform rational B-splines - BSpline, // Non-rational B-spline basis - Spectral, // Spectral element basis - Serendipity, // Serendipity elements - Hermite, // Hermite C1 continuity basis - RaviartThomas, // H(div) Raviart-Thomas family - Nedelec, // H(curl) Nedelec edge elements - BDM, // H(div) Brezzi-Douglas-Marini family - Bubble, // Interior bubble functions for enrichment - Custom // User-defined basis -}; - -/** - * @brief Field types for function spaces - */ -enum class FieldType : std::uint8_t { - Scalar, // Scalar field - Vector, // Vector field - Tensor, // Tensor field - SymmetricTensor, // Symmetric tensor field - Mixed // Mixed/composite field -}; - -/** - * @brief Continuity requirements for function spaces - */ -enum class Continuity : std::uint8_t { - C0, // Continuous (standard FEM) - C1, // C1 continuous (for plates/shells) - L2, // L2 (discontinuous) - H_div, // H(div) conforming - H_curl, // H(curl) conforming - Custom -}; - -/** - * @brief Assembly strategies - */ -enum class AssemblyStrategy : std::uint8_t { - ElementByElement, // Traditional element loop - Vectorized, // SIMD vectorized assembly - MatrixFree, // Matrix-free operators - Hybrid // Mixed strategy -}; - -/** - * @brief Status codes for FE operations - */ -enum class FEStatus : std::uint8_t { - Success = 0, - InvalidArgument = 1, - InvalidElement = 2, - SingularMapping = 3, - QuadratureError = 4, - AssemblyError = 5, - BackendError = 6, - NotImplemented = 7, - ConvergenceError = 8, - AllocationError = 9, - MPIError = 10, - IOError = 11, - Unknown = 255 -}; - -// ============================================================================ -// Geometric Types -// ============================================================================ - -/** - * @brief Point in reference element coordinates - */ -template -using ReferencePoint = std::array(Dim)>; - -/** - * @brief Point in physical coordinates - */ -using PhysicalPoint = std::array; - -/** - * @brief Jacobian matrix type - */ -template -using Jacobian = std::array(ReferenceDim)>, static_cast(SpatialDim)>; - -// ============================================================================ -// Strong Type Wrappers -// ============================================================================ - -/** - * @brief Strong type wrapper template for type-safe programming - * - * Prevents accidental mixing of conceptually different types that have - * the same underlying representation. - */ -template -class StrongType { -public: - using ValueType = T; - - constexpr StrongType() noexcept(std::is_nothrow_default_constructible_v) - : value_{} {} - - constexpr explicit StrongType(T value) noexcept(std::is_nothrow_move_constructible_v) - : value_(std::move(value)) {} - - constexpr T& get() noexcept { return value_; } - constexpr const T& get() const noexcept { return value_; } - - // Explicit conversion - constexpr explicit operator T() const noexcept { return value_; } - - // Comparison operators - constexpr bool operator==(const StrongType& other) const noexcept { - return value_ == other.value_; - } - constexpr bool operator!=(const StrongType& other) const noexcept { - return value_ != other.value_; - } - constexpr bool operator<(const StrongType& other) const noexcept { - return value_ < other.value_; - } - -private: - T value_; -}; - -// Specific strong types for common use cases -struct QuadraturePointTag {}; -struct QuadratureWeightTag {}; -struct BasisValueTag {}; -struct BasisGradientTag {}; - -using QuadraturePointIndex = StrongType; -using QuadratureWeight = StrongType; - -// ============================================================================ -// Type Traits -// ============================================================================ - -/** - * @brief Check if a type is a valid index type - */ -template -struct is_index_type : std::false_type {}; - -template<> -struct is_index_type : std::true_type {}; - -template<> -struct is_index_type : std::true_type {}; - -template<> -struct is_index_type : std::true_type {}; - -template -inline constexpr bool is_index_type_v = is_index_type::value; - -/** - * @brief Check if a type represents a field type - */ -template -struct is_field_type : std::false_type {}; - -template<> -struct is_field_type : std::true_type {}; - -template -inline constexpr bool is_field_type_v = is_field_type::value; - -// ============================================================================ -// Utility Functions -// ============================================================================ - -/** - * @brief Convert FE ElementType to Mesh CellFamily - */ -constexpr svmp::CellFamily to_mesh_family(ElementType elem) noexcept { - switch(elem) { - case ElementType::Line2: - case ElementType::Line3: - return svmp::CellFamily::Line; - - case ElementType::Triangle3: - case ElementType::Triangle6: - return svmp::CellFamily::Triangle; - - case ElementType::Quad4: - case ElementType::Quad8: - case ElementType::Quad9: - return svmp::CellFamily::Quad; - - case ElementType::Tetra4: - case ElementType::Tetra10: - return svmp::CellFamily::Tetra; - - case ElementType::Hex8: - case ElementType::Hex20: - case ElementType::Hex27: - return svmp::CellFamily::Hex; - - case ElementType::Wedge6: - case ElementType::Wedge15: - case ElementType::Wedge18: - return svmp::CellFamily::Wedge; - - case ElementType::Pyramid5: - case ElementType::Pyramid13: - case ElementType::Pyramid14: - return svmp::CellFamily::Pyramid; - - case ElementType::Point1: - return svmp::CellFamily::Point; - - default: - return svmp::CellFamily::Point; // Fallback - } -} - -/** - * @brief Get spatial dimension of element type - */ -constexpr int element_dimension(ElementType elem) noexcept { - switch(elem) { - case ElementType::Point1: - return 0; - case ElementType::Line2: - case ElementType::Line3: - return 1; - case ElementType::Triangle3: - case ElementType::Triangle6: - case ElementType::Quad4: - case ElementType::Quad8: - case ElementType::Quad9: - return 2; - case ElementType::Tetra4: - case ElementType::Tetra10: - case ElementType::Hex8: - case ElementType::Hex20: - case ElementType::Hex27: - case ElementType::Wedge6: - case ElementType::Wedge15: - case ElementType::Wedge18: - case ElementType::Pyramid5: - case ElementType::Pyramid13: - case ElementType::Pyramid14: - return 3; - default: - return -1; - } -} - -/** - * @brief Convert status code to string for error reporting - */ -inline const char* status_to_string(FEStatus status) noexcept { - switch(status) { - case FEStatus::Success: return "Success"; - case FEStatus::InvalidArgument: return "Invalid argument"; - case FEStatus::InvalidElement: return "Invalid element"; - case FEStatus::SingularMapping: return "Singular mapping"; - case FEStatus::QuadratureError: return "Quadrature error"; - case FEStatus::AssemblyError: return "Assembly error"; - case FEStatus::BackendError: return "Backend error"; - case FEStatus::NotImplemented: return "Not implemented"; - case FEStatus::ConvergenceError: return "Convergence error"; - case FEStatus::AllocationError: return "Allocation error"; - case FEStatus::MPIError: return "MPI error"; - case FEStatus::IOError: return "I/O error"; - default: return "Unknown error"; - } -} - -} // namespace FE -} // namespace svmp - -#endif // SVMP_FE_TYPES_H From dddefd641c09a167d93acab67b372f80a3096eb1 Mon Sep 17 00:00:00 2001 From: Zachary Sexton Date: Tue, 21 Apr 2026 01:27:35 -0700 Subject: [PATCH 04/15] adding cmake changes to compile basic exception infrastructure --- Code/Source/solver/CMakeLists.txt | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Code/Source/solver/CMakeLists.txt b/Code/Source/solver/CMakeLists.txt index 88ace0580..c78240cff 100644 --- a/Code/Source/solver/CMakeLists.txt +++ b/Code/Source/solver/CMakeLists.txt @@ -31,6 +31,8 @@ include_directories(${SV_SOURCE_DIR}/ThirdParty/parmetis_internal/simvascular_pa include_directories(${SV_SOURCE_DIR}/ThirdParty/tetgen/simvascular_tetgen) include_directories(${SV_SOURCE_DIR}/ThirdParty/tinyxml/simvascular_tinyxml) include_directories(${MPI_C_INCLUDE_PATH}) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/Core) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/FE/Common) # Find Trilinos package if requested if(SV_USE_TRILINOS) @@ -229,6 +231,21 @@ set(CSRCS RobinBoundaryCondition.h RobinBoundaryCondition.cpp ) +file(GLOB SOLVER_CORE_SRCS CONFIGURE_DEPENDS + Core/*.cpp + Core/*.h +) + +file(GLOB SOLVER_FE_COMMON_SRCS CONFIGURE_DEPENDS + FE/Common/*.cpp + FE/Common/*.h +) + +list(APPEND CSRCS + ${SOLVER_CORE_SRCS} + ${SOLVER_FE_COMMON_SRCS} +) + # Set PETSc interace code. #if(USE_PETSC) # set(CSRCS ${CSRCS} petsc_linear_solver.h petsc_linear_solver.c) @@ -371,4 +388,4 @@ if(ENABLE_UNIT_TEST) # gtest_discover_tests(runUnitTest) add_test(NAME all_unit_tests COMMAND run_all_unit_tests) -endif() \ No newline at end of file +endif() From 8ee5ba589cce7e781171e1e146005fafbc2a6c1d Mon Sep 17 00:00:00 2001 From: Zachary Sexton Date: Tue, 21 Apr 2026 10:50:28 -0700 Subject: [PATCH 05/15] adding example usage of the generic exception system for file reading errors --- Code/Source/solver/Parameters.cpp | 140 +++++++++++++++--------------- Code/Source/solver/Parameters.h | 14 +-- Code/Source/solver/main.cpp | 13 +++ 3 files changed, 90 insertions(+), 77 deletions(-) diff --git a/Code/Source/solver/Parameters.cpp b/Code/Source/solver/Parameters.cpp index e5dc908c0..1109b1b2a 100644 --- a/Code/Source/solver/Parameters.cpp +++ b/Code/Source/solver/Parameters.cpp @@ -73,10 +73,10 @@ void xml_util_set_parameters( std::function(SVMP_HERE, error_msg + name + "'."); } } else { - throw std::runtime_error(error_msg + name + "'."); + svmp::raise(SVMP_HERE, error_msg + name + "'."); } } @@ -98,8 +98,8 @@ IncludeParametersFile::IncludeParametersFile(const char* cfile_name) auto error = document.LoadFile(file_name.c_str()); root_element = document.FirstChildElement(Parameters::FSI_FILE.c_str()); - if (root_element == nullptr) { - throw std::runtime_error("The following error occured while reading the XML file '" + + if (error != tinyxml2::XML_SUCCESS || root_element == nullptr) { + svmp::raise(SVMP_HERE, "The following error occured while reading the XML file '" + file_name + "'.\n" + "[svMultiPhysics] ERROR " + std::string(document.ErrorStr())); } } @@ -155,8 +155,8 @@ void Parameters::read_xml(std::string file_name) auto error = doc.LoadFile(file_name.c_str()); auto root_element = doc.FirstChildElement(FSI_FILE.c_str()); - if (root_element == nullptr) { - throw std::runtime_error("The following error occured while reading the XML file '" + file_name + "'.\n" + + if (error != tinyxml2::XML_SUCCESS || root_element == nullptr) { + svmp::raise(SVMP_HERE, "The following error occured while reading the XML file '" + file_name + "'.\n" + "[svFSI] ERROR " + std::string(doc.ErrorStr())); } @@ -343,7 +343,7 @@ void BodyForceParameters::set_values(tinyxml2::XMLElement* xml_elem) const char* smesh; auto result = xml_elem->QueryStringAttribute("mesh", &smesh); if (smesh == nullptr) { - throw std::runtime_error("No MESH given in the XML element."); + svmp::raise(SVMP_HERE, "No MESH given in the XML element."); } mesh_name.set(std::string(smesh)); //auto item = xml_elem->FirstChildElement(); @@ -487,7 +487,7 @@ void BoundaryConditionParameters::set_values(tinyxml2::XMLElement* xml_elem) const char* sname; auto result = xml_elem->QueryStringAttribute("name", &sname); if (sname == nullptr) { - throw std::runtime_error("No NAME given in the XML element."); + svmp::raise(SVMP_HERE, "No NAME given in the XML element."); } name.set(std::string(sname)); @@ -505,10 +505,10 @@ void BoundaryConditionParameters::set_values(tinyxml2::XMLElement* xml_elem) try { set_parameter_value(name, value); } catch (const std::bad_function_call& exception) { - throw std::runtime_error(error_msg + name + "'."); + svmp::raise(SVMP_HERE, error_msg + name + "'."); } } else { - throw std::runtime_error(error_msg + name + "'."); + svmp::raise(SVMP_HERE, error_msg + name + "'."); } item = item->NextSiblingElement(); @@ -842,7 +842,7 @@ void CANNRowParameters::print_parameters() void CANNRowParameters::set_values(tinyxml2::XMLElement* row_elem) { if (!row_elem) { - throw std::runtime_error("CANNRowParameters::set_values: Received null XML element."); + svmp::raise(SVMP_HERE, "CANNRowParameters::set_values: Received null XML element."); } using namespace tinyxml2; @@ -862,13 +862,13 @@ void CANNRowParameters::set_values(tinyxml2::XMLElement* row_elem) auto value = item->GetText(); if (value == nullptr) { - throw std::runtime_error(error_msg + name + "'."); + svmp::raise(SVMP_HERE, error_msg + name + "'."); } try { set_parameter_value_CANN(name, value); } catch (const std::bad_function_call& exception) { - throw std::runtime_error(error_msg + name + "'."); + svmp::raise(SVMP_HERE, error_msg + name + "'."); } item = item->NextSiblingElement(); @@ -912,7 +912,7 @@ void CANNParameters::set_values(tinyxml2::XMLElement* xml_elem) } if (rows.empty()) { - throw std::runtime_error(error_msg + "Add_row'. No rows found."); + svmp::raise(SVMP_HERE, error_msg + "Add_row'. No rows found."); } value_set = true; @@ -962,7 +962,7 @@ void ConstitutiveModelParameters::set_values(tinyxml2::XMLElement* xml_elem) const char* stype; auto result = xml_elem->QueryStringAttribute("type", &stype); if (stype == nullptr) { - throw std::runtime_error("No TYPE given in the XML element."); + svmp::raise(SVMP_HERE, "No TYPE given in the XML element."); } type.set(std::string(stype)); @@ -975,7 +975,7 @@ void ConstitutiveModelParameters::set_values(tinyxml2::XMLElement* xml_elem) } msg_2 += "\n"; auto msg = msg_1 + msg_2; - throw std::runtime_error(msg); + svmp::raise(SVMP_HERE, msg); } auto model_type = constitutive_model_types.at(type.value()); type.set(model_type); @@ -995,7 +995,7 @@ void ConstitutiveModelParameters::check_constitutive_model(const Parameter(SVMP_HERE, "The " + type.value() + " constitutive model is not valid for ustruct equations."); } } } @@ -1033,7 +1033,7 @@ void CoupleCplBCParameters::set_values(tinyxml2::XMLElement* xml_elem) const char* stype; auto result = xml_elem->QueryStringAttribute("type", &stype); if (stype == nullptr) { - throw std::runtime_error("No TYPE given in the XML element."); + svmp::raise(SVMP_HERE, "No TYPE given in the XML element."); } type.set(std::string(stype)); auto item = xml_elem->FirstChildElement(); @@ -1092,7 +1092,7 @@ void CoupleGenBCParameters::set_values(tinyxml2::XMLElement* xml_elem) const char* stype; auto result = xml_elem->QueryStringAttribute("type", &stype); if (stype == nullptr) { - throw std::runtime_error("No TYPE given in the XML element."); + svmp::raise(SVMP_HERE, "No TYPE given in the XML element."); } type.set(std::string(stype)); auto item = xml_elem->FirstChildElement(); @@ -1268,7 +1268,7 @@ void VariableWallPropsParameters::set_values(tinyxml2::XMLElement* xml_elem) const char* sname; auto result = xml_elem->QueryStringAttribute("mesh_name", &sname); if (sname == nullptr) { - throw std::runtime_error("No TYPE given in the XML element."); + svmp::raise(SVMP_HERE, "No TYPE given in the XML element."); } mesh_name.set(std::string(sname)); auto item = xml_elem->FirstChildElement(); @@ -1438,13 +1438,13 @@ void FluidViscosityParameters::set_values(tinyxml2::XMLElement* xml_elem) auto result = xml_elem->QueryStringAttribute("model", &smodel); if (smodel == nullptr) { - throw std::runtime_error("No MODEL given in the XML element."); + svmp::raise(SVMP_HERE, "No MODEL given in the XML element."); } model.set(std::string(smodel)); // Check fluid_viscosity model name. if (model_names.count(model.value()) == 0) { - throw std::runtime_error("Unknown fluid viscosity model '" + model.value() + + svmp::raise(SVMP_HERE, "Unknown fluid viscosity model '" + model.value() + "' in '" + xml_elem->Name() + "'."); } @@ -1563,13 +1563,13 @@ void SolidViscosityParameters::set_values(tinyxml2::XMLElement* xml_elem) auto result = xml_elem->QueryStringAttribute("model", &smodel); if (smodel == nullptr) { - throw std::runtime_error("No MODEL given in the XML element."); + svmp::raise(SVMP_HERE, "No MODEL given in the XML element."); } model.set(std::string(smodel)); // Check solid viscosity model name. if (model_names.count(model.value()) == 0) { - throw std::runtime_error("Unknown solid viscosity model '" + model.value() + + svmp::raise(SVMP_HERE, "Unknown solid viscosity model '" + model.value() + "' in '" + xml_elem->Name() + "'."); } @@ -1693,7 +1693,7 @@ void DomainParameters::set_values(tinyxml2::XMLElement* domain_elem, bool from_e const char* sid; auto result = domain_elem->QueryStringAttribute("id", &sid); if (sid == nullptr) { - throw std::runtime_error("No ID found in the XML element."); + svmp::raise(SVMP_HERE, "No ID found in the XML element."); } id.set(std::string(sid)); } @@ -1725,7 +1725,7 @@ void DomainParameters::set_values(tinyxml2::XMLElement* domain_elem, bool from_e solid_viscosity.set_values(item); } else { - throw std::runtime_error("Viscosity model not supported for equation '" + equation.value() + "'."); + svmp::raise(SVMP_HERE, "Viscosity model not supported for equation '" + equation.value() + "'."); } } else if (name == include_xml.name()) { @@ -1738,11 +1738,11 @@ void DomainParameters::set_values(tinyxml2::XMLElement* domain_elem, bool from_e try { set_parameter_value(name, value); } catch (const std::bad_function_call& exception) { - throw std::runtime_error(error_msg + name + "'."); + svmp::raise(SVMP_HERE, error_msg + name + "'."); } } else { - throw std::runtime_error(error_msg + name + "'."); + svmp::raise(SVMP_HERE, error_msg + name + "'."); } item = item->NextSiblingElement(); @@ -1753,12 +1753,12 @@ void DomainParameters::set_values(tinyxml2::XMLElement* domain_elem, bool from_e // Check values for some parameters.. // if (Parameters::constitutive_model_names.count(constitutive_model.value()) == 0) { - throw std::runtime_error("Unknown constitutive model '" + constitutive_model.value_ + "' for '" + constitutive_model.name_ + + svmp::raise(SVMP_HERE, "Unknown constitutive model '" + constitutive_model.value_ + "' for '" + constitutive_model.name_ + "' in '" + domain_params->Name() + "'."); } if (Parameters::equation_names.count(equation.value()) == 0) { - throw std::runtime_error("Unknown equation name '" + equation.value() + "' for '" + equation.name_ + + svmp::raise(SVMP_HERE, "Unknown equation name '" + equation.value() + "' for '" + equation.name_ + "' in '" + domain_params->Name() + "'."); } */ @@ -1804,19 +1804,19 @@ void TTPInitialConditionsParameters::set_values(tinyxml2::XMLElement* xml_elem) value_set = true; } else { - throw std::runtime_error(error_msg + name + "'."); + svmp::raise(SVMP_HERE, error_msg + name + "'."); } item = item->NextSiblingElement(); } if (!initial_states.defined()) { - throw std::runtime_error(xml_element_name_ + " requires an '" + + svmp::raise(SVMP_HERE, xml_element_name_ + " requires an '" + TTPInitialStatesParameters::xml_element_name_ + "' XML section."); } if (!gating_variables.defined()) { - throw std::runtime_error(xml_element_name_ + " requires a '" + + svmp::raise(SVMP_HERE, xml_element_name_ + " requires a '" + TTPGatingVariablesParameters::xml_element_name_ + "' XML section."); } } @@ -1867,10 +1867,10 @@ void TTPInitialStatesParameters::set_values(tinyxml2::XMLElement* xml_elem) set_parameter_value(name, value); value_set = true; } catch (const std::bad_function_call& exception) { - throw std::runtime_error(error_msg + name + "'."); + svmp::raise(SVMP_HERE, error_msg + name + "'."); } } else { - throw std::runtime_error(error_msg + name + "'."); + svmp::raise(SVMP_HERE, error_msg + name + "'."); } item = item->NextSiblingElement(); @@ -1933,10 +1933,10 @@ void TTPGatingVariablesParameters::set_values(tinyxml2::XMLElement* xml_elem) set_parameter_value(name, value); value_set = true; } catch (const std::bad_function_call& exception) { - throw std::runtime_error(error_msg + name + "'."); + svmp::raise(SVMP_HERE, error_msg + name + "'."); } } else { - throw std::runtime_error(error_msg + name + "'."); + svmp::raise(SVMP_HERE, error_msg + name + "'."); } item = item->NextSiblingElement(); @@ -1993,7 +1993,7 @@ void DirectionalDistributionParameters::validate() const // Empty block is invalid - if block exists, must specify all three if (num_defined == 0) { - throw std::runtime_error("Directional_distribution block is empty. " + svmp::raise(SVMP_HERE, "Directional_distribution block is empty. " "Either remove the block entirely (to use defaults: fiber=1.0, sheet=0.0, normal=0.0) " "or specify all three directions: Fiber_direction, Sheet_direction, Sheet_normal_direction."); } @@ -2008,7 +2008,7 @@ void DirectionalDistributionParameters::validate() const if (!fiber_defined) msg += "Fiber_direction "; if (!sheet_defined) msg += "Sheet_direction "; if (!normal_defined) msg += "Sheet_normal_direction "; - throw std::runtime_error(msg); + svmp::raise(SVMP_HERE, msg); } // All three are specified, validate their values @@ -2020,7 +2020,7 @@ void DirectionalDistributionParameters::validate() const double eta_sum = eta_f + eta_s + eta_n; const double tol = 1.0e-10; if (std::abs(eta_sum - 1.0) > tol) { - throw std::runtime_error("Directional distribution fractions must sum to 1.0. " + svmp::raise(SVMP_HERE, "Directional distribution fractions must sum to 1.0. " "Got: Fiber_direction=" + std::to_string(eta_f) + ", Sheet_direction=" + std::to_string(eta_s) + ", Sheet_normal_direction=" + std::to_string(eta_n) + @@ -2029,7 +2029,7 @@ void DirectionalDistributionParameters::validate() const // Validate that each eta is non-negative if (eta_f < 0.0 || eta_s < 0.0 || eta_n < 0.0) { - throw std::runtime_error("Directional distribution fractions must be non-negative. " + svmp::raise(SVMP_HERE, "Directional distribution fractions must be non-negative. " "Got: Fiber_direction=" + std::to_string(eta_f) + ", Sheet_direction=" + std::to_string(eta_s) + ", Sheet_normal_direction=" + std::to_string(eta_n)); @@ -2079,7 +2079,7 @@ void FiberReinforcementStressParameters::set_values(tinyxml2::XMLElement* xml_el const char* stype; auto result = xml_elem->QueryStringAttribute("type", &stype); if (stype == nullptr) { - throw std::runtime_error("No TYPE given in the XML element."); + svmp::raise(SVMP_HERE, "No TYPE given in the XML element."); } type.set(std::string(stype)); auto item = xml_elem->FirstChildElement(); @@ -2095,10 +2095,10 @@ void FiberReinforcementStressParameters::set_values(tinyxml2::XMLElement* xml_el try { set_parameter_value(name, value); } catch (const std::bad_function_call& exception) { - throw std::runtime_error(error_msg + name + "'."); + svmp::raise(SVMP_HERE, error_msg + name + "'."); } } else { - throw std::runtime_error(error_msg + name + "'."); + svmp::raise(SVMP_HERE, error_msg + name + "'."); } item = item->NextSiblingElement(); @@ -2160,7 +2160,7 @@ void StimulusParameters::set_values(tinyxml2::XMLElement* xml_elem) const char* stype; auto result = xml_elem->QueryStringAttribute("type", &stype); if (stype == nullptr) { - throw std::runtime_error("No TYPE given in the XML element."); + svmp::raise(SVMP_HERE, "No TYPE given in the XML element."); } type.set(std::string(stype)); auto item = xml_elem->FirstChildElement(); @@ -2293,7 +2293,7 @@ void ContactParameters::set_values(tinyxml2::XMLElement* xml_elem) const char* mname; auto result = xml_elem->QueryStringAttribute("model", &mname); if (mname == nullptr) { - throw std::runtime_error("No MODEL given in the XML element."); + svmp::raise(SVMP_HERE, "No MODEL given in the XML element."); } model.set(std::string(mname)); @@ -2467,7 +2467,7 @@ void EquationParameters::set_values(tinyxml2::XMLElement* eq_elem, DomainParamet } else if (eq_type == consts::EquationType::phys_struct || eq_type == consts::EquationType::phys_ustruct) { domain->solid_viscosity.set_values(item); } else { - throw std::runtime_error("Viscosity model not supported for equation '" + type.value() + "'."); + svmp::raise(SVMP_HERE, "Viscosity model not supported for equation '" + type.value() + "'."); } } else if (name == ECGLeadsParameters::xml_element_name_) { @@ -2493,13 +2493,13 @@ void EquationParameters::set_values(tinyxml2::XMLElement* eq_elem, DomainParamet try { default_domain->set_parameter_value(name, value); } catch (const std::bad_function_call& exception) { - throw std::runtime_error("Unknown " + xml_element_name_ + " XML element '" + name + "."); + svmp::raise(SVMP_HERE, "Unknown " + xml_element_name_ + " XML element '" + name + "."); } } } else { - throw std::runtime_error("[Equation] Unknown " + xml_element_name_ + " XML element '" + name + "."); + svmp::raise(SVMP_HERE, "[Equation] Unknown " + xml_element_name_ + " XML element '" + name + "."); } item = item->NextSiblingElement(); @@ -2607,7 +2607,7 @@ void GeneralSimulationParameters::set_values(tinyxml2::XMLElement* xml_element, try { set_parameter_value(name, value); } catch (const std::bad_function_call& exception) { - throw std::runtime_error("Unknown XML GeneralSimulationParameters element '" + name + "."); + svmp::raise(SVMP_HERE, "Unknown XML GeneralSimulationParameters element '" + name + "."); } } @@ -2669,13 +2669,13 @@ void FaceParameters::set_values(tinyxml2::XMLElement* face_elem) auto value = item->GetText(); if (value == nullptr) { - throw std::runtime_error(error_msg + name + "'."); + svmp::raise(SVMP_HERE, error_msg + name + "'."); } try { set_parameter_value(name, value); } catch (const std::bad_function_call& exception) { - throw std::runtime_error(error_msg + name + "'."); + svmp::raise(SVMP_HERE, error_msg + name + "'."); } item = item->NextSiblingElement(); @@ -2721,7 +2721,7 @@ void RemesherParameters::set_values(tinyxml2::XMLElement* xml_elem) const char* stype; auto result = xml_elem->QueryStringAttribute("type", &stype); if (stype == nullptr) { - throw std::runtime_error("No TYPE given in the XML element."); + svmp::raise(SVMP_HERE, "No TYPE given in the XML element."); } type.set(std::string(stype)); values_set_ = true; @@ -2738,12 +2738,12 @@ void RemesherParameters::set_values(tinyxml2::XMLElement* xml_elem) const char* value; auto result = item->QueryStringAttribute("name", &name); if (name == nullptr) { - throw std::runtime_error("No NAME given in the XML Remesher element."); + svmp::raise(SVMP_HERE, "No NAME given in the XML Remesher element."); } result = item->QueryStringAttribute("value", &value); if (value == nullptr) { - throw std::runtime_error("No VALUE given in the XML Remesher element."); + svmp::raise(SVMP_HERE, "No VALUE given in the XML Remesher element."); } auto svalue = std::string(value); @@ -2751,7 +2751,7 @@ void RemesherParameters::set_values(tinyxml2::XMLElement* xml_elem) double dvalue = std::stod(svalue); max_edge_sizes_[std::string(name)] = dvalue; } catch (...) { - throw std::runtime_error("VALUE=" + svalue + + svmp::raise(SVMP_HERE, "VALUE=" + svalue + " is not a valid float in the XML Remesher element."); } @@ -2760,11 +2760,11 @@ void RemesherParameters::set_values(tinyxml2::XMLElement* xml_elem) try { set_parameter_value(name, value); } catch (const std::bad_function_call& exception) { - throw std::runtime_error(error_msg + name + "'."); + svmp::raise(SVMP_HERE, error_msg + name + "'."); } } else { - throw std::runtime_error(error_msg + name + "'."); + svmp::raise(SVMP_HERE, error_msg + name + "'."); } item = item->NextSiblingElement(); @@ -2867,10 +2867,10 @@ void MeshParameters::set_values(tinyxml2::XMLElement* mesh_elem, bool from_exter try { set_parameter_value(name, value); } catch (const std::bad_function_call& exception) { - throw std::runtime_error(error_msg + name + "'."); + svmp::raise(SVMP_HERE, error_msg + name + "'."); } } else { - throw std::runtime_error(error_msg + name + "'."); + svmp::raise(SVMP_HERE, error_msg + name + "'."); } item = item->NextSiblingElement(); @@ -2941,7 +2941,7 @@ void ProjectionParameters::set_values(tinyxml2::XMLElement* xml_elem) const char* sname; auto result = xml_elem->QueryStringAttribute("name", &sname); if (sname == nullptr) { - throw std::runtime_error("No TYPE given in the XML element."); + svmp::raise(SVMP_HERE, "No TYPE given in the XML element."); } name.set(std::string(sname)); @@ -2982,7 +2982,7 @@ void RISProjectionParameters::set_values(tinyxml2::XMLElement* xml_elem) const char* sname; auto result = xml_elem->QueryStringAttribute("name", &sname); if (sname == nullptr) { - throw std::runtime_error("No TYPE given in the XML element."); + svmp::raise(SVMP_HERE, "No TYPE given in the XML element."); } name.set(std::string(sname)); @@ -3061,10 +3061,10 @@ void URISMeshParameters::set_values(tinyxml2::XMLElement* mesh_elem) try { set_parameter_value(name, value); } catch (const std::bad_function_call& exception) { - throw std::runtime_error(error_msg + name + "'."); + svmp::raise(SVMP_HERE, error_msg + name + "'."); } } else { - throw std::runtime_error(error_msg + name + "'."); + svmp::raise(SVMP_HERE, error_msg + name + "'."); } item = item->NextSiblingElement(); @@ -3123,13 +3123,13 @@ void URISFaceParameters::set_values(tinyxml2::XMLElement* face_elem) auto value = item->GetText(); if (value == nullptr) { - throw std::runtime_error(error_msg + name + "'."); + svmp::raise(SVMP_HERE, error_msg + name + "'."); } try { set_parameter_value(name, value); } catch (const std::bad_function_call& exception) { - throw std::runtime_error(error_msg + name + "'."); + svmp::raise(SVMP_HERE, error_msg + name + "'."); } item = item->NextSiblingElement(); @@ -3187,7 +3187,7 @@ void LinearAlgebraParameters::set_values(tinyxml2::XMLElement* xml_elem) const char* stype; auto result = xml_elem->QueryStringAttribute("type", &stype); if (stype == nullptr) { - throw std::runtime_error("No TYPE given in the XML element."); + svmp::raise(SVMP_HERE, "No TYPE given in the XML element."); } type.set(std::string(stype)); @@ -3199,7 +3199,7 @@ void LinearAlgebraParameters::set_values(tinyxml2::XMLElement* xml_elem) std::string valid_types = ""; std::for_each(LinearAlgebra::name_to_type.begin(), LinearAlgebra::name_to_type.end(), [&valid_types](std::pair p) {valid_types += p.first+" ";}); - throw std::runtime_error("Unknown TYPE '" + type.value() + + svmp::raise(SVMP_HERE, "Unknown TYPE '" + type.value() + "' given in the XML element.\nValid types are: " + valid_types); } @@ -3218,7 +3218,7 @@ void LinearAlgebraParameters::set_values(tinyxml2::XMLElement* xml_elem) std::string valid_types = ""; std::for_each(consts::preconditioner_name_to_type.begin(), consts::preconditioner_name_to_type.end(), [&valid_types](std::pair p) {valid_types += p.first+" ";}); - throw std::runtime_error("Unknown TYPE '" + preconditioner() + + svmp::raise(SVMP_HERE, "Unknown TYPE '" + preconditioner() + "' given in the XML element.\nValid types are: " + valid_types); } @@ -3295,7 +3295,7 @@ void LinearSolverParameters::set_values(tinyxml2::XMLElement* xml_elem) const char* stype; auto result = xml_elem->QueryStringAttribute("type", &stype); if (stype == nullptr) { - throw std::runtime_error("No TYPE given in the XML element."); + svmp::raise(SVMP_HERE, "No TYPE given in the XML element."); } type.set(std::string(stype)); diff --git a/Code/Source/solver/Parameters.h b/Code/Source/solver/Parameters.h index 45ba95dcb..b1ed14048 100644 --- a/Code/Source/solver/Parameters.h +++ b/Code/Source/solver/Parameters.h @@ -16,6 +16,7 @@ #include #include +#include "Core/Exception.h" #include "tinyxml2.h" template @@ -136,7 +137,7 @@ class Parameter if (!(str_stream >> value_)) { std::istringstream str_stream(str); if (!(str_stream >> std::boolalpha >> value_)) { - throw std::runtime_error("Incorrect value '" + str + "' for '" + name_ + "'."); + svmp::raise(SVMP_HERE, "Incorrect value '" + str + "' for '" + name_ + "'."); } } @@ -325,7 +326,7 @@ class ParameterLists void set_parameter_value_CANN(const std::string& name, const std::string& value) { if (params_map.count(name) == 0) { - throw std::runtime_error("Unknown " + xml_element_name + " XML element '" + name + "'."); + svmp::raise(SVMP_HERE, "Unknown " + xml_element_name + " XML element '" + name + "'."); } auto& param_variant = params_map[name]; @@ -336,7 +337,7 @@ class ParameterLists (*vec_param)->value_.clear(); // Clear the vector before setting (*vec_param)->set(value); // Set the new value } else { - throw std::runtime_error("Activation_functions is not a VectorParameter."); + svmp::raise(SVMP_HERE, "Activation_functions is not a VectorParameter."); } } // Check for Weights @@ -345,7 +346,7 @@ class ParameterLists (*vec_param)->value_.clear(); // Clear the vector before setting (*vec_param)->set(value); // Set the new value } else { - throw std::runtime_error("Weights is not a VectorParameter."); + svmp::raise(SVMP_HERE, "Weights is not a VectorParameter."); } } // Default: everything else @@ -362,7 +363,7 @@ class ParameterLists void set_parameter_value(const std::string& name, const std::string& value) { if (params_map.count(name) == 0) { - throw std::runtime_error("Unknown " + xml_element_name + " XML element '" + name + "'."); + svmp::raise(SVMP_HERE, "Unknown " + xml_element_name + " XML element '" + name + "'."); } std::visit([value](auto&& p) { p->set(value); }, params_map[name]); @@ -377,7 +378,7 @@ class ParameterLists if (std::visit([](auto&& p) { return !p->check_required_set(); }, param)) { - throw std::runtime_error(xml_element_name + " XML element '" + key + "' has not been set."); + svmp::raise(SVMP_HERE, xml_element_name + " XML element '" + key + "' has not been set."); } } } @@ -1774,4 +1775,3 @@ class Parameters { }; #endif - diff --git a/Code/Source/solver/main.cpp b/Code/Source/solver/main.cpp index b2a3b3193..e1f32dde6 100644 --- a/Code/Source/solver/main.cpp +++ b/Code/Source/solver/main.cpp @@ -13,6 +13,7 @@ #include "all_fun.h" #include "bf.h" #include "contact.h" +#include "Core/Exception.h" #include "distribute.h" #include "eq_assem.h" #include "fs.h" @@ -620,6 +621,7 @@ int main(int argc, char *argv[]) dmsg.banner(); #endif + try { // Iterate for restarting a simulation after remeshing. // while (true) { @@ -691,4 +693,15 @@ int main(int argc, char *argv[]) } MPI_Finalize(); + return 0; + + } catch (const svmp::ParseException& exception) { + if (mpi_rank == 0) { + std::cerr << "[svMultiPhysics] XML parse error:" << std::endl; + std::cerr << exception.what() << std::endl; + } + svmp::ExceptionRuntime::abort_mpi_if_needed(EXIT_FAILURE); + svmp::ExceptionRuntime::finalize_mpi_if_needed(); + return EXIT_FAILURE; + } } From d2870b3dc4cff91dabb6e4addc92d7fc7f06c0d8 Mon Sep 17 00:00:00 2001 From: Zachary Sexton Date: Tue, 28 Apr 2026 11:13:04 -0700 Subject: [PATCH 06/15] #526 adding helper functions for XML required XML attributes/text --- Code/Source/solver/Parameters.cpp | 167 ++++++++++++++++++++++-------- 1 file changed, 123 insertions(+), 44 deletions(-) diff --git a/Code/Source/solver/Parameters.cpp b/Code/Source/solver/Parameters.cpp index 1109b1b2a..507079cd5 100644 --- a/Code/Source/solver/Parameters.cpp +++ b/Code/Source/solver/Parameters.cpp @@ -55,6 +55,44 @@ #include #include +namespace { + +const char* require_xml_attribute(tinyxml2::XMLElement* element, + const char* attribute_name, svmp::SourceLocation location, + const std::string& message) +{ + const char* value = nullptr; + if (element == nullptr || + element->QueryStringAttribute(attribute_name, &value) != tinyxml2::XML_SUCCESS || + value == nullptr) { + svmp::raise(location, message); + } + return value; +} + +const char* require_xml_text(tinyxml2::XMLElement* element, + svmp::SourceLocation location, const std::string& message) +{ + if (element == nullptr || element->GetText() == nullptr) { + svmp::raise(location, message); + } + return element->GetText(); +} + +template +typename MapT::mapped_type require_map_value(const MapT& map, + const typename MapT::key_type& key, svmp::SourceLocation location, + const std::string& message) +{ + auto iter = map.find(key); + if (iter == map.end()) { + svmp::raise(location, message); + } + return iter->second; +} + +} // namespace + /// @brief Set paramaters using a function pointing to the 'ParameterLists::set_parameter_value' method. // // Subsection names given in 'sub_sections' are ignored and processed elsewhere. @@ -93,8 +131,16 @@ std::string IncludeParametersFile::NAME = "Include_xml"; IncludeParametersFile::IncludeParametersFile(const char* cfile_name) { + if (cfile_name == nullptr) { + svmp::raise(SVMP_HERE, "Include_xml requires a file name."); + } + std::string file_name(cfile_name); file_name.erase(std::remove_if(file_name.begin(), file_name.end(), ::isspace), file_name.end()); + if (file_name.empty()) { + svmp::raise(SVMP_HERE, "Include_xml requires a non-empty file name."); + } + auto error = document.LoadFile(file_name.c_str()); root_element = document.FirstChildElement(Parameters::FSI_FILE.c_str()); @@ -201,8 +247,8 @@ void Parameters::set_equation_values(tinyxml2::XMLElement* root_element) auto add_eq_item = root_element->FirstChildElement(EquationParameters::xml_element_name_.c_str()); while (add_eq_item) { - const char* eq_type; - auto result = add_eq_item->QueryStringAttribute("type", &eq_type); + const char* eq_type = require_xml_attribute(add_eq_item, "type", SVMP_HERE, + "No TYPE given in the XML element."); auto eq_params = new EquationParameters(); eq_params->type.set(std::string(eq_type)); @@ -218,8 +264,8 @@ void Parameters::set_mesh_values(tinyxml2::XMLElement* root_element) auto add_mesh_item = root_element->FirstChildElement(MeshParameters::xml_element_name_.c_str()); while (add_mesh_item) { - const char* mesh_name; - auto result = add_mesh_item->QueryStringAttribute("name", &mesh_name); + const char* mesh_name = require_xml_attribute(add_mesh_item, "name", SVMP_HERE, + "No NAME given in the XML element."); MeshParameters* mesh_params = new MeshParameters(); mesh_params->name.set(std::string(mesh_name)); @@ -245,8 +291,8 @@ void Parameters::set_projection_values(tinyxml2::XMLElement* root_element) auto add_proj_item = root_element->FirstChildElement(ProjectionParameters::xml_element_name_.c_str()); while (add_proj_item) { - const char* proj_name; - auto result = add_proj_item->QueryStringAttribute("name", &proj_name); + const char* proj_name = require_xml_attribute(add_proj_item, "name", SVMP_HERE, + "No NAME given in the XML element."); ProjectionParameters* proj_params = new ProjectionParameters(); proj_params->name.set(std::string(proj_name)); @@ -262,8 +308,8 @@ void Parameters::set_RIS_projection_values(tinyxml2::XMLElement* root_element) auto add_RIS_proj_item = root_element->FirstChildElement(RISProjectionParameters::xml_element_name_.c_str()); while (add_RIS_proj_item) { - const char* RIS_proj_name; - auto result = add_RIS_proj_item->QueryStringAttribute("name", &RIS_proj_name); + const char* RIS_proj_name = require_xml_attribute(add_RIS_proj_item, "name", SVMP_HERE, + "No NAME given in the XML element."); RISProjectionParameters* RIS_proj_params = new RISProjectionParameters(); RIS_proj_params->name.set(std::string(RIS_proj_name)); @@ -279,8 +325,8 @@ void Parameters::set_URIS_mesh_values(tinyxml2::XMLElement* root_element) auto add_URIS_mesh_item = root_element->FirstChildElement(URISMeshParameters::xml_element_name_.c_str()); while (add_URIS_mesh_item) { - const char* URIS_mesh_name; - auto result = add_URIS_mesh_item->QueryStringAttribute("name", &URIS_mesh_name); + const char* URIS_mesh_name = require_xml_attribute(add_URIS_mesh_item, "name", SVMP_HERE, + "No NAME given in the XML element."); URISMeshParameters* URIS_mesh_params = new URISMeshParameters(); URIS_mesh_params->name.set(std::string(URIS_mesh_name)); @@ -850,8 +896,8 @@ void CANNRowParameters::set_values(tinyxml2::XMLElement* row_elem) std::string error_msg = "Unknown " + xml_element_name_ + " XML element '"; // Set row_name for current row element - const char* row_name_input; - auto result = row_elem->QueryStringAttribute("row_name", &row_name_input); + const char* row_name_input = require_xml_attribute(row_elem, "row_name", SVMP_HERE, + "No ROW_NAME given in the XML element."); row_name.set(std::string(row_name_input)); auto item = row_elem->FirstChildElement(); @@ -990,8 +1036,10 @@ void ConstitutiveModelParameters::set_values(tinyxml2::XMLElement* xml_elem) // void ConstitutiveModelParameters::check_constitutive_model(const Parameter& eq_type_str) { - auto eq_type = consts::equation_name_to_type.at(eq_type_str.value()); - auto model = consts::constitutive_model_name_to_type.at(type.value()); + auto eq_type = require_map_value(consts::equation_name_to_type, eq_type_str.value(), + SVMP_HERE, "Unknown equation type '" + eq_type_str.value() + "'."); + auto model = require_map_value(consts::constitutive_model_name_to_type, type.value(), + SVMP_HERE, "Unknown constitutive model '" + type.value() + "'."); if (eq_type == consts::EquationType::phys_ustruct) { if (! ustruct::constitutive_model_is_valid(model)) { @@ -1033,7 +1081,7 @@ void CoupleCplBCParameters::set_values(tinyxml2::XMLElement* xml_elem) const char* stype; auto result = xml_elem->QueryStringAttribute("type", &stype); if (stype == nullptr) { - svmp::raise(SVMP_HERE, "No TYPE given in the XML element."); + svmp::raise(SVMP_HERE, "No TYPE given in the XML element."); } type.set(std::string(stype)); auto item = xml_elem->FirstChildElement(); @@ -1183,8 +1231,8 @@ void OutputParameters::set_values(tinyxml2::XMLElement* xml_elem) std::string msg("[OutputParameters::set_values] "); std::string error_msg = "Unknown " + xml_element_name_ + " XML element '"; - const char* stype; - auto result = xml_elem->QueryStringAttribute("type", &stype); + const char* stype = require_xml_attribute(xml_elem, "type", SVMP_HERE, + "No TYPE given in the XML element."); type.set(std::string(stype)); // Get values from XML file. @@ -1196,7 +1244,8 @@ void OutputParameters::set_values(tinyxml2::XMLElement* xml_elem) auto item = xml_elem->FirstChildElement(); while (item != nullptr) { auto name = std::string(item->Name()); - auto value = std::string(item->GetText()); + auto value = std::string(require_xml_text(item, SVMP_HERE, + "Output XML element '" + name + "' requires a value.")); Parameter param(name, "", false); param.set(value); alias_list.emplace_back(param); @@ -1206,7 +1255,8 @@ void OutputParameters::set_values(tinyxml2::XMLElement* xml_elem) auto item = xml_elem->FirstChildElement(); while (item != nullptr) { auto name = std::string(item->Name()); - auto value = std::string(item->GetText()); + auto value = std::string(require_xml_text(item, SVMP_HERE, + "Output XML element '" + name + "' requires a value.")); Parameter param(name, false, false); param.set(value); output_list.emplace_back(param); @@ -1265,11 +1315,8 @@ void VariableWallPropsParameters::set_values(tinyxml2::XMLElement* xml_elem) std::string error_msg = "Unknown " + xml_element_name_ + " XML element '"; // Get the 'type' from the element. - const char* sname; - auto result = xml_elem->QueryStringAttribute("mesh_name", &sname); - if (sname == nullptr) { - svmp::raise(SVMP_HERE, "No TYPE given in the XML element."); - } + const char* sname = require_xml_attribute(xml_elem, "mesh_name", SVMP_HERE, + "No MESH_NAME given in the XML element."); mesh_name.set(std::string(sname)); auto item = xml_elem->FirstChildElement(); @@ -1718,7 +1765,8 @@ void DomainParameters::set_values(tinyxml2::XMLElement* domain_elem, bool from_e ttp_initial_conditions.set_values(item); } else if (name == FluidViscosityParameters::xml_element_name_ || name == SolidViscosityParameters::xml_element_name_) { - auto eq_type = consts::equation_name_to_type.at(equation.value()); + auto eq_type = require_map_value(consts::equation_name_to_type, equation.value(), + SVMP_HERE, "Unknown equation type '" + equation.value() + "' while parsing viscosity model."); if (eq_type == consts::EquationType::phys_fluid || eq_type == consts::EquationType::phys_CMM || eq_type == consts::EquationType::phys_stokes) { fluid_viscosity.set_values(item); } else if (eq_type == consts::EquationType::phys_struct || eq_type == consts::EquationType::phys_ustruct) { @@ -1729,7 +1777,8 @@ void DomainParameters::set_values(tinyxml2::XMLElement* domain_elem, bool from_e } } else if (name == include_xml.name()) { - auto value = item->GetText(); + auto value = require_xml_text(item, SVMP_HERE, + "Domain Include_xml requires a file name."); IncludeParametersFile include_parameters(value); set_values(include_parameters.root_element, true); @@ -2460,7 +2509,8 @@ void EquationParameters::set_values(tinyxml2::XMLElement* eq_elem, DomainParamet domain->stimulus.set_values(item); } else if (viscosity_names.count(name)) { - auto eq_type = consts::equation_name_to_type.at(type.value()); + auto eq_type = require_map_value(consts::equation_name_to_type, type.value(), + SVMP_HERE, "Unknown equation type '" + type.value() + "' while parsing viscosity model."); if (fluid_eqs.count(eq_type)) { domain->fluid_viscosity.set_values(item); @@ -2477,7 +2527,8 @@ void EquationParameters::set_values(tinyxml2::XMLElement* eq_elem, DomainParamet variable_wall_properties.set_values(item); } else if (name == include_xml.name()) { - auto value = item->GetText(); + auto value = require_xml_text(item, SVMP_HERE, + "Equation Include_xml requires a file name."); IncludeParametersFile include_parameters(value); set_values(include_parameters.root_element, default_domain); @@ -2590,19 +2641,25 @@ void GeneralSimulationParameters::set_values(tinyxml2::XMLElement* xml_element, item = xml_element->FirstChildElement(); } else { auto general_params = xml_element->FirstChildElement(xml_element_name.c_str()); + if (general_params == nullptr) { + svmp::raise(SVMP_HERE, + "No <" + xml_element_name + "> section found in the solver XML file."); + } item = general_params->FirstChildElement(); } while (item != nullptr) { std::string name = std::string(item->Value()); - auto value = item->GetText(); if (name == include_xml.name()) { - auto value = item->GetText(); + auto value = require_xml_text(item, SVMP_HERE, + "GeneralSimulationParameters Include_xml requires a file name."); IncludeParametersFile include_parameters(value); set_values(include_parameters.root_element, true); } else { + auto value = require_xml_text(item, SVMP_HERE, + "GeneralSimulationParameters XML element '" + name + "' requires a value."); try { set_parameter_value(name, value); @@ -2659,8 +2716,8 @@ void FaceParameters::set_values(tinyxml2::XMLElement* face_elem) using namespace tinyxml2; std::string error_msg = "Unknown " + xml_element_name_ + " XML element '"; - const char* face_name; - auto result = face_elem->QueryStringAttribute("name", &face_name); + const char* face_name = require_xml_attribute(face_elem, "name", SVMP_HERE, + "No NAME given in the XML element."); name.set(std::string(face_name)); auto item = face_elem->FirstChildElement(); @@ -2851,13 +2908,15 @@ void MeshParameters::set_values(tinyxml2::XMLElement* mesh_elem, bool from_exter // them as a list of VectorParameter. // } else if (name == "Fiber_direction") { - auto value = item->GetText(); + auto value = require_xml_text(item, SVMP_HERE, + "Mesh Fiber_direction XML element requires a value."); VectorParameter dir("Fiber_direction", {}, false, {}); dir.set(value); fiber_directions.push_back(dir); } else if (name == include_xml.name()) { - auto value = item->GetText(); + auto value = require_xml_text(item, SVMP_HERE, + "Mesh Include_xml requires a file name."); IncludeParametersFile include_parameters(value); set_values(include_parameters.root_element, true); @@ -2941,7 +3000,7 @@ void ProjectionParameters::set_values(tinyxml2::XMLElement* xml_elem) const char* sname; auto result = xml_elem->QueryStringAttribute("name", &sname); if (sname == nullptr) { - svmp::raise(SVMP_HERE, "No TYPE given in the XML element."); + svmp::raise(SVMP_HERE, "No NAME given in the XML element."); } name.set(std::string(sname)); @@ -2982,7 +3041,7 @@ void RISProjectionParameters::set_values(tinyxml2::XMLElement* xml_elem) const char* sname; auto result = xml_elem->QueryStringAttribute("name", &sname); if (sname == nullptr) { - svmp::raise(SVMP_HERE, "No TYPE given in the XML element."); + svmp::raise(SVMP_HERE, "No NAME given in the XML element."); } name.set(std::string(sname)); @@ -3113,8 +3172,8 @@ void URISFaceParameters::set_values(tinyxml2::XMLElement* face_elem) using namespace tinyxml2; std::string error_msg = "Unknown " + xml_element_name_ + " XML element '"; - const char* face_name; - auto result = face_elem->QueryStringAttribute("name", &face_name); + const char* face_name = require_xml_attribute(face_elem, "name", SVMP_HERE, + "No NAME given in the XML element."); name.set(std::string(face_name)); auto item = face_elem->FirstChildElement(); @@ -3230,12 +3289,32 @@ void LinearAlgebraParameters::set_values(tinyxml2::XMLElement* xml_elem) /// @brief Check the validity of the input parameters. void LinearAlgebraParameters::check_input_parameters() { - auto linear_algebra_type = LinearAlgebra::name_to_type.at(type()); - auto prec_cond_type = consts::preconditioner_name_to_type.at(preconditioner.value()); - auto assembly_type = LinearAlgebra::name_to_type.at(assembly.value()); - - auto linear_algebra = LinearAlgebraFactory::create_interface(linear_algebra_type); - linear_algebra->check_options(prec_cond_type, assembly_type); + auto linear_algebra_type = require_map_value(LinearAlgebra::name_to_type, type(), + SVMP_HERE, "Unknown TYPE '" + type() + + "' given in the XML element."); + auto prec_cond_type = require_map_value(consts::preconditioner_name_to_type, + preconditioner.value(), SVMP_HERE, "Unknown TYPE '" + preconditioner() + + "' given in the XML element."); + auto assembly_type = require_map_value(LinearAlgebra::name_to_type, assembly.value(), + SVMP_HERE, "Unknown TYPE '" + assembly() + + "' given in the XML element."); + + LinearAlgebra* linear_algebra = nullptr; + try { + linear_algebra = LinearAlgebraFactory::create_interface(linear_algebra_type); + if (linear_algebra == nullptr) { + svmp::raise(SVMP_HERE, + "Linear_algebra type '" + type() + "' cannot be used as a solver backend."); + } + linear_algebra->check_options(prec_cond_type, assembly_type); + delete linear_algebra; + } catch (const svmp::ParseException&) { + delete linear_algebra; + throw; + } catch (const std::exception& exception) { + delete linear_algebra; + svmp::raise(SVMP_HERE, exception.what()); + } } ////////////////////////////////////////////////////////// From adac79da2641bb5ddc3f8338f46c4f0cc4618726 Mon Sep 17 00:00:00 2001 From: Zachary Sexton Date: Tue, 28 Apr 2026 11:16:08 -0700 Subject: [PATCH 07/15] #526 changing "svFSI" to "svMultiPhysics" in the line Parameters.cpp:206 for the error string. --- Code/Source/solver/Parameters.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Code/Source/solver/Parameters.cpp b/Code/Source/solver/Parameters.cpp index 507079cd5..965f6bb32 100644 --- a/Code/Source/solver/Parameters.cpp +++ b/Code/Source/solver/Parameters.cpp @@ -203,7 +203,7 @@ void Parameters::read_xml(std::string file_name) auto root_element = doc.FirstChildElement(FSI_FILE.c_str()); if (error != tinyxml2::XML_SUCCESS || root_element == nullptr) { svmp::raise(SVMP_HERE, "The following error occured while reading the XML file '" + file_name + "'.\n" + - "[svFSI] ERROR " + std::string(doc.ErrorStr())); + "[svMultiPhysics] ERROR " + std::string(doc.ErrorStr())); } // Get general parameters. From ec74dcfed5aa06a580eb60ec621fc01ad87b1f4e Mon Sep 17 00:00:00 2001 From: Zachary Sexton <47196674+zasexton@users.noreply.github.com> Date: Tue, 28 Apr 2026 11:58:02 -0700 Subject: [PATCH 08/15] Update Code/Source/solver/Parameters.cpp whoops, can't spell. correcting occured -> occurred Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Code/Source/solver/Parameters.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Code/Source/solver/Parameters.cpp b/Code/Source/solver/Parameters.cpp index 965f6bb32..c5e16943e 100644 --- a/Code/Source/solver/Parameters.cpp +++ b/Code/Source/solver/Parameters.cpp @@ -145,7 +145,7 @@ IncludeParametersFile::IncludeParametersFile(const char* cfile_name) root_element = document.FirstChildElement(Parameters::FSI_FILE.c_str()); if (error != tinyxml2::XML_SUCCESS || root_element == nullptr) { - svmp::raise(SVMP_HERE, "The following error occured while reading the XML file '" + + svmp::raise(SVMP_HERE, "The following error occurred while reading the XML file '" + file_name + "'.\n" + "[svMultiPhysics] ERROR " + std::string(document.ErrorStr())); } } From d33c9709858409b259359dba827ed775509d6669 Mon Sep 17 00:00:00 2001 From: Zachary Sexton Date: Tue, 28 Apr 2026 12:12:15 -0700 Subject: [PATCH 09/15] #537 fixing two spots in Code/Source/solver/Parameters.cpp:2547 for close quotes --- Code/Source/solver/Parameters.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Code/Source/solver/Parameters.cpp b/Code/Source/solver/Parameters.cpp index c5e16943e..ab41250f3 100644 --- a/Code/Source/solver/Parameters.cpp +++ b/Code/Source/solver/Parameters.cpp @@ -2544,13 +2544,13 @@ void EquationParameters::set_values(tinyxml2::XMLElement* eq_elem, DomainParamet try { default_domain->set_parameter_value(name, value); } catch (const std::bad_function_call& exception) { - svmp::raise(SVMP_HERE, "Unknown " + xml_element_name_ + " XML element '" + name + "."); + svmp::raise(SVMP_HERE, "Unknown " + xml_element_name_ + " XML element '" + name + "'."); } } } else { - svmp::raise(SVMP_HERE, "[Equation] Unknown " + xml_element_name_ + " XML element '" + name + "."); + svmp::raise(SVMP_HERE, "[Equation] Unknown " + xml_element_name_ + " XML element '" + name + "'."); } item = item->NextSiblingElement(); From 36d8fe065eb12f472b63aef215f48f07f42a01ab Mon Sep 17 00:00:00 2001 From: Zachary Sexton Date: Tue, 28 Apr 2026 12:31:11 -0700 Subject: [PATCH 10/15] #537 - Renamed Code/Source/solver/Core/PlatformSupport.h to PlatformSupport.inl. - Updated Code/Source/solver/Core/Exception.h:131 to include it locally as "PlatformSupport.inl". - Added a private-include guard so PlatformSupport.inl errors if included directly instead of through Exception.h. - Updated Code/Source/solver/CMakeLists.txt:234 to include Core/*.inl in the solver source listing. --- Code/Source/solver/CMakeLists.txt | 1 + Code/Source/solver/Core/Exception.h | 4 +++- .../Core/{PlatformSupport.h => PlatformSupport.inl} | 10 +++++++--- 3 files changed, 11 insertions(+), 4 deletions(-) rename Code/Source/solver/Core/{PlatformSupport.h => PlatformSupport.inl} (94%) diff --git a/Code/Source/solver/CMakeLists.txt b/Code/Source/solver/CMakeLists.txt index c78240cff..5a0a74d53 100644 --- a/Code/Source/solver/CMakeLists.txt +++ b/Code/Source/solver/CMakeLists.txt @@ -234,6 +234,7 @@ set(CSRCS file(GLOB SOLVER_CORE_SRCS CONFIGURE_DEPENDS Core/*.cpp Core/*.h + Core/*.inl ) file(GLOB SOLVER_FE_COMMON_SRCS CONFIGURE_DEPENDS diff --git a/Code/Source/solver/Core/Exception.h b/Code/Source/solver/Core/Exception.h index 86fbf05b4..c33fc1bcf 100644 --- a/Code/Source/solver/Core/Exception.h +++ b/Code/Source/solver/Core/Exception.h @@ -128,7 +128,9 @@ class PlatformSupport final { }; } // namespace svmp -#include "Core/PlatformSupport.h" +#define SVMP_CORE_EXCEPTION_INCLUDE_PLATFORM_SUPPORT +#include "PlatformSupport.inl" +#undef SVMP_CORE_EXCEPTION_INCLUDE_PLATFORM_SUPPORT namespace svmp { diff --git a/Code/Source/solver/Core/PlatformSupport.h b/Code/Source/solver/Core/PlatformSupport.inl similarity index 94% rename from Code/Source/solver/Core/PlatformSupport.h rename to Code/Source/solver/Core/PlatformSupport.inl index 7117dae5b..010a27f06 100644 --- a/Code/Source/solver/Core/PlatformSupport.h +++ b/Code/Source/solver/Core/PlatformSupport.inl @@ -1,8 +1,12 @@ // SPDX-FileCopyrightText: Copyright (c) Stanford University, The Regents of the University of California, and others. // SPDX-License-Identifier: BSD-3-Clause -#ifndef SVMP_CORE_PLATFORM_SUPPORT_H -#define SVMP_CORE_PLATFORM_SUPPORT_H +#ifndef SVMP_CORE_PLATFORM_SUPPORT_INL +#define SVMP_CORE_PLATFORM_SUPPORT_INL + +#ifndef SVMP_CORE_EXCEPTION_INCLUDE_PLATFORM_SUPPORT +#error "PlatformSupport.inl is private; include Core/Exception.h instead." +#endif #include @@ -160,4 +164,4 @@ inline void PlatformSupport::abort_mpi_if_needed(int exit_code) noexcept } // namespace svmp -#endif // SVMP_CORE_PLATFORM_SUPPORT_H +#endif // SVMP_CORE_PLATFORM_SUPPORT_INL From c468b3689a0547c306acd52056f8740ccafcc371 Mon Sep 17 00:00:00 2001 From: Zachary Sexton Date: Tue, 28 Apr 2026 12:42:32 -0700 Subject: [PATCH 11/15] #537 including "Exception.h" without the Core folder path --- Code/Source/solver/FE/Common/FEException.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Code/Source/solver/FE/Common/FEException.h b/Code/Source/solver/FE/Common/FEException.h index 20ee0eef6..b7bf646a3 100644 --- a/Code/Source/solver/FE/Common/FEException.h +++ b/Code/Source/solver/FE/Common/FEException.h @@ -12,7 +12,7 @@ * solver exception infrastructure in Core/Exception.h. */ -#include "Core/Exception.h" +#include "Exception.h" #include #include From 28709d2de0654d04ed1b51bd5a5d158cdeea0a26 Mon Sep 17 00:00:00 2001 From: Zachary Sexton Date: Tue, 28 Apr 2026 12:48:41 -0700 Subject: [PATCH 12/15] #537 small replacement of the parse-only catch with a single ExceptionBase catch --- Code/Source/solver/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Code/Source/solver/main.cpp b/Code/Source/solver/main.cpp index e1f32dde6..11a3b308a 100644 --- a/Code/Source/solver/main.cpp +++ b/Code/Source/solver/main.cpp @@ -695,9 +695,9 @@ int main(int argc, char *argv[]) MPI_Finalize(); return 0; - } catch (const svmp::ParseException& exception) { + } catch (const svmp::ExceptionBase& exception) { if (mpi_rank == 0) { - std::cerr << "[svMultiPhysics] XML parse error:" << std::endl; + std::cerr << "[svMultiPhysics] ERROR: The svMultiPhysics program has failed due to unhandled exception." << std::endl; std::cerr << exception.what() << std::endl; } svmp::ExceptionRuntime::abort_mpi_if_needed(EXIT_FAILURE); From a96d700c6c4a0490088f4b149f44c5d3c6b6394d Mon Sep 17 00:00:00 2001 From: Zachary Sexton Date: Tue, 28 Apr 2026 13:02:09 -0700 Subject: [PATCH 13/15] #537 adding second `std::exception` catch --- Code/Source/solver/main.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Code/Source/solver/main.cpp b/Code/Source/solver/main.cpp index 11a3b308a..02a38de30 100644 --- a/Code/Source/solver/main.cpp +++ b/Code/Source/solver/main.cpp @@ -703,5 +703,13 @@ int main(int argc, char *argv[]) svmp::ExceptionRuntime::abort_mpi_if_needed(EXIT_FAILURE); svmp::ExceptionRuntime::finalize_mpi_if_needed(); return EXIT_FAILURE; + } catch (const std::exception& exception) { + if (mpi_rank == 0) { + std::cerr << "[svMultiPhysics] ERROR: The svMultiPhysics program has failed due to unhandled exception." << std::endl; + std::cerr << exception.what() << std::endl; + } + svmp::ExceptionRuntime::abort_mpi_if_needed(EXIT_FAILURE); + svmp::ExceptionRuntime::finalize_mpi_if_needed(); + return EXIT_FAILURE; } } From 45d13cb60ba48c9626c649f2e83692c168f6cbdf Mon Sep 17 00:00:00 2001 From: Zachary Sexton Date: Tue, 28 Apr 2026 13:17:20 -0700 Subject: [PATCH 14/15] #537 - Code/Source/solver/Parameters.cpp:205: fixed occured to occurred. - Code/Source/solver/FE/Common/FEException.h:12: updated the comment to reference Exception.h. - Code/Source/solver/main.cpp:714: added the final `catch (...)` so unknown non-std::exception throws still report an error and call the MPI cleanup helpers. --- Code/Source/solver/FE/Common/FEException.h | 2 +- Code/Source/solver/Parameters.cpp | 2 +- Code/Source/solver/main.cpp | 7 +++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Code/Source/solver/FE/Common/FEException.h b/Code/Source/solver/FE/Common/FEException.h index b7bf646a3..bac7d6565 100644 --- a/Code/Source/solver/FE/Common/FEException.h +++ b/Code/Source/solver/FE/Common/FEException.h @@ -9,7 +9,7 @@ * @brief Exception hierarchy for error handling in the FE library * * This header defines FE-specific exception types that derive from the shared - * solver exception infrastructure in Core/Exception.h. + * solver exception infrastructure in Exception.h. */ #include "Exception.h" diff --git a/Code/Source/solver/Parameters.cpp b/Code/Source/solver/Parameters.cpp index ab41250f3..886e8c67b 100644 --- a/Code/Source/solver/Parameters.cpp +++ b/Code/Source/solver/Parameters.cpp @@ -202,7 +202,7 @@ void Parameters::read_xml(std::string file_name) auto root_element = doc.FirstChildElement(FSI_FILE.c_str()); if (error != tinyxml2::XML_SUCCESS || root_element == nullptr) { - svmp::raise(SVMP_HERE, "The following error occured while reading the XML file '" + file_name + "'.\n" + + svmp::raise(SVMP_HERE, "The following error occurred while reading the XML file '" + file_name + "'.\n" + "[svMultiPhysics] ERROR " + std::string(doc.ErrorStr())); } diff --git a/Code/Source/solver/main.cpp b/Code/Source/solver/main.cpp index 02a38de30..4e778f03e 100644 --- a/Code/Source/solver/main.cpp +++ b/Code/Source/solver/main.cpp @@ -711,5 +711,12 @@ int main(int argc, char *argv[]) svmp::ExceptionRuntime::abort_mpi_if_needed(EXIT_FAILURE); svmp::ExceptionRuntime::finalize_mpi_if_needed(); return EXIT_FAILURE; + } catch (...) { + if (mpi_rank == 0) { + std::cerr << "[svMultiPhysics] ERROR: The svMultiPhysics program has failed due to an unknown unhandled exception." << std::endl; + } + svmp::ExceptionRuntime::abort_mpi_if_needed(EXIT_FAILURE); + svmp::ExceptionRuntime::finalize_mpi_if_needed(); + return EXIT_FAILURE; } } From 67924fbade184b73a11bc3b2f6eeaca93af936ce Mon Sep 17 00:00:00 2001 From: Zachary Sexton Date: Mon, 4 May 2026 19:26:20 -0700 Subject: [PATCH 15/15] #537 addressing additional comments from michele --- Code/Source/solver/Core/Exception.h | 156 +++++++++-------- Code/Source/solver/FE/Common/FEException.h | 32 +--- Code/Source/solver/Parameters.cpp | 184 ++++++++------------- 3 files changed, 159 insertions(+), 213 deletions(-) diff --git a/Code/Source/solver/Core/Exception.h b/Code/Source/solver/Core/Exception.h index c33fc1bcf..bc7012271 100644 --- a/Code/Source/solver/Core/Exception.h +++ b/Code/Source/solver/Core/Exception.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -171,95 +172,101 @@ class ExceptionContext final { StackTrace stack_trace_; }; -class ExceptionFormatter final { -public: - static std::string format(const ExceptionContext& context, - const std::string& message, - const char* subsystem_label) - { - std::ostringstream oss; +namespace ExceptionFormatter { + +inline std::string format(const ExceptionContext& context, + const std::string& message, + std::string_view subsystem_label = "Exception") +{ + if (subsystem_label.empty()) { + subsystem_label = "Exception"; + } - oss << "[" << ((subsystem_label == nullptr) ? "Exception" : subsystem_label) - << "] " << status_code_to_string(context.status_code()); - if (context.mpi_rank() >= 0) { - oss << " (Rank " << context.mpi_rank() << ")"; + std::ostringstream oss; + + oss << "[" << subsystem_label << "] " + << status_code_to_string(context.status_code()); + if (context.mpi_rank() >= 0) { + oss << " (Rank " << context.mpi_rank() << ")"; + } + oss << "\n"; + + if (!context.file().empty()) { + oss << " Location: " << context.file() << ":" << context.line(); + if (!context.function().empty()) { + oss << " in " << context.function() << "()"; } oss << "\n"; + } - if (!context.file().empty()) { - oss << " Location: " << context.file() << ":" << context.line(); - if (!context.function().empty()) { - oss << " in " << context.function() << "()"; + oss << " Message: " << message << "\n"; + + if (!context.stack_trace().empty()) { + oss << " Stack trace:\n"; + std::size_t frame_index = 0; + for (const auto& frame : context.stack_trace().frames()) { + oss << " #" << frame_index++ << " "; + if (!frame.symbol().empty()) { + oss << frame.symbol(); + } else { + std::ostringstream address; + address << "0x" << std::hex << frame.address(); + oss << address.str(); } - oss << "\n"; - } - oss << " Message: " << message << "\n"; - - if (!context.stack_trace().empty()) { - oss << " Stack trace:\n"; - std::size_t frame_index = 0; - for (const auto& frame : context.stack_trace().frames()) { - oss << " #" << frame_index++ << " "; - if (!frame.symbol().empty()) { - oss << frame.symbol(); - } else { - std::ostringstream address; - address << "0x" << std::hex << frame.address(); - oss << address.str(); - } - - if (!frame.module().empty()) { - oss << " [" << frame.module() << "]"; - } + if (!frame.module().empty()) { + oss << " [" << frame.module() << "]"; + } - if (!frame.file().empty()) { - oss << " (" << frame.file(); - if (frame.line() > 0) { - oss << ":" << frame.line(); - } - oss << ")"; + if (!frame.file().empty()) { + oss << " (" << frame.file(); + if (frame.line() > 0) { + oss << ":" << frame.line(); } - - oss << "\n"; + oss << ")"; } - } - return oss.str(); + oss << "\n"; + } } -}; + + return oss.str(); +} + +} // namespace ExceptionFormatter class ExceptionBase; -class ExceptionRuntime final { -public: - static int query_mpi_rank() noexcept +namespace ExceptionRuntime { + + inline int query_mpi_rank() noexcept { return PlatformSupport::query_mpi_rank(); } - static StackTrace capture_stack_trace() + inline StackTrace capture_stack_trace() { return PlatformSupport::capture_stack_trace(); } - static void finalize_mpi_if_needed() noexcept + inline void finalize_mpi_if_needed() noexcept { PlatformSupport::finalize_mpi_if_needed(); } - static void install_terminate_handler(); + void install_terminate_handler(); - static void report_unhandled_exception(const std::exception& exception) + inline void report_unhandled_exception(const std::exception& exception) { std::cerr << exception.what() << std::endl; } - static void abort_mpi_if_needed(int exit_code) noexcept + inline void abort_mpi_if_needed(int exit_code) noexcept { PlatformSupport::abort_mpi_if_needed(exit_code); } -}; + +} // namespace ExceptionRuntime class ExceptionBase : public std::exception { public: @@ -271,15 +278,18 @@ class ExceptionBase : public std::exception { void add_context(const std::string& context) { message_ = context + "\n -> " + message_; - rebuild_what(subsystem_label()); + rebuild_what(); } virtual ~ExceptionBase() noexcept = default; protected: - ExceptionBase(std::string message, StatusCode status, const char* file, + ExceptionBase(std::string message, StatusCode status, + std::string_view subsystem_label, const char* file, int line, const char* function) - : message_(std::move(message)) + : message_(std::move(message)), + subsystem_label_(subsystem_label.empty() ? std::string_view("Exception") + : subsystem_label) { context_.set_status_code(status); context_.set_source_location(file, line, function); @@ -287,17 +297,17 @@ class ExceptionBase : public std::exception { #if SVMP_EXCEPTION_DEBUG_MODE context_.set_stack_trace(ExceptionRuntime::capture_stack_trace()); #endif + rebuild_what(); } - virtual const char* subsystem_label() const noexcept = 0; - - void rebuild_what(const char* subsystem_label) + void rebuild_what() { - what_ = ExceptionFormatter::format(context_, message_, subsystem_label); + what_ = ExceptionFormatter::format(context_, message_, subsystem_label_); } std::string message_; ExceptionContext context_; + std::string_view subsystem_label_; std::string what_; }; @@ -308,15 +318,8 @@ class CoreException : public ExceptionBase { const char* file = "", int line = 0, const char* function = "") - : ExceptionBase(message, status, file, line, function) - { - rebuild_what(subsystem_label()); - } - -protected: - const char* subsystem_label() const noexcept override + : ExceptionBase(message, status, "Core Exception", file, line, function) { - return "Core Exception"; } }; @@ -399,4 +402,17 @@ PointerT* check_not_null(PointerT* ptr, SourceLocation location, Args&&... args) #define SVMP_HERE ::svmp::SourceLocation{__FILE__, __LINE__, __func__} +#if SVMP_EXCEPTION_DEBUG_MODE +#define SVMP_DEBUG_CHECK(ExceptionT, condition, ...) \ + do { \ + if (!(condition)) { \ + ::svmp::raise(SVMP_HERE, __VA_ARGS__); \ + } \ + } while (false) +#else +#define SVMP_DEBUG_CHECK(ExceptionT, condition, ...) \ + do { \ + } while (false) +#endif + #endif // SVMP_CORE_EXCEPTION_H diff --git a/Code/Source/solver/FE/Common/FEException.h b/Code/Source/solver/FE/Common/FEException.h index bac7d6565..d4791eeb9 100644 --- a/Code/Source/solver/FE/Common/FEException.h +++ b/Code/Source/solver/FE/Common/FEException.h @@ -9,7 +9,8 @@ * @brief Exception hierarchy for error handling in the FE library * * This header defines FE-specific exception types that derive from the shared - * solver exception infrastructure in Exception.h. + * solver exception infrastructure in Exception.h. FEException marks + * failures from finite-element assembly, backend, DOF, and element operations. */ #include "Exception.h" @@ -28,14 +29,13 @@ class FEException : public ExceptionBase { const char* file = "", int line = 0, const char* function = "") - : ExceptionBase(prefix_status_message(message, status), + : ExceptionBase(message, status, + "FE Exception", file, line, - function), - status_(status) + function) { - rebuild_what(subsystem_label()); } FEException(const std::string& message, @@ -46,27 +46,7 @@ class FEException : public ExceptionBase { { } - StatusCode status() const noexcept { return status_; } - -protected: - const char* subsystem_label() const noexcept override - { - return "FE Exception"; - } - -private: - static std::string prefix_status_message(const std::string& message, - StatusCode status) - { - if (status == StatusCode::Success || status == StatusCode::Unknown) { - return message; - } - - return std::string("[") + status_code_to_string(status) + "] " + - message; - } - - StatusCode status_ = StatusCode::Unknown; + StatusCode status() const noexcept { return status_code(); } }; class InvalidArgumentException : public FEException { diff --git a/Code/Source/solver/Parameters.cpp b/Code/Source/solver/Parameters.cpp index ee6348948..062619e9a 100644 --- a/Code/Source/solver/Parameters.cpp +++ b/Code/Source/solver/Parameters.cpp @@ -48,6 +48,8 @@ #include "LinearAlgebra.h" #include "ustruct.h" +#include +#include #include #include #include @@ -57,15 +59,42 @@ namespace { +std::string uppercase_xml_name(const std::string& value) +{ + std::string upper = value; + std::transform(upper.begin(), upper.end(), upper.begin(), + [](unsigned char c) { return static_cast(std::toupper(c)); }); + return upper; +} + +std::string missing_xml_attribute_message(tinyxml2::XMLElement* element, + const char* attribute_name) +{ + const std::string element_name = + (element != nullptr && element->Name() != nullptr) + ? std::string(element->Name()) + : std::string("unknown"); + const std::string attribute = + (attribute_name != nullptr) ? std::string(attribute_name) + : std::string("attribute"); + const std::string attribute_upper = uppercase_xml_name(attribute); + + return "No " + attribute_upper + " given in the XML <" + element_name + " " + + attribute + "=" + attribute_upper + "> element."; +} + const char* require_xml_attribute(tinyxml2::XMLElement* element, const char* attribute_name, svmp::SourceLocation location, - const std::string& message) + const std::string& message = std::string()) { const char* value = nullptr; if (element == nullptr || element->QueryStringAttribute(attribute_name, &value) != tinyxml2::XML_SUCCESS || value == nullptr) { - svmp::raise(location, message); + svmp::raise( + location, + message.empty() ? missing_xml_attribute_message(element, attribute_name) + : message); } return value; } @@ -131,15 +160,13 @@ std::string IncludeParametersFile::NAME = "Include_xml"; IncludeParametersFile::IncludeParametersFile(const char* cfile_name) { - if (cfile_name == nullptr) { - svmp::raise(SVMP_HERE, "Include_xml requires a file name."); - } + svmp::check( + cfile_name != nullptr, SVMP_HERE, "Include_xml requires a file name."); std::string file_name(cfile_name); file_name.erase(std::remove_if(file_name.begin(), file_name.end(), ::isspace), file_name.end()); - if (file_name.empty()) { - svmp::raise(SVMP_HERE, "Include_xml requires a non-empty file name."); - } + svmp::check( + !file_name.empty(), SVMP_HERE, "Include_xml requires a non-empty file name."); auto error = document.LoadFile(file_name.c_str()); root_element = document.FirstChildElement(Parameters::FSI_FILE.c_str()); @@ -247,8 +274,7 @@ void Parameters::set_equation_values(tinyxml2::XMLElement* root_element) auto add_eq_item = root_element->FirstChildElement(EquationParameters::xml_element_name_.c_str()); while (add_eq_item) { - const char* eq_type = require_xml_attribute(add_eq_item, "type", SVMP_HERE, - "No TYPE given in the XML element."); + const char* eq_type = require_xml_attribute(add_eq_item, "type", SVMP_HERE); auto eq_params = new EquationParameters(); eq_params->type.set(std::string(eq_type)); @@ -264,8 +290,7 @@ void Parameters::set_mesh_values(tinyxml2::XMLElement* root_element) auto add_mesh_item = root_element->FirstChildElement(MeshParameters::xml_element_name_.c_str()); while (add_mesh_item) { - const char* mesh_name = require_xml_attribute(add_mesh_item, "name", SVMP_HERE, - "No NAME given in the XML element."); + const char* mesh_name = require_xml_attribute(add_mesh_item, "name", SVMP_HERE); MeshParameters* mesh_params = new MeshParameters(); mesh_params->name.set(std::string(mesh_name)); @@ -291,8 +316,7 @@ void Parameters::set_projection_values(tinyxml2::XMLElement* root_element) auto add_proj_item = root_element->FirstChildElement(ProjectionParameters::xml_element_name_.c_str()); while (add_proj_item) { - const char* proj_name = require_xml_attribute(add_proj_item, "name", SVMP_HERE, - "No NAME given in the XML element."); + const char* proj_name = require_xml_attribute(add_proj_item, "name", SVMP_HERE); ProjectionParameters* proj_params = new ProjectionParameters(); proj_params->name.set(std::string(proj_name)); @@ -308,8 +332,8 @@ void Parameters::set_RIS_projection_values(tinyxml2::XMLElement* root_element) auto add_RIS_proj_item = root_element->FirstChildElement(RISProjectionParameters::xml_element_name_.c_str()); while (add_RIS_proj_item) { - const char* RIS_proj_name = require_xml_attribute(add_RIS_proj_item, "name", SVMP_HERE, - "No NAME given in the XML element."); + const char* RIS_proj_name = + require_xml_attribute(add_RIS_proj_item, "name", SVMP_HERE); RISProjectionParameters* RIS_proj_params = new RISProjectionParameters(); RIS_proj_params->name.set(std::string(RIS_proj_name)); @@ -325,8 +349,8 @@ void Parameters::set_URIS_mesh_values(tinyxml2::XMLElement* root_element) auto add_URIS_mesh_item = root_element->FirstChildElement(URISMeshParameters::xml_element_name_.c_str()); while (add_URIS_mesh_item) { - const char* URIS_mesh_name = require_xml_attribute(add_URIS_mesh_item, "name", SVMP_HERE, - "No NAME given in the XML element."); + const char* URIS_mesh_name = + require_xml_attribute(add_URIS_mesh_item, "name", SVMP_HERE); URISMeshParameters* URIS_mesh_params = new URISMeshParameters(); URIS_mesh_params->name.set(std::string(URIS_mesh_name)); @@ -386,11 +410,7 @@ void BodyForceParameters::set_values(tinyxml2::XMLElement* xml_elem) std::string error_msg = "Unknown " + xml_element_name_ + " XML element '"; // Get the 'type' from the element. - const char* smesh; - auto result = xml_elem->QueryStringAttribute("mesh", &smesh); - if (smesh == nullptr) { - svmp::raise(SVMP_HERE, "No MESH given in the XML element."); - } + const char* smesh = require_xml_attribute(xml_elem, "mesh", SVMP_HERE); mesh_name.set(std::string(smesh)); //auto item = xml_elem->FirstChildElement(); @@ -564,11 +584,7 @@ void BoundaryConditionParameters::set_values(tinyxml2::XMLElement* xml_elem) std::string error_msg = "Unknown " + xml_element_name_ + " XML element '"; // Get the 'name' from the element. - const char* sname; - auto result = xml_elem->QueryStringAttribute("name", &sname); - if (sname == nullptr) { - svmp::raise(SVMP_HERE, "No NAME given in the XML element."); - } + const char* sname = require_xml_attribute(xml_elem, "name", SVMP_HERE); name.set(std::string(sname)); auto item = xml_elem->FirstChildElement(); @@ -921,17 +937,16 @@ void CANNRowParameters::print_parameters() void CANNRowParameters::set_values(tinyxml2::XMLElement* row_elem) { - if (!row_elem) { - svmp::raise(SVMP_HERE, "CANNRowParameters::set_values: Received null XML element."); - } + row_elem = svmp::check_not_null( + row_elem, SVMP_HERE, "CANNRowParameters::set_values: Received null XML element."); using namespace tinyxml2; std::string error_msg = "Unknown " + xml_element_name_ + " XML element '"; // Set row_name for current row element - const char* row_name_input = require_xml_attribute(row_elem, "row_name", SVMP_HERE, - "No ROW_NAME given in the XML element."); + const char* row_name_input = + require_xml_attribute(row_elem, "row_name", SVMP_HERE); row_name.set(std::string(row_name_input)); auto item = row_elem->FirstChildElement(); @@ -1039,11 +1054,7 @@ void ConstitutiveModelParameters::set_values(tinyxml2::XMLElement* xml_elem) std::string error_msg = "Unknown " + xml_element_name_ + " XML element '"; // Get the 'type' from the element. - const char* stype; - auto result = xml_elem->QueryStringAttribute("type", &stype); - if (stype == nullptr) { - svmp::raise(SVMP_HERE, "No TYPE given in the XML element."); - } + const char* stype = require_xml_attribute(xml_elem, "type", SVMP_HERE); type.set(std::string(stype)); // Check constitutive model type. @@ -1106,11 +1117,7 @@ void CoupleGenBCParameters::set_values(tinyxml2::XMLElement* xml_elem) std::string error_msg = "Unknown Couple_to_genBC type=TYPE XML element '"; // Get the 'type' from the element. - const char* stype; - auto result = xml_elem->QueryStringAttribute("type", &stype); - if (stype == nullptr) { - svmp::raise(SVMP_HERE, "No TYPE given in the XML element."); - } + const char* stype = require_xml_attribute(xml_elem, "type", SVMP_HERE); type.set(std::string(stype)); auto item = xml_elem->FirstChildElement(); @@ -1200,8 +1207,7 @@ void OutputParameters::set_values(tinyxml2::XMLElement* xml_elem) std::string msg("[OutputParameters::set_values] "); std::string error_msg = "Unknown " + xml_element_name_ + " XML element '"; - const char* stype = require_xml_attribute(xml_elem, "type", SVMP_HERE, - "No TYPE given in the XML element."); + const char* stype = require_xml_attribute(xml_elem, "type", SVMP_HERE); type.set(std::string(stype)); // Get values from XML file. @@ -1284,8 +1290,7 @@ void VariableWallPropsParameters::set_values(tinyxml2::XMLElement* xml_elem) std::string error_msg = "Unknown " + xml_element_name_ + " XML element '"; // Get the 'type' from the element. - const char* sname = require_xml_attribute(xml_elem, "mesh_name", SVMP_HERE, - "No MESH_NAME given in the XML element."); + const char* sname = require_xml_attribute(xml_elem, "mesh_name", SVMP_HERE); mesh_name.set(std::string(sname)); auto item = xml_elem->FirstChildElement(); @@ -1450,12 +1455,7 @@ void FluidViscosityParameters::set_values(tinyxml2::XMLElement* xml_elem) { using namespace tinyxml2; - const char* smodel; - auto result = xml_elem->QueryStringAttribute("model", &smodel); - - if (smodel == nullptr) { - svmp::raise(SVMP_HERE, "No MODEL given in the XML element."); - } + const char* smodel = require_xml_attribute(xml_elem, "model", SVMP_HERE); model.set(std::string(smodel)); // Check fluid_viscosity model name. @@ -1575,12 +1575,7 @@ void SolidViscosityParameters::set_values(tinyxml2::XMLElement* xml_elem) { using namespace tinyxml2; - const char* smodel; - auto result = xml_elem->QueryStringAttribute("model", &smodel); - - if (smodel == nullptr) { - svmp::raise(SVMP_HERE, "No MODEL given in the XML element."); - } + const char* smodel = require_xml_attribute(xml_elem, "model", SVMP_HERE); model.set(std::string(smodel)); // Check solid viscosity model name. @@ -1706,11 +1701,7 @@ void DomainParameters::set_values(tinyxml2::XMLElement* domain_elem, bool from_e // If not reading from an external xml file then get the 'id' attrribute. // if (!from_external_xml) { - const char* sid; - auto result = domain_elem->QueryStringAttribute("id", &sid); - if (sid == nullptr) { - svmp::raise(SVMP_HERE, "No ID found in the XML element."); - } + const char* sid = require_xml_attribute(domain_elem, "id", SVMP_HERE); id.set(std::string(sid)); } @@ -2094,11 +2085,7 @@ void FiberReinforcementStressParameters::set_values(tinyxml2::XMLElement* xml_el std::string error_msg = "Unknown " + xml_element_name_ + " XML element '"; // Get the 'type' from the element attribute. - const char* stype; - auto result = xml_elem->QueryStringAttribute("type", &stype); - if (stype == nullptr) { - svmp::raise(SVMP_HERE, "No TYPE given in the XML element."); - } + const char* stype = require_xml_attribute(xml_elem, "type", SVMP_HERE); type.set(std::string(stype)); auto item = xml_elem->FirstChildElement(); @@ -2175,11 +2162,7 @@ void StimulusParameters::set_values(tinyxml2::XMLElement* xml_elem) std::string error_msg = "Unknown " + xml_element_name_ + " XML element '"; // Get the 'type' from the element. - const char* stype; - auto result = xml_elem->QueryStringAttribute("type", &stype); - if (stype == nullptr) { - svmp::raise(SVMP_HERE, "No TYPE given in the XML element."); - } + const char* stype = require_xml_attribute(xml_elem, "type", SVMP_HERE); type.set(std::string(stype)); auto item = xml_elem->FirstChildElement(); @@ -2308,11 +2291,7 @@ void ContactParameters::set_values(tinyxml2::XMLElement* xml_elem) std::string error_msg = "Unknown " + xml_element_name_ + " XML element '"; // Get the 'type' from the element. - const char* mname; - auto result = xml_elem->QueryStringAttribute("model", &mname); - if (mname == nullptr) { - svmp::raise(SVMP_HERE, "No MODEL given in the XML element."); - } + const char* mname = require_xml_attribute(xml_elem, "model", SVMP_HERE); model.set(std::string(mname)); using std::placeholders::_1; @@ -2680,8 +2659,7 @@ void FaceParameters::set_values(tinyxml2::XMLElement* face_elem) using namespace tinyxml2; std::string error_msg = "Unknown " + xml_element_name_ + " XML element '"; - const char* face_name = require_xml_attribute(face_elem, "name", SVMP_HERE, - "No NAME given in the XML element."); + const char* face_name = require_xml_attribute(face_elem, "name", SVMP_HERE); name.set(std::string(face_name)); auto item = face_elem->FirstChildElement(); @@ -2739,11 +2717,7 @@ void RemesherParameters::set_values(tinyxml2::XMLElement* xml_elem) std::string error_msg = "Unknown " + xml_element_name + " XML element '"; // Get the 'type' from the element. - const char* stype; - auto result = xml_elem->QueryStringAttribute("type", &stype); - if (stype == nullptr) { - svmp::raise(SVMP_HERE, "No TYPE given in the XML element."); - } + const char* stype = require_xml_attribute(xml_elem, "type", SVMP_HERE); type.set(std::string(stype)); values_set_ = true; @@ -2757,15 +2731,8 @@ void RemesherParameters::set_values(tinyxml2::XMLElement* xml_elem) if (name == "Max_edge_size") { const char* name; const char* value; - auto result = item->QueryStringAttribute("name", &name); - if (name == nullptr) { - svmp::raise(SVMP_HERE, "No NAME given in the XML Remesher element."); - } - - result = item->QueryStringAttribute("value", &value); - if (value == nullptr) { - svmp::raise(SVMP_HERE, "No VALUE given in the XML Remesher element."); - } + name = require_xml_attribute(item, "name", SVMP_HERE); + value = require_xml_attribute(item, "value", SVMP_HERE); auto svalue = std::string(value); try { @@ -2961,11 +2928,7 @@ void ProjectionParameters::set_values(tinyxml2::XMLElement* xml_elem) std::string error_msg = "Unknown " + xml_element_name_ + " XML element '"; // Get the 'type' from the element. - const char* sname; - auto result = xml_elem->QueryStringAttribute("name", &sname); - if (sname == nullptr) { - svmp::raise(SVMP_HERE, "No NAME given in the XML element."); - } + const char* sname = require_xml_attribute(xml_elem, "name", SVMP_HERE); name.set(std::string(sname)); using std::placeholders::_1; @@ -3002,11 +2965,7 @@ void RISProjectionParameters::set_values(tinyxml2::XMLElement* xml_elem) std::string error_msg = "Unknown " + xml_element_name_ + " XML element '"; // Get the 'type' from the element. - const char* sname; - auto result = xml_elem->QueryStringAttribute("name", &sname); - if (sname == nullptr) { - svmp::raise(SVMP_HERE, "No NAME given in the XML element."); - } + const char* sname = require_xml_attribute(xml_elem, "name", SVMP_HERE); name.set(std::string(sname)); using std::placeholders::_1; @@ -3136,8 +3095,7 @@ void URISFaceParameters::set_values(tinyxml2::XMLElement* face_elem) using namespace tinyxml2; std::string error_msg = "Unknown " + xml_element_name_ + " XML element '"; - const char* face_name = require_xml_attribute(face_elem, "name", SVMP_HERE, - "No NAME given in the XML element."); + const char* face_name = require_xml_attribute(face_elem, "name", SVMP_HERE); name.set(std::string(face_name)); auto item = face_elem->FirstChildElement(); @@ -3207,11 +3165,7 @@ void LinearAlgebraParameters::set_values(tinyxml2::XMLElement* xml_elem) std::string error_msg = "Unknown " + xml_element_name + " XML element '"; // Get the 'type' from the element. - const char* stype; - auto result = xml_elem->QueryStringAttribute("type", &stype); - if (stype == nullptr) { - svmp::raise(SVMP_HERE, "No TYPE given in the XML element."); - } + const char* stype = require_xml_attribute(xml_elem, "type", SVMP_HERE); type.set(std::string(stype)); // Check Linear_algebra type=TYPE> element. @@ -3335,11 +3289,7 @@ void LinearSolverParameters::set_values(tinyxml2::XMLElement* xml_elem) std::string error_msg = "Unknown " + xml_element_name + " XML element '"; // Get the 'type' from the element. - const char* stype; - auto result = xml_elem->QueryStringAttribute("type", &stype); - if (stype == nullptr) { - svmp::raise(SVMP_HERE, "No TYPE given in the XML element."); - } + const char* stype = require_xml_attribute(xml_elem, "type", SVMP_HERE); type.set(std::string(stype));