feat(cpp-object-oriented-modular-refactoring): step 3 - foundation-google-style

This commit is contained in:
KOKO\Mimi
2026-08-16 04:26:14 +09:00
parent 2628ed3488
commit 042edadffb
93 changed files with 3144 additions and 3175 deletions
+2 -2
View File
@@ -1,8 +1,8 @@
#pragma once
#include "fesa/core/status.hpp"
#include "fesa/core/status.h"
#include "fesa/fem/dof_manager.hpp"
#include "fesa/math/vector.hpp"
#include "fesa/math/vector.h"
#include "fesa/results/result_records.hpp"
#include <array>
@@ -3,10 +3,10 @@
#include "fesa/analysis/analysis_model.hpp"
#include "fesa/analysis/analysis_state.hpp"
#include "fesa/constraints/essential_constraints.hpp"
#include "fesa/core/status.hpp"
#include "fesa/core/status.h"
#include "fesa/fem/dof_manager.hpp"
#include "fesa/math/sparse_matrix.hpp"
#include "fesa/math/vector.hpp"
#include "fesa/math/sparse_matrix.h"
#include "fesa/math/vector.h"
#include "fesa/model/domain.hpp"
#include <filesystem>
+2 -2
View File
@@ -2,8 +2,8 @@
#include "fesa/analysis/analysis_model.hpp"
#include "fesa/fem/dof_manager.hpp"
#include "fesa/math/sparse_matrix.hpp"
#include "fesa/math/vector.hpp"
#include "fesa/math/sparse_matrix.h"
#include "fesa/math/vector.h"
namespace fesa {
+2 -2
View File
@@ -1,7 +1,7 @@
#pragma once
#include "fesa/core/status.hpp"
#include "fesa/math/sparse_matrix.hpp"
#include "fesa/core/status.h"
#include "fesa/math/sparse_matrix.h"
namespace fesa {
+13
View File
@@ -0,0 +1,13 @@
#ifndef FESA_BUILD_INFO_H_
#define FESA_BUILD_INFO_H_
#include <string_view>
namespace fesa {
/// @brief Returns the stable solver version written to result metadata.
std::string_view SolverVersion() noexcept;
} // namespace fesa
#endif // FESA_BUILD_INFO_H_
-10
View File
@@ -1,10 +0,0 @@
#pragma once
#include <string_view>
namespace fesa {
// Returns the stable solver version written to externally visible result metadata.
std::string_view solverVersion() noexcept;
} // namespace fesa
@@ -1,8 +1,8 @@
#pragma once
#include "fesa/core/status.hpp"
#include "fesa/math/sparse_matrix.hpp"
#include "fesa/math/vector.hpp"
#include "fesa/core/status.h"
#include "fesa/math/sparse_matrix.h"
#include "fesa/math/vector.h"
namespace fesa {
+31
View File
@@ -0,0 +1,31 @@
#ifndef FESA_CORE_DIAGNOSTIC_H_
#define FESA_CORE_DIAGNOSTIC_H_
#include <string>
#include <vector>
#include "fesa/core/source_identity.h"
namespace fesa {
/// @brief Distinguishes recoverable warnings from operation-stopping errors.
enum class Severity { kWarning, kError };
/// @brief Carries a structured, backend-independent diagnostic record.
struct Diagnostic {
Severity severity;
std::string code;
SourceLocation location;
std::string keyword;
std::string entity_identity;
std::string message;
};
/// @brief Orders diagnostics by their externally visible source tuple.
/// @param diagnostics Records to reorder in place.
/// @note Records with identical keys retain their discovery order.
void SortDiagnostics(std::vector<Diagnostic>& diagnostics);
} // namespace fesa
#endif // FESA_CORE_DIAGNOSTIC_H_
-30
View File
@@ -1,30 +0,0 @@
#pragma once
#include "fesa/core/source_identity.hpp"
#include <string>
#include <vector>
namespace fesa {
// Distinguishes recoverable warnings from errors that stop the current operation.
enum class Severity {
warning,
error
};
// Carries a structured, backend-independent diagnostic record.
struct Diagnostic {
Severity severity;
std::string code;
SourceLocation location;
std::string keyword;
std::string entityIdentity;
std::string message;
};
// Orders diagnostics by their externally visible source tuple while retaining
// discovery order for records with identical keys.
void sortDiagnostics(std::vector<Diagnostic>& diagnostics);
} // namespace fesa
+26
View File
@@ -0,0 +1,26 @@
#ifndef FESA_CORE_SOURCE_IDENTITY_H_
#define FESA_CORE_SOURCE_IDENTITY_H_
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <string>
namespace fesa {
/// @brief Identifies the input location that produced an item or diagnostic.
struct SourceLocation {
std::filesystem::path file;
std::size_t line;
};
/// @brief Preserves semantic and raw-text forms of a source entity identity.
struct SourceEntityId {
std::string instance_name;
std::int64_t source_label;
std::string source_label_text;
};
} // namespace fesa
#endif // FESA_CORE_SOURCE_IDENTITY_H_
-23
View File
@@ -1,23 +0,0 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <string>
namespace fesa {
// Identifies the physical input location that produced a model item or diagnostic.
struct SourceLocation {
std::filesystem::path file;
std::size_t line;
};
// Preserves both semantic and raw-text forms of an input entity identity.
struct SourceEntityId {
std::string instanceName;
std::int64_t sourceLabel;
std::string sourceLabelText;
};
} // namespace fesa
+114
View File
@@ -0,0 +1,114 @@
#ifndef FESA_CORE_STATUS_H_
#define FESA_CORE_STATUS_H_
#include <optional>
#include <stdexcept>
#include <utility>
#include <vector>
#include "fesa/core/diagnostic.h"
namespace fesa {
/// @brief Maps failures to the stable command-line exit-code categories.
enum class FailureCategory { kInput, kModel, kSolver, kOutput };
/// @brief Transports success or structured failure diagnostics.
class Status {
public:
/// @brief Creates a successful status.
/// @return A status with no failure category or diagnostics.
static Status Ok();
/// @brief Creates an uncategorized failed status.
/// @param diagnostics Structured diagnostics owned by the returned status.
/// @return A failed status with diagnostics in deterministic source order.
static Status Failure(std::vector<Diagnostic> diagnostics);
/// @brief Creates a categorized failed status.
/// @param category Stable external failure category.
/// @param diagnostics Structured diagnostics owned by the returned status.
/// @return A failed status with diagnostics in deterministic source order.
static Status Failure(FailureCategory category,
std::vector<Diagnostic> diagnostics);
/// @brief Reports whether the operation succeeded.
bool IsOk() const noexcept;
/// @brief Returns the optional stable failure category.
std::optional<FailureCategory> Category() const noexcept;
/// @brief Returns the deterministically ordered diagnostic records.
const std::vector<Diagnostic>& Diagnostics() const noexcept;
private:
/// @brief Constructs a status from its validated invariant fields.
Status(bool is_ok, std::optional<FailureCategory> category,
std::vector<Diagnostic> diagnostics);
bool is_ok_;
std::optional<FailureCategory> category_;
std::vector<Diagnostic> diagnostics_;
};
/// @brief Owns exactly one successful value or one failed Status.
template <class T>
class Result {
public:
/// @brief Creates a successful result that owns the supplied value.
static Result Success(T value) {
return Result{SuccessTag{}, std::move(value)};
}
/// @brief Creates a failed result that owns a failed status.
/// @throws std::invalid_argument if status represents success.
static Result Failure(Status status) {
if (status.IsOk()) {
throw std::invalid_argument{"A failed Result requires a failed Status."};
}
return Result{FailureTag{}, std::move(status)};
}
/// @brief Reports whether this result owns a successful value.
bool HasValue() const noexcept { return value_.has_value(); }
/// @brief Returns the owned successful value.
/// @throws std::logic_error if this result represents failure.
T& Value() {
if (!value_) {
throw std::logic_error{"Result has no value."};
}
return *value_;
}
/// @brief Returns the owned successful value.
/// @throws std::logic_error if this result represents failure.
const T& Value() const {
if (!value_) {
throw std::logic_error{"Result has no value."};
}
return *value_;
}
/// @brief Returns the success or failure status.
const Status& GetStatus() const noexcept { return status_; }
private:
struct SuccessTag {};
struct FailureTag {};
/// @brief Constructs the successful value alternative.
Result(SuccessTag, T value)
: value_{std::move(value)}, status_{Status::Ok()} {}
/// @brief Constructs the failed status alternative.
Result(FailureTag, Status status)
: value_{std::nullopt}, status_{std::move(status)} {}
std::optional<T> value_;
Status status_;
};
} // namespace fesa
#endif // FESA_CORE_STATUS_H_
-94
View File
@@ -1,94 +0,0 @@
#pragma once
#include "fesa/core/diagnostic.hpp"
#include <optional>
#include <stdexcept>
#include <utility>
#include <vector>
namespace fesa {
// Maps a failure to the stable command-line exit-code classes defined by V0.
enum class FailureCategory {
input,
model,
solver,
output
};
// Transports success or structured diagnostics without exposing backend errors.
class Status {
public:
static Status ok();
static Status failure(std::vector<Diagnostic> diagnostics);
static Status failure(
FailureCategory category, std::vector<Diagnostic> diagnostics);
bool isOk() const noexcept;
std::optional<FailureCategory> failureCategory() const noexcept;
const std::vector<Diagnostic>& diagnostics() const noexcept;
private:
Status(
bool isOk,
std::optional<FailureCategory> category,
std::vector<Diagnostic> diagnostics);
bool isOk_;
std::optional<FailureCategory> category_;
std::vector<Diagnostic> diagnostics_;
};
// Owns exactly one successful value or one failed Status.
template<class T>
class Result {
public:
static Result success(T value) {
return Result{SuccessTag{}, std::move(value)};
}
static Result failure(Status status) {
if (status.isOk()) {
throw std::invalid_argument{"A failed Result requires a failed Status."};
}
return Result{FailureTag{}, std::move(status)};
}
bool hasValue() const noexcept {
return value_.has_value();
}
T& value() {
if (!value_) {
throw std::logic_error{"Result has no value."};
}
return *value_;
}
const T& value() const {
if (!value_) {
throw std::logic_error{"Result has no value."};
}
return *value_;
}
const Status& status() const noexcept {
return status_;
}
private:
struct SuccessTag {};
struct FailureTag {};
Result(SuccessTag, T value)
: value_{std::move(value)}, status_{Status::ok()} {}
Result(FailureTag, Status status)
: value_{std::nullopt}, status_{std::move(status)} {}
std::optional<T> value_;
Status status_;
};
} // namespace fesa
+3 -3
View File
@@ -1,8 +1,8 @@
#pragma once
#include "fesa/core/status.hpp"
#include "fesa/math/matrix.hpp"
#include "fesa/math/vector.hpp"
#include "fesa/core/status.h"
#include "fesa/math/matrix.h"
#include "fesa/math/vector.h"
#include "fesa/model/model_types.hpp"
#include <array>
+3 -3
View File
@@ -1,8 +1,8 @@
#pragma once
#include "fesa/core/status.hpp"
#include "fesa/math/matrix.hpp"
#include "fesa/math/vector.hpp"
#include "fesa/core/status.h"
#include "fesa/math/matrix.h"
#include "fesa/math/vector.h"
#include "fesa/model/model_types.hpp"
#include <array>
+1 -1
View File
@@ -1,7 +1,7 @@
#pragma once
#include "fesa/analysis/analysis_model.hpp"
#include "fesa/math/vector.hpp"
#include "fesa/math/vector.h"
#include <array>
#include <cstddef>
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#include "fesa/core/status.hpp"
#include "fesa/core/status.h"
#include "fesa/io/abaqus/input_syntax.hpp"
#include "fesa/model/domain.hpp"
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#include "fesa/core/status.hpp"
#include "fesa/core/status.h"
#include "fesa/io/abaqus/input_syntax.hpp"
#include <filesystem>
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#include "fesa/core/source_identity.hpp"
#include "fesa/core/source_identity.h"
#include <filesystem>
#include <optional>
+59
View File
@@ -0,0 +1,59 @@
#ifndef FESA_MATH_MATRIX_H_
#define FESA_MATH_MATRIX_H_
#include <cstddef>
#include <vector>
#include "fesa/math/vector.h"
namespace fesa {
/// @brief Owns row-major contiguous storage independently of sparse matrices.
class Matrix {
public:
/// @brief Constructs a row-major matrix initialized to one value.
Matrix(std::size_t rows, std::size_t columns, double value = 0.0);
/// @brief Copies matrix values into independent contiguous storage.
Matrix(const Matrix& other);
/// @brief Moves matrix storage and resets other to a zero-by-zero shape.
Matrix(Matrix&& other) noexcept;
/// @brief Copies matrix values into independent contiguous storage.
Matrix& operator=(const Matrix& other);
/// @brief Moves matrix storage and resets other to a zero-by-zero shape.
Matrix& operator=(Matrix&& other) noexcept;
/// @brief Returns the row count.
std::size_t Rows() const noexcept;
/// @brief Returns the column count.
std::size_t Columns() const noexcept;
/// @brief Returns a bounds-checked mutable entry.
/// @throws std::out_of_range if the index is outside the matrix.
double& operator()(std::size_t row, std::size_t column);
/// @brief Returns a bounds-checked immutable entry.
/// @throws std::out_of_range if the index is outside the matrix.
const double& operator()(std::size_t row, std::size_t column) const;
/// @brief Multiplies this row-major matrix by a dense vector.
/// @throws std::invalid_argument if the dimensions are incompatible.
Vector Multiply(const Vector& rhs) const;
/// @brief Multiplies this row-major matrix by another dense matrix.
/// @throws std::invalid_argument if the dimensions are incompatible.
Matrix Multiply(const Matrix& rhs) const;
private:
std::size_t rows_;
std::size_t columns_;
std::vector<double> values_;
};
} // namespace fesa
#endif // FESA_MATH_MATRIX_H_
-32
View File
@@ -1,32 +0,0 @@
#pragma once
#include "fesa/math/vector.hpp"
#include <cstddef>
#include <vector>
namespace fesa {
// Owns row-major contiguous dense storage independently of sparse matrices.
class Matrix {
public:
Matrix(std::size_t rows, std::size_t columns, double value = 0.0);
Matrix(const Matrix& other);
Matrix(Matrix&& other) noexcept;
Matrix& operator=(const Matrix& other);
Matrix& operator=(Matrix&& other) noexcept;
std::size_t rows() const noexcept;
std::size_t columns() const noexcept;
double& operator()(std::size_t row, std::size_t column);
const double& operator()(std::size_t row, std::size_t column) const;
Vector multiply(const Vector& rhs) const;
Matrix multiply(const Matrix& rhs) const;
private:
std::size_t rows_;
std::size_t columns_;
std::vector<double> values_;
};
} // namespace fesa
+73
View File
@@ -0,0 +1,73 @@
#ifndef FESA_MATH_SPARSE_MATRIX_H_
#define FESA_MATH_SPARSE_MATRIX_H_
#include <cstddef>
#include <vector>
#include "fesa/core/status.h"
#include "fesa/math/vector.h"
namespace fesa {
struct SparsePattern;
/// @brief Carries one deterministic element-local COO contribution.
struct CooContribution {
std::size_t row;
std::size_t column;
double value;
std::size_t element_order;
std::size_t local_order;
};
/// @brief Owns canonical 0-based CSR independently of the dense Matrix type.
class SparseMatrix {
public:
/// @brief Reduces ordered COO contributions into an expected CSR pattern.
/// @return A validated matrix or a structured model failure.
/// @note Duplicate sums use stable element and local contribution order.
static Result<SparseMatrix> FromCoo(
std::size_t rows, std::size_t columns,
std::vector<CooContribution> contributions,
const SparsePattern& expected_pattern);
/// @brief Returns the row count.
std::size_t Rows() const noexcept;
/// @brief Returns the column count.
std::size_t Columns() const noexcept;
/// @brief Returns the canonical 0-based CSR row offsets.
const std::vector<std::size_t>& RowOffsets() const noexcept;
/// @brief Returns sorted unique 0-based CSR column indices.
const std::vector<std::size_t>& ColumnIndices() const noexcept;
/// @brief Returns CSR values including preserved structural zeros.
const std::vector<double>& Values() const noexcept;
/// @brief Multiplies this matrix by a dense vector in stable CSR order.
/// @throws std::invalid_argument if the dimensions are incompatible.
Vector Multiply(const Vector& rhs) const;
/// @brief Validates shape, indices, ordering, and finite CSR values.
/// @return Success or a structured model failure.
Status Validate() const;
private:
/// @brief Constructs CSR storage after boundary validation.
SparseMatrix(std::size_t rows, std::size_t columns,
std::vector<std::size_t> row_offsets,
std::vector<std::size_t> column_indices,
std::vector<double> values);
std::size_t rows_;
std::size_t columns_;
std::vector<std::size_t> row_offsets_;
std::vector<std::size_t> column_indices_;
std::vector<double> values_;
};
} // namespace fesa
#endif // FESA_MATH_SPARSE_MATRIX_H_
-53
View File
@@ -1,53 +0,0 @@
#pragma once
#include "fesa/core/status.hpp"
#include "fesa/math/vector.hpp"
#include <cstddef>
#include <vector>
namespace fesa {
struct SparsePattern;
struct CooContribution {
std::size_t row;
std::size_t column;
double value;
std::size_t elementOrder;
std::size_t localOrder;
};
// Owns canonical 0-based CSR data independently of the dense Matrix adapter.
class SparseMatrix {
public:
static Result<SparseMatrix> fromCoo(
std::size_t rows,
std::size_t columns,
std::vector<CooContribution> contributions,
const SparsePattern& expectedPattern);
std::size_t rows() const noexcept;
std::size_t columns() const noexcept;
const std::vector<std::size_t>& rowOffsets() const noexcept;
const std::vector<std::size_t>& columnIndices() const noexcept;
const std::vector<double>& values() const noexcept;
Vector multiply(const Vector& rhs) const;
Status validate() const;
private:
SparseMatrix(
std::size_t rows,
std::size_t columns,
std::vector<std::size_t> rowOffsets,
std::vector<std::size_t> columnIndices,
std::vector<double> values);
std::size_t rows_;
std::size_t columns_;
std::vector<std::size_t> rowOffsets_;
std::vector<std::size_t> columnIndices_;
std::vector<double> values_;
};
} // namespace fesa
+64
View File
@@ -0,0 +1,64 @@
#ifndef FESA_MATH_VECTOR_H_
#define FESA_MATH_VECTOR_H_
#include <cstddef>
#include <vector>
namespace fesa {
/// @brief Owns a contiguous dense vector while keeping MKL private.
class Vector {
public:
/// @brief Constructs a vector with all entries initialized to one value.
explicit Vector(std::size_t size, double value = 0.0);
/// @brief Copies vector values into independent contiguous storage.
Vector(const Vector& other);
/// @brief Moves vector storage and leaves other empty.
Vector(Vector&& other) noexcept;
/// @brief Copies vector values into independent contiguous storage.
Vector& operator=(const Vector& other);
/// @brief Moves vector storage and leaves other empty.
Vector& operator=(Vector&& other) noexcept;
/// @brief Returns the number of entries.
std::size_t Size() const noexcept;
/// @brief Returns mutable contiguous storage.
double* Data() noexcept;
/// @brief Returns immutable contiguous storage.
const double* Data() const noexcept;
/// @brief Returns a bounds-checked mutable entry.
/// @throws std::out_of_range if index is outside the vector.
double& operator[](std::size_t index);
/// @brief Returns a bounds-checked immutable entry.
/// @throws std::out_of_range if index is outside the vector.
const double& operator[](std::size_t index) const;
/// @brief Computes the Euclidean dot product with rhs.
/// @throws std::invalid_argument if the vector sizes differ.
double Dot(const Vector& rhs) const;
/// @brief Computes the Euclidean norm.
double Norm() const;
/// @brief Scales each entry by alpha through the dense backend.
void Scale(double alpha);
/// @brief Accumulates alpha times x into this vector.
/// @throws std::invalid_argument if the vector sizes differ.
void Axpy(double alpha, const Vector& x);
private:
std::vector<double> values_;
};
} // namespace fesa
#endif // FESA_MATH_VECTOR_H_
-31
View File
@@ -1,31 +0,0 @@
#pragma once
#include <cstddef>
#include <vector>
namespace fesa {
// Owns a contiguous dense vector while keeping the MKL backend private.
class Vector {
public:
explicit Vector(std::size_t size, double value = 0.0);
Vector(const Vector& other);
Vector(Vector&& other) noexcept;
Vector& operator=(const Vector& other);
Vector& operator=(Vector&& other) noexcept;
std::size_t size() const noexcept;
double* data() noexcept;
const double* data() const noexcept;
double& operator[](std::size_t index);
const double& operator[](std::size_t index) const;
double dot(const Vector& rhs) const;
double norm() const;
void scale(double alpha);
void axpy(double alpha, const Vector& x);
private:
std::vector<double> values_;
};
} // namespace fesa
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#include "fesa/core/status.hpp"
#include "fesa/core/status.h"
#include "fesa/model/model_types.hpp"
#include <filesystem>
+2 -2
View File
@@ -1,7 +1,7 @@
#pragma once
#include "fesa/core/diagnostic.hpp"
#include "fesa/core/source_identity.hpp"
#include "fesa/core/diagnostic.h"
#include "fesa/core/source_identity.h"
#include <array>
#include <cstdint>
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#include "fesa/core/status.hpp"
#include "fesa/core/status.h"
#include "fesa/model/model_types.hpp"
#include <array>
+2 -2
View File
@@ -2,9 +2,9 @@
#include "fesa/analysis/analysis_model.hpp"
#include "fesa/analysis/analysis_state.hpp"
#include "fesa/core/status.hpp"
#include "fesa/core/status.h"
#include "fesa/fem/dof_manager.hpp"
#include "fesa/math/sparse_matrix.hpp"
#include "fesa/math/sparse_matrix.h"
#include <array>
#include <vector>
+2 -2
View File
@@ -1,8 +1,8 @@
#pragma once
#include "fesa/analysis/analysis_state.hpp"
#include "fesa/core/diagnostic.hpp"
#include "fesa/core/status.hpp"
#include "fesa/core/diagnostic.h"
#include "fesa/core/status.h"
#include "fesa/model/domain.hpp"
#include <filesystem>
@@ -0,0 +1,30 @@
#ifndef FESA_SOLVERS_LINEAR_LINEAR_SOLVER_H_
#define FESA_SOLVERS_LINEAR_LINEAR_SOLVER_H_
#include "fesa/core/status.h"
namespace fesa {
class SparseMatrix;
class Vector;
/// @brief Separates reusable factorization from RHS substitution.
class LinearSolver {
public:
/// @brief Destroys a backend-neutral linear solver.
virtual ~LinearSolver() = default;
/// @brief Factorizes a validated free-equation matrix for reuse.
/// @return Success or a structured solver failure.
virtual Status Factorize(const SparseMatrix& matrix) = 0;
/// @brief Substitutes one right-hand side using retained factorization.
/// @param rhs Immutable right-hand side in free-equation order.
/// @param solution Updated only after successful finite substitution.
/// @return Success or a structured solver failure.
virtual Status Solve(const Vector& rhs, Vector& solution) const = 0;
};
} // namespace fesa
#endif // FESA_SOLVERS_LINEAR_LINEAR_SOLVER_H_
@@ -1,18 +0,0 @@
#pragma once
#include "fesa/core/status.hpp"
namespace fesa {
class SparseMatrix;
class Vector;
// Separates reusable matrix factorization from right-hand-side substitution.
class LinearSolver {
public:
virtual ~LinearSolver() = default;
virtual Status factorize(const SparseMatrix& matrix) = 0;
virtual Status solve(const Vector& rhs, Vector& solution) const = 0;
};
} // namespace fesa
@@ -0,0 +1,32 @@
#ifndef FESA_SOLVERS_LINEAR_MKL_PARDISO_SOLVER_H_
#define FESA_SOLVERS_LINEAR_MKL_PARDISO_SOLVER_H_
#include <memory>
#include "fesa/solvers/linear/linear_solver.h"
namespace fesa {
/// @brief Adapts retained oneMKL PARDISO state behind LinearSolver.
class MklPardisoSolver final : public LinearSolver {
public:
/// @brief Constructs an empty, unfactorized PARDISO adapter.
MklPardisoSolver();
/// @brief Releases retained PARDISO backend state.
~MklPardisoSolver() override;
/// @brief Validates and factorizes a symmetric free-equation matrix.
Status Factorize(const SparseMatrix& matrix) override;
/// @brief Substitutes one right-hand side without refactorization.
Status Solve(const Vector& rhs, Vector& solution) const override;
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace fesa
#endif // FESA_SOLVERS_LINEAR_MKL_PARDISO_SOLVER_H_
@@ -1,23 +0,0 @@
#pragma once
#include "fesa/solvers/linear/linear_solver.hpp"
#include <memory>
namespace fesa {
// Keeps every oneMKL type and the retained factorization in the private Impl.
class MklPardisoSolver final : public LinearSolver {
public:
MklPardisoSolver();
~MklPardisoSolver() override;
Status factorize(const SparseMatrix& matrix) override;
Status solve(const Vector& rhs, Vector& solution) const override;
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace fesa
+7 -7
View File
@@ -7,9 +7,9 @@ namespace fesa {
Result<AnalysisModel> AnalysisModel::create(const Domain& domain) {
if (domain.steps().empty()) {
return Result<AnalysisModel>::failure(Status::failure(
FailureCategory::input,
{{Severity::error,
return Result<AnalysisModel>::Failure(Status::Failure(
FailureCategory::kInput,
{{Severity::kError,
"invalid-model-cardinality",
{domain.sourcePath(), 0U},
"STEP",
@@ -18,16 +18,16 @@ Result<AnalysisModel> AnalysisModel::create(const Domain& domain) {
}
if (domain.steps().size() > 1U) {
const auto& secondStep = domain.steps()[1];
return Result<AnalysisModel>::failure(Status::failure(
FailureCategory::input,
{{Severity::error,
return Result<AnalysisModel>::Failure(Status::Failure(
FailureCategory::kInput,
{{Severity::kError,
"unsupported-multiple-step",
secondStep.location,
"STEP",
secondStep.name,
"AnalysisModel does not support multiple steps."}}));
}
return Result<AnalysisModel>::success(AnalysisModel{domain});
return Result<AnalysisModel>::Success(AnalysisModel{domain});
}
const Domain& AnalysisModel::domain() const noexcept {
+4 -4
View File
@@ -16,9 +16,9 @@ Status shellCandidateFailure(
const std::string& code,
const std::string& identity,
const std::string& message) {
return Status::failure(
FailureCategory::model,
{{Severity::error,
return Status::Failure(
FailureCategory::kModel,
{{Severity::kError,
code,
{},
"ANALYSIS_STATE",
@@ -224,7 +224,7 @@ Status AnalysisState::commitShellResults(
physicalStrainEnergy_ = candidate.physicalStrainEnergy;
equilibrium_ = candidate.equilibrium;
verificationMetrics_ = candidate.verificationMetrics;
return Status::ok();
return Status::Ok();
}
const std::vector<ShellResultRow>& AnalysisState::shellResults() const noexcept {
+43 -43
View File
@@ -7,7 +7,7 @@
#include "fesa/io/abaqus/input_reader.hpp"
#include "fesa/results/result_recovery.hpp"
#include "fesa/results/results_writer.hpp"
#include "fesa/solvers/linear/linear_solver.hpp"
#include "fesa/solvers/linear/linear_solver.h"
#include <utility>
@@ -15,31 +15,31 @@ namespace fesa {
Status Analysis::run(const AnalysisRequest& request) {
Status status = initialize(request);
if (!status.isOk()) {
if (!status.IsOk()) {
return status;
}
status = buildAnalysisModel();
if (!status.isOk()) {
if (!status.IsOk()) {
return status;
}
status = buildDofMapAndSparsePattern();
if (!status.isOk()) {
if (!status.IsOk()) {
return status;
}
status = assembleAndPartitionStiffness();
if (!status.isOk()) {
if (!status.IsOk()) {
return status;
}
status = factorize();
if (!status.isOk()) {
if (!status.IsOk()) {
return status;
}
status = assembleLoadsAndEffectiveRhs();
if (!status.isOk()) {
if (!status.IsOk()) {
return status;
}
status = substituteAndReconstruct();
if (!status.isOk()) {
if (!status.IsOk()) {
return status;
}
return recoverAndWriteResults();
@@ -67,100 +67,100 @@ Status LinearStaticAnalysis::initialize(const AnalysisRequest& request) {
request_ = request;
const auto parsed = AbaqusInputReader{}.read(request_.inputPath);
if (!parsed.hasValue()) {
return parsed.status();
if (!parsed.HasValue()) {
return parsed.GetStatus();
}
auto domain = AbaqusDomainMapper{}.map(parsed.value());
if (!domain.hasValue()) {
return domain.status();
auto domain = AbaqusDomainMapper{}.map(parsed.Value());
if (!domain.HasValue()) {
return domain.GetStatus();
}
domain_ = std::make_unique<Domain>(std::move(domain.value()));
domain_ = std::make_unique<Domain>(std::move(domain.Value()));
diagnostics_ = domain_->warnings();
sortDiagnostics(diagnostics_);
return Status::ok();
SortDiagnostics(diagnostics_);
return Status::Ok();
}
Status LinearStaticAnalysis::buildAnalysisModel() {
auto model = AnalysisModel::create(*domain_);
if (!model.hasValue()) {
return model.status();
if (!model.HasValue()) {
return model.GetStatus();
}
model_ = std::make_unique<AnalysisModel>(std::move(model.value()));
return Status::ok();
model_ = std::make_unique<AnalysisModel>(std::move(model.Value()));
return Status::Ok();
}
Status LinearStaticAnalysis::buildDofMapAndSparsePattern() {
auto dofs = DofManager::create(*model_);
if (!dofs.hasValue()) {
return dofs.status();
if (!dofs.HasValue()) {
return dofs.GetStatus();
}
dofs_ = std::make_unique<DofManager>(std::move(dofs.value()));
dofs_ = std::make_unique<DofManager>(std::move(dofs.Value()));
state_ = std::make_unique<AnalysisState>(
AnalysisState::create(*dofs_, {"Step-1", 0U}));
return Status::ok();
return Status::Ok();
}
Status LinearStaticAnalysis::assembleAndPartitionStiffness() {
auto stiffness = SparseAssembler::assembleStiffness(
*model_, *dofs_, parallelFor_);
if (!stiffness.hasValue()) {
return stiffness.status();
if (!stiffness.HasValue()) {
return stiffness.GetStatus();
}
fullStiffness_ =
std::make_unique<SparseMatrix>(std::move(stiffness.value()));
std::make_unique<SparseMatrix>(std::move(stiffness.Value()));
auto partitioned = EssentialConstraints::partition(
*fullStiffness_, *dofs_);
if (!partitioned.hasValue()) {
return partitioned.status();
if (!partitioned.HasValue()) {
return partitioned.GetStatus();
}
partitionedStiffness_ = std::make_unique<PartitionedStiffness>(
std::move(partitioned.value()));
return Status::ok();
std::move(partitioned.Value()));
return Status::Ok();
}
Status LinearStaticAnalysis::factorize() {
// This call intentionally precedes all load assembly in Analysis::run.
return linearSolver_.factorize(partitionedStiffness_->kff);
return linearSolver_.Factorize(partitionedStiffness_->kff);
}
Status LinearStaticAnalysis::assembleLoadsAndEffectiveRhs() {
auto fullLoad = LoadAssembler::assembleFullNodalLoad(*model_, *dofs_);
if (!fullLoad.hasValue()) {
return fullLoad.status();
if (!fullLoad.HasValue()) {
return fullLoad.GetStatus();
}
state_->externalForce() = std::move(fullLoad.value());
state_->externalForce() = std::move(fullLoad.Value());
auto rhs = LoadAssembler::effectiveFreeRhs(
state_->externalForce(),
partitionedStiffness_->kfc,
dofs_->prescribedValues(),
*dofs_);
if (!rhs.hasValue()) {
return rhs.status();
if (!rhs.HasValue()) {
return rhs.GetStatus();
}
effectiveRhs_ = std::make_unique<Vector>(std::move(rhs.value()));
return Status::ok();
effectiveRhs_ = std::make_unique<Vector>(std::move(rhs.Value()));
return Status::Ok();
}
Status LinearStaticAnalysis::substituteAndReconstruct() {
Vector freeDisplacement{dofs_->freeDofCount()};
const Status solveStatus =
linearSolver_.solve(*effectiveRhs_, freeDisplacement);
if (!solveStatus.isOk()) {
linearSolver_.Solve(*effectiveRhs_, freeDisplacement);
if (!solveStatus.IsOk()) {
return solveStatus;
}
state_->displacement() = EssentialConstraints::reconstructFull(
freeDisplacement, dofs_->prescribedValues(), *dofs_);
return Status::ok();
return Status::Ok();
}
Status LinearStaticAnalysis::recoverAndWriteResults() {
const Status recoveryStatus = ResultRecovery::recover(
*model_, *dofs_, *fullStiffness_, *state_);
if (!recoveryStatus.isOk()) {
if (!recoveryStatus.IsOk()) {
return recoveryStatus;
}
return resultsWriter_.write(
+13 -13
View File
@@ -2,9 +2,9 @@
#include "fesa/analysis/linear_static_analysis.hpp"
#include "fesa/assembly/parallel_for.hpp"
#include "fesa/core/diagnostic.hpp"
#include "fesa/core/diagnostic.h"
#include "fesa/io/hdf5/hdf5_results_writer.hpp"
#include "fesa/solvers/linear/mkl_pardiso_solver.hpp"
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
#include <filesystem>
#include <iostream>
@@ -27,7 +27,7 @@ bool startsWithOption(const std::string& argument) {
Diagnostic usageDiagnostic() {
return {
Severity::error,
Severity::kError,
"cli-usage",
{{}, 0U},
"",
@@ -36,11 +36,11 @@ Diagnostic usageDiagnostic() {
}
const char* severityName(const Severity severity) {
return severity == Severity::warning ? "warning" : "error";
return severity == Severity::kWarning ? "warning" : "error";
}
void writeDiagnostics(std::vector<Diagnostic> diagnostics) {
sortDiagnostics(diagnostics);
SortDiagnostics(diagnostics);
for (const auto& diagnostic : diagnostics) {
// Stable field labels and tab separators keep empty source fields
// explicit without depending on locale-specific formatting.
@@ -51,21 +51,21 @@ void writeDiagnostics(std::vector<Diagnostic> diagnostics) {
<< diagnostic.location.file.generic_u8string()
<< '\t' << "line=" << diagnostic.location.line
<< '\t' << "keyword=" << diagnostic.keyword
<< '\t' << "entity_identity=" << diagnostic.entityIdentity
<< '\t' << "entity_identity=" << diagnostic.entity_identity
<< '\t' << "message=" << diagnostic.message
<< '\n';
}
}
int exitCodeFor(const Status& status) {
switch (status.failureCategory().value_or(FailureCategory::input)) {
case FailureCategory::input:
switch (status.Category().value_or(FailureCategory::kInput)) {
case FailureCategory::kInput:
return kInputExitCode;
case FailureCategory::model:
case FailureCategory::kModel:
return kModelExitCode;
case FailureCategory::solver:
case FailureCategory::kSolver:
return kSolverExitCode;
case FailureCategory::output:
case FailureCategory::kOutput:
return kOutputExitCode;
}
return kInputExitCode;
@@ -102,11 +102,11 @@ int FesaApplication::run(const std::vector<std::string>& arguments) {
LinearStaticAnalysis analysis{
parallelFor, linearSolver, resultsWriter};
const Status status = analysis.run(request);
if (status.isOk()) {
if (status.IsOk()) {
return kSuccessExitCode;
}
writeDiagnostics(status.diagnostics());
writeDiagnostics(status.Diagnostics());
return exitCodeFor(status);
}
+62 -62
View File
@@ -25,9 +25,9 @@ Status loadFailure(
const std::string& keyword,
const std::string& identity,
const std::string& message) {
return Status::failure(
FailureCategory::model,
{{Severity::error, code, location, keyword, identity, message}});
return Status::Failure(
FailureCategory::kModel,
{{Severity::kError, code, location, keyword, identity, message}});
}
char asciiLower(const char value) {
@@ -74,7 +74,7 @@ Status validateDofOrder(
if (fullCount != expectedFullCount ||
freeDofs.size() != dofs.freeDofCount() ||
constrainedDofs.size() != dofs.constrainedDofCount() ||
dofs.prescribedValues().size() != constrainedDofs.size() ||
dofs.prescribedValues().Size() != constrainedDofs.size() ||
constrainedDofs.size() > fullCount ||
freeDofs.size() != fullCount - constrainedDofs.size()) {
return loadFailure(
@@ -139,7 +139,7 @@ Status validateDofOrder(
std::to_string(fullCount),
"Free and constrained DOFs must partition the full range.");
}
return Status::ok();
return Status::Ok();
}
Result<std::vector<EntityIndex>> resolveTarget(
@@ -156,7 +156,7 @@ Result<std::vector<EntityIndex>> resolveTarget(
std::int64_t label = 0;
if (tryPositiveInteger(load.target, label)) {
for (std::size_t index = 0U; index < domain.nodes().size(); ++index) {
if (domain.nodes()[index].sourceId.sourceLabel == label) {
if (domain.nodes()[index].sourceId.source_label == label) {
matchingNodes.push_back(static_cast<EntityIndex>(index));
}
}
@@ -164,7 +164,7 @@ Result<std::vector<EntityIndex>> resolveTarget(
if (matchingSets.size() > 1U || matchingNodes.size() > 1U ||
(!matchingSets.empty() && !matchingNodes.empty())) {
return Result<std::vector<EntityIndex>>::failure(loadFailure(
return Result<std::vector<EntityIndex>>::Failure(loadFailure(
"invalid-load-target",
load.location,
"CLOAD",
@@ -176,7 +176,7 @@ Result<std::vector<EntityIndex>> resolveTarget(
std::vector<unsigned char> seen(domain.nodes().size(), 0U);
for (const EntityIndex node : nodes) {
if (node >= domain.nodes().size() || seen[node] != 0U) {
return Result<std::vector<EntityIndex>>::failure(loadFailure(
return Result<std::vector<EntityIndex>>::Failure(loadFailure(
"invalid-load-target",
load.location,
"CLOAD",
@@ -185,13 +185,13 @@ Result<std::vector<EntityIndex>> resolveTarget(
}
seen[node] = 1U;
}
return Result<std::vector<EntityIndex>>::success(nodes);
return Result<std::vector<EntityIndex>>::Success(nodes);
}
if (!matchingNodes.empty()) {
return Result<std::vector<EntityIndex>>::success(
return Result<std::vector<EntityIndex>>::Success(
std::move(matchingNodes));
}
return Result<std::vector<EntityIndex>>::failure(loadFailure(
return Result<std::vector<EntityIndex>>::Failure(loadFailure(
"invalid-load-target",
load.location,
"CLOAD",
@@ -203,7 +203,7 @@ Status validateFiniteVector(
const Vector& values,
const SourceLocation& location,
const std::string& identity) {
for (std::size_t index = 0U; index < values.size(); ++index) {
for (std::size_t index = 0U; index < values.Size(); ++index) {
if (!std::isfinite(values[index])) {
return loadFailure(
"nonfinite-load-value",
@@ -213,14 +213,14 @@ Status validateFiniteVector(
"Load and prescribed displacement vectors must contain finite values.");
}
}
return Status::ok();
return Status::Ok();
}
Status validateShellMoments(
const Domain& domain,
const Vector& fullLoad) {
if (domain.shellElements().empty()) {
return Status::ok();
return Status::Ok();
}
std::vector<const ShellNodeInitialFrame*> frameByNode(
@@ -252,7 +252,7 @@ Status validateShellMoments(
"invalid-shell-director",
domain.nodes()[node].location,
"NODE",
domain.nodes()[node].sourceId.sourceLabelText,
domain.nodes()[node].sourceId.source_label_text,
"A loaded shell node must have an approved initial director.");
}
@@ -273,11 +273,11 @@ Status validateShellMoments(
"unsupported-drilling-load",
domain.nodes()[node].location,
"CLOAD",
domain.nodes()[node].sourceId.sourceLabelText,
domain.nodes()[node].sourceId.source_label_text,
"The aggregate nodal moment has an unsupported director-parallel component.");
}
}
return Status::ok();
return Status::Ok();
}
} // namespace
@@ -288,7 +288,7 @@ Result<Vector> LoadAssembler::assembleFullNodalLoad(
const Domain& domain = model.domain();
if (domain.nodes().size() >
(std::numeric_limits<std::size_t>::max)() / dofsPerNode) {
return Result<Vector>::failure(loadFailure(
return Result<Vector>::Failure(loadFailure(
"invalid-load-dimensions",
{domain.sourcePath(), 0U},
"LOAD_ASSEMBLER",
@@ -299,8 +299,8 @@ Result<Vector> LoadAssembler::assembleFullNodalLoad(
domain.nodes().size() * dofsPerNode;
const Status dofStatus = validateDofOrder(
dofs, expectedFullCount, {domain.sourcePath(), 0U});
if (!dofStatus.isOk()) {
return Result<Vector>::failure(dofStatus);
if (!dofStatus.IsOk()) {
return Result<Vector>::Failure(dofStatus);
}
for (std::size_t node = 0U; node < domain.nodes().size(); ++node) {
for (std::size_t component = 0U;
@@ -311,19 +311,19 @@ Result<Vector> LoadAssembler::assembleFullNodalLoad(
static_cast<EntityIndex>(node),
static_cast<DofComponent>(component)) !=
node * dofsPerNode + component) {
return Result<Vector>::failure(loadFailure(
return Result<Vector>::Failure(loadFailure(
"invalid-load-order",
domain.nodes()[node].location,
"LOAD_ASSEMBLER",
domain.nodes()[node].sourceId.sourceLabelText,
domain.nodes()[node].sourceId.source_label_text,
"DofManager node/component identity must match full-DOF order."));
}
} catch (const std::out_of_range&) {
return Result<Vector>::failure(loadFailure(
return Result<Vector>::Failure(loadFailure(
"invalid-load-dimensions",
domain.nodes()[node].location,
"LOAD_ASSEMBLER",
domain.nodes()[node].sourceId.sourceLabelText,
domain.nodes()[node].sourceId.source_label_text,
"DofManager must provide all six DOFs for every semantic node."));
}
}
@@ -332,7 +332,7 @@ Result<Vector> LoadAssembler::assembleFullNodalLoad(
const auto& activeLoads = model.activeLoads();
const auto& loads = model.step().loads;
if (activeLoads.size() != loads.size()) {
return Result<Vector>::failure(loadFailure(
return Result<Vector>::Failure(loadFailure(
"invalid-load-order",
model.step().location,
"CLOAD",
@@ -349,7 +349,7 @@ Result<Vector> LoadAssembler::assembleFullNodalLoad(
const EntityIndex loadIndex = activeLoads[sourceOrder];
if (static_cast<std::size_t>(loadIndex) != sourceOrder ||
loadIndex >= loads.size()) {
return Result<Vector>::failure(loadFailure(
return Result<Vector>::Failure(loadFailure(
"invalid-load-order",
model.step().location,
"CLOAD",
@@ -358,7 +358,7 @@ Result<Vector> LoadAssembler::assembleFullNodalLoad(
}
const auto& load = loads[loadIndex];
if (load.dof < 1 || load.dof > static_cast<int>(dofsPerNode)) {
return Result<Vector>::failure(loadFailure(
return Result<Vector>::Failure(loadFailure(
"invalid-load-dof",
load.location,
"CLOAD",
@@ -366,7 +366,7 @@ Result<Vector> LoadAssembler::assembleFullNodalLoad(
"A nodal load component must be in the range 1 through 6."));
}
if (!std::isfinite(load.magnitude)) {
return Result<Vector>::failure(loadFailure(
return Result<Vector>::Failure(loadFailure(
"nonfinite-load-value",
load.location,
"CLOAD",
@@ -375,15 +375,15 @@ Result<Vector> LoadAssembler::assembleFullNodalLoad(
}
auto target = resolveTarget(domain, load);
if (!target.hasValue()) {
return Result<Vector>::failure(target.status());
if (!target.HasValue()) {
return Result<Vector>::Failure(target.GetStatus());
}
const auto component = static_cast<DofComponent>(load.dof - 1);
for (const EntityIndex node : target.value()) {
for (const EntityIndex node : target.Value()) {
const std::size_t fullDof = dofs.fullDof(node, component);
const double accumulated = fullLoad[fullDof] + load.magnitude;
if (!std::isfinite(accumulated)) {
return Result<Vector>::failure(loadFailure(
return Result<Vector>::Failure(loadFailure(
"nonfinite-load-accumulation",
load.location,
"CLOAD",
@@ -394,10 +394,10 @@ Result<Vector> LoadAssembler::assembleFullNodalLoad(
}
}
const Status shellMomentStatus = validateShellMoments(domain, fullLoad);
if (!shellMomentStatus.isOk()) {
return Result<Vector>::failure(shellMomentStatus);
if (!shellMomentStatus.IsOk()) {
return Result<Vector>::Failure(shellMomentStatus);
}
return Result<Vector>::success(std::move(fullLoad));
return Result<Vector>::Success(std::move(fullLoad));
}
Result<Vector> LoadAssembler::effectiveFreeRhs(
@@ -407,46 +407,46 @@ Result<Vector> LoadAssembler::effectiveFreeRhs(
const DofManager& dofs) {
const SourceLocation location{{}, 0U};
const Status dofStatus =
validateDofOrder(dofs, fullLoad.size(), location);
if (!dofStatus.isOk()) {
return Result<Vector>::failure(dofStatus);
validateDofOrder(dofs, fullLoad.Size(), location);
if (!dofStatus.IsOk()) {
return Result<Vector>::Failure(dofStatus);
}
if (kfc.rows() != dofs.freeDofCount() ||
kfc.columns() != dofs.constrainedDofCount() ||
prescribedValues.size() != dofs.constrainedDofCount()) {
return Result<Vector>::failure(loadFailure(
if (kfc.Rows() != dofs.freeDofCount() ||
kfc.Columns() != dofs.constrainedDofCount() ||
prescribedValues.Size() != dofs.constrainedDofCount()) {
return Result<Vector>::Failure(loadFailure(
"invalid-load-dimensions",
location,
"LOAD_ASSEMBLER",
std::to_string(kfc.rows()) + "x" +
std::to_string(kfc.columns()),
std::to_string(kfc.Rows()) + "x" +
std::to_string(kfc.Columns()),
"Kfc rows/columns and prescribed values must match free/constrained order."));
}
const Status matrixStatus = kfc.validate();
if (!matrixStatus.isOk()) {
return Result<Vector>::failure(matrixStatus);
const Status matrixStatus = kfc.Validate();
if (!matrixStatus.IsOk()) {
return Result<Vector>::Failure(matrixStatus);
}
const Status loadStatus =
validateFiniteVector(fullLoad, location, "full-load");
if (!loadStatus.isOk()) {
return Result<Vector>::failure(loadStatus);
if (!loadStatus.IsOk()) {
return Result<Vector>::Failure(loadStatus);
}
const Status prescribedStatus = validateFiniteVector(
prescribedValues, location, "prescribed-values");
if (!prescribedStatus.isOk()) {
return Result<Vector>::failure(prescribedStatus);
if (!prescribedStatus.IsOk()) {
return Result<Vector>::Failure(prescribedStatus);
}
Vector correction{kfc.rows()};
for (std::size_t row = 0U; row < kfc.rows(); ++row) {
Vector correction{kfc.Rows()};
for (std::size_t row = 0U; row < kfc.Rows(); ++row) {
double sum = 0.0;
for (std::size_t position = kfc.rowOffsets()[row];
position < kfc.rowOffsets()[row + 1U];
for (std::size_t position = kfc.RowOffsets()[row];
position < kfc.RowOffsets()[row + 1U];
++position) {
const double product = kfc.values()[position] *
prescribedValues[kfc.columnIndices()[position]];
const double product = kfc.Values()[position] *
prescribedValues[kfc.ColumnIndices()[position]];
if (!std::isfinite(product)) {
return Result<Vector>::failure(loadFailure(
return Result<Vector>::Failure(loadFailure(
"nonfinite-load-accumulation",
location,
"LOAD_ASSEMBLER",
@@ -455,7 +455,7 @@ Result<Vector> LoadAssembler::effectiveFreeRhs(
}
sum += product;
if (!std::isfinite(sum)) {
return Result<Vector>::failure(loadFailure(
return Result<Vector>::Failure(loadFailure(
"nonfinite-load-accumulation",
location,
"LOAD_ASSEMBLER",
@@ -469,10 +469,10 @@ Result<Vector> LoadAssembler::effectiveFreeRhs(
Vector rhs = EssentialConstraints::gatherFree(fullLoad, dofs);
// The constrained vector is already in DofManager order, so this is the
// approved elimination equation rhs = Ff - Kfc*dc without reordering dc.
for (std::size_t row = 0U; row < rhs.size(); ++row) {
for (std::size_t row = 0U; row < rhs.Size(); ++row) {
const double value = rhs[row] - correction[row];
if (!std::isfinite(value)) {
return Result<Vector>::failure(loadFailure(
return Result<Vector>::Failure(loadFailure(
"nonfinite-load-accumulation",
location,
"LOAD_ASSEMBLER",
@@ -481,7 +481,7 @@ Result<Vector> LoadAssembler::effectiveFreeRhs(
}
rhs[row] = value;
}
return Result<Vector>::success(std::move(rhs));
return Result<Vector>::Success(std::move(rhs));
}
} // namespace fesa
+23 -23
View File
@@ -35,9 +35,9 @@ Result<SparseMatrix> assemblyFailure(
const SourceLocation& location,
const std::string& identity,
const std::string& message) {
return Result<SparseMatrix>::failure(Status::failure(
FailureCategory::model,
{{Severity::error,
return Result<SparseMatrix>::Failure(Status::Failure(
FailureCategory::kModel,
{{Severity::kError,
code,
location,
"*ELEMENT",
@@ -112,7 +112,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
return assemblyFailure(
"invalid-assembly-element",
element.location,
element.sourceId.sourceLabelText,
element.sourceId.source_label_text,
"Shell element references an entity outside the Domain.");
}
@@ -126,7 +126,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
return assemblyFailure(
"invalid-assembly-scatter",
element.location,
element.sourceId.sourceLabelText,
element.sourceId.source_label_text,
"DofManager does not contain the active shell scatter.");
}
for (std::size_t nodePosition = 0U;
@@ -138,7 +138,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
return assemblyFailure(
"invalid-assembly-element",
element.location,
element.sourceId.sourceLabelText,
element.sourceId.source_label_text,
"Shell element requires a valid node and initial director.");
}
input.nodes[nodePosition] = &domain.nodes()[nodeIndex];
@@ -156,7 +156,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
return assemblyFailure(
"invalid-assembly-scatter",
element.location,
element.sourceId.sourceLabelText,
element.sourceId.source_label_text,
"Shell scatter does not match the active model topology.");
}
}
@@ -175,13 +175,13 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
input.directors,
*input.section,
*input.material);
if (!shell.hasValue()) {
localFailures[elementOrder] = shell.status();
if (!shell.HasValue()) {
localFailures[elementOrder] = shell.GetStatus();
return;
}
const auto stiffness = shell.value().stiffness();
if (!stiffness.hasValue()) {
localFailures[elementOrder] = stiffness.status();
const auto stiffness = shell.Value().stiffness();
if (!stiffness.HasValue()) {
localFailures[elementOrder] = stiffness.GetStatus();
return;
}
@@ -197,7 +197,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
buffer[localOrder] = {
input.scatter[localRow],
input.scatter[localColumn],
stiffness.value().stabilizedGlobal24(
stiffness.Value().stabilizedGlobal24(
localRow, localColumn),
elementOrder,
localOrder};
@@ -209,7 +209,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
elementOrder < localFailures.size();
++elementOrder) {
if (localFailures[elementOrder]) {
return Result<SparseMatrix>::failure(
return Result<SparseMatrix>::Failure(
*localFailures[elementOrder]);
}
}
@@ -223,7 +223,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
contributions.insert(
contributions.end(), buffer.begin(), buffer.end());
}
return SparseMatrix::fromCoo(
return SparseMatrix::FromCoo(
dofs.fullDofCount(),
dofs.fullDofCount(),
std::move(contributions),
@@ -258,7 +258,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
return assemblyFailure(
"invalid-assembly-element",
element.location,
element.sourceId.sourceLabelText,
element.sourceId.source_label_text,
"Element references an entity outside the Domain.");
}
@@ -269,7 +269,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
return assemblyFailure(
"invalid-assembly-scatter",
element.location,
element.sourceId.sourceLabelText,
element.sourceId.source_label_text,
"DofManager does not contain the active element scatter.");
}
for (std::size_t endpoint = 0U; endpoint < 2U; ++endpoint) {
@@ -286,7 +286,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
return assemblyFailure(
"invalid-assembly-scatter",
element.location,
element.sourceId.sourceLabelText,
element.sourceId.source_label_text,
"Element scatter does not match the active model topology.");
}
}
@@ -307,12 +307,12 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
domain.nodes()[definition.nodeIndices[1U]],
domain.sections()[definition.sectionIndex],
domain.materials()[definition.materialIndex]);
if (!beam.hasValue()) {
localFailures[elementOrder] = beam.status();
if (!beam.HasValue()) {
localFailures[elementOrder] = beam.GetStatus();
return;
}
const Matrix stiffness = beam.value().globalStiffness();
const Matrix stiffness = beam.Value().globalStiffness();
auto& buffer = localBuffers[elementOrder];
const auto& scatter = scatters[elementOrder];
for (std::size_t localRow = 0U;
@@ -337,7 +337,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
elementOrder < localFailures.size();
++elementOrder) {
if (localFailures[elementOrder]) {
return Result<SparseMatrix>::failure(
return Result<SparseMatrix>::Failure(
*localFailures[elementOrder]);
}
}
@@ -350,7 +350,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
contributions.insert(
contributions.end(), buffer.begin(), buffer.end());
}
return SparseMatrix::fromCoo(
return SparseMatrix::FromCoo(
dofs.fullDofCount(),
dofs.fullDofCount(),
std::move(contributions),
+6 -5
View File
@@ -1,10 +1,11 @@
#include "fesa/build_info.hpp"
#include "fesa/build_info.h"
namespace fesa {
std::string_view solverVersion() noexcept {
// Keep this value stable until a reviewed solver release changes the metadata contract.
return "0.1.0";
std::string_view SolverVersion() noexcept {
// Keep this value stable until a reviewed solver release changes the metadata
// contract.
return "0.1.0";
}
} // namespace fesa
} // namespace fesa
+40 -40
View File
@@ -16,9 +16,9 @@ Status constraintFailure(
const std::string& code,
const std::string& identity,
const std::string& message) {
return Status::failure(
FailureCategory::model,
{{Severity::error,
return Status::Failure(
FailureCategory::kModel,
{{Severity::kError,
code,
{{}, 0U},
"ESSENTIAL_CONSTRAINTS",
@@ -41,7 +41,7 @@ Status validateDofOrder(const DofManager& dofs) {
const auto& constrainedDofs = dofs.constrainedDofs();
if (freeDofs.size() != dofs.freeDofCount() ||
constrainedDofs.size() != dofs.constrainedDofCount() ||
dofs.prescribedValues().size() != constrainedDofs.size() ||
dofs.prescribedValues().Size() != constrainedDofs.size() ||
constrainedDofs.size() > fullCount ||
freeDofs.size() != fullCount - constrainedDofs.size()) {
return constraintFailure(
@@ -94,7 +94,7 @@ Status validateDofOrder(const DofManager& dofs) {
std::to_string(fullCount),
"Free and constrained DOFs must partition the complete full-DOF range.");
}
return Status::ok();
return Status::Ok();
}
Result<SparseMatrix> extractBlock(
@@ -102,7 +102,7 @@ Result<SparseMatrix> extractBlock(
const std::vector<std::size_t>& rowDofs,
const std::vector<std::size_t>& columnDofs) {
const std::size_t absent = (std::numeric_limits<std::size_t>::max)();
std::vector<std::size_t> localColumn(full.columns(), absent);
std::vector<std::size_t> localColumn(full.Columns(), absent);
for (std::size_t column = 0U; column < columnDofs.size(); ++column) {
localColumn[columnDofs[column]] = column;
}
@@ -111,16 +111,16 @@ Result<SparseMatrix> extractBlock(
pattern.rowOffsets.reserve(rowDofs.size() + 1U);
pattern.rowOffsets.push_back(0U);
std::vector<CooContribution> contributions;
contributions.reserve(full.values().size());
contributions.reserve(full.Values().size());
for (std::size_t localRow = 0U;
localRow < rowDofs.size();
++localRow) {
const std::size_t fullRow = rowDofs[localRow];
for (std::size_t position = full.rowOffsets()[fullRow];
position < full.rowOffsets()[fullRow + 1U];
for (std::size_t position = full.RowOffsets()[fullRow];
position < full.RowOffsets()[fullRow + 1U];
++position) {
const std::size_t column =
localColumn[full.columnIndices()[position]];
localColumn[full.ColumnIndices()[position]];
if (column == absent) {
continue;
}
@@ -130,13 +130,13 @@ Result<SparseMatrix> extractBlock(
contributions.push_back({
localRow,
column,
full.values()[position],
full.Values()[position],
localRow,
position});
}
pattern.rowOffsets.push_back(pattern.columnIndices.size());
}
return SparseMatrix::fromCoo(
return SparseMatrix::FromCoo(
rowDofs.size(),
columnDofs.size(),
std::move(contributions),
@@ -144,7 +144,7 @@ Result<SparseMatrix> extractBlock(
}
void requireDofOrder(const DofManager& dofs) {
if (!validateDofOrder(dofs).isOk()) {
if (!validateDofOrder(dofs).IsOk()) {
throw std::invalid_argument{
"DofManager constraint dimensions or order are invalid."};
}
@@ -155,53 +155,53 @@ void requireDofOrder(const DofManager& dofs) {
Result<PartitionedStiffness> EssentialConstraints::partition(
const SparseMatrix& full,
const DofManager& dofs) {
const Status matrixStatus = full.validate();
if (!matrixStatus.isOk()) {
return Result<PartitionedStiffness>::failure(matrixStatus);
const Status matrixStatus = full.Validate();
if (!matrixStatus.IsOk()) {
return Result<PartitionedStiffness>::Failure(matrixStatus);
}
if (full.rows() != full.columns() ||
full.rows() != dofs.fullDofCount()) {
return Result<PartitionedStiffness>::failure(constraintFailure(
if (full.Rows() != full.Columns() ||
full.Rows() != dofs.fullDofCount()) {
return Result<PartitionedStiffness>::Failure(constraintFailure(
"invalid-constraint-dimensions",
std::to_string(full.rows()) + "x" +
std::to_string(full.columns()),
std::to_string(full.Rows()) + "x" +
std::to_string(full.Columns()),
"Full stiffness must be square and match the DofManager full dimension."));
}
const Status dofStatus = validateDofOrder(dofs);
if (!dofStatus.isOk()) {
return Result<PartitionedStiffness>::failure(dofStatus);
if (!dofStatus.IsOk()) {
return Result<PartitionedStiffness>::Failure(dofStatus);
}
auto kff = extractBlock(full, dofs.freeDofs(), dofs.freeDofs());
if (!kff.hasValue()) {
return Result<PartitionedStiffness>::failure(kff.status());
if (!kff.HasValue()) {
return Result<PartitionedStiffness>::Failure(kff.GetStatus());
}
auto kfc = extractBlock(full, dofs.freeDofs(), dofs.constrainedDofs());
if (!kfc.hasValue()) {
return Result<PartitionedStiffness>::failure(kfc.status());
if (!kfc.HasValue()) {
return Result<PartitionedStiffness>::Failure(kfc.GetStatus());
}
auto kcf = extractBlock(full, dofs.constrainedDofs(), dofs.freeDofs());
if (!kcf.hasValue()) {
return Result<PartitionedStiffness>::failure(kcf.status());
if (!kcf.HasValue()) {
return Result<PartitionedStiffness>::Failure(kcf.GetStatus());
}
auto kcc = extractBlock(
full, dofs.constrainedDofs(), dofs.constrainedDofs());
if (!kcc.hasValue()) {
return Result<PartitionedStiffness>::failure(kcc.status());
if (!kcc.HasValue()) {
return Result<PartitionedStiffness>::Failure(kcc.GetStatus());
}
return Result<PartitionedStiffness>::success({
std::move(kff.value()),
std::move(kfc.value()),
std::move(kcf.value()),
std::move(kcc.value())});
return Result<PartitionedStiffness>::Success({
std::move(kff.Value()),
std::move(kfc.Value()),
std::move(kcf.Value()),
std::move(kcc.Value())});
}
Vector EssentialConstraints::gatherFree(
const Vector& full,
const DofManager& dofs) {
requireDofOrder(dofs);
if (full.size() != dofs.fullDofCount()) {
if (full.Size() != dofs.fullDofCount()) {
throw std::invalid_argument{
"Full vector size must match the DofManager full dimension."};
}
@@ -218,7 +218,7 @@ Vector EssentialConstraints::gatherConstrained(
const Vector& full,
const DofManager& dofs) {
requireDofOrder(dofs);
if (full.size() != dofs.fullDofCount()) {
if (full.Size() != dofs.fullDofCount()) {
throw std::invalid_argument{
"Full vector size must match the DofManager full dimension."};
}
@@ -236,8 +236,8 @@ Vector EssentialConstraints::reconstructFull(
const Vector& constrainedValues,
const DofManager& dofs) {
requireDofOrder(dofs);
if (freeValues.size() != dofs.freeDofCount() ||
constrainedValues.size() != dofs.constrainedDofCount()) {
if (freeValues.Size() != dofs.freeDofCount() ||
constrainedValues.Size() != dofs.constrainedDofCount()) {
throw std::invalid_argument{
"Reduced vector sizes must match the DofManager order."};
}
+13 -22
View File
@@ -1,30 +1,21 @@
#include "fesa/core/diagnostic.hpp"
#include "fesa/core/diagnostic.h"
#include <algorithm>
#include <tuple>
namespace fesa {
void sortDiagnostics(std::vector<Diagnostic>& diagnostics) {
// stable_sort makes discovery order the final tie-breaker without storing it
// in the externally visible Diagnostic record.
std::stable_sort(
diagnostics.begin(),
diagnostics.end(),
[](const Diagnostic& left, const Diagnostic& right) {
return std::tie(
left.location.file,
left.location.line,
left.keyword,
left.entityIdentity,
left.code) <
std::tie(
right.location.file,
right.location.line,
right.keyword,
right.entityIdentity,
right.code);
});
void SortDiagnostics(std::vector<Diagnostic>& diagnostics) {
// stable_sort makes discovery order the final tie-breaker without storing it
// in the externally visible Diagnostic record.
std::stable_sort(
diagnostics.begin(), diagnostics.end(),
[](const Diagnostic& left, const Diagnostic& right) {
return std::tie(left.location.file, left.location.line, left.keyword,
left.entity_identity, left.code) <
std::tie(right.location.file, right.location.line, right.keyword,
right.entity_identity, right.code);
});
}
} // namespace fesa
} // namespace fesa
+20 -26
View File
@@ -1,42 +1,36 @@
#include "fesa/core/status.hpp"
#include "fesa/core/status.h"
#include <utility>
namespace fesa {
Status Status::ok() {
return Status{true, std::nullopt, {}};
Status Status::Ok() { return Status{true, std::nullopt, {}}; }
Status Status::Failure(std::vector<Diagnostic> diagnostics) {
SortDiagnostics(diagnostics);
return Status{false, std::nullopt, std::move(diagnostics)};
}
Status Status::failure(std::vector<Diagnostic> diagnostics) {
sortDiagnostics(diagnostics);
return Status{false, std::nullopt, std::move(diagnostics)};
Status Status::Failure(FailureCategory category,
std::vector<Diagnostic> diagnostics) {
SortDiagnostics(diagnostics);
return Status{false, category, std::move(diagnostics)};
}
Status Status::failure(
FailureCategory category, std::vector<Diagnostic> diagnostics) {
sortDiagnostics(diagnostics);
return Status{false, category, std::move(diagnostics)};
bool Status::IsOk() const noexcept { return is_ok_; }
std::optional<FailureCategory> Status::Category() const noexcept {
return category_;
}
bool Status::isOk() const noexcept {
return isOk_;
const std::vector<Diagnostic>& Status::Diagnostics() const noexcept {
return diagnostics_;
}
std::optional<FailureCategory> Status::failureCategory() const noexcept {
return category_;
}
const std::vector<Diagnostic>& Status::diagnostics() const noexcept {
return diagnostics_;
}
Status::Status(
bool isOk,
std::optional<FailureCategory> category,
std::vector<Diagnostic> diagnostics)
: isOk_{isOk},
Status::Status(bool is_ok, std::optional<FailureCategory> category,
std::vector<Diagnostic> diagnostics)
: is_ok_{is_ok},
category_{category},
diagnostics_{std::move(diagnostics)} {}
} // namespace fesa
} // namespace fesa
+14 -14
View File
@@ -40,18 +40,18 @@ bool isFinite(const Vector3& value) {
}
std::string elementIdentity(const Node& firstNode, const Node& secondNode) {
return firstNode.sourceId.instanceName + ":" +
firstNode.sourceId.sourceLabelText + "-" +
secondNode.sourceId.sourceLabelText;
return firstNode.sourceId.instance_name + ":" +
firstNode.sourceId.source_label_text + "-" +
secondNode.sourceId.source_label_text;
}
Result<EulerBeam3D> modelFailure(const std::string& code,
const SourceLocation& location,
const std::string& identity,
const std::string& message) {
return Result<EulerBeam3D>::failure(Status::failure(
FailureCategory::model,
{{Severity::error, code, location, "*ELEMENT", identity, message}}));
return Result<EulerBeam3D>::Failure(Status::Failure(
FailureCategory::kModel,
{{Severity::kError, code, location, "*ELEMENT", identity, message}}));
}
Matrix transformation(const std::array<double, 9>& rotation) {
@@ -148,8 +148,8 @@ Matrix closedStiffness(double length,
double normalizedMatrixError(const Matrix& lhs, const Matrix& rhs) {
double maximumDifference = 0.0;
double scale = 1.0;
for (std::size_t row = 0; row < lhs.rows(); ++row) {
for (std::size_t column = 0; column < lhs.columns(); ++column) {
for (std::size_t row = 0; row < lhs.Rows(); ++row) {
for (std::size_t column = 0; column < lhs.Columns(); ++column) {
const double lhsValue = lhs(row, column);
const double rhsValue = rhs(row, column);
if (!std::isfinite(lhsValue) || !std::isfinite(rhsValue)) {
@@ -181,7 +181,7 @@ std::array<double, kGeneralizedComponentCount> generalizedStrain(
const Vector& localDisplacement) {
std::array<double, kGeneralizedComponentCount> strain{};
for (std::size_t component = 0; component < strain.size(); ++component) {
for (std::size_t dof = 0; dof < localDisplacement.size(); ++dof) {
for (std::size_t dof = 0; dof < localDisplacement.Size(); ++dof) {
strain[component] += b(component, dof) * localDisplacement[dof];
}
}
@@ -356,7 +356,7 @@ Result<EulerBeam3D> EulerBeam3D::create(
ey[0], ey[1], ey[2],
ez[0], ez[1], ez[2]};
return Result<EulerBeam3D>::success(EulerBeam3D{
return Result<EulerBeam3D>::Success(EulerBeam3D{
length,
material.youngsModulus,
shearModulus,
@@ -401,7 +401,7 @@ Matrix EulerBeam3D::localStiffness() const {
Matrix EulerBeam3D::globalStiffness() const {
const Matrix local = localStiffness();
const Matrix transform = transformation(rotation_);
const Matrix localTimesTransform = local.multiply(transform);
const Matrix localTimesTransform = local.Multiply(transform);
Matrix global{kElementDofCount, kElementDofCount};
// Kg=T^T*Kl*T while dl=T*dg.
for (std::size_t row = 0; row < kElementDofCount; ++row) {
@@ -423,7 +423,7 @@ Vector EulerBeam3D::localEquivalentLoad(const ConstantLocalLineLoad& load) const
const double jacobian = 0.5 * length_;
for (const double xi : gaussPoints) {
const Matrix interpolation = kinematicInterpolation(xi, length_);
for (std::size_t dof = 0; dof < equivalent.size(); ++dof) {
for (std::size_t dof = 0; dof < equivalent.Size(); ++dof) {
for (std::size_t component = 0; component < components.size(); ++component) {
equivalent[dof] +=
interpolation(component, dof) * components[component] * jacobian;
@@ -435,13 +435,13 @@ Vector EulerBeam3D::localEquivalentLoad(const ConstantLocalLineLoad& load) const
BeamRecovery EulerBeam3D::recover(const Vector& globalElementDisplacement) const {
const Matrix transform = transformation(rotation_);
const Vector localDisplacement = transform.multiply(globalElementDisplacement);
const Vector localDisplacement = transform.Multiply(globalElementDisplacement);
const auto diagonal = constitutiveDiagonal(
youngsModulus_, shearModulus_, area_, iy_, iz_, torsionalConstant_);
BeamRecovery recovery{};
// With parser/CLI distributed loading excluded, Kl*dl is the local outward end action.
const Vector endAction = localStiffness().multiply(localDisplacement);
const Vector endAction = localStiffness().Multiply(localDisplacement);
for (std::size_t endpoint = 0; endpoint < 2U; ++endpoint) {
for (std::size_t component = 0; component < 6U; ++component) {
recovery.equilibriumEndActions[endpoint][component] =
+33 -33
View File
@@ -126,7 +126,7 @@ std::string elementIdentity(const std::array<const Node*, kNodeCount>& nodes) {
if (!identity.empty()) {
identity += "-";
}
identity += node->sourceId.sourceLabelText;
identity += node->sourceId.source_label_text;
}
return identity;
}
@@ -136,9 +136,9 @@ Result<Mitc4Shell> modelFailure(
const SourceLocation& location,
const std::string& identity,
std::string message) {
return Result<Mitc4Shell>::failure(Status::failure(
FailureCategory::model,
{{Severity::error,
return Result<Mitc4Shell>::Failure(Status::Failure(
FailureCategory::kModel,
{{Severity::kError,
std::move(code),
location,
"*ELEMENT",
@@ -205,9 +205,9 @@ std::array<double, 5> localEngineeringComponents(
}
Matrix scaledMatrix(const Matrix& source, double factor) {
Matrix result{source.rows(), source.columns()};
for (std::size_t row = 0U; row < source.rows(); ++row) {
for (std::size_t column = 0U; column < source.columns(); ++column) {
Matrix result{source.Rows(), source.Columns()};
for (std::size_t row = 0U; row < source.Rows(); ++row) {
for (std::size_t column = 0U; column < source.Columns(); ++column) {
result(row, column) = factor * source(row, column);
}
}
@@ -215,17 +215,17 @@ Matrix scaledMatrix(const Matrix& source, double factor) {
}
Matrix congruence(const Matrix& local, const Matrix& transformation) {
if (local.rows() != local.columns() ||
local.rows() != transformation.rows()) {
if (local.Rows() != local.Columns() ||
local.Rows() != transformation.Rows()) {
throw std::invalid_argument{"MITC4 congruence dimensions are incompatible."};
}
Matrix result{transformation.columns(), transformation.columns()};
for (std::size_t row = 0U; row < result.rows(); ++row) {
for (std::size_t column = row; column < result.columns(); ++column) {
Matrix result{transformation.Columns(), transformation.Columns()};
for (std::size_t row = 0U; row < result.Rows(); ++row) {
for (std::size_t column = row; column < result.Columns(); ++column) {
double value = 0.0;
for (std::size_t localRow = 0U; localRow < local.rows(); ++localRow) {
for (std::size_t localRow = 0U; localRow < local.Rows(); ++localRow) {
for (std::size_t localColumn = 0U;
localColumn < local.columns(); ++localColumn) {
localColumn < local.Columns(); ++localColumn) {
value += transformation(localRow, row) *
local(localRow, localColumn) *
transformation(localColumn, column);
@@ -239,8 +239,8 @@ Matrix congruence(const Matrix& local, const Matrix& transformation) {
}
bool isFinite(const Matrix& matrix) {
for (std::size_t row = 0U; row < matrix.rows(); ++row) {
for (std::size_t column = 0U; column < matrix.columns(); ++column) {
for (std::size_t row = 0U; row < matrix.Rows(); ++row) {
for (std::size_t column = 0U; column < matrix.Columns(); ++column) {
if (!std::isfinite(matrix(row, column))) {
return false;
}
@@ -250,7 +250,7 @@ bool isFinite(const Matrix& matrix) {
}
bool isFinite(const Vector& vector) {
for (std::size_t index = 0U; index < vector.size(); ++index) {
for (std::size_t index = 0U; index < vector.Size(); ++index) {
if (!std::isfinite(vector[index])) {
return false;
}
@@ -262,9 +262,9 @@ Result<Mitc4Stiffness> stiffnessFailure(
const SourceLocation& location,
const std::string& identity,
std::string message) {
return Result<Mitc4Stiffness>::failure(Status::failure(
FailureCategory::model,
{{Severity::error,
return Result<Mitc4Stiffness>::Failure(Status::Failure(
FailureCategory::kModel,
{{Severity::kError,
"invalid-shell-stiffness",
location,
"*ELEMENT",
@@ -276,9 +276,9 @@ Result<Mitc4PhysicalRecovery> recoveryFailure(
const SourceLocation& location,
const std::string& identity,
std::string message) {
return Result<Mitc4PhysicalRecovery>::failure(Status::failure(
FailureCategory::model,
{{Severity::error,
return Result<Mitc4PhysicalRecovery>::Failure(Status::Failure(
FailureCategory::kModel,
{{Severity::kError,
"invalid-shell-recovery",
location,
"*ELEMENT",
@@ -430,7 +430,7 @@ Result<Mitc4Shell> Mitc4Shell::create(
}
}
return Result<Mitc4Shell>::success(std::move(shell));
return Result<Mitc4Shell>::Success(std::move(shell));
}
Mitc4ShapeFunctions Mitc4Shell::shapeFunctions(
@@ -688,7 +688,7 @@ Result<Mitc4Stiffness> Mitc4Shell::stiffness() const {
"MITC4 transformed stiffness must contain only finite values.");
}
return Result<Mitc4Stiffness>::success(Mitc4Stiffness{
return Result<Mitc4Stiffness>::Success(Mitc4Stiffness{
std::move(physicalLocal),
std::move(physicalGlobal),
std::move(drillingGlobal),
@@ -698,7 +698,7 @@ Result<Mitc4Stiffness> Mitc4Shell::stiffness() const {
Result<Mitc4PhysicalRecovery> Mitc4Shell::recoverPhysical(
const Vector& globalElementDisplacement24) const {
if (globalElementDisplacement24.size() != kGlobalDofCount) {
if (globalElementDisplacement24.Size() != kGlobalDofCount) {
return recoveryFailure(
sourceLocation_, identity_,
"MITC4 physical recovery requires exactly 24 global element DOFs.");
@@ -710,7 +710,7 @@ Result<Mitc4PhysicalRecovery> Mitc4Shell::recoverPhysical(
}
const Vector physicalDisplacement =
physicalTransformation20().multiply(globalElementDisplacement24);
physicalTransformation20().Multiply(globalElementDisplacement24);
const Matrix tyingSamples = covariantTyingShearSamples20();
const Matrix constitutive = materialConstitutive5();
const Matrix planeStress = planeStressConstitutive();
@@ -742,8 +742,8 @@ Result<Mitc4PhysicalRecovery> Mitc4Shell::recoverPhysical(
const Matrix strainMatrix = strainDisplacement(
point.naturalCoordinates[0], point.naturalCoordinates[1], zeta,
&tyingSamples);
const Vector strain = strainMatrix.multiply(physicalDisplacement);
const Vector stress = constitutive.multiply(strain);
const Vector strain = strainMatrix.Multiply(physicalDisplacement);
const Vector stress = constitutive.Multiply(strain);
GeometryData geometry{};
if (!evaluateGeometry(
point.naturalCoordinates[0], point.naturalCoordinates[1],
@@ -769,7 +769,7 @@ Result<Mitc4PhysicalRecovery> Mitc4Shell::recoverPhysical(
0.5 * thickness_ * stress[3U + component];
}
recovery.strainEnergy +=
0.5 * strain.dot(stress) * geometry.jacobian;
0.5 * strain.Dot(stress) * geometry.jacobian;
}
for (std::size_t position = 0U;
@@ -785,12 +785,12 @@ Result<Mitc4PhysicalRecovery> Mitc4Shell::recoverPhysical(
const Vector strain = strainDisplacement(
point.naturalCoordinates[0], point.naturalCoordinates[1],
sectionPositions[position], &tyingSamples)
.multiply(physicalDisplacement);
.Multiply(physicalDisplacement);
Vector inPlaneStrain{3U};
for (std::size_t component = 0U; component < 3U; ++component) {
inPlaneStrain[component] = strain[component];
}
const Vector stress = planeStress.multiply(inPlaneStrain);
const Vector stress = planeStress.Multiply(inPlaneStrain);
for (std::size_t component = 0U; component < 3U; ++component) {
point.inPlaneStress[position][component] = stress[component];
}
@@ -819,7 +819,7 @@ Result<Mitc4PhysicalRecovery> Mitc4Shell::recoverPhysical(
}
}
return Result<Mitc4PhysicalRecovery>::success(std::move(recovery));
return Result<Mitc4PhysicalRecovery>::Success(std::move(recovery));
}
Mitc4Shell::Mitc4Shell(
+5 -5
View File
@@ -46,7 +46,7 @@ std::vector<EntityIndex> expandBoundaryTarget(
std::int64_t sourceLabel = 0;
if (tryPositiveInteger(boundary.target, sourceLabel)) {
for (std::size_t node = 0U; node < domain.nodes().size(); ++node) {
if (domain.nodes()[node].sourceId.sourceLabel == sourceLabel) {
if (domain.nodes()[node].sourceId.source_label == sourceLabel) {
return {static_cast<EntityIndex>(node)};
}
}
@@ -111,9 +111,9 @@ Result<DofManager> DofManager::create(const AnalysisModel& model) {
static_cast<std::size_t>(component - 1);
auto& prescribed = prescribedByFullDof[fullDof];
if (prescribed && *prescribed != boundary.value) {
return Result<DofManager>::failure(Status::failure(
FailureCategory::input,
{{Severity::error,
return Result<DofManager>::Failure(Status::Failure(
FailureCategory::kInput,
{{Severity::kError,
"conflicting-boundary-condition",
boundary.location,
"BOUNDARY",
@@ -188,7 +188,7 @@ Result<DofManager> DofManager::create(const AnalysisModel& model) {
model.activeElements(),
elementScatters,
shellElementScatters);
return Result<DofManager>::success(DofManager{
return Result<DofManager>::Success(DofManager{
fullCount,
std::move(freeEquations),
std::move(elementScatters),
+12 -12
View File
@@ -147,10 +147,10 @@ public:
finalizeModel();
}
if (failure_) {
return Result<Domain>::failure(Status::failure(
return Result<Domain>::Failure(Status::Failure(
failure_->category, {std::move(failure_->diagnostic)}));
}
sortDiagnostics(definition_.warnings);
SortDiagnostics(definition_.warnings);
return Domain::create(std::move(definition_));
}
@@ -177,7 +177,7 @@ private:
if (!failure_) {
failure_ = MappingFailure{
category,
{Severity::error,
{Severity::kError,
std::move(code),
location,
std::move(keyword),
@@ -194,7 +194,7 @@ private:
std::string entityIdentity,
std::string message) {
return fail(
FailureCategory::input,
FailureCategory::kInput,
std::move(code),
location,
std::move(keyword),
@@ -209,7 +209,7 @@ private:
std::string entityIdentity,
std::string message) {
return fail(
FailureCategory::model,
FailureCategory::kModel,
std::move(code),
location,
std::move(keyword),
@@ -1677,7 +1677,7 @@ private:
// One warning per allowlisted keyword keeps no-op provenance stable;
// subordinate variable rows remain attached to that keyword record.
definition_.warnings.push_back({
Severity::warning,
Severity::kWarning,
"ignored-input-keyword",
block.location,
block.canonicalName,
@@ -1781,15 +1781,15 @@ private:
definition_.nodes,
definition_.shellElements,
definition_.shellSections);
if (!geometry.hasValue()) {
const auto& status = geometry.status();
if (!geometry.HasValue()) {
const auto& status = geometry.GetStatus();
failure_ = MappingFailure{
status.failureCategory().value_or(FailureCategory::model),
status.diagnostics().front()};
status.Category().value_or(FailureCategory::kModel),
status.Diagnostics().front()};
return;
}
definition_.shellNodeInitialFrames =
std::move(geometry.value().nodalFrames);
std::move(geometry.Value().nodalFrames);
}
expandAssemblySets();
if (failure_) {
@@ -2357,7 +2357,7 @@ private:
for (std::size_t index = 0U;
index < definition_.nodes.size();
++index) {
if (definition_.nodes[index].sourceId.sourceLabel == label) {
if (definition_.nodes[index].sourceId.source_label == label) {
matchingNodes.push_back(static_cast<EntityIndex>(index));
}
}
+4 -4
View File
@@ -86,14 +86,14 @@ Result<ParsedInput> failure(
std::string keyword,
std::string message) {
Diagnostic diagnostic{
Severity::error,
Severity::kError,
std::move(code),
{sourcePath, line},
std::move(keyword),
"",
std::move(message)};
return Result<ParsedInput>::failure(Status::failure(
FailureCategory::input, {std::move(diagnostic)}));
return Result<ParsedInput>::Failure(Status::Failure(
FailureCategory::kInput, {std::move(diagnostic)}));
}
} // namespace
@@ -210,7 +210,7 @@ Result<ParsedInput> AbaqusInputReader::read(
++lineNumber;
}
return Result<ParsedInput>::success(std::move(parsed));
return Result<ParsedInput>::Success(std::move(parsed));
}
} // namespace fesa
+47 -47
View File
@@ -4,7 +4,7 @@
#include "fesa/io/hdf5/hdf5_results_writer.hpp"
#include "fesa/analysis/analysis_model.hpp"
#include "fesa/build_info.hpp"
#include "fesa/build_info.h"
#include "fesa/fem/dof_manager.hpp"
#include <hdf5.h>
@@ -153,9 +153,9 @@ hid_t requireHdf5Id(const hid_t result, const char* message) {
}
Status outputFailure(const std::string& code, const std::string& message) {
return Status::failure(
FailureCategory::output,
{{Severity::error, code, {}, "", "", message}});
return Status::Failure(
FailureCategory::kOutput,
{{Severity::kError, code, {}, "", "", message}});
}
bool isFinite(const std::array<double, 3>& values) {
@@ -217,9 +217,9 @@ bool isValidUtf8(const std::string& value) {
}
bool sameIdentity(const SourceEntityId& left, const SourceEntityId& right) {
return left.instanceName == right.instanceName &&
left.sourceLabel == right.sourceLabel &&
left.sourceLabelText == right.sourceLabelText;
return left.instance_name == right.instance_name &&
left.source_label == right.source_label &&
left.source_label_text == right.source_label_text;
}
using AxisSet = std::array<double, 9>;
@@ -346,10 +346,10 @@ Status validateShellWriterInput(
for (const auto& element : domain.shellElements()) {
if ((element.sourceType != ShellSourceElementType::s4 &&
element.sourceType != ShellSourceElementType::s4r) ||
element.sourceId.sourceLabel <= 0 ||
element.sourceId.sourceLabelText.empty() ||
!isValidUtf8(element.sourceId.instanceName) ||
!isValidUtf8(element.sourceId.sourceLabelText) ||
element.sourceId.source_label <= 0 ||
element.sourceId.source_label_text.empty() ||
!isValidUtf8(element.sourceId.instance_name) ||
!isValidUtf8(element.sourceId.source_label_text) ||
element.materialIndex >= domain.materials().size() ||
element.sectionIndex >= domain.shellSections().size() ||
domain.shellSections()[element.sectionIndex].materialIndex !=
@@ -444,7 +444,7 @@ Status validateShellWriterInput(
"invalid-result-rows",
"Shell energy, equilibrium, and verification metrics must be finite.");
}
return Status::ok();
return Status::Ok();
}
Status validateWriterInput(
@@ -467,7 +467,7 @@ Status validateWriterInput(
const bool shell = isShellDomain(domain);
if (shell) {
const Status shellValidation = validateShellWriterInput(domain, state);
if (!shellValidation.isOk()) {
if (!shellValidation.IsOk()) {
return shellValidation;
}
} else if (!state.shellResults().empty()) {
@@ -488,12 +488,12 @@ Status validateWriterInput(
&state.residual(),
&state.reaction()};
for (const Vector* vector : vectors) {
if (vector->size() != fullDofCount) {
if (vector->Size() != fullDofCount) {
return outputFailure(
"invalid-result-state",
"Every V0 analysis vector must have node_count*6 values.");
}
for (std::size_t index = 0U; index < vector->size(); ++index) {
for (std::size_t index = 0U; index < vector->Size(); ++index) {
if (!std::isfinite((*vector)[index])) {
return outputFailure(
"invalid-result-state",
@@ -509,10 +509,10 @@ Status validateWriterInput(
"Source path and UTF-8 content identity are required.");
}
for (const Node& node : domain.nodes()) {
if (node.sourceId.sourceLabel <= 0 ||
node.sourceId.sourceLabelText.empty() ||
!isValidUtf8(node.sourceId.instanceName) ||
!isValidUtf8(node.sourceId.sourceLabelText) ||
if (node.sourceId.source_label <= 0 ||
node.sourceId.source_label_text.empty() ||
!isValidUtf8(node.sourceId.instance_name) ||
!isValidUtf8(node.sourceId.source_label_text) ||
!isFinite(node.coordinates)) {
return outputFailure(
"invalid-result-identity",
@@ -524,10 +524,10 @@ Status validateWriterInput(
modelData.beamLocalAxes.reserve(domain.elements().size());
for (const EulerBeam3DDefinition& element : domain.elements()) {
AxisSet axes{};
if (element.sourceId.sourceLabel <= 0 ||
element.sourceId.sourceLabelText.empty() ||
!isValidUtf8(element.sourceId.instanceName) ||
!isValidUtf8(element.sourceId.sourceLabelText) ||
if (element.sourceId.source_label <= 0 ||
element.sourceId.source_label_text.empty() ||
!isValidUtf8(element.sourceId.instance_name) ||
!isValidUtf8(element.sourceId.source_label_text) ||
element.nodeIndices[0U] == element.nodeIndices[1U] ||
element.materialIndex >= domain.materials().size() ||
!computeLocalAxes(domain, element, axes)) {
@@ -626,7 +626,7 @@ Status validateWriterInput(
for (const Diagnostic& diagnostic : diagnostics) {
if (!isValidUtf8(diagnostic.code) ||
!isValidUtf8(diagnostic.keyword) ||
!isValidUtf8(diagnostic.entityIdentity) ||
!isValidUtf8(diagnostic.entity_identity) ||
!isValidUtf8(diagnostic.message)) {
return outputFailure(
"invalid-result-diagnostic",
@@ -636,23 +636,23 @@ Status validateWriterInput(
auto analysisModelResult = AnalysisModel::create(domain);
if (!analysisModelResult.hasValue()) {
if (!analysisModelResult.HasValue()) {
return outputFailure(
"invalid-result-state",
"The HDF5 writer could not reconstruct the active model view.");
}
const AnalysisModel analysisModel =
std::move(analysisModelResult.value());
std::move(analysisModelResult.Value());
auto dofResult = DofManager::create(analysisModel);
if (!dofResult.hasValue()) {
if (!dofResult.HasValue()) {
return outputFailure(
"invalid-result-state",
"The HDF5 writer could not reconstruct stable constraint identity.");
}
const DofManager dofs = std::move(dofResult.value());
const DofManager dofs = std::move(dofResult.Value());
modelData.constraintMask.assign(fullDofCount, 0U);
modelData.prescribedDisplacement.assign(fullDofCount, 0.0);
if (dofs.constrainedDofs().size() != dofs.prescribedValues().size()) {
if (dofs.constrainedDofs().size() != dofs.prescribedValues().Size()) {
return outputFailure(
"invalid-result-state",
"Constraint identities and prescribed values have inconsistent sizes.");
@@ -670,7 +670,7 @@ Status validateWriterInput(
modelData.constraintMask[fullDof] = 1U;
modelData.prescribedDisplacement[fullDof] = prescribed;
}
return Status::ok();
return Status::Ok();
}
std::string normalizedPathString(const std::filesystem::path& path) {
@@ -948,7 +948,7 @@ void writeMetadata(const hid_t file, const Domain& domain) {
auto metadata = createGroup(file, "/metadata");
writeUint64Attribute(metadata.get(), "schema_version", 0U);
writeStringAttribute(
metadata.get(), "solver_version", std::string{solverVersion()});
metadata.get(), "solver_version", std::string{SolverVersion()});
writeStringAttribute(
metadata.get(), "source_input_identity", sourceInputIdentity(domain));
writeStringAttribute(
@@ -987,8 +987,8 @@ void writeNodes(const hid_t file, const Domain& domain) {
const Node& node = domain.nodes()[index];
rows.push_back({
static_cast<std::uint64_t>(index),
node.sourceId.instanceName.c_str(),
node.sourceId.sourceLabelText.c_str(),
node.sourceId.instance_name.c_str(),
node.sourceId.source_label_text.c_str(),
{node.coordinates[0U], node.coordinates[1U], node.coordinates[2U]}});
}
@@ -1062,8 +1062,8 @@ void writeBeamElements(
const auto& element = domain.elements()[index];
ElementWriteRow row{
static_cast<std::uint64_t>(index),
element.sourceId.instanceName.c_str(),
element.sourceId.sourceLabelText.c_str(),
element.sourceId.instance_name.c_str(),
element.sourceId.source_label_text.c_str(),
{static_cast<std::uint64_t>(element.nodeIndices[0U]),
static_cast<std::uint64_t>(element.nodeIndices[1U])},
{}};
@@ -1146,8 +1146,8 @@ void writeShellElements(const hid_t file, const Domain& domain) {
const auto& element = domain.shellElements()[index];
rows.push_back({
static_cast<std::uint64_t>(index),
element.sourceId.instanceName.c_str(),
element.sourceId.sourceLabelText.c_str(),
element.sourceId.instance_name.c_str(),
element.sourceId.source_label_text.c_str(),
shellSourceTypeName(element.sourceType),
kMitc4InternalFormulation.data(),
{static_cast<std::uint64_t>(element.nodeIndices[0U]),
@@ -1660,7 +1660,7 @@ void writeShellResultDatasets(
void writeDiagnostics(
const hid_t file, const std::vector<Diagnostic>& inputDiagnostics) {
std::vector<Diagnostic> diagnostics = inputDiagnostics;
sortDiagnostics(diagnostics);
SortDiagnostics(diagnostics);
std::vector<std::string> files;
files.reserve(diagnostics.size());
for (const auto& diagnostic : diagnostics) {
@@ -1671,12 +1671,12 @@ void writeDiagnostics(
for (std::size_t index = 0U; index < diagnostics.size(); ++index) {
const auto& diagnostic = diagnostics[index];
rows.push_back({
diagnostic.severity == Severity::warning ? "warning" : "error",
diagnostic.severity == Severity::kWarning ? "warning" : "error",
diagnostic.code.c_str(),
files[index].c_str(),
static_cast<std::uint64_t>(diagnostic.location.line),
diagnostic.keyword.c_str(),
diagnostic.entityIdentity.c_str(),
diagnostic.entity_identity.c_str(),
diagnostic.message.c_str()});
}
@@ -1735,8 +1735,8 @@ void writeResultDatasets(
file,
std::string{kStepRoot} + "/nodal/displacement",
nodalDimensions,
state.displacement().data(),
state.displacement().size(),
state.displacement().Data(),
state.displacement().Size(),
"UX,UY,UZ,URX,URY,URZ",
"length,length,length,radian,radian,radian",
"global-cartesian",
@@ -1745,8 +1745,8 @@ void writeResultDatasets(
file,
std::string{kStepRoot} + "/nodal/reaction",
nodalDimensions,
state.reaction().data(),
state.reaction().size(),
state.reaction().Data(),
state.reaction().Size(),
"RF1,RF2,RF3,RM1,RM2,RM3",
"force,force,force,force*length,force*length,force*length",
"global-cartesian",
@@ -2253,7 +2253,7 @@ void selfCheckFile(
H5Gclose};
requireUint64Attribute(metadata.get(), "schema_version", 0U);
requireStringAttribute(
metadata.get(), "solver_version", std::string{solverVersion()});
metadata.get(), "solver_version", std::string{SolverVersion()});
requireStringAttribute(
metadata.get(), "source_input_identity", sourceInputIdentity(domain));
requireStringAttribute(
@@ -2572,7 +2572,7 @@ Status Hdf5ResultsWriter::write(
try {
const Status validation = validateWriterInput(
outputPath, domain, state, diagnostics, modelData);
if (!validation.isOk()) {
if (!validation.IsOk()) {
return validation;
}
@@ -2590,7 +2590,7 @@ Status Hdf5ResultsWriter::write(
"The checked temporary HDF5 file could not replace the final output.");
}
cleanup.release();
return Status::ok();
return Status::Ok();
} catch (const Hdf5Failure& failure) {
return outputFailure("hdf5-write-failure", failure.what());
} catch (const std::exception& failure) {
+99 -110
View File
@@ -1,4 +1,4 @@
#include "fesa/math/matrix.hpp"
#include "fesa/math/matrix.h"
#include <mkl.h>
@@ -9,151 +9,140 @@
namespace fesa {
namespace {
std::size_t checkedStorageSize(const std::size_t rows, const std::size_t columns) {
// Reject shape multiplication overflow before logical dimensions and storage diverge.
if (columns != 0 &&
rows > (std::numeric_limits<std::size_t>::max)() / columns) {
throw std::length_error{"Dense matrix dimensions exceed the storage size range."};
}
return rows * columns;
/// @brief Rejects shape overflow before logical dimensions diverge from
/// storage.
std::size_t CheckedStorageSize(const std::size_t rows,
const std::size_t columns) {
if (columns != 0 &&
rows > (std::numeric_limits<std::size_t>::max)() / columns) {
throw std::length_error{
"Dense matrix dimensions exceed the storage size range."};
}
return rows * columns;
}
MKL_INT toMklSize(const std::size_t size) {
if (size > static_cast<std::size_t>((std::numeric_limits<MKL_INT>::max)())) {
throw std::length_error{"Dense matrix dimension exceeds the MKL integer range."};
}
return static_cast<MKL_INT>(size);
/// @brief Converts a dense matrix dimension to the private MKL integer
/// contract.
MKL_INT ToMklSize(const std::size_t size) {
if (size > static_cast<std::size_t>((std::numeric_limits<MKL_INT>::max)())) {
throw std::length_error{
"Dense matrix dimension exceeds the MKL integer range."};
}
return static_cast<MKL_INT>(size);
}
void copyValues(const std::vector<double>& source, std::vector<double>& destination) {
if (source.empty()) {
return;
}
/// @brief Copies owned values without exposing the dense backend publicly.
void CopyValues(const std::vector<double>& source,
std::vector<double>& destination) {
if (source.empty()) {
return;
}
cblas_dcopy(toMklSize(source.size()), source.data(), 1, destination.data(), 1);
cblas_dcopy(ToMklSize(source.size()), source.data(), 1, destination.data(),
1);
}
} // namespace
} // namespace
Matrix::Matrix(
const std::size_t rows,
const std::size_t columns,
const double value)
: rows_(rows), columns_(columns), values_(checkedStorageSize(rows, columns), value) {}
Matrix::Matrix(const std::size_t rows, const std::size_t columns,
const double value)
: rows_(rows),
columns_(columns),
values_(CheckedStorageSize(rows, columns), value) {}
Matrix::Matrix(const Matrix& other)
: rows_(other.rows_), columns_(other.columns_), values_(other.values_.size()) {
copyValues(other.values_, values_);
: rows_(other.rows_),
columns_(other.columns_),
values_(other.values_.size()) {
CopyValues(other.values_, values_);
}
Matrix::Matrix(Matrix&& other) noexcept
: rows_(other.rows_),
columns_(other.columns_),
values_(std::move(other.values_)) {
other.rows_ = 0;
other.columns_ = 0;
other.values_.clear();
other.rows_ = 0;
other.columns_ = 0;
other.values_.clear();
}
Matrix& Matrix::operator=(const Matrix& other) {
if (this != &other) {
std::vector<double> copied(other.values_.size());
copyValues(other.values_, copied);
rows_ = other.rows_;
columns_ = other.columns_;
values_.swap(copied);
}
return *this;
if (this != &other) {
std::vector<double> copied(other.values_.size());
CopyValues(other.values_, copied);
rows_ = other.rows_;
columns_ = other.columns_;
values_.swap(copied);
}
return *this;
}
Matrix& Matrix::operator=(Matrix&& other) noexcept {
if (this != &other) {
rows_ = other.rows_;
columns_ = other.columns_;
values_ = std::move(other.values_);
other.rows_ = 0;
other.columns_ = 0;
other.values_.clear();
}
return *this;
if (this != &other) {
rows_ = other.rows_;
columns_ = other.columns_;
values_ = std::move(other.values_);
other.rows_ = 0;
other.columns_ = 0;
other.values_.clear();
}
return *this;
}
std::size_t Matrix::rows() const noexcept {
return rows_;
}
std::size_t Matrix::Rows() const noexcept { return rows_; }
std::size_t Matrix::columns() const noexcept {
return columns_;
}
std::size_t Matrix::Columns() const noexcept { return columns_; }
double& Matrix::operator()(const std::size_t row, const std::size_t column) {
if (row >= rows_ || column >= columns_) {
throw std::out_of_range{"Matrix index is outside its dimensions."};
}
return values_[row * columns_ + column];
if (row >= rows_ || column >= columns_) {
throw std::out_of_range{"Matrix index is outside its dimensions."};
}
return values_[row * columns_ + column];
}
const double& Matrix::operator()(const std::size_t row, const std::size_t column) const {
if (row >= rows_ || column >= columns_) {
throw std::out_of_range{"Matrix index is outside its dimensions."};
}
return values_[row * columns_ + column];
const double& Matrix::operator()(const std::size_t row,
const std::size_t column) const {
if (row >= rows_ || column >= columns_) {
throw std::out_of_range{"Matrix index is outside its dimensions."};
}
return values_[row * columns_ + column];
}
Vector Matrix::multiply(const Vector& rhs) const {
if (columns_ != rhs.size()) {
throw std::invalid_argument{"Matrix-vector multiplication has incompatible dimensions."};
}
Vector Matrix::Multiply(const Vector& rhs) const {
if (columns_ != rhs.Size()) {
throw std::invalid_argument{
"Matrix-vector multiplication has incompatible dimensions."};
}
Vector result{rows_};
if (rows_ == 0 || columns_ == 0) {
return result;
}
// The owned layout is row-major, so the leading dimension is the column
// count for the adapter call and remains invisible to public consumers.
cblas_dgemv(
CblasRowMajor,
CblasNoTrans,
toMklSize(rows_),
toMklSize(columns_),
1.0,
values_.data(),
toMklSize(columns_),
rhs.data(),
1,
0.0,
result.data(),
1);
Vector result{rows_};
if (rows_ == 0 || columns_ == 0) {
return result;
}
// The owned layout is row-major, so the leading dimension is the column
// count for the adapter call and remains invisible to public consumers.
cblas_dgemv(CblasRowMajor, CblasNoTrans, ToMklSize(rows_),
ToMklSize(columns_), 1.0, values_.data(), ToMklSize(columns_),
rhs.Data(), 1, 0.0, result.Data(), 1);
return result;
}
Matrix Matrix::multiply(const Matrix& rhs) const {
if (columns_ != rhs.rows_) {
throw std::invalid_argument{"Matrix multiplication has incompatible dimensions."};
}
Matrix Matrix::Multiply(const Matrix& rhs) const {
if (columns_ != rhs.rows_) {
throw std::invalid_argument{
"Matrix multiplication has incompatible dimensions."};
}
Matrix result{rows_, rhs.columns_};
if (rows_ == 0 || columns_ == 0 || rhs.columns_ == 0) {
return result;
}
cblas_dgemm(
CblasRowMajor,
CblasNoTrans,
CblasNoTrans,
toMklSize(rows_),
toMklSize(rhs.columns_),
toMklSize(columns_),
1.0,
values_.data(),
toMklSize(columns_),
rhs.values_.data(),
toMklSize(rhs.columns_),
0.0,
result.values_.data(),
toMklSize(rhs.columns_));
Matrix result{rows_, rhs.columns_};
if (rows_ == 0 || columns_ == 0 || rhs.columns_ == 0) {
return result;
}
cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, ToMklSize(rows_),
ToMklSize(rhs.columns_), ToMklSize(columns_), 1.0, values_.data(),
ToMklSize(columns_), rhs.values_.data(), ToMklSize(rhs.columns_),
0.0, result.values_.data(), ToMklSize(rhs.columns_));
return result;
}
} // namespace fesa
} // namespace fesa
+165 -204
View File
@@ -1,6 +1,4 @@
#include "fesa/math/sparse_matrix.hpp"
#include "fesa/fem/dof_manager.hpp"
#include "fesa/math/sparse_matrix.h"
#include <algorithm>
#include <cmath>
@@ -10,230 +8,193 @@
#include <tuple>
#include <utility>
#include "fesa/fem/dof_manager.hpp"
namespace fesa {
namespace {
Status sparseFailure(
const std::string& code,
const std::string& identity,
const std::string& message) {
return Status::failure(
FailureCategory::model,
{{Severity::error,
code,
{{}, 0U},
"SPARSE_MATRIX",
identity,
message}});
/// @brief Builds a structured sparse-matrix model failure.
Status SparseFailure(const std::string& code, const std::string& identity,
const std::string& message) {
return Status::Failure(
FailureCategory::kModel,
{{Severity::kError, code, {{}, 0U}, "SPARSE_MATRIX", identity, message}});
}
Status validateCsr(
const std::size_t rows,
const std::size_t columns,
const std::vector<std::size_t>& rowOffsets,
const std::vector<std::size_t>& columnIndices,
const std::vector<double>* const values) {
if (rows == (std::numeric_limits<std::size_t>::max)() ||
rowOffsets.size() != rows + 1U) {
return sparseFailure(
"invalid-sparse-shape",
"row-offset-count",
"CSR row offsets must contain exactly rows plus one entries.");
}
if (rowOffsets.empty() || rowOffsets.front() != 0U ||
rowOffsets.back() != columnIndices.size()) {
return sparseFailure(
"invalid-sparse-pattern",
"row-offset-range",
"CSR row offsets must start at zero and end at the column count.");
}
if (values != nullptr && values->size() != columnIndices.size()) {
return sparseFailure(
"invalid-sparse-shape",
"value-count",
"CSR column and value arrays must have equal sizes.");
}
/// @brief Validates canonical CSR shape, order, index, and finite-value rules.
Status ValidateCsr(const std::size_t rows, const std::size_t columns,
const std::vector<std::size_t>& row_offsets,
const std::vector<std::size_t>& column_indices,
const std::vector<double>* const values) {
if (rows == (std::numeric_limits<std::size_t>::max)() ||
row_offsets.size() != rows + 1U) {
return SparseFailure(
"invalid-sparse-shape", "row-offset-count",
"CSR row offsets must contain exactly rows plus one entries.");
}
if (row_offsets.empty() || row_offsets.front() != 0U ||
row_offsets.back() != column_indices.size()) {
return SparseFailure(
"invalid-sparse-pattern", "row-offset-range",
"CSR row offsets must start at zero and end at the column count.");
}
if (values != nullptr && values->size() != column_indices.size()) {
return SparseFailure("invalid-sparse-shape", "value-count",
"CSR column and value arrays must have equal sizes.");
}
for (std::size_t row = 0U; row < rows; ++row) {
const std::size_t begin = rowOffsets[row];
const std::size_t end = rowOffsets[row + 1U];
if (begin > end || end > columnIndices.size()) {
return sparseFailure(
"invalid-sparse-pattern",
std::to_string(row),
"CSR row offsets must be nondecreasing and remain in range.");
}
for (std::size_t position = begin; position < end; ++position) {
if (columnIndices[position] >= columns) {
return sparseFailure(
"invalid-sparse-index",
std::to_string(position),
"CSR column index is outside the matrix dimensions.");
}
if (position > begin &&
columnIndices[position - 1U] >= columnIndices[position]) {
return sparseFailure(
"invalid-sparse-pattern",
std::to_string(row),
"CSR columns must be sorted and unique within each row.");
}
if (values != nullptr && !std::isfinite((*values)[position])) {
return sparseFailure(
"nonfinite-sparse-value",
std::to_string(position),
"CSR values must be finite.");
}
}
for (std::size_t row = 0U; row < rows; ++row) {
const std::size_t begin = row_offsets[row];
const std::size_t end = row_offsets[row + 1U];
if (begin > end || end > column_indices.size()) {
return SparseFailure(
"invalid-sparse-pattern", std::to_string(row),
"CSR row offsets must be nondecreasing and remain in range.");
}
return Status::ok();
for (std::size_t position = begin; position < end; ++position) {
if (column_indices[position] >= columns) {
return SparseFailure(
"invalid-sparse-index", std::to_string(position),
"CSR column index is outside the matrix dimensions.");
}
if (position > begin &&
column_indices[position - 1U] >= column_indices[position]) {
return SparseFailure(
"invalid-sparse-pattern", std::to_string(row),
"CSR columns must be sorted and unique within each row.");
}
if (values != nullptr && !std::isfinite((*values)[position])) {
return SparseFailure("nonfinite-sparse-value", std::to_string(position),
"CSR values must be finite.");
}
}
}
return Status::Ok();
}
} // namespace
} // namespace
Result<SparseMatrix> SparseMatrix::fromCoo(
const std::size_t rows,
const std::size_t columns,
Result<SparseMatrix> SparseMatrix::FromCoo(
const std::size_t rows, const std::size_t columns,
std::vector<CooContribution> contributions,
const SparsePattern& expectedPattern) {
const Status patternStatus = validateCsr(
rows,
columns,
expectedPattern.rowOffsets,
expectedPattern.columnIndices,
nullptr);
if (!patternStatus.isOk()) {
return Result<SparseMatrix>::failure(patternStatus);
const SparsePattern& expected_pattern) {
const Status pattern_status =
ValidateCsr(rows, columns, expected_pattern.rowOffsets,
expected_pattern.columnIndices, nullptr);
if (!pattern_status.IsOk()) {
return Result<SparseMatrix>::Failure(pattern_status);
}
for (const auto& contribution : contributions) {
if (contribution.row >= rows || contribution.column >= columns) {
return Result<SparseMatrix>::Failure(SparseFailure(
"invalid-sparse-index",
std::to_string(contribution.row) + ":" +
std::to_string(contribution.column),
"COO contribution index is outside the matrix dimensions."));
}
if (!std::isfinite(contribution.value)) {
return Result<SparseMatrix>::Failure(
SparseFailure("nonfinite-sparse-value",
std::to_string(contribution.element_order) + ":" +
std::to_string(contribution.local_order),
"COO contribution values must be finite."));
}
}
// The complete tuple fixes duplicate summation order independently of
// worker completion order. stable_sort also preserves exact tuple ties.
std::stable_sort(
contributions.begin(), contributions.end(),
[](const CooContribution& left, const CooContribution& right) {
return std::tie(left.row, left.column, left.element_order,
left.local_order) < std::tie(right.row, right.column,
right.element_order,
right.local_order);
});
std::vector<double> values(expected_pattern.columnIndices.size(), 0.0);
for (const auto& contribution : contributions) {
const std::size_t begin = expected_pattern.rowOffsets[contribution.row];
const std::size_t end = expected_pattern.rowOffsets[contribution.row + 1U];
const auto first = expected_pattern.columnIndices.begin() + begin;
const auto last = expected_pattern.columnIndices.begin() + end;
const auto found = std::lower_bound(first, last, contribution.column);
if (found == last || *found != contribution.column) {
return Result<SparseMatrix>::Failure(SparseFailure(
"sparse-pattern-mismatch",
std::to_string(contribution.row) + ":" +
std::to_string(contribution.column),
"COO contribution is absent from the expected sparse pattern."));
}
for (const auto& contribution : contributions) {
if (contribution.row >= rows || contribution.column >= columns) {
return Result<SparseMatrix>::failure(sparseFailure(
"invalid-sparse-index",
std::to_string(contribution.row) + ":" +
std::to_string(contribution.column),
"COO contribution index is outside the matrix dimensions."));
}
if (!std::isfinite(contribution.value)) {
return Result<SparseMatrix>::failure(sparseFailure(
"nonfinite-sparse-value",
std::to_string(contribution.elementOrder) + ":" +
std::to_string(contribution.localOrder),
"COO contribution values must be finite."));
}
const std::size_t position = static_cast<std::size_t>(
std::distance(expected_pattern.columnIndices.begin(), found));
values[position] += contribution.value;
if (!std::isfinite(values[position])) {
return Result<SparseMatrix>::Failure(SparseFailure(
"nonfinite-sparse-value",
std::to_string(contribution.row) + ":" +
std::to_string(contribution.column),
"Ordered COO duplicate summation produced a nonfinite value."));
}
}
// The complete tuple fixes duplicate summation order independently of
// worker completion order. stable_sort also preserves exact tuple ties.
std::stable_sort(
contributions.begin(),
contributions.end(),
[](const CooContribution& left, const CooContribution& right) {
return std::tie(
left.row,
left.column,
left.elementOrder,
left.localOrder) <
std::tie(
right.row,
right.column,
right.elementOrder,
right.localOrder);
});
SparseMatrix matrix{rows, columns, expected_pattern.rowOffsets,
expected_pattern.columnIndices, std::move(values)};
const Status status = matrix.Validate();
if (!status.IsOk()) {
return Result<SparseMatrix>::Failure(status);
}
return Result<SparseMatrix>::Success(std::move(matrix));
}
std::vector<double> values(expectedPattern.columnIndices.size(), 0.0);
for (const auto& contribution : contributions) {
const std::size_t begin = expectedPattern.rowOffsets[contribution.row];
const std::size_t end = expectedPattern.rowOffsets[contribution.row + 1U];
const auto first = expectedPattern.columnIndices.begin() + begin;
const auto last = expectedPattern.columnIndices.begin() + end;
const auto found = std::lower_bound(first, last, contribution.column);
if (found == last || *found != contribution.column) {
return Result<SparseMatrix>::failure(sparseFailure(
"sparse-pattern-mismatch",
std::to_string(contribution.row) + ":" +
std::to_string(contribution.column),
"COO contribution is absent from the expected sparse pattern."));
}
std::size_t SparseMatrix::Rows() const noexcept { return rows_; }
const std::size_t position = static_cast<std::size_t>(
std::distance(expectedPattern.columnIndices.begin(), found));
values[position] += contribution.value;
if (!std::isfinite(values[position])) {
return Result<SparseMatrix>::failure(sparseFailure(
"nonfinite-sparse-value",
std::to_string(contribution.row) + ":" +
std::to_string(contribution.column),
"Ordered COO duplicate summation produced a nonfinite value."));
}
std::size_t SparseMatrix::Columns() const noexcept { return columns_; }
const std::vector<std::size_t>& SparseMatrix::RowOffsets() const noexcept {
return row_offsets_;
}
const std::vector<std::size_t>& SparseMatrix::ColumnIndices() const noexcept {
return column_indices_;
}
const std::vector<double>& SparseMatrix::Values() const noexcept {
return values_;
}
Vector SparseMatrix::Multiply(const Vector& rhs) const {
if (columns_ != rhs.Size()) {
throw std::invalid_argument{
"Sparse matrix-vector multiplication has incompatible dimensions."};
}
Vector result{rows_};
for (std::size_t row = 0U; row < rows_; ++row) {
double value = 0.0;
for (std::size_t position = row_offsets_[row];
position < row_offsets_[row + 1U]; ++position) {
value += values_[position] * rhs[column_indices_[position]];
}
SparseMatrix matrix{
rows,
columns,
expectedPattern.rowOffsets,
expectedPattern.columnIndices,
std::move(values)};
const Status status = matrix.validate();
if (!status.isOk()) {
return Result<SparseMatrix>::failure(status);
}
return Result<SparseMatrix>::success(std::move(matrix));
result[row] = value;
}
return result;
}
std::size_t SparseMatrix::rows() const noexcept {
return rows_;
Status SparseMatrix::Validate() const {
return ValidateCsr(rows_, columns_, row_offsets_, column_indices_, &values_);
}
std::size_t SparseMatrix::columns() const noexcept {
return columns_;
}
const std::vector<std::size_t>& SparseMatrix::rowOffsets() const noexcept {
return rowOffsets_;
}
const std::vector<std::size_t>& SparseMatrix::columnIndices() const noexcept {
return columnIndices_;
}
const std::vector<double>& SparseMatrix::values() const noexcept {
return values_;
}
Vector SparseMatrix::multiply(const Vector& rhs) const {
if (columns_ != rhs.size()) {
throw std::invalid_argument{
"Sparse matrix-vector multiplication has incompatible dimensions."};
}
Vector result{rows_};
for (std::size_t row = 0U; row < rows_; ++row) {
double value = 0.0;
for (std::size_t position = rowOffsets_[row];
position < rowOffsets_[row + 1U];
++position) {
value += values_[position] * rhs[columnIndices_[position]];
}
result[row] = value;
}
return result;
}
Status SparseMatrix::validate() const {
return validateCsr(
rows_, columns_, rowOffsets_, columnIndices_, &values_);
}
SparseMatrix::SparseMatrix(
const std::size_t rows,
const std::size_t columns,
std::vector<std::size_t> rowOffsets,
std::vector<std::size_t> columnIndices,
std::vector<double> values)
SparseMatrix::SparseMatrix(const std::size_t rows, const std::size_t columns,
std::vector<std::size_t> row_offsets,
std::vector<std::size_t> column_indices,
std::vector<double> values)
: rows_{rows},
columns_{columns},
rowOffsets_{std::move(rowOffsets)},
columnIndices_{std::move(columnIndices)},
row_offsets_{std::move(row_offsets)},
column_indices_{std::move(column_indices)},
values_{std::move(values)} {}
} // namespace fesa
} // namespace fesa
+66 -69
View File
@@ -1,4 +1,4 @@
#include "fesa/math/vector.hpp"
#include "fesa/math/vector.h"
#include <mkl.h>
@@ -9,111 +9,108 @@
namespace fesa {
namespace {
MKL_INT toMklSize(const std::size_t size) {
if (size > static_cast<std::size_t>((std::numeric_limits<MKL_INT>::max)())) {
throw std::length_error{"Dense vector size exceeds the MKL integer range."};
}
return static_cast<MKL_INT>(size);
/// @brief Converts a dense vector size to the private MKL integer contract.
MKL_INT ToMklSize(const std::size_t size) {
if (size > static_cast<std::size_t>((std::numeric_limits<MKL_INT>::max)())) {
throw std::length_error{"Dense vector size exceeds the MKL integer range."};
}
return static_cast<MKL_INT>(size);
}
void copyValues(const std::vector<double>& source, std::vector<double>& destination) {
if (source.empty()) {
return;
}
/// @brief Copies owned values without exposing the dense backend publicly.
void CopyValues(const std::vector<double>& source,
std::vector<double>& destination) {
if (source.empty()) {
return;
}
// Keep the backend operation in this translation unit so public ownership
// remains independent of MKL headers and integer types.
cblas_dcopy(toMklSize(source.size()), source.data(), 1, destination.data(), 1);
// Keep the backend operation in this translation unit so public ownership
// remains independent of MKL headers and integer types.
cblas_dcopy(ToMklSize(source.size()), source.data(), 1, destination.data(),
1);
}
} // namespace
} // namespace
Vector::Vector(const std::size_t size, const double value)
: values_(size, value) {}
Vector::Vector(const Vector& other)
: values_(other.size()) {
copyValues(other.values_, values_);
Vector::Vector(const Vector& other) : values_(other.Size()) {
CopyValues(other.values_, values_);
}
Vector::Vector(Vector&& other) noexcept
: values_(std::move(other.values_)) {
other.values_.clear();
Vector::Vector(Vector&& other) noexcept : values_(std::move(other.values_)) {
other.values_.clear();
}
Vector& Vector::operator=(const Vector& other) {
if (this != &other) {
std::vector<double> copied(other.size());
copyValues(other.values_, copied);
values_.swap(copied);
}
return *this;
if (this != &other) {
std::vector<double> copied(other.Size());
CopyValues(other.values_, copied);
values_.swap(copied);
}
return *this;
}
Vector& Vector::operator=(Vector&& other) noexcept {
if (this != &other) {
values_ = std::move(other.values_);
other.values_.clear();
}
return *this;
if (this != &other) {
values_ = std::move(other.values_);
other.values_.clear();
}
return *this;
}
std::size_t Vector::size() const noexcept {
return values_.size();
}
std::size_t Vector::Size() const noexcept { return values_.size(); }
double* Vector::data() noexcept {
return values_.data();
}
double* Vector::Data() noexcept { return values_.data(); }
const double* Vector::data() const noexcept {
return values_.data();
}
const double* Vector::Data() const noexcept { return values_.data(); }
double& Vector::operator[](const std::size_t index) {
return values_.at(index);
return values_.at(index);
}
const double& Vector::operator[](const std::size_t index) const {
return values_.at(index);
return values_.at(index);
}
double Vector::dot(const Vector& rhs) const {
if (size() != rhs.size()) {
throw std::invalid_argument{"Vector dot product requires equal dimensions."};
}
if (values_.empty()) {
return 0.0;
}
double Vector::Dot(const Vector& rhs) const {
if (Size() != rhs.Size()) {
throw std::invalid_argument{
"Vector dot product requires equal dimensions."};
}
if (values_.empty()) {
return 0.0;
}
return cblas_ddot(toMklSize(size()), data(), 1, rhs.data(), 1);
return cblas_ddot(ToMklSize(Size()), Data(), 1, rhs.Data(), 1);
}
double Vector::norm() const {
if (values_.empty()) {
return 0.0;
}
double Vector::Norm() const {
if (values_.empty()) {
return 0.0;
}
return cblas_dnrm2(toMklSize(size()), data(), 1);
return cblas_dnrm2(ToMklSize(Size()), Data(), 1);
}
void Vector::scale(const double alpha) {
if (values_.empty()) {
return;
}
void Vector::Scale(const double alpha) {
if (values_.empty()) {
return;
}
cblas_dscal(toMklSize(size()), alpha, data(), 1);
cblas_dscal(ToMklSize(Size()), alpha, Data(), 1);
}
void Vector::axpy(const double alpha, const Vector& x) {
if (size() != x.size()) {
throw std::invalid_argument{"Vector axpy requires equal dimensions."};
}
if (values_.empty()) {
return;
}
void Vector::Axpy(const double alpha, const Vector& x) {
if (Size() != x.Size()) {
throw std::invalid_argument{"Vector axpy requires equal dimensions."};
}
if (values_.empty()) {
return;
}
cblas_daxpy(toMklSize(size()), alpha, x.data(), 1, data(), 1);
cblas_daxpy(ToMklSize(Size()), alpha, x.Data(), 1, Data(), 1);
}
} // namespace fesa
} // namespace fesa
+1 -1
View File
@@ -5,7 +5,7 @@
namespace fesa {
Result<Domain> Domain::create(ModelDefinition definition) {
return Result<Domain>::success(Domain{std::move(definition)});
return Result<Domain>::Success(Domain{std::move(definition)});
}
const std::vector<Node>& Domain::nodes() const noexcept {
+26 -26
View File
@@ -102,9 +102,9 @@ Result<ShellGeometry> geometryFailure(
std::string keyword,
std::string identity,
std::string message) {
return Result<ShellGeometry>::failure(Status::failure(
FailureCategory::model,
{{Severity::error,
return Result<ShellGeometry>::Failure(Status::Failure(
FailureCategory::kModel,
{{Severity::kError,
std::move(code),
location,
std::move(keyword),
@@ -153,14 +153,14 @@ bool sourceIdentityLess(
const Mitc4ShellDefinition& right,
std::size_t rightIndex) {
return std::tie(
left.sourceId.instanceName,
left.sourceId.sourceLabel,
left.sourceId.sourceLabelText,
left.sourceId.instance_name,
left.sourceId.source_label,
left.sourceId.source_label_text,
leftIndex) <
std::tie(
right.sourceId.instanceName,
right.sourceId.sourceLabel,
right.sourceId.sourceLabelText,
right.sourceId.instance_name,
right.sourceId.source_label,
right.sourceId.source_label_text,
rightIndex);
}
@@ -218,7 +218,7 @@ Result<ShellGeometry> preprocessShellGeometry(
if (element.nodeIndices[localNode] >= nodes.size()) {
return geometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.sourceId.sourceLabelText,
element.sourceId.source_label_text,
"Shell geometry references an unavailable internal node.");
}
current.coordinates[localNode] =
@@ -226,7 +226,7 @@ Result<ShellGeometry> preprocessShellGeometry(
if (!isFinite(current.coordinates[localNode])) {
return geometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.sourceId.sourceLabelText,
element.sourceId.source_label_text,
"Shell geometry contains a nonfinite source coordinate.");
}
}
@@ -237,7 +237,7 @@ Result<ShellGeometry> preprocessShellGeometry(
current.coordinates[first], current.coordinates[second])) {
return geometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.sourceId.sourceLabelText,
element.sourceId.source_label_text,
"Shell geometry contains duplicate nodes.");
}
}
@@ -255,7 +255,7 @@ Result<ShellGeometry> preprocessShellGeometry(
!(centerMeasure > 0.0)) {
return geometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.sourceId.sourceLabelText,
element.sourceId.source_label_text,
"Shell center has no finite nonzero normal candidate.");
}
current.normal = scale(1.0 / centerMeasure, centerCross);
@@ -268,7 +268,7 @@ Result<ShellGeometry> preprocessShellGeometry(
current.coordinates[3], current.coordinates[0], current.normal)) {
return geometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.sourceId.sourceLabelText,
element.sourceId.source_label_text,
"Shell boundary is self-intersecting in the center-normal projection.");
}
@@ -286,7 +286,7 @@ Result<ShellGeometry> preprocessShellGeometry(
!(measure > 0.0) || !(dot(areaVector, current.normal) > 0.0)) {
return geometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.sourceId.sourceLabelText,
element.sourceId.source_label_text,
"Shell surface is zero-area or locally reversed at a required point.");
}
areaWeight += measure;
@@ -294,7 +294,7 @@ Result<ShellGeometry> preprocessShellGeometry(
if (!std::isfinite(areaWeight) || !(areaWeight > 0.0)) {
return geometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.sourceId.sourceLabelText,
element.sourceId.source_label_text,
"Shell surface-area weight is nonfinite or zero.");
}
current.areaWeight = areaWeight;
@@ -335,7 +335,7 @@ Result<ShellGeometry> preprocessShellGeometry(
if (!std::isfinite(pairDot) || !(pairDot > 0.0)) {
return geometryFailure(
"opposed-incident-normal", nodes[nodeIndex].location,
"NODE", nodes[nodeIndex].sourceId.sourceLabelText,
"NODE", nodes[nodeIndex].sourceId.source_label_text,
"Incident shell normal candidates do not share a positive orientation hemisphere.");
}
}
@@ -358,7 +358,7 @@ Result<ShellGeometry> preprocessShellGeometry(
!(directorNorm > 0.0)) {
return geometryFailure(
"invalid-shell-director", nodes[nodeIndex].location, "NODE",
nodes[nodeIndex].sourceId.sourceLabelText,
nodes[nodeIndex].sourceId.source_label_text,
"Area-weighted shell director is nonfinite or zero.");
}
const Vector3 director = scale(1.0 / directorNorm, directorSum);
@@ -384,7 +384,7 @@ Result<ShellGeometry> preprocessShellGeometry(
!(tangentNorm > 0.0)) {
return geometryFailure(
"invalid-shell-director", nodes[nodeIndex].location, "NODE",
nodes[nodeIndex].sourceId.sourceLabelText,
nodes[nodeIndex].sourceId.source_label_text,
"Least-aligned-axis tangent frame construction failed.");
}
const Vector3 tangentA = scale(1.0 / tangentNorm, tangentCandidate);
@@ -392,7 +392,7 @@ Result<ShellGeometry> preprocessShellGeometry(
if (!isFinite(tangentB) || !(norm(tangentB) > 0.0)) {
return geometryFailure(
"invalid-shell-director", nodes[nodeIndex].location, "NODE",
nodes[nodeIndex].sourceId.sourceLabelText,
nodes[nodeIndex].sourceId.source_label_text,
"Right-handed shell tangent frame construction failed.");
}
geometry.nodalFrames.push_back({
@@ -412,7 +412,7 @@ Result<ShellGeometry> preprocessShellGeometry(
if (element.sectionIndex >= sections.size()) {
return geometryFailure(
"invalid-shell-jacobian", element.location, "ELEMENT",
element.sourceId.sourceLabelText,
element.sourceId.source_label_text,
"Shell geometry cannot resolve its thickness for Jacobian validation.");
}
const double thickness = sections[element.sectionIndex].thickness;
@@ -422,7 +422,7 @@ Result<ShellGeometry> preprocessShellGeometry(
if (frame == nullptr) {
return geometryFailure(
"invalid-shell-director", element.location, "ELEMENT",
element.sourceId.sourceLabelText,
element.sourceId.source_label_text,
"Shell element is missing a nodal director.");
}
directors[localNode] = frame->director;
@@ -445,7 +445,7 @@ Result<ShellGeometry> preprocessShellGeometry(
!(dot(areaVector, work[elementIndex].normal) > 0.0)) {
return geometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.sourceId.sourceLabelText,
element.sourceId.source_label_text,
"Shell surface basis is nonfinite, zero, or reversed at a required point.");
}
@@ -466,7 +466,7 @@ Result<ShellGeometry> preprocessShellGeometry(
!(jacobian > 0.0)) {
return geometryFailure(
"invalid-shell-jacobian", element.location, "ELEMENT",
element.sourceId.sourceLabelText,
element.sourceId.source_label_text,
"Shell Jacobian is nonfinite or nonpositive at a required point.");
}
const Vector3 reciprocalXi =
@@ -479,13 +479,13 @@ Result<ShellGeometry> preprocessShellGeometry(
!isFinite(reciprocalZeta)) {
return geometryFailure(
"invalid-shell-jacobian", element.location, "ELEMENT",
element.sourceId.sourceLabelText,
element.sourceId.source_label_text,
"Shell reciprocal basis is nonfinite at a required point.");
}
}
}
return Result<ShellGeometry>::success(std::move(geometry));
return Result<ShellGeometry>::Success(std::move(geometry));
}
} // namespace fesa
+68 -68
View File
@@ -34,9 +34,9 @@ Status recoveryFailure(const std::string& code,
const SourceLocation& location,
const std::string& identity,
const std::string& message) {
return Status::failure(
FailureCategory::model,
{{Severity::error,
return Status::Failure(
FailureCategory::kModel,
{{Severity::kError,
code,
location,
"RESULT_RECOVERY",
@@ -49,15 +49,15 @@ Result<T> recoveryResultFailure(const std::string& code,
const SourceLocation& location,
const std::string& identity,
const std::string& message) {
return Result<T>::failure(
return Result<T>::Failure(
recoveryFailure(code, location, identity, message));
}
bool sameSourceIdentity(const SourceEntityId& left,
const SourceEntityId& right) {
return left.instanceName == right.instanceName &&
left.sourceLabel == right.sourceLabel &&
left.sourceLabelText == right.sourceLabelText;
return left.instance_name == right.instance_name &&
left.source_label == right.source_label &&
left.source_label_text == right.source_label_text;
}
template<std::size_t Size>
@@ -68,7 +68,7 @@ bool finite(const std::array<double, Size>& values) {
}
bool finite(const Vector& values) {
for (std::size_t index = 0U; index < values.size(); ++index) {
for (std::size_t index = 0U; index < values.Size(); ++index) {
if (!std::isfinite(values[index])) {
return false;
}
@@ -99,12 +99,12 @@ std::array<double, 2> freeEquationInternalTermNorms(
for (const std::size_t row : dofs.freeDofs()) {
double freeTerm = 0.0;
double constrainedTerm = 0.0;
for (std::size_t position = stiffness.rowOffsets()[row];
position < stiffness.rowOffsets()[row + 1U];
for (std::size_t position = stiffness.RowOffsets()[row];
position < stiffness.RowOffsets()[row + 1U];
++position) {
const std::size_t column = stiffness.columnIndices()[position];
const std::size_t column = stiffness.ColumnIndices()[position];
const double contribution =
stiffness.values()[position] * displacement[column];
stiffness.Values()[position] * displacement[column];
if (freeColumn[column] != 0U) {
freeTerm += contribution;
} else {
@@ -141,21 +141,21 @@ Status validateRecoveryInputs(const AnalysisModel& model,
}
const std::size_t fullCount = domain.nodes().size() * kDofsPerNode;
if (dofs.fullDofCount() != fullCount ||
fullStiffness.rows() != fullCount ||
fullStiffness.columns() != fullCount ||
state.displacement().size() != fullCount ||
state.externalForce().size() != fullCount ||
state.internalForce().size() != fullCount ||
state.residual().size() != fullCount ||
state.reaction().size() != fullCount) {
fullStiffness.Rows() != fullCount ||
fullStiffness.Columns() != fullCount ||
state.displacement().Size() != fullCount ||
state.externalForce().Size() != fullCount ||
state.internalForce().Size() != fullCount ||
state.residual().Size() != fullCount ||
state.reaction().Size() != fullCount) {
return recoveryFailure(
"invalid-recovery-dimensions",
{domain.sourcePath(), 0U},
domain.sourceContentIdentity(),
"Model, DOF, stiffness, and AnalysisState full-space dimensions must agree.");
}
const Status matrixStatus = fullStiffness.validate();
if (!matrixStatus.isOk()) {
const Status matrixStatus = fullStiffness.Validate();
if (!matrixStatus.IsOk()) {
return matrixStatus;
}
if (!finite(state.displacement()) || !finite(state.externalForce())) {
@@ -170,7 +170,7 @@ Status validateRecoveryInputs(const AnalysisModel& model,
const auto& constrainedDofs = dofs.constrainedDofs();
if (freeDofs.size() != dofs.freeDofCount() ||
constrainedDofs.size() != dofs.constrainedDofCount() ||
dofs.prescribedValues().size() != constrainedDofs.size() ||
dofs.prescribedValues().Size() != constrainedDofs.size() ||
freeDofs.size() + constrainedDofs.size() != fullCount ||
!strictlyIncreasing(freeDofs) ||
!strictlyIncreasing(constrainedDofs)) {
@@ -253,7 +253,7 @@ Status validateRecoveryInputs(const AnalysisModel& model,
return recoveryFailure(
"invalid-recovery-entity",
definition.location,
definition.sourceId.sourceLabelText,
definition.sourceId.source_label_text,
"Active beam references must resolve before recovery.");
}
try {
@@ -271,7 +271,7 @@ Status validateRecoveryInputs(const AnalysisModel& model,
return recoveryFailure(
"invalid-recovery-order",
definition.location,
definition.sourceId.sourceLabelText,
definition.sourceId.source_label_text,
"Element scatter must preserve endpoint/component full-DOF order.");
}
}
@@ -280,7 +280,7 @@ Status validateRecoveryInputs(const AnalysisModel& model,
return recoveryFailure(
"invalid-recovery-entity",
definition.location,
definition.sourceId.sourceLabelText,
definition.sourceId.source_label_text,
"Every active element requires one twelve-DOF scatter map.");
}
}
@@ -310,7 +310,7 @@ Status validateRecoveryInputs(const AnalysisModel& model,
return recoveryFailure(
"invalid-recovery-entity",
definition.location,
definition.sourceId.sourceLabelText,
definition.sourceId.source_label_text,
"Active shell material and section references must resolve before recovery.");
}
try {
@@ -324,7 +324,7 @@ Status validateRecoveryInputs(const AnalysisModel& model,
return recoveryFailure(
"invalid-recovery-entity",
definition.location,
definition.sourceId.sourceLabelText,
definition.sourceId.source_label_text,
"Active shell node references must resolve before recovery.");
}
for (std::size_t component = 0U;
@@ -339,7 +339,7 @@ Status validateRecoveryInputs(const AnalysisModel& model,
return recoveryFailure(
"invalid-recovery-order",
definition.location,
definition.sourceId.sourceLabelText,
definition.sourceId.source_label_text,
"Shell scatter must preserve node/component full-DOF order.");
}
}
@@ -348,11 +348,11 @@ Status validateRecoveryInputs(const AnalysisModel& model,
return recoveryFailure(
"invalid-recovery-entity",
definition.location,
definition.sourceId.sourceLabelText,
definition.sourceId.source_label_text,
"Every active shell requires one twenty-four-DOF scatter map.");
}
}
return Status::ok();
return Status::Ok();
}
char asciiLower(const char value) {
@@ -390,7 +390,7 @@ Result<std::vector<EntityIndex>> resolveLoadTarget(const Domain& domain,
std::int64_t sourceLabel = 0;
if (tryPositiveInteger(load.target, sourceLabel)) {
for (std::size_t node = 0U; node < domain.nodes().size(); ++node) {
if (domain.nodes()[node].sourceId.sourceLabel == sourceLabel) {
if (domain.nodes()[node].sourceId.source_label == sourceLabel) {
nodes.push_back(static_cast<EntityIndex>(node));
}
}
@@ -415,11 +415,11 @@ Result<std::vector<EntityIndex>> resolveLoadTarget(const Domain& domain,
}
seen[node] = 1U;
}
return Result<std::vector<EntityIndex>>::success(
return Result<std::vector<EntityIndex>>::Success(
sets.front()->nodeIndices);
}
if (!nodes.empty()) {
return Result<std::vector<EntityIndex>>::success(std::move(nodes));
return Result<std::vector<EntityIndex>>::Success(std::move(nodes));
}
return recoveryResultFailure<std::vector<EntityIndex>>(
"invalid-node-station-entity",
@@ -542,7 +542,7 @@ Status populateShellGlobalEvidence(
return recoveryFailure(
"nonfinite-recovery-value",
domain.nodes()[node].location,
domain.nodes()[node].sourceId.sourceLabelText,
domain.nodes()[node].sourceId.source_label_text,
"Global force and moment evidence must remain finite in source-node order.");
}
}
@@ -581,7 +581,7 @@ Status populateShellGlobalEvidence(
"global-equilibrium",
"Normalized global force or moment balance exceeds 1e-10.");
}
return Status::ok();
return Status::Ok();
}
std::optional<AxisSet> localAxes(const Domain& domain,
@@ -646,11 +646,11 @@ Status ResultRecovery::recover(const AnalysisModel& model,
AnalysisState& state) {
const Status inputStatus =
validateRecoveryInputs(model, dofs, fullStiffness, state);
if (!inputStatus.isOk()) {
if (!inputStatus.IsOk()) {
return inputStatus;
}
Vector internalForce = fullStiffness.multiply(state.displacement());
Vector internalForce = fullStiffness.Multiply(state.displacement());
if (!finite(internalForce)) {
return recoveryFailure(
"nonfinite-recovery-value",
@@ -659,7 +659,7 @@ Status ResultRecovery::recover(const AnalysisModel& model,
"Full stiffness multiplication must produce finite internal force.");
}
Vector residual{dofs.fullDofCount()};
for (std::size_t fullDof = 0U; fullDof < residual.size(); ++fullDof) {
for (std::size_t fullDof = 0U; fullDof < residual.Size(); ++fullDof) {
residual[fullDof] =
internalForce[fullDof] - state.externalForce()[fullDof];
if (!std::isfinite(residual[fullDof])) {
@@ -722,8 +722,8 @@ Status ResultRecovery::recover(const AnalysisModel& model,
domain.nodes()[definition.nodeIndices[1U]],
domain.sections()[definition.sectionIndex],
domain.materials()[definition.materialIndex]);
if (!beam.hasValue()) {
return beam.status();
if (!beam.HasValue()) {
return beam.GetStatus();
}
Vector elementDisplacement{kElementDofCount};
@@ -735,14 +735,14 @@ Status ResultRecovery::recover(const AnalysisModel& model,
state.displacement()[scatter[localDof]];
}
const BeamRecovery recovered =
beam.value().recover(elementDisplacement);
beam.Value().recover(elementDisplacement);
for (std::size_t endpoint = 0U; endpoint < 2U; ++endpoint) {
if (!finite(recovered.equilibriumEndActions[endpoint]) ||
!finite(recovered.endpointSectionResultants[endpoint])) {
return recoveryFailure(
"nonfinite-recovery-value",
definition.location,
definition.sourceId.sourceLabelText,
definition.sourceId.source_label_text,
"Endpoint recovery values must be finite.");
}
endpointRows.push_back({
@@ -758,7 +758,7 @@ Status ResultRecovery::recover(const AnalysisModel& model,
return recoveryFailure(
"nonfinite-recovery-value",
definition.location,
definition.sourceId.sourceLabelText,
definition.sourceId.source_label_text,
"Gauss recovery values must be finite.");
}
gaussRows.push_back({
@@ -774,7 +774,7 @@ Status ResultRecovery::recover(const AnalysisModel& model,
return recoveryFailure(
"nonfinite-recovery-value",
definition.location,
definition.sourceId.sourceLabelText,
definition.sourceId.source_label_text,
"Stress recovery identity and values must be finite and ordered.");
}
stressRows.push_back({
@@ -844,7 +844,7 @@ Status ResultRecovery::recover(const AnalysisModel& model,
return recoveryFailure(
"invalid-recovery-entity",
definition.location,
definition.sourceId.sourceLabelText,
definition.sourceId.source_label_text,
"Shell recovery requires one initial director per element node.");
}
nodes[nodePosition] = &domain.nodes()[node];
@@ -856,8 +856,8 @@ Status ResultRecovery::recover(const AnalysisModel& model,
directors,
domain.shellSections()[definition.sectionIndex],
domain.materials()[definition.materialIndex]);
if (!shell.hasValue()) {
return shell.status();
if (!shell.HasValue()) {
return shell.GetStatus();
}
Vector elementDisplacement{kShellElementDofCount};
const auto& scatter = dofs.shellElementScatter(elementIndex);
@@ -867,28 +867,28 @@ Status ResultRecovery::recover(const AnalysisModel& model,
elementDisplacement[localDof] =
state.displacement()[scatter[localDof]];
}
auto recovered = shell.value().recoverPhysical(
auto recovered = shell.Value().recoverPhysical(
elementDisplacement);
if (!recovered.hasValue()) {
return recovered.status();
if (!recovered.HasValue()) {
return recovered.GetStatus();
}
const double accumulatedEnergy =
shellCandidate.physicalStrainEnergy +
recovered.value().strainEnergy;
recovered.Value().strainEnergy;
if (!std::isfinite(accumulatedEnergy)) {
return recoveryFailure(
"nonfinite-recovery-value",
definition.location,
definition.sourceId.sourceLabelText,
definition.sourceId.source_label_text,
"Source-order physical shell energy reduction must remain finite.");
}
shellCandidate.physicalStrainEnergy = accumulatedEnergy;
expectedShellElements.push_back(elementIndex);
for (std::size_t point = 0U;
point < recovered.value().points.size();
point < recovered.Value().points.size();
++point) {
const auto& physicalPoint = recovered.value().points[point];
const auto& physicalPoint = recovered.Value().points[point];
ShellResultRow row{};
row.element = elementIndex;
row.location = locations[point];
@@ -917,7 +917,7 @@ Status ResultRecovery::recover(const AnalysisModel& model,
residual,
normalizedResidual,
shellCandidate);
if (!evidenceStatus.isOk()) {
if (!evidenceStatus.IsOk()) {
return evidenceStatus;
}
}
@@ -934,11 +934,11 @@ Status ResultRecovery::recover(const AnalysisModel& model,
candidateState.stressResults() = std::move(stressRows);
const Status shellCommitStatus = candidateState.commitShellResults(
expectedShellElements, std::move(shellCandidate));
if (!shellCommitStatus.isOk()) {
if (!shellCommitStatus.IsOk()) {
return shellCommitStatus;
}
state = std::move(candidateState);
return Status::ok();
return Status::Ok();
}
Result<std::vector<NodeStationResultRow>>
@@ -990,14 +990,14 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
"invalid-node-station-entity",
definition.location,
definition.sourceId.sourceLabelText,
definition.sourceId.source_label_text,
"Endpoint rows must preserve active element, endpoint, and source-node order.");
}
if (!finite(row.sectionResultant)) {
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
"nonfinite-node-station-value",
definition.location,
definition.sourceId.sourceLabelText,
definition.sourceId.source_label_text,
"Node-station section resultants must be finite.");
}
rowsByNode[nodeIndex].push_back(&row);
@@ -1030,12 +1030,12 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
"Station eligibility requires finite concentrated loads.");
}
auto targets = resolveLoadTarget(domain, load);
if (!targets.hasValue()) {
return Result<std::vector<NodeStationResultRow>>::failure(
targets.status());
if (!targets.HasValue()) {
return Result<std::vector<NodeStationResultRow>>::Failure(
targets.GetStatus());
}
if (load.magnitude != 0.0) {
for (const EntityIndex node : targets.value()) {
for (const EntityIndex node : targets.Value()) {
loadedNodes[node] = 1U;
}
}
@@ -1061,7 +1061,7 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
"ineligible-node-station",
domain.nodes()[nodeIndex].location,
domain.nodes()[nodeIndex].sourceId.sourceLabelText,
domain.nodes()[nodeIndex].sourceId.source_label_text,
"Interior station collapse requires exactly two unloaded endpoints.");
}
@@ -1080,7 +1080,7 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
"ineligible-node-station",
domain.nodes()[nodeIndex].location,
domain.nodes()[nodeIndex].sourceId.sourceLabelText,
domain.nodes()[nodeIndex].sourceId.source_label_text,
"Interior station endpoints require one consistent section and local-axis chain.");
}
@@ -1097,14 +1097,14 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
"nonfinite-node-station-value",
domain.nodes()[nodeIndex].location,
domain.nodes()[nodeIndex].sourceId.sourceLabelText,
domain.nodes()[nodeIndex].sourceId.source_label_text,
"Endpoint comparison must produce a finite difference.");
}
if (difference > componentTolerances[component]) {
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
"node-station-tolerance-failure",
domain.nodes()[nodeIndex].location,
domain.nodes()[nodeIndex].sourceId.sourceLabelText,
domain.nodes()[nodeIndex].sourceId.source_label_text,
"Interior endpoint resultants disagree beyond component tolerance.");
}
}
@@ -1118,7 +1118,7 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
representative->element,
representative->sectionResultant});
}
return Result<std::vector<NodeStationResultRow>>::success(
return Result<std::vector<NodeStationResultRow>>::Success(
std::move(stations));
}
+290 -325
View File
@@ -1,7 +1,4 @@
#include "fesa/solvers/linear/mkl_pardiso_solver.hpp"
#include "fesa/math/sparse_matrix.hpp"
#include "fesa/math/vector.hpp"
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
#include <mkl.h>
@@ -15,369 +12,337 @@
#include <utility>
#include <vector>
#include "fesa/math/sparse_matrix.h"
#include "fesa/math/vector.h"
namespace fesa {
namespace {
Status solverFailure(
const std::string& code,
const std::string& identity,
const std::string& message) {
return Status::failure(
FailureCategory::solver,
{{Severity::error,
code,
{{}, 0U},
"PARDISO",
identity,
message}});
/// @brief Builds a structured linear-solver failure.
Status SolverFailure(const std::string& code, const std::string& identity,
const std::string& message) {
return Status::Failure(
FailureCategory::kSolver,
{{Severity::kError, code, {{}, 0U}, "PARDISO", identity, message}});
}
Status pardisoFailure(
const MKL_INT phase,
const MKL_INT error) {
std::string code;
std::string reason;
switch (error) {
/// @brief Translates a PARDISO phase error into the stable solver taxonomy.
Status PardisoFailure(const MKL_INT phase, const MKL_INT error) {
std::string code;
std::string reason;
switch (error) {
case -4:
code = "pardiso-zero-or-negative-pivot";
reason = "zero or negative pivot";
break;
code = "pardiso-zero-or-negative-pivot";
reason = "zero or negative pivot";
break;
case -7:
code = "pardiso-singular-diagonal";
reason = "singular diagonal";
break;
code = "pardiso-singular-diagonal";
reason = "singular diagonal";
break;
case -8:
code = "pardiso-integer-overflow";
reason = "32-bit backend integer overflow";
break;
code = "pardiso-integer-overflow";
reason = "32-bit backend integer overflow";
break;
case 21:
case 22:
case 23:
case 24:
code = "pardiso-invalid-csr";
reason = "matrix checker rejected the CSR indices";
break;
code = "pardiso-invalid-csr";
reason = "matrix checker rejected the CSR indices";
break;
default:
code = phase == 11 ? "pardiso-analysis-failed" :
phase == 22 ? "pardiso-factorization-failed" :
phase == 33 ? "pardiso-solve-failed" :
"pardiso-release-failed";
reason = "backend error";
break;
}
code = phase == 11 ? "pardiso-analysis-failed"
: phase == 22 ? "pardiso-factorization-failed"
: phase == 33 ? "pardiso-solve-failed"
: "pardiso-release-failed";
reason = "backend error";
break;
}
const std::string phaseText = std::to_string(phase);
const std::string errorText = std::to_string(error);
return solverFailure(
code,
"phase=" + phaseText + ",error=" + errorText,
"oneMKL PARDISO phase " + phaseText + " failed with error " +
errorText + " (" + reason + ").");
const std::string phase_text = std::to_string(phase);
const std::string error_text = std::to_string(error);
return SolverFailure(code, "phase=" + phase_text + ",error=" + error_text,
"oneMKL PARDISO phase " + phase_text +
" failed with error " + error_text + " (" + reason +
").");
}
bool convertsToMklInt(const std::size_t value) {
return value <=
static_cast<std::size_t>((std::numeric_limits<MKL_INT>::max)());
/// @brief Reports whether a public size fits the private MKL integer type.
bool ConvertsToMklInt(const std::size_t value) {
return value <=
static_cast<std::size_t>((std::numeric_limits<MKL_INT>::max)());
}
} // namespace
} // namespace
class MklPardisoSolver::Impl {
public:
Impl() = default;
public:
Impl() = default;
~Impl() {
static_cast<void>(release());
~Impl() { static_cast<void>(Release()); }
/// @brief Validates and factorizes one public CSR matrix.
Status Factorize(const SparseMatrix& matrix) {
const MKL_INT release_error = Release();
if (release_error != 0) {
return PardisoFailure(-1, release_error);
}
Status factorize(const SparseMatrix& matrix) {
const MKL_INT releaseError = release();
if (releaseError != 0) {
return pardisoFailure(-1, releaseError);
}
const Status csrStatus = matrix.validate();
if (!csrStatus.isOk()) {
return solverFailure(
"solver-invalid-csr",
"public-csr",
"The public sparse matrix failed CSR validation.");
}
if (matrix.rows() != matrix.columns()) {
return solverFailure(
"solver-matrix-not-square",
"matrix-shape",
"PARDISO factorization requires a square matrix.");
}
if (matrix.rows() == 0U) {
// A fully constrained model has no free equations. Preserve the
// observable factorize/solve lifecycle without creating backend
// state or calling PARDISO with its invalid n=0 input.
factorized_ = true;
return Status::ok();
}
if (!convertsToMklInt(matrix.rows()) ||
!convertsToMklInt(matrix.values().size())) {
return solverFailure(
"solver-dimension-overflow",
"matrix-shape",
"Sparse matrix dimensions exceed the oneMKL integer range.");
}
const Status copyStatus = copyValidatedUpperTriangle(matrix);
if (!copyStatus.isOk()) {
clearOwnedArrays();
return copyStatus;
}
// PARDISO owns internal memory behind pt after phase 11. Initialize
// once per factorization and retain it until refactorization/destruction.
pt_.fill(nullptr);
iparm_.fill(0);
pardisoinit(pt_.data(), &mtype_, iparm_.data());
iparm_[26] = 1; // Validate sorted CSR integer arrays.
iparm_[34] = 1; // Consume the project's native zero-based CSR.
permutation_.assign(static_cast<std::size_t>(equationCount_), 0);
ownsPardisoState_ = true;
MKL_INT phase = 11;
MKL_INT error = 0;
callPardiso(phase, nullptr, nullptr, error);
if (error != 0) {
const Status failure = pardisoFailure(phase, error);
static_cast<void>(release());
return failure;
}
phase = 22;
error = 0;
callPardiso(phase, nullptr, nullptr, error);
if (error != 0) {
const Status failure = pardisoFailure(phase, error);
static_cast<void>(release());
return failure;
}
factorized_ = true;
return Status::ok();
const Status csr_status = matrix.Validate();
if (!csr_status.IsOk()) {
return SolverFailure("solver-invalid-csr", "public-csr",
"The public sparse matrix failed CSR validation.");
}
if (matrix.Rows() != matrix.Columns()) {
return SolverFailure("solver-matrix-not-square", "matrix-shape",
"PARDISO factorization requires a square matrix.");
}
if (matrix.Rows() == 0U) {
// A fully constrained model has no free equations. Preserve the
// observable factorize/solve lifecycle without creating backend
// state or calling PARDISO with its invalid n=0 input.
factorized_ = true;
return Status::Ok();
}
if (!ConvertsToMklInt(matrix.Rows()) ||
!ConvertsToMklInt(matrix.Values().size())) {
return SolverFailure(
"solver-dimension-overflow", "matrix-shape",
"Sparse matrix dimensions exceed the oneMKL integer range.");
}
Status solve(const Vector& rhs, Vector& solution) {
if (!factorized_) {
return solverFailure(
"solver-not-factorized",
"factorization-state",
"Substitution requires a successful retained factorization.");
}
const std::size_t size = static_cast<std::size_t>(equationCount_);
if (rhs.size() != size || solution.size() != size) {
return solverFailure(
"solver-vector-dimension-mismatch",
"rhs-or-solution",
"RHS and solution dimensions must match the factorized matrix.");
}
for (std::size_t index = 0U; index < rhs.size(); ++index) {
if (!std::isfinite(rhs[index])) {
return solverFailure(
"nonfinite-solver-rhs",
std::to_string(index),
"PARDISO RHS values must be finite.");
}
}
if (size == 0U) {
return Status::ok();
}
std::vector<double> rhsCopy(rhs.data(), rhs.data() + rhs.size());
Vector candidate{size};
MKL_INT phase = 33;
MKL_INT error = 0;
callPardiso(phase, rhsCopy.data(), candidate.data(), error);
if (error != 0) {
return pardisoFailure(phase, error);
}
for (std::size_t index = 0U; index < candidate.size(); ++index) {
if (!std::isfinite(candidate[index])) {
return solverFailure(
"nonfinite-solver-solution",
std::to_string(index),
"PARDISO substitution produced a nonfinite solution.");
}
}
solution = std::move(candidate);
return Status::ok();
const Status copy_status = CopyValidatedUpperTriangle(matrix);
if (!copy_status.IsOk()) {
ClearOwnedArrays();
return copy_status;
}
private:
Status copyValidatedUpperTriangle(const SparseMatrix& matrix) {
const auto& publicOffsets = matrix.rowOffsets();
const auto& publicColumns = matrix.columnIndices();
const auto& publicValues = matrix.values();
// PARDISO owns internal memory behind pt after phase 11. Initialize
// once per factorization and retain it until refactorization/destruction.
pt_.fill(nullptr);
iparm_.fill(0);
pardisoinit(pt_.data(), &mtype_, iparm_.data());
iparm_[26] = 1; // Validate sorted CSR integer arrays.
iparm_[34] = 1; // Consume the project's native zero-based CSR.
permutation_.assign(static_cast<std::size_t>(equation_count_), 0);
owns_pardiso_state_ = true;
double matrixScale = 0.0;
for (const double value : publicValues) {
matrixScale = (std::max)(matrixScale, std::abs(value));
}
for (std::size_t row = 0U; row < matrix.rows(); ++row) {
for (std::size_t position = publicOffsets[row];
position < publicOffsets[row + 1U];
++position) {
const std::size_t column = publicColumns[position];
const auto reverseBegin = publicColumns.begin() +
static_cast<std::ptrdiff_t>(publicOffsets[column]);
const auto reverseEnd = publicColumns.begin() +
static_cast<std::ptrdiff_t>(publicOffsets[column + 1U]);
const auto reverse =
std::lower_bound(reverseBegin, reverseEnd, row);
if (reverse == reverseEnd || *reverse != row) {
return solverFailure(
"solver-matrix-not-symmetric",
std::to_string(row) + ":" + std::to_string(column),
"The full public CSR must contain both symmetric entries.");
}
const std::size_t reversePosition = static_cast<std::size_t>(
std::distance(publicColumns.begin(), reverse));
const double left = publicValues[position];
const double right = publicValues[reversePosition];
const double difference = std::abs(left - right);
// The approved symmetry test is normalized by the matrix's
// actual nonzero scale, without an absolute unit-size floor.
const bool isSymmetric = matrixScale == 0.0 ?
difference == 0.0 :
difference <= 1.0e-12 * matrixScale;
if (!isSymmetric) {
return solverFailure(
"solver-matrix-not-symmetric",
std::to_string(row) + ":" + std::to_string(column),
"The full public CSR values violate the approved symmetry tolerance.");
}
}
}
equationCount_ = static_cast<MKL_INT>(matrix.rows());
rowOffsets_.clear();
columnIndices_.clear();
values_.clear();
rowOffsets_.reserve(matrix.rows() + 1U);
rowOffsets_.push_back(0);
for (std::size_t row = 0U; row < matrix.rows(); ++row) {
bool hasDiagonal = false;
for (std::size_t position = publicOffsets[row];
position < publicOffsets[row + 1U];
++position) {
const std::size_t column = publicColumns[position];
if (column < row) {
continue;
}
if (!convertsToMklInt(column) ||
!convertsToMklInt(columnIndices_.size())) {
return solverFailure(
"solver-dimension-overflow",
std::to_string(row) + ":" + std::to_string(column),
"CSR indices exceed the oneMKL integer range.");
}
hasDiagonal = hasDiagonal || column == row;
columnIndices_.push_back(static_cast<MKL_INT>(column));
values_.push_back(publicValues[position]);
}
if (!hasDiagonal) {
return solverFailure(
"solver-missing-diagonal",
std::to_string(row),
"Every PARDISO SPD row must retain its diagonal slot.");
}
if (!convertsToMklInt(columnIndices_.size())) {
return solverFailure(
"solver-dimension-overflow",
std::to_string(row),
"CSR row offsets exceed the oneMKL integer range.");
}
rowOffsets_.push_back(
static_cast<MKL_INT>(columnIndices_.size()));
}
return Status::ok();
MKL_INT phase = 11;
MKL_INT error = 0;
CallPardiso(phase, nullptr, nullptr, error);
if (error != 0) {
const Status failure = PardisoFailure(phase, error);
static_cast<void>(Release());
return failure;
}
void callPardiso(
const MKL_INT phase,
double* rhs,
double* solution,
MKL_INT& error) {
pardiso(
pt_.data(),
&maxFactorizations_,
&matrixNumber_,
&mtype_,
&phase,
&equationCount_,
values_.data(),
rowOffsets_.data(),
columnIndices_.data(),
permutation_.data(),
&rhsCount_,
iparm_.data(),
&messageLevel_,
rhs,
solution,
&error);
phase = 22;
error = 0;
CallPardiso(phase, nullptr, nullptr, error);
if (error != 0) {
const Status failure = PardisoFailure(phase, error);
static_cast<void>(Release());
return failure;
}
MKL_INT release() noexcept {
MKL_INT error = 0;
if (ownsPardisoState_) {
const MKL_INT phase = -1;
double placeholder = 0.0;
callPardiso(phase, &placeholder, &placeholder, error);
factorized_ = true;
return Status::Ok();
}
/// @brief Substitutes one RHS while preserving solution on failure.
Status Solve(const Vector& rhs, Vector& solution) {
if (!factorized_) {
return SolverFailure(
"solver-not-factorized", "factorization-state",
"Substitution requires a successful retained factorization.");
}
const std::size_t size = static_cast<std::size_t>(equation_count_);
if (rhs.Size() != size || solution.Size() != size) {
return SolverFailure(
"solver-vector-dimension-mismatch", "rhs-or-solution",
"RHS and solution dimensions must match the factorized matrix.");
}
for (std::size_t index = 0U; index < rhs.Size(); ++index) {
if (!std::isfinite(rhs[index])) {
return SolverFailure("nonfinite-solver-rhs", std::to_string(index),
"PARDISO RHS values must be finite.");
}
}
if (size == 0U) {
return Status::Ok();
}
std::vector<double> rhs_copy(rhs.Data(), rhs.Data() + rhs.Size());
Vector candidate{size};
MKL_INT phase = 33;
MKL_INT error = 0;
CallPardiso(phase, rhs_copy.data(), candidate.Data(), error);
if (error != 0) {
return PardisoFailure(phase, error);
}
for (std::size_t index = 0U; index < candidate.Size(); ++index) {
if (!std::isfinite(candidate[index])) {
return SolverFailure(
"nonfinite-solver-solution", std::to_string(index),
"PARDISO substitution produced a nonfinite solution.");
}
}
solution = std::move(candidate);
return Status::Ok();
}
private:
/// @brief Validates symmetry and copies the upper triangle for PARDISO.
Status CopyValidatedUpperTriangle(const SparseMatrix& matrix) {
const auto& public_offsets = matrix.RowOffsets();
const auto& public_columns = matrix.ColumnIndices();
const auto& public_values = matrix.Values();
double matrix_scale = 0.0;
for (const double value : public_values) {
matrix_scale = (std::max)(matrix_scale, std::abs(value));
}
for (std::size_t row = 0U; row < matrix.Rows(); ++row) {
for (std::size_t position = public_offsets[row];
position < public_offsets[row + 1U]; ++position) {
const std::size_t column = public_columns[position];
const auto reverse_begin =
public_columns.begin() +
static_cast<std::ptrdiff_t>(public_offsets[column]);
const auto reverse_end =
public_columns.begin() +
static_cast<std::ptrdiff_t>(public_offsets[column + 1U]);
const auto reverse = std::lower_bound(reverse_begin, reverse_end, row);
if (reverse == reverse_end || *reverse != row) {
return SolverFailure(
"solver-matrix-not-symmetric",
std::to_string(row) + ":" + std::to_string(column),
"The full public CSR must contain both symmetric entries.");
}
ownsPardisoState_ = false;
factorized_ = false;
pt_.fill(nullptr);
iparm_.fill(0);
permutation_.clear();
clearOwnedArrays();
return error;
const std::size_t reverse_position = static_cast<std::size_t>(
std::distance(public_columns.begin(), reverse));
const double left = public_values[position];
const double right = public_values[reverse_position];
const double difference = std::abs(left - right);
// The approved symmetry test is normalized by the matrix's
// actual nonzero scale, without an absolute unit-size floor.
const bool is_symmetric = matrix_scale == 0.0
? difference == 0.0
: difference <= 1.0e-12 * matrix_scale;
if (!is_symmetric) {
return SolverFailure(
"solver-matrix-not-symmetric",
std::to_string(row) + ":" + std::to_string(column),
"The full public CSR values violate the approved symmetry "
"tolerance.");
}
}
}
void clearOwnedArrays() noexcept {
equationCount_ = 0;
rowOffsets_.clear();
columnIndices_.clear();
values_.clear();
}
equation_count_ = static_cast<MKL_INT>(matrix.Rows());
row_offsets_.clear();
column_indices_.clear();
values_.clear();
row_offsets_.reserve(matrix.Rows() + 1U);
row_offsets_.push_back(0);
std::array<void*, 64U> pt_{};
std::array<MKL_INT, 64U> iparm_{};
std::vector<MKL_INT> rowOffsets_;
std::vector<MKL_INT> columnIndices_;
std::vector<MKL_INT> permutation_;
std::vector<double> values_;
MKL_INT equationCount_{0};
MKL_INT maxFactorizations_{1};
MKL_INT matrixNumber_{1};
MKL_INT mtype_{2};
MKL_INT rhsCount_{1};
MKL_INT messageLevel_{0};
bool ownsPardisoState_{false};
bool factorized_{false};
for (std::size_t row = 0U; row < matrix.Rows(); ++row) {
bool has_diagonal = false;
for (std::size_t position = public_offsets[row];
position < public_offsets[row + 1U]; ++position) {
const std::size_t column = public_columns[position];
if (column < row) {
continue;
}
if (!ConvertsToMklInt(column) ||
!ConvertsToMklInt(column_indices_.size())) {
return SolverFailure(
"solver-dimension-overflow",
std::to_string(row) + ":" + std::to_string(column),
"CSR indices exceed the oneMKL integer range.");
}
has_diagonal = has_diagonal || column == row;
column_indices_.push_back(static_cast<MKL_INT>(column));
values_.push_back(public_values[position]);
}
if (!has_diagonal) {
return SolverFailure(
"solver-missing-diagonal", std::to_string(row),
"Every PARDISO SPD row must retain its diagonal slot.");
}
if (!ConvertsToMklInt(column_indices_.size())) {
return SolverFailure(
"solver-dimension-overflow", std::to_string(row),
"CSR row offsets exceed the oneMKL integer range.");
}
row_offsets_.push_back(static_cast<MKL_INT>(column_indices_.size()));
}
return Status::Ok();
}
/// @brief Calls PARDISO with retained private arrays and phase state.
void CallPardiso(const MKL_INT phase, double* rhs, double* solution,
MKL_INT& error) {
pardiso(pt_.data(), &max_factorizations_, &matrix_number_, &mtype_, &phase,
&equation_count_, values_.data(), row_offsets_.data(),
column_indices_.data(), permutation_.data(), &rhs_count_,
iparm_.data(), &message_level_, rhs, solution, &error);
}
/// @brief Releases backend state and resets every retained array.
MKL_INT Release() noexcept {
MKL_INT error = 0;
if (owns_pardiso_state_) {
const MKL_INT phase = -1;
double placeholder = 0.0;
CallPardiso(phase, &placeholder, &placeholder, error);
}
owns_pardiso_state_ = false;
factorized_ = false;
pt_.fill(nullptr);
iparm_.fill(0);
permutation_.clear();
ClearOwnedArrays();
return error;
}
/// @brief Clears FESA-owned CSR arrays without touching backend state.
void ClearOwnedArrays() noexcept {
equation_count_ = 0;
row_offsets_.clear();
column_indices_.clear();
values_.clear();
}
std::array<void*, 64U> pt_{};
std::array<MKL_INT, 64U> iparm_{};
std::vector<MKL_INT> row_offsets_;
std::vector<MKL_INT> column_indices_;
std::vector<MKL_INT> permutation_;
std::vector<double> values_;
MKL_INT equation_count_{0};
MKL_INT max_factorizations_{1};
MKL_INT matrix_number_{1};
MKL_INT mtype_{2};
MKL_INT rhs_count_{1};
MKL_INT message_level_{0};
bool owns_pardiso_state_{false};
bool factorized_{false};
};
MklPardisoSolver::MklPardisoSolver()
: impl_{std::make_unique<Impl>()} {}
MklPardisoSolver::MklPardisoSolver() : impl_{std::make_unique<Impl>()} {}
MklPardisoSolver::~MklPardisoSolver() = default;
Status MklPardisoSolver::factorize(const SparseMatrix& matrix) {
return impl_->factorize(matrix);
Status MklPardisoSolver::Factorize(const SparseMatrix& matrix) {
return impl_->Factorize(matrix);
}
Status MklPardisoSolver::solve(
const Vector& rhs,
Vector& solution) const {
return impl_->solve(rhs, solution);
Status MklPardisoSolver::Solve(const Vector& rhs, Vector& solution) const {
return impl_->Solve(rhs, solution);
}
} // namespace fesa
} // namespace fesa
@@ -2,7 +2,7 @@
#include "fesa/assembly/parallel_for.hpp"
#include "fesa/results/results_writer.hpp"
#include "fesa/solvers/linear/mkl_pardiso_solver.hpp"
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
#include <gtest/gtest.h>
@@ -183,7 +183,7 @@ protected:
private:
fesa::Status record(const char* event) {
events_.emplace_back(event);
return fesa::Status::ok();
return fesa::Status::Ok();
}
std::vector<std::string> events_;
@@ -196,21 +196,21 @@ public:
explicit SpyLinearSolver(std::vector<std::string>& events)
: events_{events} {}
fesa::Status factorize(const fesa::SparseMatrix&) override {
fesa::Status Factorize(const fesa::SparseMatrix&) override {
++factorizeCalls_;
events_.emplace_back("solver-factorize");
return fesa::Status::ok();
return fesa::Status::Ok();
}
fesa::Status solve(
fesa::Status Solve(
const fesa::Vector& rhs, fesa::Vector& solution) const override {
++solveCalls_;
events_.emplace_back("solver-solve");
for (std::size_t index = 0U;
index < rhs.size() && index < solution.size(); ++index) {
index < rhs.Size() && index < solution.Size(); ++index) {
solution[index] = 0.0;
}
return fesa::Status::ok();
return fesa::Status::Ok();
}
int factorizeCalls() const noexcept { return factorizeCalls_; }
@@ -224,25 +224,25 @@ private:
class RecordingMklSolver final : public fesa::LinearSolver {
public:
fesa::Status factorize(const fesa::SparseMatrix& matrix) override {
fesa::Status Factorize(const fesa::SparseMatrix& matrix) override {
++factorizeCalls_;
factorizedDimension_ = matrix.rows();
if (matrix.rows() == 1U && matrix.columns() == 1U &&
matrix.values().size() == 1U) {
scalarStiffness_ = matrix.values()[0U];
factorizedDimension_ = matrix.Rows();
if (matrix.Rows() == 1U && matrix.Columns() == 1U &&
matrix.Values().size() == 1U) {
scalarStiffness_ = matrix.Values()[0U];
}
return backend_.factorize(matrix);
return backend_.Factorize(matrix);
}
fesa::Status solve(
fesa::Status Solve(
const fesa::Vector& rhs, fesa::Vector& solution) const override {
++solveCalls_;
if (rhs.size() == 0U) {
if (rhs.Size() == 0U) {
rhs_.clear();
} else {
rhs_.assign(rhs.data(), rhs.data() + rhs.size());
rhs_.assign(rhs.Data(), rhs.Data() + rhs.Size());
}
return backend_.solve(rhs, solution);
return backend_.Solve(rhs, solution);
}
int factorizeCalls() const noexcept { return factorizeCalls_; }
@@ -264,16 +264,16 @@ private:
class NonfiniteLinearSolver final : public fesa::LinearSolver {
public:
fesa::Status factorize(const fesa::SparseMatrix&) override {
return fesa::Status::ok();
fesa::Status Factorize(const fesa::SparseMatrix&) override {
return fesa::Status::Ok();
}
fesa::Status solve(
fesa::Status Solve(
const fesa::Vector&, fesa::Vector& solution) const override {
for (std::size_t index = 0U; index < solution.size(); ++index) {
for (std::size_t index = 0U; index < solution.Size(); ++index) {
solution[index] = (std::numeric_limits<double>::quiet_NaN)();
}
return fesa::Status::ok();
return fesa::Status::Ok();
}
};
@@ -289,7 +289,7 @@ public:
const std::vector<fesa::Diagnostic>&) override {
++writeCalls_;
events_.emplace_back("writer-write");
return fesa::Status::ok();
return fesa::Status::Ok();
}
int writeCalls() const noexcept { return writeCalls_; }
@@ -311,7 +311,7 @@ public:
shellElementCount_ = domain.shellElements().size();
state_ = std::make_unique<fesa::AnalysisState>(state);
diagnostics_ = diagnostics;
return fesa::Status::ok();
return fesa::Status::Ok();
}
const fesa::AnalysisState& state() const {
@@ -345,7 +345,7 @@ private:
TEST(LinearStaticCli, FactorizesBeforeLoadAndSolvesWithoutRefactorization) {
SpyAnalysis lifecycle;
const fesa::AnalysisRequest emptyRequest{};
ASSERT_TRUE(lifecycle.run(emptyRequest).isOk());
ASSERT_TRUE(lifecycle.run(emptyRequest).IsOk());
EXPECT_EQ(
lifecycle.events(),
(std::vector<std::string>{
@@ -369,7 +369,7 @@ TEST(LinearStaticCli, FactorizesBeforeLoadAndSolvesWithoutRefactorization) {
SpyResultsWriter writer{adapterEvents};
fesa::LinearStaticAnalysis analysis{serial, solver, writer};
ASSERT_TRUE(analysis.run({input, output}).isOk());
ASSERT_TRUE(analysis.run({input, output}).IsOk());
EXPECT_EQ(solver.factorizeCalls(), 1);
EXPECT_EQ(solver.solveCalls(), 1);
EXPECT_EQ(writer.writeCalls(), 1);
@@ -391,13 +391,13 @@ TEST(LinearStaticCli, RealPipelineHandlesAnalyticalAndNonzeroPrescription) {
fesa::LinearStaticAnalysis analysis{serial, solver, writer};
const auto status = analysis.run({input, output});
ASSERT_TRUE(status.isOk());
ASSERT_TRUE(status.IsOk());
EXPECT_EQ(writer.outputPath(), output);
EXPECT_EQ(writer.nodeCount(), 2U);
EXPECT_TRUE(writer.diagnostics().empty());
const auto& state = writer.state();
ASSERT_EQ(state.displacement().size(), 12U);
ASSERT_EQ(state.displacement().Size(), 12U);
EXPECT_EQ(state.identity().stepName, "Step-1");
EXPECT_EQ(state.identity().frameIndex, 0U);
@@ -427,7 +427,7 @@ TEST(Mitc4ShellCli, UsesExistingLifecycleAndExactlyOneFactorization) {
SpyResultsWriter writer{adapterEvents};
fesa::LinearStaticAnalysis analysis{serial, solver, writer};
ASSERT_TRUE(analysis.run({input, output}).isOk());
ASSERT_TRUE(analysis.run({input, output}).IsOk());
EXPECT_EQ(solver.factorizeCalls(), 1);
EXPECT_EQ(solver.solveCalls(), 1);
EXPECT_EQ(writer.writeCalls(), 1);
@@ -450,11 +450,11 @@ TEST(Mitc4ShellCli, AppliesKfcForNonzeroPrescribedDisplacement) {
fesa::LinearStaticAnalysis analysis{serial, solver, writer};
const auto status = analysis.run({input, output});
for (const auto& diagnostic : status.diagnostics()) {
EXPECT_TRUE(status.isOk())
for (const auto& diagnostic : status.Diagnostics()) {
EXPECT_TRUE(status.IsOk())
<< diagnostic.code << ": " << diagnostic.message;
}
ASSERT_TRUE(status.isOk());
ASSERT_TRUE(status.IsOk());
ASSERT_EQ(solver.factorizeCalls(), 1);
ASSERT_EQ(solver.solveCalls(), 1);
ASSERT_EQ(solver.factorizedDimension(), 1U);
@@ -467,7 +467,7 @@ TEST(Mitc4ShellCli, AppliesKfcForNonzeroPrescribedDisplacement) {
EXPECT_EQ(writer.nodeCount(), 4U);
EXPECT_EQ(writer.shellElementCount(), 1U);
const auto& state = writer.state();
ASSERT_EQ(state.displacement().size(), 24U);
ASSERT_EQ(state.displacement().Size(), 24U);
EXPECT_NEAR(state.displacement()[6U], 0.1, 1.0e-12);
EXPECT_NEAR(state.displacement()[12U], -1.0 / 110.0, 1.0e-12);
EXPECT_NEAR(state.verificationMetrics()[0U], 0.0, 1.0e-10);
@@ -491,8 +491,8 @@ TEST(Mitc4ShellCli, RejectsSingularAndAcceptsZeroByZeroFreeSystem) {
serial, singularSolver, singularWriter};
const auto singular = singularAnalysis.run(
{singularInput, directory.path() / "singular.h5"});
ASSERT_FALSE(singular.isOk());
EXPECT_EQ(singular.failureCategory(), fesa::FailureCategory::solver);
ASSERT_FALSE(singular.IsOk());
EXPECT_EQ(singular.Category(), fesa::FailureCategory::kSolver);
EXPECT_EQ(singularWriter.writeCalls(), 0);
RecordingMklSolver constrainedSolver;
@@ -501,7 +501,7 @@ TEST(Mitc4ShellCli, RejectsSingularAndAcceptsZeroByZeroFreeSystem) {
serial, constrainedSolver, constrainedWriter};
const auto constrained = constrainedAnalysis.run(
{constrainedInput, directory.path() / "constrained.h5"});
ASSERT_TRUE(constrained.isOk());
ASSERT_TRUE(constrained.IsOk());
EXPECT_EQ(constrainedSolver.factorizeCalls(), 1);
EXPECT_EQ(constrainedSolver.factorizedDimension(), 0U);
EXPECT_EQ(constrainedSolver.solveCalls(), 1);
@@ -523,10 +523,10 @@ TEST(Mitc4ShellCli, DoesNotWriteAnInvalidRecoveryCandidate) {
const auto status = analysis.run(
{input, directory.path() / "must-not-exist.h5"});
ASSERT_FALSE(status.isOk());
EXPECT_EQ(status.failureCategory(), fesa::FailureCategory::model);
ASSERT_FALSE(status.diagnostics().empty());
EXPECT_EQ(status.diagnostics().front().code, "nonfinite-recovery-value");
ASSERT_FALSE(status.IsOk());
EXPECT_EQ(status.Category(), fesa::FailureCategory::kModel);
ASSERT_FALSE(status.Diagnostics().empty());
EXPECT_EQ(status.Diagnostics().front().code, "nonfinite-recovery-value");
EXPECT_EQ(writer.writeCalls(), 0);
EXPECT_TRUE(adapterEvents.empty());
}
@@ -202,8 +202,8 @@ TEST(B33ReferenceComparison,
auto comparisonResult = fesa::test::ReferenceComparison::compare(
results, referenceDirectory);
ASSERT_TRUE(comparisonResult.hasValue());
const auto& report = comparisonResult.value();
ASSERT_TRUE(comparisonResult.HasValue());
const auto& report = comparisonResult.Value();
ASSERT_TRUE(report.passed);
ASSERT_EQ(report.rows.size(), kExpectedRowCount);
ASSERT_EQ(report.metrics.size(), kExpectedMetricCount);
@@ -255,7 +255,7 @@ TEST(B33ReferenceComparison,
ASSERT_TRUE(
fesa::test::ReferenceComparison::writeDeterministicJson(
report, comparison)
.isOk());
.IsOk());
ASSERT_TRUE(std::filesystem::is_regular_file(comparison));
const std::string json = readBytes(comparison);
EXPECT_NE(json.find("\"stress_comparison_applicable\":false"),
@@ -86,10 +86,10 @@ CaseEvidence runCase(
EXPECT_TRUE(std::filesystem::is_regular_file(results));
auto comparisonResult = fesa::test::Mitc4ReferenceComparison::compare(
{caseId, sourceElementType, input, csv, results});
if (!comparisonResult.hasValue()) {
if (!comparisonResult.HasValue()) {
std::string diagnostics;
for (const auto& diagnostic :
comparisonResult.status().diagnostics()) {
comparisonResult.GetStatus().Diagnostics()) {
diagnostics += "\n" + diagnostic.code + ": " + diagnostic.message;
}
ADD_FAILURE() << "MITC4 comparison precheck failed for " << caseId
@@ -98,8 +98,8 @@ CaseEvidence runCase(
}
EXPECT_TRUE(
fesa::test::Mitc4ReferenceComparison::writeDeterministicJson(
comparisonResult.value(), comparison)
.isOk());
comparisonResult.Value(), comparison)
.IsOk());
EXPECT_TRUE(std::filesystem::is_regular_file(comparison));
std::vector<std::string> generated;
@@ -114,7 +114,7 @@ CaseEvidence runCase(
EXPECT_TRUE(std::filesystem::is_directory(referenceDirectory));
expectUnchanged(input, inputBefore);
expectUnchanged(csv, csvBefore);
return {std::move(comparisonResult.value()), comparison};
return {std::move(comparisonResult.Value()), comparison};
}
void expectCommonMetadata(
@@ -58,9 +58,9 @@ Status failureStatus(
const std::string& caseId,
const std::string& code,
const std::string& message) {
return Status::failure(
FailureCategory::model,
{{Severity::error, code, {}, "", caseId, message}});
return Status::Failure(
FailureCategory::kModel,
{{Severity::kError, code, {}, "", caseId, message}});
}
std::string trim(const std::string& value) {
@@ -835,12 +835,12 @@ Result<Mitc4ComparisonReport> Mitc4ReferenceComparison::compare(
errorNorms[component],
worstRows[component]});
}
return Result<Mitc4ComparisonReport>::success(std::move(report));
return Result<Mitc4ComparisonReport>::Success(std::move(report));
} catch (const ComparisonFailure& exception) {
return Result<Mitc4ComparisonReport>::failure(failureStatus(
return Result<Mitc4ComparisonReport>::Failure(failureStatus(
referenceCase.caseId, exception.code(), exception.what()));
} catch (const std::exception& exception) {
return Result<Mitc4ComparisonReport>::failure(failureStatus(
return Result<Mitc4ComparisonReport>::Failure(failureStatus(
referenceCase.caseId, "comparison-failure", exception.what()));
}
}
@@ -936,7 +936,7 @@ Status Mitc4ReferenceComparison::writeDeterministicJson(
"comparison-report-write-failed",
"The deterministic MITC4 JSON report could not be completed.");
}
return Status::ok();
return Status::Ok();
} catch (const std::exception& exception) {
return failureStatus(
report.caseId, "comparison-report-write-failed", exception.what());
@@ -1,6 +1,6 @@
#pragma once
#include "fesa/core/status.hpp"
#include "fesa/core/status.h"
#include <cstddef>
#include <cstdint>
@@ -501,9 +501,9 @@ private:
void expectFailureCode(
const fesa::Result<fesa::test::Mitc4ComparisonReport>& result,
const std::string& code) {
ASSERT_FALSE(result.hasValue());
ASSERT_FALSE(result.status().diagnostics().empty());
EXPECT_EQ(result.status().diagnostics().front().code, code);
ASSERT_FALSE(result.HasValue());
ASSERT_FALSE(result.GetStatus().Diagnostics().empty());
EXPECT_EQ(result.GetStatus().Diagnostics().front().code, code);
}
const fesa::test::Mitc4RowDecision* findRow(
@@ -550,8 +550,8 @@ TEST(Mitc4ReferenceComparison, MapsTrimmedHeaderAndSixComponentsBySourceIdentity
auto result = fesa::test::Mitc4ReferenceComparison::compare(
fixture.referenceCase());
ASSERT_TRUE(result.hasValue());
const auto& report = result.value();
ASSERT_TRUE(result.HasValue());
const auto& report = result.Value();
ASSERT_TRUE(report.passed);
ASSERT_EQ(report.rows.size(), 12U);
EXPECT_EQ(report.rows[0U].sourceNodeLabel, 1);
@@ -676,8 +676,8 @@ TEST(Mitc4ReferenceComparison, AppliesFixedAbsoluteToleranceWithoutScaleClampOrR
auto result = fesa::test::Mitc4ReferenceComparison::compare(
fixture.referenceCase());
ASSERT_TRUE(result.hasValue());
const auto& report = result.value();
ASSERT_TRUE(result.HasValue());
const auto& report = result.Value();
EXPECT_FALSE(report.passed);
const auto* u1Zero = findRow(report, 1, "U1");
const auto* u1Scaled = findRow(report, 2, "U1");
@@ -717,8 +717,8 @@ TEST(Mitc4ReferenceComparison, RotationExceedanceWarnsWithoutBlockingTranslation
auto result = fesa::test::Mitc4ReferenceComparison::compare(
fixture.referenceCase());
ASSERT_TRUE(result.hasValue());
const auto& report = result.value();
ASSERT_TRUE(result.HasValue());
const auto& report = result.Value();
EXPECT_TRUE(report.passed);
ASSERT_EQ(report.warnings.size(), 1U);
EXPECT_EQ(report.warnings[0U].code, "rotation-reference-exceedance");
@@ -744,8 +744,8 @@ TEST(Mitc4ReferenceComparison, ReportsMetricsVectorsWorstRowAndJsonDeterministic
auto result = fesa::test::Mitc4ReferenceComparison::compare(
fixture.referenceCase());
ASSERT_TRUE(result.hasValue());
const auto& report = result.value();
ASSERT_TRUE(result.HasValue());
const auto& report = result.Value();
ASSERT_TRUE(report.passed);
ASSERT_EQ(report.metrics.size(), 6U);
ASSERT_EQ(report.vectorMetrics.size(), 2U);
@@ -771,11 +771,11 @@ TEST(Mitc4ReferenceComparison, ReportsMetricsVectorsWorstRowAndJsonDeterministic
ASSERT_TRUE(
fesa::test::Mitc4ReferenceComparison::writeDeterministicJson(
report, jsonA)
.isOk());
.IsOk());
ASSERT_TRUE(
fesa::test::Mitc4ReferenceComparison::writeDeterministicJson(
report, jsonB)
.isOk());
.IsOk());
const std::string first = readBytes(jsonA);
EXPECT_EQ(first, readBytes(jsonB));
for (const char* key : {
@@ -798,8 +798,8 @@ TEST(Mitc4ReferenceComparison, RequiresOnlyDeclaredInputCsvAndHdf5) {
(std::vector<std::string>{"case.inp", "displacements.csv", "results.h5"}));
auto result = fesa::test::Mitc4ReferenceComparison::compare(
fixture.referenceCase());
ASSERT_TRUE(result.hasValue());
EXPECT_TRUE(result.value().passed);
ASSERT_TRUE(result.HasValue());
EXPECT_TRUE(result.Value().passed);
}
} // namespace
+40 -40
View File
@@ -71,9 +71,9 @@ private:
Status comparisonFailureStatus(
const std::string& code, const std::string& message) {
return Status::failure(
FailureCategory::model,
{{Severity::error, code, {}, "", kModelId, message}});
return Status::Failure(
FailureCategory::kModel,
{{Severity::kError, code, {}, "", kModelId, message}});
}
std::string trim(const std::string& value) {
@@ -256,17 +256,17 @@ void requireExactArtifactInventory(
Domain readApprovedDomain(const std::filesystem::path& inputPath) {
AbaqusInputReader reader;
auto parsed = reader.read(inputPath);
if (!parsed.hasValue()) {
if (!parsed.HasValue()) {
fail("needs-reference-artifacts", "The approved reference input cannot be parsed.");
}
AbaqusDomainMapper mapper;
auto domain = mapper.map(parsed.value());
if (!domain.hasValue()) {
auto domain = mapper.map(parsed.Value());
if (!domain.HasValue()) {
fail(
"needs-reference-artifacts",
"The approved reference input is not the required B33 model.");
}
return std::move(domain.value());
return std::move(domain.Value());
}
class Hdf5Handle {
@@ -878,9 +878,9 @@ HdfProjection readHdfProjection(
const auto& actual = projection.nodes[node];
const auto& expected = domain.nodes()[node];
if (actual.internalNodeId != node ||
actual.instanceName != expected.sourceId.instanceName ||
actual.sourceNodeLabel != expected.sourceId.sourceLabel ||
actual.sourceNodeLabelText != expected.sourceId.sourceLabelText ||
actual.instanceName != expected.sourceId.instance_name ||
actual.sourceNodeLabel != expected.sourceId.source_label ||
actual.sourceNodeLabelText != expected.sourceId.source_label_text ||
actual.coordinates != expected.coordinates) {
fail("schema-mismatch", "An HDF5 node identity does not match the input.");
}
@@ -889,9 +889,9 @@ HdfProjection readHdfProjection(
const auto& actual = projection.elements[element];
const auto& expected = domain.elements()[element];
if (actual.internalElementId != element ||
actual.instanceName != expected.sourceId.instanceName ||
actual.sourceElementLabel != expected.sourceId.sourceLabel ||
actual.sourceElementLabelText != expected.sourceId.sourceLabelText ||
actual.instanceName != expected.sourceId.instance_name ||
actual.sourceElementLabel != expected.sourceId.source_label ||
actual.sourceElementLabelText != expected.sourceId.source_label_text ||
actual.nodeInternalIds[0U] != expected.nodeIndices[0U] ||
actual.nodeInternalIds[1U] != expected.nodeIndices[1U]) {
fail("schema-mismatch", "An HDF5 element identity does not match the input.");
@@ -963,10 +963,10 @@ std::vector<NodeStationResultRow> normalizeStations(
const HdfProjection& hdf,
const ReferenceTable& sectionTable) {
auto modelResult = AnalysisModel::create(domain);
if (!modelResult.hasValue()) {
if (!modelResult.HasValue()) {
fail("schema-mismatch", "The approved input cannot create an analysis view.");
}
const AnalysisModel model = std::move(modelResult.value());
const AnalysisModel model = std::move(modelResult.Value());
const std::array<double, 4> tolerances = {
kForceMomentFloor + kRelativeCoefficient * tableScale(sectionTable, 0U),
kForceMomentFloor + kRelativeCoefficient * tableScale(sectionTable, 3U),
@@ -993,15 +993,15 @@ std::vector<NodeStationResultRow> normalizeStations(
}
auto normalized = ResultRecovery::normalizeSectionResultantsToNodeStations(
model, endpoints, tolerances);
if (!normalized.hasValue()) {
const auto& diagnostics = normalized.status().diagnostics();
if (!normalized.HasValue()) {
const auto& diagnostics = normalized.GetStatus().Diagnostics();
const std::string code = diagnostics.empty() ? std::string{} : diagnostics[0U].code;
if (code == "node-station-tolerance-failure") {
fail("tolerance-failure", "Interior endpoint section resultants disagree.");
}
fail("schema-mismatch", "A node station is not eligible for legacy projection.");
}
return std::move(normalized.value());
return std::move(normalized.Value());
}
const NodeStationResultRow& findStation(
@@ -1009,8 +1009,8 @@ const NodeStationResultRow& findStation(
const HdfNode& node) {
const auto found = std::find_if(
stations.begin(), stations.end(), [&](const NodeStationResultRow& row) {
return row.node.instanceName == node.instanceName &&
row.node.sourceLabel == node.sourceNodeLabel;
return row.node.instance_name == node.instanceName &&
row.node.source_label == node.sourceNodeLabel;
});
if (found == stations.end()) {
fail("schema-mismatch", "A projected HDF5 node station is missing.");
@@ -1165,21 +1165,21 @@ PhysicsEvidence makePhysicsEvidence(
const Domain& domain,
const HdfProjection& hdf) {
auto modelResult = AnalysisModel::create(domain);
if (!modelResult.hasValue()) {
if (!modelResult.HasValue()) {
fail("schema-mismatch", "The approved input cannot create physics evidence.");
}
const AnalysisModel model = std::move(modelResult.value());
const AnalysisModel model = std::move(modelResult.Value());
auto dofsResult = DofManager::create(model);
if (!dofsResult.hasValue()) {
if (!dofsResult.HasValue()) {
fail("schema-mismatch", "The approved input cannot create a DOF map.");
}
const DofManager dofs = std::move(dofsResult.value());
const DofManager dofs = std::move(dofsResult.Value());
auto loadResult = LoadAssembler::assembleFullNodalLoad(model, dofs);
if (!loadResult.hasValue()) {
if (!loadResult.HasValue()) {
fail("schema-mismatch", "The approved input load cannot be assembled.");
}
const Vector load = std::move(loadResult.value());
if (load.size() != hdf.reaction.size()) {
const Vector load = std::move(loadResult.Value());
if (load.Size() != hdf.reaction.size()) {
fail("schema-mismatch", "The load and reaction spaces are inconsistent.");
}
@@ -1407,12 +1407,12 @@ Result<ComparisonReport> ReferenceComparison::compare(
report.stressComparisonReason =
"Abaqus beam stress comparison is N/A; analytical/unit and HDF5 "
"schema tests provide stress evidence.";
return Result<ComparisonReport>::success(std::move(report));
return Result<ComparisonReport>::Success(std::move(report));
} catch (const ComparisonFailure& failure) {
return Result<ComparisonReport>::failure(
return Result<ComparisonReport>::Failure(
comparisonFailureStatus(failure.code(), failure.what()));
} catch (const std::exception& failure) {
return Result<ComparisonReport>::failure(comparisonFailureStatus(
return Result<ComparisonReport>::Failure(comparisonFailureStatus(
"schema-mismatch", failure.what()));
}
}
@@ -1422,9 +1422,9 @@ Status ReferenceComparison::writeDeterministicJson(
const std::filesystem::path& outputJson) {
if (outputJson.empty() || outputJson.filename().empty() ||
!finiteReport(report)) {
return Status::failure(
FailureCategory::output,
{{Severity::error,
return Status::Failure(
FailureCategory::kOutput,
{{Severity::kError,
"comparison-json-write-failure",
{},
"",
@@ -1433,9 +1433,9 @@ Status ReferenceComparison::writeDeterministicJson(
}
std::ofstream stream{outputJson, std::ios::binary | std::ios::trunc};
if (!stream) {
return Status::failure(
FailureCategory::output,
{{Severity::error,
return Status::Failure(
FailureCategory::kOutput,
{{Severity::kError,
"comparison-json-write-failure",
{},
"",
@@ -1495,16 +1495,16 @@ Status ReferenceComparison::writeDeterministicJson(
writeJsonString(stream, report.stressComparisonReason);
stream << ",\"passed\":" << (report.passed ? "true" : "false") << "}\n";
if (!stream) {
return Status::failure(
FailureCategory::output,
{{Severity::error,
return Status::Failure(
FailureCategory::kOutput,
{{Severity::kError,
"comparison-json-write-failure",
{},
"",
kModelId,
"The deterministic comparison JSON write failed."}});
}
return Status::ok();
return Status::Ok();
}
} // namespace fesa::test
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#include "fesa/core/status.hpp"
#include "fesa/core/status.h"
#include <array>
#include <cstddef>
+21 -21
View File
@@ -214,25 +214,25 @@ void writeResultsFixture(
const std::filesystem::path& input,
const ComparisonValues& values) {
auto parsedInput = fesa::AbaqusInputReader{}.read(input);
if (!parsedInput.hasValue()) {
if (!parsedInput.HasValue()) {
throw std::runtime_error{"Reference fixture input identity read failed."};
}
auto domainResult = fesa::Domain::create(
makeDefinition(input, parsedInput.value().sourceContentIdentity));
if (!domainResult.hasValue()) {
makeDefinition(input, parsedInput.Value().sourceContentIdentity));
if (!domainResult.HasValue()) {
throw std::runtime_error{"Reference fixture Domain construction failed."};
}
fesa::Domain domain = std::move(domainResult.value());
fesa::Domain domain = std::move(domainResult.Value());
auto modelResult = fesa::AnalysisModel::create(domain);
if (!modelResult.hasValue()) {
if (!modelResult.HasValue()) {
throw std::runtime_error{"Reference fixture AnalysisModel construction failed."};
}
fesa::AnalysisModel model = std::move(modelResult.value());
fesa::AnalysisModel model = std::move(modelResult.Value());
auto dofsResult = fesa::DofManager::create(model);
if (!dofsResult.hasValue()) {
if (!dofsResult.HasValue()) {
throw std::runtime_error{"Reference fixture DofManager construction failed."};
}
fesa::DofManager dofs = std::move(dofsResult.value());
fesa::DofManager dofs = std::move(dofsResult.Value());
fesa::AnalysisState state =
fesa::AnalysisState::create(dofs, {"Step-1", 0U});
@@ -278,7 +278,7 @@ void writeResultsFixture(
fesa::Hdf5ResultsWriter writer;
const fesa::Status status = writer.write(output, domain, state, {});
if (!status.isOk()) {
if (!status.IsOk()) {
throw std::runtime_error{"Reference fixture HDF5 write failed."};
}
}
@@ -330,10 +330,10 @@ private:
void expectFailureCode(
const fesa::Result<fesa::test::ComparisonReport>& result,
const std::string& expectedCode) {
ASSERT_FALSE(result.hasValue());
ASSERT_FALSE(result.status().isOk());
ASSERT_FALSE(result.status().diagnostics().empty());
EXPECT_EQ(result.status().diagnostics().front().code, expectedCode);
ASSERT_FALSE(result.HasValue());
ASSERT_FALSE(result.GetStatus().IsOk());
ASSERT_FALSE(result.GetStatus().Diagnostics().empty());
EXPECT_EQ(result.GetStatus().Diagnostics().front().code, expectedCode);
}
const fesa::test::RowDecision* findRow(
@@ -557,8 +557,8 @@ TEST(ReferenceComparisonContract,
auto result = fesa::test::ReferenceComparison::compare(
fixture.results(), fixture.legacy());
ASSERT_TRUE(result.hasValue());
const auto& report = result.value();
ASSERT_TRUE(result.HasValue());
const auto& report = result.Value();
EXPECT_FALSE(report.passed);
EXPECT_EQ(report.rows.size(), kExpectedRowCount);
EXPECT_EQ(report.metrics.size(), kExpectedMetricCount);
@@ -608,8 +608,8 @@ TEST(ReferenceComparisonContract,
auto result = fesa::test::ReferenceComparison::compare(
fixture.results(), fixture.legacy());
ASSERT_TRUE(result.hasValue());
const auto& report = result.value();
ASSERT_TRUE(result.HasValue());
const auto& report = result.Value();
ASSERT_TRUE(report.passed);
expectExactRowInventory(report);
ASSERT_EQ(report.metrics.size(), kExpectedMetricCount);
@@ -658,10 +658,10 @@ TEST(ReferenceComparisonContract,
const auto jsonB = fixture.root() / "comparison-b.json";
ASSERT_TRUE(
fesa::test::ReferenceComparison::writeDeterministicJson(report, jsonA)
.isOk());
.IsOk());
ASSERT_TRUE(
fesa::test::ReferenceComparison::writeDeterministicJson(report, jsonB)
.isOk());
.IsOk());
const std::string first = readBytes(jsonA);
EXPECT_EQ(first, readBytes(jsonB));
for (const char* required : {
@@ -690,8 +690,8 @@ TEST(ReferenceComparisonContract,
auto result = fesa::test::ReferenceComparison::compare(
fixture.results(), fixture.legacy());
ASSERT_TRUE(result.hasValue());
const auto& report = result.value();
ASSERT_TRUE(result.HasValue());
const auto& report = result.Value();
ASSERT_TRUE(report.passed);
EXPECT_TRUE(report.physicsEvidence.endpointConsistencyPassed);
+27 -27
View File
@@ -58,11 +58,11 @@ fesa::ModelDefinition makeDefinition() {
TEST(AnalysisModel, ClassifiesActiveEntitiesInStableOrder) {
auto domainResult = fesa::Domain::create(makeDefinition());
ASSERT_TRUE(domainResult.hasValue());
ASSERT_TRUE(domainResult.HasValue());
auto modelResult = fesa::AnalysisModel::create(domainResult.value());
ASSERT_TRUE(modelResult.hasValue());
const auto& model = modelResult.value();
auto modelResult = fesa::AnalysisModel::create(domainResult.Value());
ASSERT_TRUE(modelResult.HasValue());
const auto& model = modelResult.Value();
EXPECT_EQ(
model.activeElements(),
@@ -83,8 +83,8 @@ TEST(AnalysisModel, ClassifiesActiveEntitiesInStableOrder) {
TEST(AnalysisModel, ReferencesWithoutCopyingOrMutatingDomain) {
auto domainResult = fesa::Domain::create(makeDefinition());
ASSERT_TRUE(domainResult.hasValue());
const fesa::Domain& domain = domainResult.value();
ASSERT_TRUE(domainResult.HasValue());
const fesa::Domain& domain = domainResult.Value();
const auto* const elementAddress = domain.elements().data();
const auto* const materialAddress = domain.materials().data();
const auto* const sectionAddress = domain.sections().data();
@@ -92,8 +92,8 @@ TEST(AnalysisModel, ReferencesWithoutCopyingOrMutatingDomain) {
const double firstLoadMagnitude = domain.steps()[0].loads[0].magnitude;
auto modelResult = fesa::AnalysisModel::create(domain);
ASSERT_TRUE(modelResult.hasValue());
const auto& model = modelResult.value();
ASSERT_TRUE(modelResult.HasValue());
const auto& model = modelResult.Value();
EXPECT_EQ(&model.domain(), &domain);
EXPECT_EQ(&model.step(), &domain.steps()[0]);
@@ -117,19 +117,19 @@ TEST(AnalysisModel, RejectsMissingOrMultipleStep) {
auto missingDefinition = makeDefinition();
missingDefinition.steps.clear();
auto missingDomain = fesa::Domain::create(std::move(missingDefinition));
ASSERT_TRUE(missingDomain.hasValue());
ASSERT_TRUE(missingDomain.HasValue());
auto missing = fesa::AnalysisModel::create(missingDomain.value());
ASSERT_FALSE(missing.hasValue());
auto missing = fesa::AnalysisModel::create(missingDomain.Value());
ASSERT_FALSE(missing.HasValue());
EXPECT_EQ(
missing.status().failureCategory(),
fesa::FailureCategory::input);
ASSERT_EQ(missing.status().diagnostics().size(), 1U);
missing.GetStatus().Category(),
fesa::FailureCategory::kInput);
ASSERT_EQ(missing.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(
missing.status().diagnostics()[0].code,
missing.GetStatus().Diagnostics()[0].code,
"invalid-model-cardinality");
EXPECT_EQ(missing.status().diagnostics()[0].keyword, "STEP");
EXPECT_EQ(missing.status().diagnostics()[0].entityIdentity, "0");
EXPECT_EQ(missing.GetStatus().Diagnostics()[0].keyword, "STEP");
EXPECT_EQ(missing.GetStatus().Diagnostics()[0].entity_identity, "0");
auto multipleDefinition = makeDefinition();
auto secondStep = multipleDefinition.steps.front();
@@ -137,18 +137,18 @@ TEST(AnalysisModel, RejectsMissingOrMultipleStep) {
secondStep.location.line = 60U;
multipleDefinition.steps.push_back(std::move(secondStep));
auto multipleDomain = fesa::Domain::create(std::move(multipleDefinition));
ASSERT_TRUE(multipleDomain.hasValue());
ASSERT_TRUE(multipleDomain.HasValue());
auto multiple = fesa::AnalysisModel::create(multipleDomain.value());
ASSERT_FALSE(multiple.hasValue());
auto multiple = fesa::AnalysisModel::create(multipleDomain.Value());
ASSERT_FALSE(multiple.HasValue());
EXPECT_EQ(
multiple.status().failureCategory(),
fesa::FailureCategory::input);
ASSERT_EQ(multiple.status().diagnostics().size(), 1U);
multiple.GetStatus().Category(),
fesa::FailureCategory::kInput);
ASSERT_EQ(multiple.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(
multiple.status().diagnostics()[0].code,
multiple.GetStatus().Diagnostics()[0].code,
"unsupported-multiple-step");
EXPECT_EQ(multiple.status().diagnostics()[0].keyword, "STEP");
EXPECT_EQ(multiple.status().diagnostics()[0].entityIdentity, "Step-2");
EXPECT_EQ(multiple.status().diagnostics()[0].location.line, 60U);
EXPECT_EQ(multiple.GetStatus().Diagnostics()[0].keyword, "STEP");
EXPECT_EQ(multiple.GetStatus().Diagnostics()[0].entity_identity, "Step-2");
EXPECT_EQ(multiple.GetStatus().Diagnostics()[0].location.line, 60U);
}
+13 -13
View File
@@ -65,16 +65,16 @@ fesa::DofManager makeDofs() {
{definition.sourcePath, 19U}}};
auto domain = fesa::Domain::create(std::move(definition));
EXPECT_TRUE(domain.hasValue());
auto model = fesa::AnalysisModel::create(domain.value());
EXPECT_TRUE(model.hasValue());
auto dofs = fesa::DofManager::create(model.value());
EXPECT_TRUE(dofs.hasValue());
return std::move(dofs.value());
EXPECT_TRUE(domain.HasValue());
auto model = fesa::AnalysisModel::create(domain.Value());
EXPECT_TRUE(model.HasValue());
auto dofs = fesa::DofManager::create(model.Value());
EXPECT_TRUE(dofs.HasValue());
return std::move(dofs.Value());
}
void expectAllZero(const fesa::Vector& vector) {
for (std::size_t index = 0U; index < vector.size(); ++index) {
for (std::size_t index = 0U; index < vector.Size(); ++index) {
EXPECT_DOUBLE_EQ(vector[index], 0.0);
}
}
@@ -96,12 +96,12 @@ TEST(AnalysisState, AllocatesOnlyV0FullVectors) {
&constState.reaction()};
for (const auto* vector : vectors) {
EXPECT_EQ(vector->size(), dofs.fullDofCount());
EXPECT_EQ(vector->Size(), dofs.fullDofCount());
expectAllZero(*vector);
}
for (std::size_t left = 0U; left < std::size(vectors); ++left) {
for (std::size_t right = left + 1U; right < std::size(vectors); ++right) {
EXPECT_NE(vectors[left]->data(), vectors[right]->data());
EXPECT_NE(vectors[left]->Data(), vectors[right]->Data());
}
}
@@ -142,8 +142,8 @@ TEST(AnalysisState, CopiesOrMovesWithoutAliasing) {
{0U, 1, 0U, 0.0, 0.0, 10.0, "fesa-default"});
auto copied = original;
EXPECT_NE(copied.displacement().data(), original.displacement().data());
EXPECT_NE(copied.reaction().data(), original.reaction().data());
EXPECT_NE(copied.displacement().Data(), original.displacement().Data());
EXPECT_NE(copied.reaction().Data(), original.reaction().Data());
EXPECT_NE(copied.endpointResults().data(), original.endpointResults().data());
EXPECT_NE(copied.gaussResults().data(), original.gaussResults().data());
EXPECT_NE(copied.stressResults().data(), original.stressResults().data());
@@ -156,7 +156,7 @@ TEST(AnalysisState, CopiesOrMovesWithoutAliasing) {
EXPECT_EQ(moved.identity().stepName, "Step-1");
EXPECT_DOUBLE_EQ(moved.displacement()[0], 30.0);
EXPECT_DOUBLE_EQ(moved.endpointResults()[0].endAction[0], 20.0);
EXPECT_NE(moved.displacement().data(), original.displacement().data());
EXPECT_NE(moved.displacement().Data(), original.displacement().Data());
EXPECT_NE(moved.endpointResults().data(), original.endpointResults().data());
auto copyAssigned = fesa::AnalysisState::create(dofs, {"Other", 3U});
@@ -169,5 +169,5 @@ TEST(AnalysisState, CopiesOrMovesWithoutAliasing) {
moveAssigned = std::move(copyAssigned);
EXPECT_EQ(moveAssigned.identity().stepName, "Step-1");
EXPECT_DOUBLE_EQ(moveAssigned.reaction()[11], -20.0);
EXPECT_NE(moveAssigned.reaction().data(), original.reaction().data());
EXPECT_NE(moveAssigned.reaction().Data(), original.reaction().Data());
}
+62 -62
View File
@@ -54,25 +54,25 @@ LoadFixture makeFixture(
{source, 20U}}};
auto domainResult = fesa::Domain::create(std::move(definition));
if (!domainResult.hasValue()) {
if (!domainResult.HasValue()) {
throw std::runtime_error{"Load fixture Domain construction failed."};
}
auto domain = std::make_unique<fesa::Domain>(
std::move(domainResult.value()));
std::move(domainResult.Value()));
auto modelResult = fesa::AnalysisModel::create(*domain);
if (!modelResult.hasValue()) {
if (!modelResult.HasValue()) {
throw std::runtime_error{"Load fixture AnalysisModel construction failed."};
}
auto model = std::make_unique<fesa::AnalysisModel>(
std::move(modelResult.value()));
std::move(modelResult.Value()));
auto dofResult = fesa::DofManager::create(*model);
if (!dofResult.hasValue()) {
if (!dofResult.HasValue()) {
throw std::runtime_error{"Load fixture DofManager construction failed."};
}
auto dofs = std::make_unique<fesa::DofManager>(
std::move(dofResult.value()));
std::move(dofResult.Value()));
return {std::move(domain), std::move(model), std::move(dofs)};
}
@@ -117,25 +117,25 @@ LoadFixture makeShellFixture(
{source, 20U}}};
auto domainResult = fesa::Domain::create(std::move(definition));
if (!domainResult.hasValue()) {
if (!domainResult.HasValue()) {
throw std::runtime_error{"Shell load fixture Domain construction failed."};
}
auto domain = std::make_unique<fesa::Domain>(
std::move(domainResult.value()));
std::move(domainResult.Value()));
auto modelResult = fesa::AnalysisModel::create(*domain);
if (!modelResult.hasValue()) {
if (!modelResult.HasValue()) {
throw std::runtime_error{"Shell load fixture AnalysisModel construction failed."};
}
auto model = std::make_unique<fesa::AnalysisModel>(
std::move(modelResult.value()));
std::move(modelResult.Value()));
auto dofResult = fesa::DofManager::create(*model);
if (!dofResult.hasValue()) {
if (!dofResult.HasValue()) {
throw std::runtime_error{"Shell load fixture DofManager construction failed."};
}
auto dofs = std::make_unique<fesa::DofManager>(
std::move(dofResult.value()));
std::move(dofResult.Value()));
return {std::move(domain), std::move(model), std::move(dofs)};
}
@@ -164,21 +164,21 @@ fesa::SparseMatrix makeDenseSparse(
pattern.rowOffsets.push_back(pattern.columnIndices.size());
}
auto result = fesa::SparseMatrix::fromCoo(
auto result = fesa::SparseMatrix::FromCoo(
rows, columns, std::move(contributions), pattern);
if (!result.hasValue()) {
if (!result.HasValue()) {
throw std::runtime_error{"Sparse fixture construction failed."};
}
return std::move(result.value());
return std::move(result.Value());
}
void expectFailureCode(
const fesa::Result<fesa::Vector>& result,
const std::string& code) {
ASSERT_FALSE(result.hasValue());
EXPECT_EQ(result.status().failureCategory(), fesa::FailureCategory::model);
ASSERT_EQ(result.status().diagnostics().size(), 1U);
EXPECT_EQ(result.status().diagnostics()[0U].code, code);
ASSERT_FALSE(result.HasValue());
EXPECT_EQ(result.GetStatus().Category(), fesa::FailureCategory::kModel);
ASSERT_EQ(result.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(result.GetStatus().Diagnostics()[0U].code, code);
}
} // namespace
@@ -198,16 +198,16 @@ TEST(LoadAssembly, AssemblesNodeSetAndSixComponentLoads) {
auto result = fesa::LoadAssembler::assembleFullNodalLoad(
*fixture.model, *fixture.dofs);
ASSERT_TRUE(result.hasValue());
ASSERT_EQ(result.value().size(), 12U);
ASSERT_TRUE(result.HasValue());
ASSERT_EQ(result.Value().Size(), 12U);
EXPECT_EQ(
std::vector<double>(result.value().data(), result.value().data() + 12U),
std::vector<double>(result.Value().Data(), result.Value().Data() + 12U),
(std::vector<double>{
1.0, 2.0, 0.0, -4.0, 5.0, 0.0,
1.0, 0.0, 3.0, 0.0, 5.0, 6.0}));
EXPECT_EQ(fixture.dofs->constrainedDofs(),
(std::vector<std::size_t>{0U}));
EXPECT_DOUBLE_EQ(result.value()[0U], 1.0);
EXPECT_DOUBLE_EQ(result.Value()[0U], 1.0);
}
TEST(LoadAssembly, AccumulatesSignedLoadsInSourceOrder) {
@@ -231,10 +231,10 @@ TEST(LoadAssembly, AccumulatesSignedLoadsInSourceOrder) {
*firstOrder.model, *firstOrder.dofs);
auto second = fesa::LoadAssembler::assembleFullNodalLoad(
*secondOrder.model, *secondOrder.dofs);
ASSERT_TRUE(first.hasValue());
ASSERT_TRUE(second.hasValue());
EXPECT_DOUBLE_EQ(first.value()[0U], 1.0);
EXPECT_DOUBLE_EQ(second.value()[0U], 0.0);
ASSERT_TRUE(first.HasValue());
ASSERT_TRUE(second.HasValue());
EXPECT_DOUBLE_EQ(first.Value()[0U], 1.0);
EXPECT_DOUBLE_EQ(second.Value()[0U], 0.0);
}
// MITC4-LOAD-001
@@ -257,10 +257,10 @@ TEST(LoadAssembly, AggregatesAllSixGlobalShellLoadComponentsInSourceOrder) {
const auto result = fesa::LoadAssembler::assembleFullNodalLoad(
*fixture.model, *fixture.dofs);
ASSERT_TRUE(result.hasValue());
ASSERT_EQ(result.value().size(), 24U);
ASSERT_TRUE(result.HasValue());
ASSERT_EQ(result.Value().Size(), 24U);
EXPECT_EQ(
std::vector<double>(result.value().data(), result.value().data() + 6U),
std::vector<double>(result.Value().Data(), result.Value().Data() + 6U),
(std::vector<double>{1.0, 2.0, 3.0, 4.0, 5.0, 0.0}));
}
@@ -279,10 +279,10 @@ TEST(LoadAssembly, AcceptsExactlyZeroAggregateShellMoment) {
const auto result = fesa::LoadAssembler::assembleFullNodalLoad(
*fixture.model, *fixture.dofs);
ASSERT_TRUE(result.hasValue());
EXPECT_DOUBLE_EQ(result.value()[3U], 0.0);
EXPECT_DOUBLE_EQ(result.value()[4U], 0.0);
EXPECT_DOUBLE_EQ(result.value()[5U], 0.0);
ASSERT_TRUE(result.HasValue());
EXPECT_DOUBLE_EQ(result.Value()[3U], 0.0);
EXPECT_DOUBLE_EQ(result.Value()[4U], 0.0);
EXPECT_DOUBLE_EQ(result.Value()[5U], 0.0);
}
// MITC4-LOAD-003
@@ -302,15 +302,15 @@ TEST(LoadAssembly, EnforcesAggregateShellMomentDirectorProjectionThreshold) {
const auto rejected = fesa::LoadAssembler::assembleFullNodalLoad(
*rejectedFixture.model, *rejectedFixture.dofs);
ASSERT_TRUE(accepted.hasValue());
ASSERT_FALSE(rejected.hasValue());
EXPECT_EQ(rejected.status().failureCategory(), fesa::FailureCategory::model);
ASSERT_EQ(rejected.status().diagnostics().size(), 1U);
ASSERT_TRUE(accepted.HasValue());
ASSERT_FALSE(rejected.HasValue());
EXPECT_EQ(rejected.GetStatus().Category(), fesa::FailureCategory::kModel);
ASSERT_EQ(rejected.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(
rejected.status().diagnostics()[0U].code,
rejected.GetStatus().Diagnostics()[0U].code,
"unsupported-drilling-load");
EXPECT_EQ(rejected.status().diagnostics()[0U].keyword, "CLOAD");
EXPECT_EQ(rejected.status().diagnostics()[0U].entityIdentity, "10");
EXPECT_EQ(rejected.GetStatus().Diagnostics()[0U].keyword, "CLOAD");
EXPECT_EQ(rejected.GetStatus().Diagnostics()[0U].entity_identity, "10");
}
// MITC4-LOAD-004
@@ -323,11 +323,11 @@ TEST(LoadAssembly, RejectsDrillingMomentBeforeEffectiveRhsCanBeFormed) {
const auto rejected = fesa::LoadAssembler::assembleFullNodalLoad(
*fixture.model, *fixture.dofs);
ASSERT_FALSE(rejected.hasValue());
EXPECT_EQ(rejected.status().failureCategory(), fesa::FailureCategory::model);
ASSERT_EQ(rejected.status().diagnostics().size(), 1U);
ASSERT_FALSE(rejected.HasValue());
EXPECT_EQ(rejected.GetStatus().Category(), fesa::FailureCategory::kModel);
ASSERT_EQ(rejected.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(
rejected.status().diagnostics()[0U].code,
rejected.GetStatus().Diagnostics()[0U].code,
"unsupported-drilling-load");
}
@@ -346,7 +346,7 @@ TEST(LoadAssembly, FormsNonzeroPrescribedEffectiveRhs) {
{"10", 6, 40.0, {source, 35U}}});
auto full = fesa::LoadAssembler::assembleFullNodalLoad(
*fixture.model, *fixture.dofs);
ASSERT_TRUE(full.hasValue());
ASSERT_TRUE(full.HasValue());
const auto kfc = makeDenseSparse(
4U,
2U,
@@ -356,11 +356,11 @@ TEST(LoadAssembly, FormsNonzeroPrescribedEffectiveRhs) {
0.5, -1.0});
auto rhs = fesa::LoadAssembler::effectiveFreeRhs(
full.value(), kfc, fixture.dofs->prescribedValues(), *fixture.dofs);
ASSERT_TRUE(rhs.hasValue());
ASSERT_EQ(rhs.value().size(), 4U);
full.Value(), kfc, fixture.dofs->prescribedValues(), *fixture.dofs);
ASSERT_TRUE(rhs.HasValue());
ASSERT_EQ(rhs.Value().Size(), 4U);
EXPECT_EQ(
std::vector<double>(rhs.value().data(), rhs.value().data() + 4U),
std::vector<double>(rhs.Value().Data(), rhs.Value().Data() + 4U),
(std::vector<double>{10.0, 18.0, 39.0, 38.0}));
}
@@ -469,22 +469,22 @@ TEST(LoadAssembly, ZeroLoadsRemainZero) {
{{"10", 3, 0.0, {source, 30U}}});
auto full = fesa::LoadAssembler::assembleFullNodalLoad(
*freeFixture.model, *freeFixture.dofs);
ASSERT_TRUE(full.hasValue());
ASSERT_TRUE(full.HasValue());
EXPECT_TRUE(std::all_of(
full.value().data(),
full.value().data() + full.value().size(),
full.Value().Data(),
full.Value().Data() + full.Value().Size(),
[](const double value) { return value == 0.0; }));
const auto noConstrainedColumns = makeDenseSparse(6U, 0U, {});
auto freeRhs = fesa::LoadAssembler::effectiveFreeRhs(
full.value(),
full.Value(),
noConstrainedColumns,
freeFixture.dofs->prescribedValues(),
*freeFixture.dofs);
ASSERT_TRUE(freeRhs.hasValue());
EXPECT_EQ(freeRhs.value().size(), 6U);
ASSERT_TRUE(freeRhs.HasValue());
EXPECT_EQ(freeRhs.Value().Size(), 6U);
EXPECT_TRUE(std::all_of(
freeRhs.value().data(),
freeRhs.value().data() + freeRhs.value().size(),
freeRhs.Value().Data(),
freeRhs.Value().Data() + freeRhs.Value().Size(),
[](const double value) { return value == 0.0; }));
auto constrainedFixture = makeFixture(
@@ -494,13 +494,13 @@ TEST(LoadAssembly, ZeroLoadsRemainZero) {
{});
auto constrainedFull = fesa::LoadAssembler::assembleFullNodalLoad(
*constrainedFixture.model, *constrainedFixture.dofs);
ASSERT_TRUE(constrainedFull.hasValue());
ASSERT_TRUE(constrainedFull.HasValue());
const auto noFreeRows = makeDenseSparse(0U, 6U, {});
auto constrainedRhs = fesa::LoadAssembler::effectiveFreeRhs(
constrainedFull.value(),
constrainedFull.Value(),
noFreeRows,
constrainedFixture.dofs->prescribedValues(),
*constrainedFixture.dofs);
ASSERT_TRUE(constrainedRhs.hasValue());
EXPECT_EQ(constrainedRhs.value().size(), 0U);
ASSERT_TRUE(constrainedRhs.HasValue());
EXPECT_EQ(constrainedRhs.Value().Size(), 0U);
}
+84 -84
View File
@@ -115,10 +115,10 @@ fesa::Result<fesa::Mitc4Stiffness> directShellStiffness(
directors,
domain.shellSections().at(definition.sectionIndex),
domain.materials().at(definition.materialIndex));
if (!shell.hasValue()) {
return fesa::Result<fesa::Mitc4Stiffness>::failure(shell.status());
if (!shell.HasValue()) {
return fesa::Result<fesa::Mitc4Stiffness>::Failure(shell.GetStatus());
}
return shell.value().stiffness();
return shell.Value().stiffness();
}
fesa::Result<fesa::SparseMatrix> assembleShell(
@@ -127,19 +127,19 @@ fesa::Result<fesa::SparseMatrix> assembleShell(
const bool twoElements = false) {
auto domain = fesa::Domain::create(
makeShellDefinition(sourceType, twoElements));
if (!domain.hasValue()) {
return fesa::Result<fesa::SparseMatrix>::failure(domain.status());
if (!domain.HasValue()) {
return fesa::Result<fesa::SparseMatrix>::Failure(domain.GetStatus());
}
auto model = fesa::AnalysisModel::create(domain.value());
if (!model.hasValue()) {
return fesa::Result<fesa::SparseMatrix>::failure(model.status());
auto model = fesa::AnalysisModel::create(domain.Value());
if (!model.HasValue()) {
return fesa::Result<fesa::SparseMatrix>::Failure(model.GetStatus());
}
auto dofs = fesa::DofManager::create(model.value());
if (!dofs.hasValue()) {
return fesa::Result<fesa::SparseMatrix>::failure(dofs.status());
auto dofs = fesa::DofManager::create(model.Value());
if (!dofs.HasValue()) {
return fesa::Result<fesa::SparseMatrix>::Failure(dofs.GetStatus());
}
return fesa::SparseAssembler::assembleStiffness(
model.value(), dofs.value(), parallelFor);
model.Value(), dofs.Value(), parallelFor);
}
template<class T>
@@ -154,14 +154,14 @@ double entry(
const fesa::SparseMatrix& matrix,
const std::size_t row,
const std::size_t column) {
const auto begin = matrix.columnIndices().begin() + matrix.rowOffsets()[row];
const auto end = matrix.columnIndices().begin() + matrix.rowOffsets()[row + 1U];
const auto begin = matrix.ColumnIndices().begin() + matrix.RowOffsets()[row];
const auto end = matrix.ColumnIndices().begin() + matrix.RowOffsets()[row + 1U];
const auto found = std::lower_bound(begin, end, column);
if (found == end || *found != column) {
return 0.0;
}
return matrix.values()[static_cast<std::size_t>(
std::distance(matrix.columnIndices().begin(), found))];
return matrix.Values()[static_cast<std::size_t>(
std::distance(matrix.ColumnIndices().begin(), found))];
}
class ReverseParallelFor final : public fesa::ParallelFor {
@@ -192,66 +192,66 @@ private:
void expectByteIdentical(
const fesa::SparseMatrix& actual,
const fesa::SparseMatrix& expected) {
EXPECT_TRUE(byteIdentical(actual.rowOffsets(), expected.rowOffsets()));
EXPECT_TRUE(byteIdentical(actual.columnIndices(), expected.columnIndices()));
EXPECT_TRUE(byteIdentical(actual.values(), expected.values()));
EXPECT_TRUE(byteIdentical(actual.RowOffsets(), expected.RowOffsets()));
EXPECT_TRUE(byteIdentical(actual.ColumnIndices(), expected.ColumnIndices()));
EXPECT_TRUE(byteIdentical(actual.Values(), expected.Values()));
}
TEST(SparseAssembly, SerialTbbAndRepeatedRunsAreByteIdentical) {
auto domainResult = fesa::Domain::create(makeDefinition());
ASSERT_TRUE(domainResult.hasValue());
auto modelResult = fesa::AnalysisModel::create(domainResult.value());
ASSERT_TRUE(modelResult.hasValue());
auto dofsResult = fesa::DofManager::create(modelResult.value());
ASSERT_TRUE(dofsResult.hasValue());
ASSERT_TRUE(domainResult.HasValue());
auto modelResult = fesa::AnalysisModel::create(domainResult.Value());
ASSERT_TRUE(modelResult.HasValue());
auto dofsResult = fesa::DofManager::create(modelResult.Value());
ASSERT_TRUE(dofsResult.HasValue());
fesa::SerialParallelFor serialExecutor;
fesa::TbbParallelFor tbbExecutor;
ReverseParallelFor reverseExecutor;
auto serial = fesa::SparseAssembler::assembleStiffness(
modelResult.value(), dofsResult.value(), serialExecutor);
modelResult.Value(), dofsResult.Value(), serialExecutor);
auto tbb = fesa::SparseAssembler::assembleStiffness(
modelResult.value(), dofsResult.value(), tbbExecutor);
modelResult.Value(), dofsResult.Value(), tbbExecutor);
auto reversed = fesa::SparseAssembler::assembleStiffness(
modelResult.value(), dofsResult.value(), reverseExecutor);
ASSERT_TRUE(serial.hasValue());
ASSERT_TRUE(tbb.hasValue());
ASSERT_TRUE(reversed.hasValue());
modelResult.Value(), dofsResult.Value(), reverseExecutor);
ASSERT_TRUE(serial.HasValue());
ASSERT_TRUE(tbb.HasValue());
ASSERT_TRUE(reversed.HasValue());
EXPECT_EQ(reverseExecutor.calls(), 1U);
EXPECT_EQ(reverseExecutor.observedCount(), 2U);
EXPECT_EQ(serial.value().rows(), 18U);
EXPECT_EQ(serial.value().columns(), 18U);
EXPECT_EQ(serial.value().rowOffsets(), dofsResult.value().sparsePattern().rowOffsets);
EXPECT_EQ(serial.Value().Rows(), 18U);
EXPECT_EQ(serial.Value().Columns(), 18U);
EXPECT_EQ(serial.Value().RowOffsets(), dofsResult.Value().sparsePattern().rowOffsets);
EXPECT_EQ(
serial.value().columnIndices(),
dofsResult.value().sparsePattern().columnIndices);
EXPECT_TRUE(serial.value().validate().isOk());
expectByteIdentical(tbb.value(), serial.value());
expectByteIdentical(reversed.value(), serial.value());
serial.Value().ColumnIndices(),
dofsResult.Value().sparsePattern().columnIndices);
EXPECT_TRUE(serial.Value().Validate().IsOk());
expectByteIdentical(tbb.Value(), serial.Value());
expectByteIdentical(reversed.Value(), serial.Value());
for (std::size_t repetition = 0U; repetition < 8U; ++repetition) {
auto repeated = fesa::SparseAssembler::assembleStiffness(
modelResult.value(), dofsResult.value(), tbbExecutor);
ASSERT_TRUE(repeated.hasValue());
expectByteIdentical(repeated.value(), serial.value());
modelResult.Value(), dofsResult.Value(), tbbExecutor);
ASSERT_TRUE(repeated.HasValue());
expectByteIdentical(repeated.Value(), serial.Value());
}
for (std::size_t row = 0U; row < serial.value().rows(); ++row) {
for (std::size_t row = 0U; row < serial.Value().Rows(); ++row) {
for (std::size_t column = 0U;
column < serial.value().columns();
column < serial.Value().Columns();
++column) {
EXPECT_DOUBLE_EQ(
entry(serial.value(), row, column),
entry(serial.value(), column, row));
entry(serial.Value(), row, column),
entry(serial.Value(), column, row));
}
}
EXPECT_NEAR(entry(serial.value(), 0U, 0U), 120.0, 1.0e-12);
EXPECT_NEAR(entry(serial.value(), 0U, 6U), -120.0, 1.0e-12);
EXPECT_NEAR(entry(serial.value(), 6U, 6U), 200.0, 1.0e-12);
EXPECT_NEAR(entry(serial.value(), 6U, 12U), -80.0, 1.0e-12);
EXPECT_NEAR(entry(serial.value(), 12U, 12U), 80.0, 1.0e-12);
EXPECT_NEAR(entry(serial.Value(), 0U, 0U), 120.0, 1.0e-12);
EXPECT_NEAR(entry(serial.Value(), 0U, 6U), -120.0, 1.0e-12);
EXPECT_NEAR(entry(serial.Value(), 6U, 6U), 200.0, 1.0e-12);
EXPECT_NEAR(entry(serial.Value(), 6U, 12U), -80.0, 1.0e-12);
EXPECT_NEAR(entry(serial.Value(), 12U, 12U), 80.0, 1.0e-12);
}
TEST(
@@ -259,38 +259,38 @@ TEST(
AssemblesFourNodeTwentyFourDofKernelAndPreservesDiagonalSlots) {
auto domain = fesa::Domain::create(
makeShellDefinition(fesa::ShellSourceElementType::s4));
ASSERT_TRUE(domain.hasValue());
auto model = fesa::AnalysisModel::create(domain.value());
ASSERT_TRUE(model.hasValue());
auto dofs = fesa::DofManager::create(model.value());
ASSERT_TRUE(dofs.hasValue());
ASSERT_TRUE(domain.HasValue());
auto model = fesa::AnalysisModel::create(domain.Value());
ASSERT_TRUE(model.HasValue());
auto dofs = fesa::DofManager::create(model.Value());
ASSERT_TRUE(dofs.HasValue());
fesa::SerialParallelFor serialExecutor;
auto assembled = fesa::SparseAssembler::assembleStiffness(
model.value(), dofs.value(), serialExecutor);
auto expected = directShellStiffness(domain.value(), 0U);
ASSERT_TRUE(assembled.hasValue());
ASSERT_TRUE(expected.hasValue());
model.Value(), dofs.Value(), serialExecutor);
auto expected = directShellStiffness(domain.Value(), 0U);
ASSERT_TRUE(assembled.HasValue());
ASSERT_TRUE(expected.HasValue());
EXPECT_EQ(assembled.value().rows(), 24U);
EXPECT_EQ(assembled.value().columns(), 24U);
EXPECT_EQ(assembled.value().values().size(), 24U * 24U);
EXPECT_EQ(assembled.Value().Rows(), 24U);
EXPECT_EQ(assembled.Value().Columns(), 24U);
EXPECT_EQ(assembled.Value().Values().size(), 24U * 24U);
EXPECT_EQ(
assembled.value().rowOffsets(),
dofs.value().sparsePattern().rowOffsets);
assembled.Value().RowOffsets(),
dofs.Value().sparsePattern().rowOffsets);
EXPECT_EQ(
assembled.value().columnIndices(),
dofs.value().sparsePattern().columnIndices);
assembled.Value().ColumnIndices(),
dofs.Value().sparsePattern().columnIndices);
for (std::size_t row = 0U; row < 24U; ++row) {
const auto begin = assembled.value().columnIndices().begin() +
assembled.value().rowOffsets()[row];
const auto end = assembled.value().columnIndices().begin() +
assembled.value().rowOffsets()[row + 1U];
const auto begin = assembled.Value().ColumnIndices().begin() +
assembled.Value().RowOffsets()[row];
const auto end = assembled.Value().ColumnIndices().begin() +
assembled.Value().RowOffsets()[row + 1U];
EXPECT_NE(std::lower_bound(begin, end, row), end);
for (std::size_t column = 0U; column < 24U; ++column) {
EXPECT_DOUBLE_EQ(
entry(assembled.value(), row, column),
expected.value().stabilizedGlobal24(row, column));
entry(assembled.Value(), row, column),
expected.Value().stabilizedGlobal24(row, column));
}
}
}
@@ -305,19 +305,19 @@ TEST(SparseAssembly, ShellSerialTbbReverseAndRepeatedRunsAreByteIdentical) {
fesa::ShellSourceElementType::s4, tbbExecutor, true);
auto reversed = assembleShell(
fesa::ShellSourceElementType::s4, reverseExecutor, true);
ASSERT_TRUE(serial.hasValue());
ASSERT_TRUE(tbb.hasValue());
ASSERT_TRUE(reversed.hasValue());
ASSERT_TRUE(serial.HasValue());
ASSERT_TRUE(tbb.HasValue());
ASSERT_TRUE(reversed.HasValue());
EXPECT_EQ(reverseExecutor.calls(), 1U);
EXPECT_EQ(reverseExecutor.observedCount(), 2U);
expectByteIdentical(tbb.value(), serial.value());
expectByteIdentical(reversed.value(), serial.value());
expectByteIdentical(tbb.Value(), serial.Value());
expectByteIdentical(reversed.Value(), serial.Value());
for (std::size_t repetition = 0U; repetition < 8U; ++repetition) {
auto repeated = assembleShell(
fesa::ShellSourceElementType::s4, tbbExecutor, true);
ASSERT_TRUE(repeated.hasValue());
expectByteIdentical(repeated.value(), serial.value());
ASSERT_TRUE(repeated.HasValue());
expectByteIdentical(repeated.Value(), serial.Value());
}
}
@@ -327,13 +327,13 @@ TEST(SparseAssembly, S4AndS4rSemanticFixturesAssembleIdenticalStiffness) {
fesa::ShellSourceElementType::s4, serialExecutor);
auto s4r = assembleShell(
fesa::ShellSourceElementType::s4r, serialExecutor);
ASSERT_TRUE(s4.hasValue());
ASSERT_TRUE(s4r.hasValue());
ASSERT_TRUE(s4.HasValue());
ASSERT_TRUE(s4r.HasValue());
EXPECT_TRUE(std::any_of(
s4.value().values().begin(),
s4.value().values().end(),
s4.Value().Values().begin(),
s4.Value().Values().end(),
[](const double value) { return value != 0.0; }));
expectByteIdentical(s4r.value(), s4.value());
expectByteIdentical(s4r.Value(), s4.Value());
}
} // namespace
+12 -12
View File
@@ -1,4 +1,4 @@
#include "fesa/build_info.hpp"
#include "fesa/build_info.h"
#include <gtest/gtest.h>
@@ -8,20 +8,20 @@
#include <type_traits>
TEST(BuildInfo, VersionIsStableAndNonEmpty) {
const std::string_view first = fesa::solverVersion();
const std::string_view second = fesa::solverVersion();
const std::string_view first = fesa::SolverVersion();
const std::string_view second = fesa::SolverVersion();
EXPECT_FALSE(first.empty());
EXPECT_EQ(first, second);
EXPECT_TRUE(std::regex_match(
std::string(first), std::regex{R"(^[0-9]+\.[0-9]+\.[0-9]+$)"}));
EXPECT_FALSE(first.empty());
EXPECT_EQ(first, second);
EXPECT_TRUE(std::regex_match(std::string(first),
std::regex{R"(^[0-9]+\.[0-9]+\.[0-9]+$)"}));
}
TEST(BuildInfo, PublicHeaderHasNoBackendDependency) {
static_assert(
std::is_same_v<decltype(fesa::solverVersion()), std::string_view>,
"The public BuildInfo API must use only a standard-library value type.");
static_assert(noexcept(fesa::solverVersion()));
static_assert(
std::is_same_v<decltype(fesa::SolverVersion()), std::string_view>,
"The public BuildInfo API must use only a standard-library value type.");
static_assert(noexcept(fesa::SolverVersion()));
SUCCEED();
SUCCEED();
}
@@ -34,12 +34,12 @@ fesa::DofManager makeDofs(
{source, 10U}}};
auto domain = fesa::Domain::create(std::move(definition));
EXPECT_TRUE(domain.hasValue());
auto model = fesa::AnalysisModel::create(domain.value());
EXPECT_TRUE(model.hasValue());
auto dofs = fesa::DofManager::create(model.value());
EXPECT_TRUE(dofs.hasValue());
return std::move(dofs.value());
EXPECT_TRUE(domain.HasValue());
auto model = fesa::AnalysisModel::create(domain.Value());
EXPECT_TRUE(model.HasValue());
auto dofs = fesa::DofManager::create(model.Value());
EXPECT_TRUE(dofs.HasValue());
return std::move(dofs.Value());
}
fesa::DofManager makeShellSizedDofs(
@@ -75,12 +75,12 @@ fesa::DofManager makeShellSizedDofs(
{source, 10U}}};
auto domain = fesa::Domain::create(std::move(definition));
EXPECT_TRUE(domain.hasValue());
auto model = fesa::AnalysisModel::create(domain.value());
EXPECT_TRUE(model.hasValue());
auto dofs = fesa::DofManager::create(model.value());
EXPECT_TRUE(dofs.hasValue());
return std::move(dofs.value());
EXPECT_TRUE(domain.HasValue());
auto model = fesa::AnalysisModel::create(domain.Value());
EXPECT_TRUE(model.HasValue());
auto dofs = fesa::DofManager::create(model.Value());
EXPECT_TRUE(dofs.HasValue());
return std::move(dofs.Value());
}
fesa::SparseMatrix makeMatrix(
@@ -105,10 +105,10 @@ fesa::SparseMatrix makeMatrix(
pattern.rowOffsets.push_back(pattern.columnIndices.size());
}
auto matrix = fesa::SparseMatrix::fromCoo(
auto matrix = fesa::SparseMatrix::FromCoo(
rows, columns, std::move(contributions), pattern);
EXPECT_TRUE(matrix.hasValue());
return std::move(matrix.value());
EXPECT_TRUE(matrix.HasValue());
return std::move(matrix.Value());
}
std::vector<double> sequentialDense(const std::size_t size) {
@@ -126,9 +126,9 @@ void expectShape(
const fesa::SparseMatrix& matrix,
const std::size_t rows,
const std::size_t columns) {
EXPECT_EQ(matrix.rows(), rows);
EXPECT_EQ(matrix.columns(), columns);
EXPECT_TRUE(matrix.validate().isOk());
EXPECT_EQ(matrix.Rows(), rows);
EXPECT_EQ(matrix.Columns(), columns);
EXPECT_TRUE(matrix.Validate().IsOk());
}
} // namespace
@@ -142,45 +142,45 @@ TEST(EssentialConstraints, ExtractsHandComputedBlocksInStableOrder) {
const auto full = makeMatrix(6U, 6U, fullValues);
auto result = fesa::EssentialConstraints::partition(full, dofs);
ASSERT_TRUE(result.hasValue());
const auto& blocks = result.value();
ASSERT_TRUE(result.HasValue());
const auto& blocks = result.Value();
EXPECT_EQ(blocks.kff.rowOffsets(), (std::vector<std::size_t>{0U, 4U, 8U, 12U, 16U}));
EXPECT_EQ(blocks.kff.RowOffsets(), (std::vector<std::size_t>{0U, 4U, 8U, 12U, 16U}));
EXPECT_EQ(
blocks.kff.columnIndices(),
blocks.kff.ColumnIndices(),
(std::vector<std::size_t>{
0U, 1U, 2U, 3U, 0U, 1U, 2U, 3U,
0U, 1U, 2U, 3U, 0U, 1U, 2U, 3U}));
EXPECT_EQ(
blocks.kff.values(),
blocks.kff.Values(),
(std::vector<double>{
1.0, 3.0, 4.0, 6.0,
21.0, 23.0, 24.0, 26.0,
31.0, 33.0, 34.0, 36.0,
51.0, 53.0, 54.0, 56.0}));
EXPECT_EQ(blocks.kfc.rowOffsets(), (std::vector<std::size_t>{0U, 2U, 4U, 6U, 8U}));
EXPECT_EQ(blocks.kfc.RowOffsets(), (std::vector<std::size_t>{0U, 2U, 4U, 6U, 8U}));
EXPECT_EQ(
blocks.kfc.columnIndices(),
blocks.kfc.ColumnIndices(),
(std::vector<std::size_t>{0U, 1U, 0U, 1U, 0U, 1U, 0U, 1U}));
EXPECT_EQ(
blocks.kfc.values(),
blocks.kfc.Values(),
(std::vector<double>{2.0, 5.0, 22.0, 0.0, 32.0, 35.0, 52.0, 55.0}));
EXPECT_EQ(blocks.kcf.rowOffsets(), (std::vector<std::size_t>{0U, 4U, 8U}));
EXPECT_EQ(blocks.kcf.RowOffsets(), (std::vector<std::size_t>{0U, 4U, 8U}));
EXPECT_EQ(
blocks.kcf.columnIndices(),
blocks.kcf.ColumnIndices(),
(std::vector<std::size_t>{0U, 1U, 2U, 3U, 0U, 1U, 2U, 3U}));
EXPECT_EQ(
blocks.kcf.values(),
blocks.kcf.Values(),
(std::vector<double>{11.0, 13.0, 14.0, 16.0, 41.0, 43.0, 44.0, 46.0}));
EXPECT_EQ(blocks.kcc.rowOffsets(), (std::vector<std::size_t>{0U, 2U, 4U}));
EXPECT_EQ(blocks.kcc.columnIndices(), (std::vector<std::size_t>{0U, 1U, 0U, 1U}));
EXPECT_EQ(blocks.kcc.values(), (std::vector<double>{12.0, 15.0, 42.0, 45.0}));
EXPECT_EQ(blocks.kcc.RowOffsets(), (std::vector<std::size_t>{0U, 2U, 4U}));
EXPECT_EQ(blocks.kcc.ColumnIndices(), (std::vector<std::size_t>{0U, 1U, 0U, 1U}));
EXPECT_EQ(blocks.kcc.Values(), (std::vector<double>{12.0, 15.0, 42.0, 45.0}));
EXPECT_EQ(blocks.kfc.values()[3U], 0.0);
EXPECT_TRUE(blocks.kff.validate().isOk());
EXPECT_TRUE(blocks.kfc.validate().isOk());
EXPECT_TRUE(blocks.kcf.validate().isOk());
EXPECT_TRUE(blocks.kcc.validate().isOk());
EXPECT_EQ(blocks.kfc.Values()[3U], 0.0);
EXPECT_TRUE(blocks.kff.Validate().IsOk());
EXPECT_TRUE(blocks.kfc.Validate().IsOk());
EXPECT_TRUE(blocks.kcf.Validate().IsOk());
EXPECT_TRUE(blocks.kcc.Validate().IsOk());
}
TEST(EssentialConstraints, HandlesNoAllAndMixedConstraints) {
@@ -188,29 +188,29 @@ TEST(EssentialConstraints, HandlesNoAllAndMixedConstraints) {
const auto noConstraints = makeDofs({});
auto none = fesa::EssentialConstraints::partition(full, noConstraints);
ASSERT_TRUE(none.hasValue());
expectShape(none.value().kff, 6U, 6U);
expectShape(none.value().kfc, 6U, 0U);
expectShape(none.value().kcf, 0U, 6U);
expectShape(none.value().kcc, 0U, 0U);
EXPECT_EQ(none.value().kff.values(), full.values());
ASSERT_TRUE(none.HasValue());
expectShape(none.Value().kff, 6U, 6U);
expectShape(none.Value().kfc, 6U, 0U);
expectShape(none.Value().kcf, 0U, 6U);
expectShape(none.Value().kcc, 0U, 0U);
EXPECT_EQ(none.Value().kff.Values(), full.Values());
const auto allConstraints = makeDofs({{"1", 1, 6, 1.0, {{}, 12U}}});
auto all = fesa::EssentialConstraints::partition(full, allConstraints);
ASSERT_TRUE(all.hasValue());
expectShape(all.value().kff, 0U, 0U);
expectShape(all.value().kfc, 0U, 6U);
expectShape(all.value().kcf, 6U, 0U);
expectShape(all.value().kcc, 6U, 6U);
EXPECT_EQ(all.value().kcc.values(), full.values());
ASSERT_TRUE(all.HasValue());
expectShape(all.Value().kff, 0U, 0U);
expectShape(all.Value().kfc, 0U, 6U);
expectShape(all.Value().kcf, 6U, 0U);
expectShape(all.Value().kcc, 6U, 6U);
EXPECT_EQ(all.Value().kcc.Values(), full.Values());
const auto mixedConstraints = makeDofs({{"1", 3, 4, 0.0, {{}, 12U}}});
auto mixed = fesa::EssentialConstraints::partition(full, mixedConstraints);
ASSERT_TRUE(mixed.hasValue());
expectShape(mixed.value().kff, 4U, 4U);
expectShape(mixed.value().kfc, 4U, 2U);
expectShape(mixed.value().kcf, 2U, 4U);
expectShape(mixed.value().kcc, 2U, 2U);
ASSERT_TRUE(mixed.HasValue());
expectShape(mixed.Value().kff, 4U, 4U);
expectShape(mixed.Value().kfc, 4U, 2U);
expectShape(mixed.Value().kcf, 2U, 4U);
expectShape(mixed.Value().kcc, 2U, 2U);
}
// MITC4-DOF-003
@@ -219,24 +219,24 @@ TEST(EssentialConstraints, PreservesShellSizedNoMixedAndAllConstraintRoundTrips)
const auto noConstraints = makeShellSizedDofs({});
auto none = fesa::EssentialConstraints::partition(full, noConstraints);
ASSERT_TRUE(none.hasValue());
expectShape(none.value().kff, 24U, 24U);
expectShape(none.value().kfc, 24U, 0U);
expectShape(none.value().kcf, 0U, 24U);
expectShape(none.value().kcc, 0U, 0U);
ASSERT_TRUE(none.HasValue());
expectShape(none.Value().kff, 24U, 24U);
expectShape(none.Value().kfc, 24U, 0U);
expectShape(none.Value().kcf, 0U, 24U);
expectShape(none.Value().kcc, 0U, 0U);
const auto mixedConstraints = makeShellSizedDofs({
{"1", 1, 6, 0.0, {{}, 12U}},
{"4", 2, 2, 2.5, {{}, 13U}}});
auto mixed = fesa::EssentialConstraints::partition(full, mixedConstraints);
ASSERT_TRUE(mixed.hasValue());
expectShape(mixed.value().kff, 17U, 17U);
expectShape(mixed.value().kfc, 17U, 7U);
expectShape(mixed.value().kcf, 7U, 17U);
expectShape(mixed.value().kcc, 7U, 7U);
ASSERT_TRUE(mixed.HasValue());
expectShape(mixed.Value().kff, 17U, 17U);
expectShape(mixed.Value().kfc, 17U, 7U);
expectShape(mixed.Value().kcf, 7U, 17U);
expectShape(mixed.Value().kcc, 7U, 7U);
fesa::Vector mixedFull{24U};
for (std::size_t index = 0U; index < mixedFull.size(); ++index) {
for (std::size_t index = 0U; index < mixedFull.Size(); ++index) {
mixedFull[index] = static_cast<double>(index) + 0.5;
}
for (std::size_t index = 0U;
@@ -250,8 +250,8 @@ TEST(EssentialConstraints, PreservesShellSizedNoMixedAndAllConstraintRoundTrips)
const auto mixedReconstructed =
fesa::EssentialConstraints::reconstructFull(
mixedFree, mixedConstraints.prescribedValues(), mixedConstraints);
ASSERT_EQ(mixedReconstructed.size(), mixedFull.size());
for (std::size_t index = 0U; index < mixedFull.size(); ++index) {
ASSERT_EQ(mixedReconstructed.Size(), mixedFull.Size());
for (std::size_t index = 0U; index < mixedFull.Size(); ++index) {
EXPECT_DOUBLE_EQ(mixedReconstructed[index], mixedFull[index]);
}
@@ -261,18 +261,18 @@ TEST(EssentialConstraints, PreservesShellSizedNoMixedAndAllConstraintRoundTrips)
{"3", 1, 6, 3.0, {{}, 16U}},
{"4", 1, 6, 4.0, {{}, 17U}}});
auto all = fesa::EssentialConstraints::partition(full, allConstraints);
ASSERT_TRUE(all.hasValue());
expectShape(all.value().kff, 0U, 0U);
expectShape(all.value().kfc, 0U, 24U);
expectShape(all.value().kcf, 24U, 0U);
expectShape(all.value().kcc, 24U, 24U);
ASSERT_TRUE(all.HasValue());
expectShape(all.Value().kff, 0U, 0U);
expectShape(all.Value().kfc, 0U, 24U);
expectShape(all.Value().kcf, 24U, 0U);
expectShape(all.Value().kcc, 24U, 24U);
const auto allReconstructed =
fesa::EssentialConstraints::reconstructFull(
fesa::Vector{0U},
allConstraints.prescribedValues(),
allConstraints);
ASSERT_EQ(allReconstructed.size(), 24U);
ASSERT_EQ(allReconstructed.Size(), 24U);
for (std::size_t node = 0U; node < 4U; ++node) {
for (std::size_t component = 0U; component < 6U; ++component) {
EXPECT_DOUBLE_EQ(allReconstructed[node * 6U + component], node + 1.0);
@@ -295,12 +295,12 @@ TEST(EssentialConstraints, ReconstructsNonzeroPrescribedValues) {
const auto free = fesa::EssentialConstraints::gatherFree(full, dofs);
const auto constrained =
fesa::EssentialConstraints::gatherConstrained(full, dofs);
EXPECT_EQ(free.size(), 4U);
EXPECT_EQ(free.Size(), 4U);
EXPECT_DOUBLE_EQ(free[0U], 10.0);
EXPECT_DOUBLE_EQ(free[1U], 20.0);
EXPECT_DOUBLE_EQ(free[2U], 30.0);
EXPECT_DOUBLE_EQ(free[3U], 40.0);
EXPECT_EQ(constrained.size(), 2U);
EXPECT_EQ(constrained.Size(), 2U);
EXPECT_DOUBLE_EQ(constrained[0U], 2.5);
EXPECT_DOUBLE_EQ(constrained[1U], -3.25);
EXPECT_EQ(constrained[0U], dofs.prescribedValues()[0U]);
@@ -308,8 +308,8 @@ TEST(EssentialConstraints, ReconstructsNonzeroPrescribedValues) {
const auto reconstructed = fesa::EssentialConstraints::reconstructFull(
free, dofs.prescribedValues(), dofs);
ASSERT_EQ(reconstructed.size(), full.size());
for (std::size_t index = 0U; index < full.size(); ++index) {
ASSERT_EQ(reconstructed.Size(), full.Size());
for (std::size_t index = 0U; index < full.Size(); ++index) {
EXPECT_DOUBLE_EQ(reconstructed[index], full[index]);
}
}
@@ -319,21 +319,21 @@ TEST(EssentialConstraints, RejectsDimensionOrOrderMismatch) {
const auto wrongSquare = makeMatrix(5U, 5U, sequentialDense(5U));
auto wrongDimension =
fesa::EssentialConstraints::partition(wrongSquare, dofs);
ASSERT_FALSE(wrongDimension.hasValue());
ASSERT_FALSE(wrongDimension.HasValue());
EXPECT_EQ(
wrongDimension.status().failureCategory(),
fesa::FailureCategory::model);
ASSERT_EQ(wrongDimension.status().diagnostics().size(), 1U);
wrongDimension.GetStatus().Category(),
fesa::FailureCategory::kModel);
ASSERT_EQ(wrongDimension.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(
wrongDimension.status().diagnostics()[0U].code,
wrongDimension.GetStatus().Diagnostics()[0U].code,
"invalid-constraint-dimensions");
const auto rectangular = makeMatrix(
6U, 5U, std::vector<double>(30U, 0.0));
auto wrongOrder = fesa::EssentialConstraints::partition(rectangular, dofs);
ASSERT_FALSE(wrongOrder.hasValue());
ASSERT_FALSE(wrongOrder.HasValue());
EXPECT_EQ(
wrongOrder.status().diagnostics()[0U].code,
wrongOrder.GetStatus().Diagnostics()[0U].code,
"invalid-constraint-dimensions");
EXPECT_THROW(
+37 -41
View File
@@ -1,4 +1,4 @@
#include "fesa/core/diagnostic.hpp"
#include "fesa/core/diagnostic.h"
#include <gtest/gtest.h>
@@ -9,51 +9,47 @@
namespace {
fesa::Diagnostic makeDiagnostic(
std::string file,
std::size_t line,
std::string keyword,
std::string entityIdentity,
std::string code,
std::string message) {
return fesa::Diagnostic{
fesa::Severity::error,
std::move(code),
{std::filesystem::path{std::move(file)}, line},
std::move(keyword),
std::move(entityIdentity),
std::move(message)};
fesa::Diagnostic MakeDiagnostic(std::string file, std::size_t line,
std::string keyword,
std::string entity_identity, std::string code,
std::string message) {
return fesa::Diagnostic{fesa::Severity::kError,
std::move(code),
{std::filesystem::path{std::move(file)}, line},
std::move(keyword),
std::move(entity_identity),
std::move(message)};
}
} // namespace
} // namespace
TEST(CoreDiagnostics, DiagnosticsSortDeterministically) {
std::vector<fesa::Diagnostic> diagnostics{
makeDiagnostic("b.inp", 1U, "*NODE", "I.1", "a", "file-b"),
makeDiagnostic("a.inp", 3U, "*NODE", "I.1", "z", "code-z"),
makeDiagnostic("a.inp", 3U, "*NODE", "I.1", "a", "first-equal"),
makeDiagnostic("a.inp", 3U, "*NODE", "I.1", "a", "second-equal"),
makeDiagnostic("a.inp", 3U, "*NODE", "I.2", "a", "entity-2"),
makeDiagnostic("a.inp", 3U, "*BOUNDARY", "I.1", "a", "keyword"),
makeDiagnostic("a.inp", 2U, "*NODE", "I.1", "a", "line")};
std::vector<fesa::Diagnostic> diagnostics{
MakeDiagnostic("b.inp", 1U, "*NODE", "I.1", "a", "file-b"),
MakeDiagnostic("a.inp", 3U, "*NODE", "I.1", "z", "code-z"),
MakeDiagnostic("a.inp", 3U, "*NODE", "I.1", "a", "first-equal"),
MakeDiagnostic("a.inp", 3U, "*NODE", "I.1", "a", "second-equal"),
MakeDiagnostic("a.inp", 3U, "*NODE", "I.2", "a", "entity-2"),
MakeDiagnostic("a.inp", 3U, "*BOUNDARY", "I.1", "a", "keyword"),
MakeDiagnostic("a.inp", 2U, "*NODE", "I.1", "a", "line")};
fesa::sortDiagnostics(diagnostics);
fesa::SortDiagnostics(diagnostics);
ASSERT_EQ(diagnostics.size(), 7U);
EXPECT_EQ(diagnostics[0].message, "line");
EXPECT_EQ(diagnostics[1].message, "keyword");
EXPECT_EQ(diagnostics[2].message, "first-equal");
EXPECT_EQ(diagnostics[3].message, "second-equal");
EXPECT_EQ(diagnostics[4].message, "code-z");
EXPECT_EQ(diagnostics[5].message, "entity-2");
EXPECT_EQ(diagnostics[6].message, "file-b");
ASSERT_EQ(diagnostics.size(), 7U);
EXPECT_EQ(diagnostics[0].message, "line");
EXPECT_EQ(diagnostics[1].message, "keyword");
EXPECT_EQ(diagnostics[2].message, "first-equal");
EXPECT_EQ(diagnostics[3].message, "second-equal");
EXPECT_EQ(diagnostics[4].message, "code-z");
EXPECT_EQ(diagnostics[5].message, "entity-2");
EXPECT_EQ(diagnostics[6].message, "file-b");
const fesa::Diagnostic& exact = diagnostics[2];
EXPECT_EQ(exact.severity, fesa::Severity::error);
EXPECT_EQ(exact.code, "a");
EXPECT_EQ(exact.location.file, std::filesystem::path{"a.inp"});
EXPECT_EQ(exact.location.line, 3U);
EXPECT_EQ(exact.keyword, "*NODE");
EXPECT_EQ(exact.entityIdentity, "I.1");
EXPECT_EQ(exact.message, "first-equal");
const fesa::Diagnostic& exact = diagnostics[2];
EXPECT_EQ(exact.severity, fesa::Severity::kError);
EXPECT_EQ(exact.code, "a");
EXPECT_EQ(exact.location.file, std::filesystem::path{"a.inp"});
EXPECT_EQ(exact.location.line, 3U);
EXPECT_EQ(exact.keyword, "*NODE");
EXPECT_EQ(exact.entity_identity, "I.1");
EXPECT_EQ(exact.message, "first-equal");
}
+10 -10
View File
@@ -1,4 +1,4 @@
#include "fesa/core/source_identity.hpp"
#include "fesa/core/source_identity.h"
#include <gtest/gtest.h>
@@ -7,14 +7,14 @@
#include <string>
TEST(CoreDiagnostics, SourceIdentityPreservesRawIdentity) {
const fesa::SourceLocation location{
std::filesystem::path{"models/My Beam.inp"}, 27U};
const fesa::SourceEntityId identity{
"Beam-Instance_A", std::int64_t{42}, "00042"};
const fesa::SourceLocation location{
std::filesystem::path{"models/My Beam.inp"}, 27U};
const fesa::SourceEntityId identity{"Beam-Instance_A", std::int64_t{42},
"00042"};
EXPECT_EQ(location.file, std::filesystem::path{"models/My Beam.inp"});
EXPECT_EQ(location.line, 27U);
EXPECT_EQ(identity.instanceName, "Beam-Instance_A");
EXPECT_EQ(identity.sourceLabel, 42);
EXPECT_EQ(identity.sourceLabelText, "00042");
EXPECT_EQ(location.file, std::filesystem::path{"models/My Beam.inp"});
EXPECT_EQ(location.line, 27U);
EXPECT_EQ(identity.instance_name, "Beam-Instance_A");
EXPECT_EQ(identity.source_label, 42);
EXPECT_EQ(identity.source_label_text, "00042");
}
+42 -44
View File
@@ -1,4 +1,4 @@
#include "fesa/core/status.hpp"
#include "fesa/core/status.h"
#include <gtest/gtest.h>
@@ -10,57 +10,55 @@
namespace {
fesa::Diagnostic modelDiagnostic() {
return fesa::Diagnostic{
fesa::Severity::error,
"invalid-beam-length",
{std::filesystem::path{"beam.inp"}, 12U},
"*ELEMENT",
"Beam-1.10",
"Beam length must be positive."};
fesa::Diagnostic ModelDiagnostic() {
return fesa::Diagnostic{fesa::Severity::kError,
"invalid-beam-length",
{std::filesystem::path{"beam.inp"}, 12U},
"*ELEMENT",
"Beam-1.10",
"Beam length must be positive."};
}
} // namespace
} // namespace
TEST(CoreDiagnostics, ResultEnforcesValueErrorExclusivity) {
const fesa::Status ok = fesa::Status::ok();
EXPECT_TRUE(ok.isOk());
EXPECT_FALSE(ok.failureCategory().has_value());
EXPECT_TRUE(ok.diagnostics().empty());
const fesa::Status ok = fesa::Status::Ok();
EXPECT_TRUE(ok.IsOk());
EXPECT_FALSE(ok.Category().has_value());
EXPECT_TRUE(ok.Diagnostics().empty());
const fesa::Status uncategorized =
fesa::Status::failure(std::vector<fesa::Diagnostic>{modelDiagnostic()});
EXPECT_FALSE(uncategorized.isOk());
EXPECT_FALSE(uncategorized.failureCategory().has_value());
ASSERT_EQ(uncategorized.diagnostics().size(), 1U);
EXPECT_EQ(uncategorized.diagnostics()[0].code, "invalid-beam-length");
const fesa::Status uncategorized =
fesa::Status::Failure(std::vector<fesa::Diagnostic>{ModelDiagnostic()});
EXPECT_FALSE(uncategorized.IsOk());
EXPECT_FALSE(uncategorized.Category().has_value());
ASSERT_EQ(uncategorized.Diagnostics().size(), 1U);
EXPECT_EQ(uncategorized.Diagnostics()[0].code, "invalid-beam-length");
const fesa::Status categorized = fesa::Status::failure(
fesa::FailureCategory::model,
std::vector<fesa::Diagnostic>{modelDiagnostic()});
EXPECT_FALSE(categorized.isOk());
ASSERT_TRUE(categorized.failureCategory().has_value());
EXPECT_EQ(*categorized.failureCategory(), fesa::FailureCategory::model);
const fesa::Status categorized =
fesa::Status::Failure(fesa::FailureCategory::kModel,
std::vector<fesa::Diagnostic>{ModelDiagnostic()});
EXPECT_FALSE(categorized.IsOk());
ASSERT_TRUE(categorized.Category().has_value());
EXPECT_EQ(*categorized.Category(), fesa::FailureCategory::kModel);
const auto success = fesa::Result<std::string>::success("solved");
EXPECT_TRUE(success.hasValue());
EXPECT_TRUE(success.status().isOk());
EXPECT_EQ(success.value(), "solved");
const auto success = fesa::Result<std::string>::Success("solved");
EXPECT_TRUE(success.HasValue());
EXPECT_TRUE(success.GetStatus().IsOk());
EXPECT_EQ(success.Value(), "solved");
auto copied = success;
EXPECT_EQ(copied.value(), "solved");
auto moved = std::move(copied);
EXPECT_EQ(moved.value(), "solved");
auto copied = success;
EXPECT_EQ(copied.Value(), "solved");
auto moved = std::move(copied);
EXPECT_EQ(moved.Value(), "solved");
auto failure = fesa::Result<std::string>::failure(categorized);
EXPECT_FALSE(failure.hasValue());
EXPECT_FALSE(failure.status().isOk());
EXPECT_EQ(failure.status().failureCategory(), fesa::FailureCategory::model);
EXPECT_THROW(failure.value(), std::logic_error);
auto failure = fesa::Result<std::string>::Failure(categorized);
EXPECT_FALSE(failure.HasValue());
EXPECT_FALSE(failure.GetStatus().IsOk());
EXPECT_EQ(failure.GetStatus().Category(), fesa::FailureCategory::kModel);
EXPECT_THROW(failure.Value(), std::logic_error);
const auto& constFailure = failure;
EXPECT_THROW(constFailure.value(), std::logic_error);
EXPECT_THROW(
(void)fesa::Result<std::string>::failure(fesa::Status::ok()),
std::invalid_argument);
const auto& const_failure = failure;
EXPECT_THROW(const_failure.Value(), std::logic_error);
EXPECT_THROW((void)fesa::Result<std::string>::Failure(fesa::Status::Ok()),
std::invalid_argument);
}
+44 -44
View File
@@ -50,10 +50,10 @@ EulerBeam3D requireBeam(const Node& firstNode,
const GeneralBeamSection& section,
const LinearElasticMaterial& material) {
auto result = EulerBeam3D::create(firstNode, secondNode, section, material);
if (!result.hasValue()) {
if (!result.HasValue()) {
throw std::runtime_error{"Expected a valid EulerBeam3D fixture."};
}
return std::move(result.value());
return std::move(result.Value());
}
EulerBeam3D alignedBeam(double length,
@@ -68,8 +68,8 @@ EulerBeam3D alignedBeam(double length,
double maximumAbsoluteEntry(const Matrix& matrix) {
double maximum = 0.0;
for (std::size_t row = 0; row < matrix.rows(); ++row) {
for (std::size_t column = 0; column < matrix.columns(); ++column) {
for (std::size_t row = 0; row < matrix.Rows(); ++row) {
for (std::size_t column = 0; column < matrix.Columns(); ++column) {
maximum = (std::max)(maximum, std::abs(matrix(row, column)));
}
}
@@ -77,8 +77,8 @@ double maximumAbsoluteEntry(const Matrix& matrix) {
}
bool matrixIsFinite(const Matrix& matrix) {
for (std::size_t row = 0; row < matrix.rows(); ++row) {
for (std::size_t column = 0; column < matrix.columns(); ++column) {
for (std::size_t row = 0; row < matrix.Rows(); ++row) {
for (std::size_t column = 0; column < matrix.Columns(); ++column) {
if (!std::isfinite(matrix(row, column))) {
return false;
}
@@ -88,13 +88,13 @@ bool matrixIsFinite(const Matrix& matrix) {
}
double normalizedMatrixError(const Matrix& actual, const Matrix& expected) {
if (actual.rows() != expected.rows() || actual.columns() != expected.columns()) {
if (actual.Rows() != expected.Rows() || actual.Columns() != expected.Columns()) {
throw std::invalid_argument{"Matrix comparison requires equal shapes."};
}
double maximumDifference = 0.0;
for (std::size_t row = 0; row < actual.rows(); ++row) {
for (std::size_t column = 0; column < actual.columns(); ++column) {
for (std::size_t row = 0; row < actual.Rows(); ++row) {
for (std::size_t column = 0; column < actual.Columns(); ++column) {
maximumDifference = (std::max)(
maximumDifference,
std::abs(actual(row, column) - expected(row, column)));
@@ -109,16 +109,16 @@ double normalizedMatrixError(const Matrix& actual, const Matrix& expected) {
double vectorNorm(const Vector& vector) {
double sum = 0.0;
for (std::size_t index = 0; index < vector.size(); ++index) {
for (std::size_t index = 0; index < vector.Size(); ++index) {
sum += vector[index] * vector[index];
}
return std::sqrt(sum);
}
double quadraticEnergy(const Matrix& matrix, const Vector& vector) {
const Vector product = matrix.multiply(vector);
const Vector product = matrix.Multiply(vector);
double value = 0.0;
for (std::size_t index = 0; index < vector.size(); ++index) {
for (std::size_t index = 0; index < vector.Size(); ++index) {
value += vector[index] * product[index];
}
return value;
@@ -328,14 +328,14 @@ Vector solveFixedFirstNode(const Matrix& stiffness,
}
Vector solveDenseSystem(Matrix matrix, Vector rightHandSide) {
if (matrix.rows() != matrix.columns() ||
matrix.rows() != rightHandSide.size()) {
if (matrix.Rows() != matrix.Columns() ||
matrix.Rows() != rightHandSide.Size()) {
throw std::invalid_argument{"Dense test solve requires a square system."};
}
for (std::size_t pivot = 0; pivot < matrix.rows(); ++pivot) {
for (std::size_t pivot = 0; pivot < matrix.Rows(); ++pivot) {
std::size_t pivotRow = pivot;
for (std::size_t row = pivot + 1U; row < matrix.rows(); ++row) {
for (std::size_t row = pivot + 1U; row < matrix.Rows(); ++row) {
if (std::abs(matrix(row, pivot)) >
std::abs(matrix(pivotRow, pivot))) {
pivotRow = row;
@@ -345,22 +345,22 @@ Vector solveDenseSystem(Matrix matrix, Vector rightHandSide) {
!std::isfinite(matrix(pivotRow, pivot))) {
throw std::runtime_error{"Uniform-load test fixture is singular."};
}
for (std::size_t column = pivot; column < matrix.columns(); ++column) {
for (std::size_t column = pivot; column < matrix.Columns(); ++column) {
std::swap(matrix(pivot, column), matrix(pivotRow, column));
}
std::swap(rightHandSide[pivot], rightHandSide[pivotRow]);
const double pivotValue = matrix(pivot, pivot);
for (std::size_t column = pivot; column < matrix.columns(); ++column) {
for (std::size_t column = pivot; column < matrix.Columns(); ++column) {
matrix(pivot, column) /= pivotValue;
}
rightHandSide[pivot] /= pivotValue;
for (std::size_t row = 0; row < matrix.rows(); ++row) {
for (std::size_t row = 0; row < matrix.Rows(); ++row) {
if (row == pivot) {
continue;
}
const double factor = matrix(row, pivot);
for (std::size_t column = pivot; column < matrix.columns(); ++column) {
for (std::size_t column = pivot; column < matrix.Columns(); ++column) {
matrix(row, column) -= factor * matrix(pivot, column);
}
rightHandSide[row] -= factor * rightHandSide[pivot];
@@ -489,12 +489,12 @@ Matrix transformationFromKnownRows(
}
Vector transposeMultiply(const Matrix& matrix, const Vector& vector) {
if (matrix.rows() != vector.size()) {
if (matrix.Rows() != vector.Size()) {
throw std::invalid_argument{"Transpose multiply dimension mismatch."};
}
Vector result{matrix.columns()};
for (std::size_t column = 0; column < matrix.columns(); ++column) {
for (std::size_t row = 0; row < matrix.rows(); ++row) {
Vector result{matrix.Columns()};
for (std::size_t column = 0; column < matrix.Columns(); ++column) {
for (std::size_t row = 0; row < matrix.Rows(); ++row) {
result[column] += matrix(row, column) * vector[row];
}
}
@@ -595,9 +595,9 @@ TEST(EulerBeam3D, TwoPointGaussMatchesClosedStiffness) {
const Matrix closed = expectedClosedStiffness(length, section, material);
EXPECT_LE(normalizedMatrixError(actual, closed), kMatrixTolerance);
Matrix transpose{actual.rows(), actual.columns()};
for (std::size_t row = 0; row < actual.rows(); ++row) {
for (std::size_t column = 0; column < actual.columns(); ++column) {
Matrix transpose{actual.Rows(), actual.Columns()};
for (std::size_t row = 0; row < actual.Rows(); ++row) {
for (std::size_t column = 0; column < actual.Columns(); ++column) {
transpose(row, column) = actual(column, row);
}
}
@@ -629,7 +629,7 @@ TEST(EulerBeam3D, HasSixRigidModesRankSixAndPositiveDeformationEnergy) {
const double stiffnessScale = (std::max)(1.0, maximumAbsoluteEntry(stiffness));
for (const Vector& mode : rigidModes) {
const double normalizedResidual =
vectorNorm(stiffness.multiply(mode)) /
vectorNorm(stiffness.Multiply(mode)) /
(stiffnessScale * (std::max)(1.0, vectorNorm(mode)));
EXPECT_LE(normalizedResidual, kRigidTolerance);
}
@@ -695,7 +695,7 @@ TEST(EulerBeam3D, RotatedTransformPreservesWorkAndEnergy) {
const Matrix global = beam.globalStiffness();
Matrix expectedGlobal{kElementDofCount, kElementDofCount};
const Matrix localTimesTransform = local.multiply(transformation);
const Matrix localTimesTransform = local.Multiply(transformation);
for (std::size_t row = 0; row < kElementDofCount; ++row) {
for (std::size_t column = 0; column < kElementDofCount; ++column) {
for (std::size_t inner = 0; inner < kElementDofCount; ++inner) {
@@ -707,12 +707,12 @@ TEST(EulerBeam3D, RotatedTransformPreservesWorkAndEnergy) {
EXPECT_LE(normalizedMatrixError(global, expectedGlobal), kMatrixTolerance);
Vector localDisplacement{kElementDofCount};
for (std::size_t index = 0; index < localDisplacement.size(); ++index) {
for (std::size_t index = 0; index < localDisplacement.Size(); ++index) {
localDisplacement[index] = 0.01 * static_cast<double>(index + 1U) - 0.04;
}
const Vector globalDisplacement = transposeMultiply(transformation, localDisplacement);
const Vector localForce = local.multiply(localDisplacement);
const Vector globalForce = global.multiply(globalDisplacement);
const Vector localForce = local.Multiply(localDisplacement);
const Vector globalForce = global.Multiply(globalDisplacement);
const Vector expectedGlobalForce = transposeMultiply(transformation, localForce);
for (std::size_t index = 0; index < kElementDofCount; ++index) {
expectScaledNear(globalForce[index], expectedGlobalForce[index], kMatrixTolerance);
@@ -723,13 +723,13 @@ TEST(EulerBeam3D, RotatedTransformPreservesWorkAndEnergy) {
kMatrixTolerance);
Vector globalVariation{kElementDofCount};
for (std::size_t index = 0; index < globalVariation.size(); ++index) {
for (std::size_t index = 0; index < globalVariation.Size(); ++index) {
globalVariation[index] = 0.03 - 0.002 * static_cast<double>(index);
}
const Vector localVariation = transformation.multiply(globalVariation);
const Vector localVariation = transformation.Multiply(globalVariation);
expectScaledNear(
globalVariation.dot(globalForce),
localVariation.dot(localForce),
globalVariation.Dot(globalForce),
localVariation.Dot(localForce),
kMatrixTolerance);
const BeamRecovery recovery = beam.recover(globalDisplacement);
@@ -746,7 +746,7 @@ TEST(EulerBeam3D, ConstantLineLoadMatchesAllSignedComponents) {
const std::array<double, kElementDofCount> expected = {
5.0, -6.0, 11.0, -14.0, -22.0 / 3.0, -4.0,
5.0, -6.0, 11.0, -14.0, 22.0 / 3.0, 4.0};
ASSERT_EQ(equivalent.size(), expected.size());
ASSERT_EQ(equivalent.Size(), expected.size());
for (std::size_t index = 0; index < expected.size(); ++index) {
expectScaledNear(equivalent[index], expected[index], kMatrixTolerance);
}
@@ -931,17 +931,17 @@ TEST(EulerBeam3D, RejectsInvalidGeometryAndProperties) {
const auto expectFailure = [](const Result<EulerBeam3D>& result,
const std::string& code) {
if (result.hasValue()) {
const Matrix stiffness = result.value().localStiffness();
if (result.HasValue()) {
const Matrix stiffness = result.Value().localStiffness();
ADD_FAILURE()
<< "Invalid fixture was accepted; local stiffness finite="
<< matrixIsFinite(stiffness)
<< ", maximum absolute entry=" << maximumAbsoluteEntry(stiffness);
return;
}
EXPECT_EQ(result.status().failureCategory(), FailureCategory::model);
ASSERT_EQ(result.status().diagnostics().size(), 1U);
EXPECT_EQ(result.status().diagnostics()[0U].code, code);
EXPECT_EQ(result.GetStatus().Category(), FailureCategory::kModel);
ASSERT_EQ(result.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(result.GetStatus().Diagnostics()[0U].code, code);
};
expectFailure(
@@ -964,7 +964,7 @@ TEST(EulerBeam3D, RejectsInvalidGeometryAndProperties) {
"invalid-beam-length");
EXPECT_TRUE(EulerBeam3D::create(
scaledFirst, aboveThreshold, validSection, validMaterial)
.hasValue());
.HasValue());
auto parallelGuide = validSection;
parallelGuide.firstAxis = {1.0, 0.0, 0.0};
@@ -978,7 +978,7 @@ TEST(EulerBeam3D, RejectsInvalidGeometryAndProperties) {
"invalid-beam-guide-vector");
auto guideAboveThreshold = validSection;
guideAboveThreshold.firstAxis = {1.0, 2.0e-12, 0.0};
EXPECT_TRUE(EulerBeam3D::create(origin, unitX, guideAboveThreshold, validMaterial).hasValue());
EXPECT_TRUE(EulerBeam3D::create(origin, unitX, guideAboveThreshold, validMaterial).HasValue());
auto invalidMaterial = validMaterial;
invalidMaterial.youngsModulus = 0.0;
+113 -113
View File
@@ -84,10 +84,10 @@ void expectMatrixNear(
const fesa::Matrix& actual,
const fesa::Matrix& expected,
double tolerance = 1.0e-12) {
ASSERT_EQ(actual.rows(), expected.rows());
ASSERT_EQ(actual.columns(), expected.columns());
for (std::size_t row = 0U; row < actual.rows(); ++row) {
for (std::size_t column = 0U; column < actual.columns(); ++column) {
ASSERT_EQ(actual.Rows(), expected.Rows());
ASSERT_EQ(actual.Columns(), expected.Columns());
for (std::size_t row = 0U; row < actual.Rows(); ++row) {
for (std::size_t column = 0U; column < actual.Columns(); ++column) {
EXPECT_NEAR(actual(row, column), expected(row, column), tolerance)
<< "at (" << row << ", " << column << ")";
}
@@ -95,20 +95,20 @@ void expectMatrixNear(
}
void expectSymmetric(const fesa::Matrix& matrix) {
ASSERT_EQ(matrix.rows(), matrix.columns());
for (std::size_t row = 0U; row < matrix.rows(); ++row) {
for (std::size_t column = 0U; column < matrix.columns(); ++column) {
ASSERT_EQ(matrix.Rows(), matrix.Columns());
for (std::size_t row = 0U; row < matrix.Rows(); ++row) {
for (std::size_t column = 0U; column < matrix.Columns(); ++column) {
EXPECT_NEAR(matrix(row, column), matrix(column, row), 1.0e-12);
}
}
}
bool hasPositiveCholeskyPivots(const fesa::Matrix& matrix) {
if (matrix.rows() != matrix.columns()) {
if (matrix.Rows() != matrix.Columns()) {
return false;
}
fesa::Matrix lower{matrix.rows(), matrix.columns()};
for (std::size_t row = 0U; row < matrix.rows(); ++row) {
fesa::Matrix lower{matrix.Rows(), matrix.Columns()};
for (std::size_t row = 0U; row < matrix.Rows(); ++row) {
for (std::size_t column = 0U; column <= row; ++column) {
double value = matrix(row, column);
for (std::size_t inner = 0U; inner < column; ++inner) {
@@ -129,8 +129,8 @@ bool hasPositiveCholeskyPivots(const fesa::Matrix& matrix) {
double frobeniusNorm(const fesa::Matrix& matrix) {
double squaredNorm = 0.0;
for (std::size_t row = 0U; row < matrix.rows(); ++row) {
for (std::size_t column = 0U; column < matrix.columns(); ++column) {
for (std::size_t row = 0U; row < matrix.Rows(); ++row) {
for (std::size_t column = 0U; column < matrix.Columns(); ++column) {
squaredNorm += matrix(row, column) * matrix(row, column);
}
}
@@ -141,11 +141,11 @@ double scaledSymmetryError(
const fesa::Matrix& matrix,
std::size_t dofsPerNode,
double elementLength) {
fesa::Matrix difference{matrix.rows(), matrix.columns()};
fesa::Matrix scaled{matrix.rows(), matrix.columns()};
for (std::size_t row = 0U; row < matrix.rows(); ++row) {
fesa::Matrix difference{matrix.Rows(), matrix.Columns()};
fesa::Matrix scaled{matrix.Rows(), matrix.Columns()};
for (std::size_t row = 0U; row < matrix.Rows(); ++row) {
const double rowScale = row % dofsPerNode < 3U ? elementLength : 1.0;
for (std::size_t column = 0U; column < matrix.columns(); ++column) {
for (std::size_t column = 0U; column < matrix.Columns(); ++column) {
const double columnScale =
column % dofsPerNode < 3U ? elementLength : 1.0;
scaled(row, column) =
@@ -161,10 +161,10 @@ fesa::Matrix scaledStiffness(
const fesa::Matrix& matrix,
std::size_t dofsPerNode,
double elementLength) {
fesa::Matrix scaled{matrix.rows(), matrix.columns()};
for (std::size_t row = 0U; row < matrix.rows(); ++row) {
fesa::Matrix scaled{matrix.Rows(), matrix.Columns()};
for (std::size_t row = 0U; row < matrix.Rows(); ++row) {
const double rowScale = row % dofsPerNode < 3U ? elementLength : 1.0;
for (std::size_t column = 0U; column < matrix.columns(); ++column) {
for (std::size_t column = 0U; column < matrix.Columns(); ++column) {
const double columnScale =
column % dofsPerNode < 3U ? elementLength : 1.0;
scaled(row, column) =
@@ -175,10 +175,10 @@ fesa::Matrix scaledStiffness(
}
std::vector<double> symmetricEigenvalues(fesa::Matrix matrix) {
if (matrix.rows() != matrix.columns()) {
if (matrix.Rows() != matrix.Columns()) {
throw std::invalid_argument{"Symmetric eigensolve requires a square matrix."};
}
const std::size_t size = matrix.rows();
const std::size_t size = matrix.Rows();
double matrixScale = 0.0;
for (std::size_t row = 0U; row < size; ++row) {
for (std::size_t column = 0U; column < size; ++column) {
@@ -266,7 +266,7 @@ double symmetricOperatorNorm(const fesa::Matrix& matrix) {
}
double quadraticEnergy(const fesa::Matrix& stiffness, const fesa::Vector& vector) {
return 0.5 * vector.dot(stiffness.multiply(vector));
return 0.5 * vector.Dot(stiffness.Multiply(vector));
}
fesa::Vector physicalField(const std::array<std::array<double, 5>, 4>& values) {
@@ -286,7 +286,7 @@ void expectStrain(
double eta,
double zeta,
const std::array<double, 5>& expected) {
const auto actual = shell.strainDisplacement20(xi, eta, zeta).multiply(field);
const auto actual = shell.strainDisplacement20(xi, eta, zeta).Multiply(field);
for (std::size_t component = 0U; component < expected.size(); ++component) {
EXPECT_NEAR(actual[component], expected[component], 1.0e-12)
<< "component " << component;
@@ -367,8 +367,8 @@ TEST(Mitc4ShellKinematics, BuildsRightHandedFramesAndSeparatePhysicalDrillingMap
node(4, {0.0, -1.0, 1.0})};
const auto candidate = fesa::Mitc4Shell::create(
nodePointers(nodes), directors({1.0, 0.0, 0.0}), section(), material());
ASSERT_TRUE(candidate.hasValue());
const auto& shell = candidate.value();
ASSERT_TRUE(candidate.HasValue());
const auto& shell = candidate.Value();
const auto frame = shell.localFrame(0.0, 0.0);
expectVectorNear(frame.e1, {0.0, 1.0, 0.0});
@@ -378,10 +378,10 @@ TEST(Mitc4ShellKinematics, BuildsRightHandedFramesAndSeparatePhysicalDrillingMap
const auto physical = shell.physicalTransformation20();
const auto drilling = shell.drillingTransformation4();
ASSERT_EQ(physical.rows(), 20U);
ASSERT_EQ(physical.columns(), 24U);
ASSERT_EQ(drilling.rows(), 4U);
ASSERT_EQ(drilling.columns(), 24U);
ASSERT_EQ(physical.Rows(), 20U);
ASSERT_EQ(physical.Columns(), 24U);
ASSERT_EQ(drilling.Rows(), 4U);
ASSERT_EQ(drilling.Columns(), 24U);
for (std::size_t nodeIndex = 0U; nodeIndex < 4U; ++nodeIndex) {
const std::size_t physicalOffset = 5U * nodeIndex;
const std::size_t globalOffset = 6U * nodeIndex;
@@ -411,7 +411,7 @@ TEST(Mitc4ShellKinematics, BuildsRightHandedFramesAndSeparatePhysicalDrillingMap
invalidDirectors[2] = {0.0, 0.0, 0.0};
EXPECT_FALSE(fesa::Mitc4Shell::create(
nodePointers(nodes), invalidDirectors, section(), material())
.hasValue());
.HasValue());
}
// MITC4-KIN-003
@@ -419,12 +419,12 @@ TEST(Mitc4ShellKinematics, FormsDirectColumnsAndAllCovariantTyingSamples) {
const auto nodes = planarNodes();
const auto candidate = fesa::Mitc4Shell::create(
nodePointers(nodes), directors(), section(), material());
ASSERT_TRUE(candidate.hasValue());
const auto& shell = candidate.value();
ASSERT_TRUE(candidate.HasValue());
const auto& shell = candidate.Value();
const auto direct = shell.directStrainDisplacement20(0.0, 0.0, 0.5);
ASSERT_EQ(direct.rows(), 5U);
ASSERT_EQ(direct.columns(), 20U);
ASSERT_EQ(direct.Rows(), 5U);
ASSERT_EQ(direct.Columns(), 20U);
EXPECT_DOUBLE_EQ(direct(0U, 0U), -0.25);
EXPECT_DOUBLE_EQ(direct(0U, 4U), -0.125);
EXPECT_DOUBLE_EQ(direct(1U, 1U), -0.25);
@@ -439,8 +439,8 @@ TEST(Mitc4ShellKinematics, FormsDirectColumnsAndAllCovariantTyingSamples) {
EXPECT_DOUBLE_EQ(direct(4U, 3U), -0.25);
const auto samples = shell.covariantTyingShearSamples20();
ASSERT_EQ(samples.rows(), 4U);
ASSERT_EQ(samples.columns(), 20U);
ASSERT_EQ(samples.Rows(), 4U);
ASSERT_EQ(samples.Columns(), 20U);
EXPECT_DOUBLE_EQ(samples(0U, 2U), -0.25);
EXPECT_DOUBLE_EQ(samples(0U, 4U), 0.25);
EXPECT_DOUBLE_EQ(samples(0U, 7U), 0.25);
@@ -476,21 +476,21 @@ TEST(Mitc4ShellConstitutive, BuildsExactPositiveDefiniteSectionMatricesAndRescal
const auto nodes = planarNodes();
const auto candidate = fesa::Mitc4Shell::create(
nodePointers(nodes), directors(), section(), material());
ASSERT_TRUE(candidate.hasValue());
const auto& shell = candidate.value();
ASSERT_TRUE(candidate.HasValue());
const auto& shell = candidate.Value();
const auto cps = shell.planeStressConstitutive();
const auto c5 = shell.materialConstitutive5();
const auto a = shell.membraneSectionMatrix();
const auto d = shell.bendingSectionMatrix();
const auto as = shell.transverseShearSectionMatrix();
EXPECT_EQ(cps.rows(), 3U);
EXPECT_EQ(cps.columns(), 3U);
EXPECT_EQ(c5.rows(), 5U);
EXPECT_EQ(c5.columns(), 5U);
EXPECT_EQ(a.rows(), 3U);
EXPECT_EQ(d.rows(), 3U);
EXPECT_EQ(as.rows(), 2U);
EXPECT_EQ(cps.Rows(), 3U);
EXPECT_EQ(cps.Columns(), 3U);
EXPECT_EQ(c5.Rows(), 5U);
EXPECT_EQ(c5.Columns(), 5U);
EXPECT_EQ(a.Rows(), 3U);
EXPECT_EQ(d.Rows(), 3U);
EXPECT_EQ(as.Rows(), 2U);
EXPECT_DOUBLE_EQ(cps(0U, 0U), 128.0);
EXPECT_DOUBLE_EQ(cps(0U, 1U), 32.0);
EXPECT_DOUBLE_EQ(cps(2U, 2U), 48.0);
@@ -514,8 +514,8 @@ TEST(Mitc4ShellConstitutive, BuildsExactPositiveDefiniteSectionMatricesAndRescal
directors(),
section(2.0 * lengthScale),
material(120.0 * forceScale / (lengthScale * lengthScale), 0.25));
ASSERT_TRUE(scaledCandidate.hasValue());
const auto& scaled = scaledCandidate.value();
ASSERT_TRUE(scaledCandidate.HasValue());
const auto& scaled = scaledCandidate.Value();
fesa::Matrix expectedCps{3U, 3U};
fesa::Matrix expectedC5{5U, 5U};
fesa::Matrix expectedA{3U, 3U};
@@ -548,13 +548,13 @@ TEST(Mitc4ShellConstitutive, BuildsExactPositiveDefiniteSectionMatricesAndRescal
EXPECT_FALSE(fesa::Mitc4Shell::create(
nodePointers(nodes), directors(), section(0.0), material())
.hasValue());
.HasValue());
EXPECT_FALSE(fesa::Mitc4Shell::create(
nodePointers(nodes), directors(), section(), material(0.0, 0.25))
.hasValue());
.HasValue());
EXPECT_FALSE(fesa::Mitc4Shell::create(
nodePointers(nodes), directors(), section(), material(120.0, 0.5))
.hasValue());
.HasValue());
}
// MITC4-KIN-005
@@ -578,23 +578,23 @@ TEST(Mitc4ShellKernel, FormsFiniteScaledSymmetricPhysicalAndStabilizedStiffness)
const auto nodes = planarNodes();
const auto shellCandidate = fesa::Mitc4Shell::create(
nodePointers(nodes), directors(), section(), material());
ASSERT_TRUE(shellCandidate.hasValue());
ASSERT_TRUE(shellCandidate.HasValue());
const auto stiffnessCandidate = shellCandidate.value().stiffness();
ASSERT_TRUE(stiffnessCandidate.hasValue());
const auto& stiffness = stiffnessCandidate.value();
EXPECT_EQ(stiffness.physicalLocal20.rows(), 20U);
EXPECT_EQ(stiffness.physicalLocal20.columns(), 20U);
EXPECT_EQ(stiffness.physicalGlobal24.rows(), 24U);
EXPECT_EQ(stiffness.drillingGlobal24.rows(), 24U);
EXPECT_EQ(stiffness.stabilizedGlobal24.rows(), 24U);
const auto stiffnessCandidate = shellCandidate.Value().stiffness();
ASSERT_TRUE(stiffnessCandidate.HasValue());
const auto& stiffness = stiffnessCandidate.Value();
EXPECT_EQ(stiffness.physicalLocal20.Rows(), 20U);
EXPECT_EQ(stiffness.physicalLocal20.Columns(), 20U);
EXPECT_EQ(stiffness.physicalGlobal24.Rows(), 24U);
EXPECT_EQ(stiffness.drillingGlobal24.Rows(), 24U);
EXPECT_EQ(stiffness.stabilizedGlobal24.Rows(), 24U);
for (const fesa::Matrix* matrix : {
&stiffness.physicalLocal20,
&stiffness.physicalGlobal24,
&stiffness.drillingGlobal24,
&stiffness.stabilizedGlobal24}) {
for (std::size_t row = 0U; row < matrix->rows(); ++row) {
for (std::size_t column = 0U; column < matrix->columns(); ++column) {
for (std::size_t row = 0U; row < matrix->Rows(); ++row) {
for (std::size_t column = 0U; column < matrix->Columns(); ++column) {
EXPECT_TRUE(std::isfinite((*matrix)(row, column)));
}
}
@@ -604,9 +604,9 @@ TEST(Mitc4ShellKernel, FormsFiniteScaledSymmetricPhysicalAndStabilizedStiffness)
EXPECT_LE(scaledSymmetryError(stiffness.drillingGlobal24, 6U, 2.0), 1.0e-12);
EXPECT_LE(scaledSymmetryError(stiffness.stabilizedGlobal24, 6U, 2.0), 1.0e-12);
const auto repeatedCandidate = shellCandidate.value().stiffness();
ASSERT_TRUE(repeatedCandidate.hasValue());
const auto& repeated = repeatedCandidate.value();
const auto repeatedCandidate = shellCandidate.Value().stiffness();
ASSERT_TRUE(repeatedCandidate.HasValue());
const auto& repeated = repeatedCandidate.Value();
expectMatrixNear(repeated.physicalLocal20, stiffness.physicalLocal20, 0.0);
expectMatrixNear(repeated.physicalGlobal24, stiffness.physicalGlobal24, 0.0);
expectMatrixNear(repeated.drillingGlobal24, stiffness.drillingGlobal24, 0.0);
@@ -619,18 +619,18 @@ TEST(Mitc4ShellKernel, PreservesPhysicalEnergyUnderTwentyToTwentyFourCongruence)
const auto nodes = planarNodes();
const auto shellCandidate = fesa::Mitc4Shell::create(
nodePointers(nodes), directors(), section(), material());
ASSERT_TRUE(shellCandidate.hasValue());
const auto stiffnessCandidate = shellCandidate.value().stiffness();
ASSERT_TRUE(stiffnessCandidate.hasValue());
const auto& stiffness = stiffnessCandidate.value();
ASSERT_TRUE(shellCandidate.HasValue());
const auto stiffnessCandidate = shellCandidate.Value().stiffness();
ASSERT_TRUE(stiffnessCandidate.HasValue());
const auto& stiffness = stiffnessCandidate.Value();
fesa::Vector globalField{24U};
for (std::size_t index = 0U; index < globalField.size(); ++index) {
for (std::size_t index = 0U; index < globalField.Size(); ++index) {
globalField[index] = 0.125 * static_cast<double>(
static_cast<int>(index % 7U) - 3);
}
const auto physicalField20 =
shellCandidate.value().physicalTransformation20().multiply(globalField);
shellCandidate.Value().physicalTransformation20().Multiply(globalField);
const double localEnergy = quadraticEnergy(
stiffness.physicalLocal20, physicalField20);
const double globalEnergy = quadraticEnergy(
@@ -648,10 +648,10 @@ TEST(Mitc4ShellKernel, RetainsSixRigidModesAndHasExpectedPhysicalAndStabilizedRa
const auto nodes = planarNodes();
const auto shellCandidate = fesa::Mitc4Shell::create(
nodePointers(nodes), directors(), section(), material());
ASSERT_TRUE(shellCandidate.hasValue());
const auto stiffnessCandidate = shellCandidate.value().stiffness();
ASSERT_TRUE(stiffnessCandidate.hasValue());
const auto& stiffness = stiffnessCandidate.value();
ASSERT_TRUE(shellCandidate.HasValue());
const auto stiffnessCandidate = shellCandidate.Value().stiffness();
ASSERT_TRUE(stiffnessCandidate.HasValue());
const auto& stiffness = stiffnessCandidate.Value();
const auto scaledPhysical20 =
scaledStiffness(stiffness.physicalLocal20, 5U, 2.0);
@@ -680,14 +680,14 @@ TEST(Mitc4ShellKernel, RetainsSixRigidModesAndHasExpectedPhysicalAndStabilizedRa
scaledMode[6U * nodeIndex + component] /= 2.0;
}
}
const double modeNorm = scaledMode.norm();
const double modeNorm = scaledMode.Norm();
ASSERT_GT(modeNorm, 0.0);
EXPECT_LE(
scaledPhysical24.multiply(scaledMode).norm() /
scaledPhysical24.Multiply(scaledMode).Norm() /
(physicalNorm * modeNorm),
1.0e-10);
EXPECT_LE(
scaledStabilized24.multiply(scaledMode).norm() /
scaledStabilized24.Multiply(scaledMode).Norm() /
(stabilizedNorm * modeNorm),
1.0e-10);
}
@@ -698,11 +698,11 @@ TEST(Mitc4ShellPatch, ReproducesIndependentMembraneBendingShearAndTwistFields) {
const auto nodes = planarNodes();
const auto shellCandidate = fesa::Mitc4Shell::create(
nodePointers(nodes), directors(), section(), material());
ASSERT_TRUE(shellCandidate.hasValue());
const auto& shell = shellCandidate.value();
ASSERT_TRUE(shellCandidate.HasValue());
const auto& shell = shellCandidate.Value();
const auto stiffnessCandidate = shell.stiffness();
ASSERT_TRUE(stiffnessCandidate.hasValue());
const auto& stiffness = stiffnessCandidate.value().physicalLocal20;
ASSERT_TRUE(stiffnessCandidate.HasValue());
const auto& stiffness = stiffnessCandidate.Value().physicalLocal20;
constexpr double magnitude = 0.2;
const double gauss = 1.0 / std::sqrt(3.0);
@@ -755,10 +755,10 @@ TEST(Mitc4ShellDrilling, UsesOnlyEightPositivePhysicalRotationDiagonalsAndFixedF
node(3, {50.0, 50.0, 0.0}), node(4, {-50.0, 50.0, 0.0})};
const auto shellCandidate = fesa::Mitc4Shell::create(
nodePointers(nodes), directors(), section(0.1), material());
ASSERT_TRUE(shellCandidate.hasValue());
const auto stiffnessCandidate = shellCandidate.value().stiffness();
ASSERT_TRUE(stiffnessCandidate.hasValue());
const auto& stiffness = stiffnessCandidate.value();
ASSERT_TRUE(shellCandidate.HasValue());
const auto stiffnessCandidate = shellCandidate.Value().stiffness();
ASSERT_TRUE(stiffnessCandidate.HasValue());
const auto& stiffness = stiffnessCandidate.Value();
double expectedReference = (std::numeric_limits<double>::max)();
double allDiagonalMinimum = (std::numeric_limits<double>::max)();
@@ -788,17 +788,17 @@ TEST(Mitc4ShellDrilling, FailsNonfiniteReferenceAndStabilizesEachPureDrillCoordi
const auto nodes = planarNodes();
const auto shellCandidate = fesa::Mitc4Shell::create(
nodePointers(nodes), directors(), section(), material());
ASSERT_TRUE(shellCandidate.hasValue());
const auto stiffnessCandidate = shellCandidate.value().stiffness();
ASSERT_TRUE(stiffnessCandidate.hasValue());
const auto& stiffness = stiffnessCandidate.value();
ASSERT_TRUE(shellCandidate.HasValue());
const auto stiffnessCandidate = shellCandidate.Value().stiffness();
ASSERT_TRUE(stiffnessCandidate.HasValue());
const auto& stiffness = stiffnessCandidate.Value();
for (std::size_t nodeIndex = 0U; nodeIndex < 4U; ++nodeIndex) {
fesa::Vector pureDrill{24U};
pureDrill[6U * nodeIndex + 5U] = 1.0;
EXPECT_DOUBLE_EQ(
stiffness.physicalGlobal24.multiply(pureDrill).norm(), 0.0);
const auto drillAction = stiffness.drillingGlobal24.multiply(pureDrill);
stiffness.physicalGlobal24.Multiply(pureDrill).Norm(), 0.0);
const auto drillAction = stiffness.drillingGlobal24.Multiply(pureDrill);
EXPECT_DOUBLE_EQ(drillAction[6U * nodeIndex + 5U], stiffness.drillingStiffness);
EXPECT_GT(quadraticEnergy(stiffness.drillingGlobal24, pureDrill), 0.0);
}
@@ -808,20 +808,20 @@ TEST(Mitc4ShellDrilling, FailsNonfiniteReferenceAndStabilizesEachPureDrillCoordi
node(3, {5.0e9, 5.0e9, 0.0}), node(4, {-5.0e9, 5.0e9, 0.0})};
const auto extremeShell = fesa::Mitc4Shell::create(
nodePointers(extremeNodes), directors(), section(1.0), material(1.0e300));
ASSERT_TRUE(extremeShell.hasValue());
const auto failure = extremeShell.value().stiffness();
ASSERT_FALSE(failure.hasValue());
ASSERT_EQ(failure.status().diagnostics().size(), 1U);
EXPECT_EQ(failure.status().diagnostics()[0].code, "invalid-shell-stiffness");
const auto repeatedFailure = extremeShell.value().stiffness();
ASSERT_FALSE(repeatedFailure.hasValue());
ASSERT_EQ(repeatedFailure.status().diagnostics().size(), 1U);
ASSERT_TRUE(extremeShell.HasValue());
const auto failure = extremeShell.Value().stiffness();
ASSERT_FALSE(failure.HasValue());
ASSERT_EQ(failure.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(failure.GetStatus().Diagnostics()[0].code, "invalid-shell-stiffness");
const auto repeatedFailure = extremeShell.Value().stiffness();
ASSERT_FALSE(repeatedFailure.HasValue());
ASSERT_EQ(repeatedFailure.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(
repeatedFailure.status().diagnostics()[0].code,
failure.status().diagnostics()[0].code);
repeatedFailure.GetStatus().Diagnostics()[0].code,
failure.GetStatus().Diagnostics()[0].code);
EXPECT_EQ(
repeatedFailure.status().diagnostics()[0].message,
failure.status().diagnostics()[0].message);
repeatedFailure.GetStatus().Diagnostics()[0].message,
failure.GetStatus().Diagnostics()[0].message);
}
// MITC4-KERNEL-007
@@ -829,21 +829,21 @@ TEST(Mitc4ShellDrilling, ExcludesPureDrillFromPhysicalRecoveryAndEnergy) {
const auto nodes = planarNodes();
const auto shellCandidate = fesa::Mitc4Shell::create(
nodePointers(nodes), directors(), section(), material());
ASSERT_TRUE(shellCandidate.hasValue());
const auto& shell = shellCandidate.value();
ASSERT_TRUE(shellCandidate.HasValue());
const auto& shell = shellCandidate.Value();
const auto stiffnessCandidate = shell.stiffness();
ASSERT_TRUE(stiffnessCandidate.hasValue());
ASSERT_TRUE(stiffnessCandidate.HasValue());
for (std::size_t nodeIndex = 0U; nodeIndex < nodes.size(); ++nodeIndex) {
fesa::Vector pureDrill{24U};
pureDrill[6U * nodeIndex + 5U] = 1.0;
EXPECT_GT(
stiffnessCandidate.value().stabilizedGlobal24.multiply(pureDrill).norm(),
stiffnessCandidate.Value().stabilizedGlobal24.Multiply(pureDrill).Norm(),
0.0);
const auto recoveryCandidate = shell.recoverPhysical(pureDrill);
ASSERT_TRUE(recoveryCandidate.hasValue());
const auto& recovery = recoveryCandidate.value();
ASSERT_TRUE(recoveryCandidate.HasValue());
const auto& recovery = recoveryCandidate.Value();
EXPECT_DOUBLE_EQ(recovery.strainEnergy, 0.0);
for (const auto& point : recovery.points) {
for (double value : point.generalizedStrain) {
@@ -866,8 +866,8 @@ TEST(Mitc4ShellPhysicalRecovery, RecoversHandFieldAtFixedLocationsAndSectionPosi
const auto nodes = planarNodes();
const auto shellCandidate = fesa::Mitc4Shell::create(
nodePointers(nodes), directors(), section(), material());
ASSERT_TRUE(shellCandidate.hasValue());
const auto& shell = shellCandidate.value();
ASSERT_TRUE(shellCandidate.HasValue());
const auto& shell = shellCandidate.Value();
constexpr std::array<double, 8> generalized{
0.1, -0.05, 0.2, 0.3, -0.15, 0.25, 0.4, -0.3};
@@ -889,8 +889,8 @@ TEST(Mitc4ShellPhysicalRecovery, RecoversHandFieldAtFixedLocationsAndSectionPosi
}
const auto recoveryCandidate = shell.recoverPhysical(globalField);
ASSERT_TRUE(recoveryCandidate.hasValue());
const auto& recovery = recoveryCandidate.value();
ASSERT_TRUE(recoveryCandidate.HasValue());
const auto& recovery = recoveryCandidate.Value();
const double gauss = 1.0 / std::sqrt(3.0);
const std::array<std::array<double, 2>, 4> expectedCoordinates{
std::array<double, 2>{-gauss, -gauss},
+21 -21
View File
@@ -78,12 +78,12 @@ struct DofFixture {
DofFixture makeDofFixture(fesa::ModelDefinition definition = makeDefinition()) {
auto domain = fesa::Domain::create(std::move(definition));
EXPECT_TRUE(domain.hasValue());
auto model = fesa::AnalysisModel::create(domain.value());
EXPECT_TRUE(model.hasValue());
auto dofs = fesa::DofManager::create(model.value());
EXPECT_TRUE(dofs.hasValue());
return {std::move(dofs.value())};
EXPECT_TRUE(domain.HasValue());
auto model = fesa::AnalysisModel::create(domain.Value());
EXPECT_TRUE(model.HasValue());
auto dofs = fesa::DofManager::create(model.Value());
EXPECT_TRUE(dofs.HasValue());
return {std::move(dofs.Value())};
}
std::vector<std::size_t> rowColumns(
@@ -122,7 +122,7 @@ TEST(DofManager, NumbersSixDofsAndFreeEquationsStably) {
TEST(DofManager, ExpandsAndValidatesPrescribedValues) {
const auto fixture = makeDofFixture();
const auto& values = fixture.dofs.prescribedValues();
ASSERT_EQ(values.size(), 5U);
ASSERT_EQ(values.Size(), 5U);
EXPECT_DOUBLE_EQ(values[0], 0.0);
EXPECT_DOUBLE_EQ(values[1], 0.0);
EXPECT_DOUBLE_EQ(values[2], 0.25);
@@ -133,18 +133,18 @@ TEST(DofManager, ExpandsAndValidatesPrescribedValues) {
conflictingDefinition.steps[0].boundaries.push_back(
{"root", 1, 1, 1.0, {conflictingDefinition.sourcePath, 77U}});
auto domain = fesa::Domain::create(std::move(conflictingDefinition));
ASSERT_TRUE(domain.hasValue());
auto model = fesa::AnalysisModel::create(domain.value());
ASSERT_TRUE(model.hasValue());
ASSERT_TRUE(domain.HasValue());
auto model = fesa::AnalysisModel::create(domain.Value());
ASSERT_TRUE(model.HasValue());
auto conflict = fesa::DofManager::create(model.value());
ASSERT_FALSE(conflict.hasValue());
EXPECT_EQ(conflict.status().failureCategory(), fesa::FailureCategory::input);
ASSERT_EQ(conflict.status().diagnostics().size(), 1U);
const auto& diagnostic = conflict.status().diagnostics()[0];
auto conflict = fesa::DofManager::create(model.Value());
ASSERT_FALSE(conflict.HasValue());
EXPECT_EQ(conflict.GetStatus().Category(), fesa::FailureCategory::kInput);
ASSERT_EQ(conflict.GetStatus().Diagnostics().size(), 1U);
const auto& diagnostic = conflict.GetStatus().Diagnostics()[0];
EXPECT_EQ(diagnostic.code, "conflicting-boundary-condition");
EXPECT_EQ(diagnostic.keyword, "BOUNDARY");
EXPECT_EQ(diagnostic.entityIdentity, "root");
EXPECT_EQ(diagnostic.entity_identity, "root");
EXPECT_EQ(diagnostic.location.file, std::filesystem::path{"models/dof-manager.inp"});
EXPECT_EQ(diagnostic.location.line, 77U);
}
@@ -227,7 +227,7 @@ TEST(DofManager, ReconstructsFullReducedRoundTrip) {
const auto fixture = makeDofFixture();
const auto& dofs = fixture.dofs;
fesa::Vector full{dofs.fullDofCount()};
for (std::size_t index = 0U; index < full.size(); ++index) {
for (std::size_t index = 0U; index < full.Size(); ++index) {
full[index] = static_cast<double>(index) + 0.5;
}
for (std::size_t index = 0U; index < dofs.constrainedDofCount(); ++index) {
@@ -235,19 +235,19 @@ TEST(DofManager, ReconstructsFullReducedRoundTrip) {
}
fesa::Vector reduced{dofs.freeDofCount()};
for (std::size_t equation = 0U; equation < reduced.size(); ++equation) {
for (std::size_t equation = 0U; equation < reduced.Size(); ++equation) {
reduced[equation] = full[dofs.freeDofs()[equation]];
}
fesa::Vector reconstructed{dofs.fullDofCount()};
for (std::size_t equation = 0U; equation < reduced.size(); ++equation) {
for (std::size_t equation = 0U; equation < reduced.Size(); ++equation) {
reconstructed[dofs.freeDofs()[equation]] = reduced[equation];
}
for (std::size_t index = 0U; index < dofs.constrainedDofCount(); ++index) {
reconstructed[dofs.constrainedDofs()[index]] = dofs.prescribedValues()[index];
}
ASSERT_EQ(reconstructed.size(), full.size());
for (std::size_t index = 0U; index < full.size(); ++index) {
ASSERT_EQ(reconstructed.Size(), full.Size());
for (std::size_t index = 0U; index < full.Size(); ++index) {
EXPECT_DOUBLE_EQ(reconstructed[index], full[index]);
}
}
+161 -161
View File
@@ -45,10 +45,10 @@ fesa::Result<fesa::Domain> mapText(
const std::string& content) {
const TemporaryInputFile input{stem, content};
auto parsed = fesa::AbaqusInputReader{}.read(input.path());
if (!parsed.hasValue()) {
return fesa::Result<fesa::Domain>::failure(parsed.status());
if (!parsed.HasValue()) {
return fesa::Result<fesa::Domain>::Failure(parsed.GetStatus());
}
return fesa::AbaqusDomainMapper{}.map(parsed.value());
return fesa::AbaqusDomainMapper{}.map(parsed.Value());
}
std::string readExactBytes(const std::filesystem::path& path) {
@@ -73,12 +73,12 @@ const fesa::Diagnostic* findDiagnostic(
const fesa::Status& status,
const std::string& code) {
const auto found = std::find_if(
status.diagnostics().begin(),
status.diagnostics().end(),
status.Diagnostics().begin(),
status.Diagnostics().end(),
[&code](const fesa::Diagnostic& diagnostic) {
return diagnostic.code == code;
});
return found == status.diagnostics().end() ? nullptr : &*found;
return found == status.Diagnostics().end() ? nullptr : &*found;
}
std::string replaceOnce(
@@ -246,18 +246,18 @@ RootAssembly, 6, -12.5
TEST(InpDomainMapping, MapsEverySupportedKeywordAndLegacyDeck) {
auto result = mapText("supported-inventory", supportedInventoryDeck(true));
ASSERT_TRUE(result.hasValue());
const fesa::Domain& domain = result.value();
ASSERT_TRUE(result.HasValue());
const fesa::Domain& domain = result.Value();
ASSERT_EQ(domain.nodes().size(), 2U);
EXPECT_EQ(domain.nodes()[0].sourceId.instanceName, "Beam-1");
EXPECT_EQ(domain.nodes()[0].sourceId.sourceLabel, 1);
EXPECT_EQ(domain.nodes()[0].sourceId.sourceLabelText, "0001");
EXPECT_EQ(domain.nodes()[1].sourceId.sourceLabelText, "0002");
EXPECT_EQ(domain.nodes()[0].sourceId.instance_name, "Beam-1");
EXPECT_EQ(domain.nodes()[0].sourceId.source_label, 1);
EXPECT_EQ(domain.nodes()[0].sourceId.source_label_text, "0001");
EXPECT_EQ(domain.nodes()[1].sourceId.source_label_text, "0002");
EXPECT_DOUBLE_EQ(domain.nodes()[1].coordinates[0], 2.0);
ASSERT_EQ(domain.elements().size(), 1U);
EXPECT_EQ(domain.elements()[0].sourceId.sourceLabelText, "0007");
EXPECT_EQ(domain.elements()[0].sourceId.source_label_text, "0007");
EXPECT_EQ(domain.elements()[0].nodeIndices[0], 0U);
EXPECT_EQ(domain.elements()[0].nodeIndices[1], 1U);
@@ -304,15 +304,15 @@ TEST(InpDomainMapping, MapsEverySupportedKeywordAndLegacyDeck) {
const auto bytesBefore = readExactBytes(legacyPath);
const auto timestampBefore = std::filesystem::last_write_time(legacyPath);
auto parsedLegacy = fesa::AbaqusInputReader{}.read(legacyPath);
ASSERT_TRUE(parsedLegacy.hasValue());
auto legacy = fesa::AbaqusDomainMapper{}.map(parsedLegacy.value());
ASSERT_TRUE(legacy.hasValue());
EXPECT_EQ(legacy.value().nodes().size(), 11U);
EXPECT_EQ(legacy.value().elements().size(), 10U);
EXPECT_EQ(legacy.value().materials().size(), 1U);
EXPECT_EQ(legacy.value().sections().size(), 1U);
EXPECT_EQ(legacy.value().steps().size(), 1U);
EXPECT_EQ(legacy.value().warnings().size(), 7U);
ASSERT_TRUE(parsedLegacy.HasValue());
auto legacy = fesa::AbaqusDomainMapper{}.map(parsedLegacy.Value());
ASSERT_TRUE(legacy.HasValue());
EXPECT_EQ(legacy.Value().nodes().size(), 11U);
EXPECT_EQ(legacy.Value().elements().size(), 10U);
EXPECT_EQ(legacy.Value().materials().size(), 1U);
EXPECT_EQ(legacy.Value().sections().size(), 1U);
EXPECT_EQ(legacy.Value().steps().size(), 1U);
EXPECT_EQ(legacy.Value().warnings().size(), 7U);
EXPECT_EQ(readExactBytes(legacyPath), bytesBefore);
EXPECT_EQ(std::filesystem::last_write_time(legacyPath), timestampBefore);
}
@@ -356,24 +356,24 @@ OnlySecond, 2, 5.
)inp";
auto result = mapText("multiple-instances", deck);
ASSERT_TRUE(result.hasValue());
const fesa::Domain& domain = result.value();
ASSERT_TRUE(result.HasValue());
const fesa::Domain& domain = result.Value();
ASSERT_EQ(domain.nodes().size(), 4U);
EXPECT_EQ(domain.nodes()[0].sourceId.instanceName, "First");
EXPECT_EQ(domain.nodes()[0].sourceId.sourceLabel, 10);
EXPECT_EQ(domain.nodes()[1].sourceId.instanceName, "First");
EXPECT_EQ(domain.nodes()[1].sourceId.sourceLabel, 20);
EXPECT_EQ(domain.nodes()[2].sourceId.instanceName, "Second");
EXPECT_EQ(domain.nodes()[2].sourceId.sourceLabel, 10);
EXPECT_EQ(domain.nodes()[3].sourceId.instanceName, "Second");
EXPECT_EQ(domain.nodes()[3].sourceId.sourceLabel, 20);
EXPECT_EQ(domain.nodes()[0].sourceId.instance_name, "First");
EXPECT_EQ(domain.nodes()[0].sourceId.source_label, 10);
EXPECT_EQ(domain.nodes()[1].sourceId.instance_name, "First");
EXPECT_EQ(domain.nodes()[1].sourceId.source_label, 20);
EXPECT_EQ(domain.nodes()[2].sourceId.instance_name, "Second");
EXPECT_EQ(domain.nodes()[2].sourceId.source_label, 10);
EXPECT_EQ(domain.nodes()[3].sourceId.instance_name, "Second");
EXPECT_EQ(domain.nodes()[3].sourceId.source_label, 20);
ASSERT_EQ(domain.elements().size(), 2U);
EXPECT_EQ(domain.elements()[0].sourceId.instanceName, "First");
EXPECT_EQ(domain.elements()[0].sourceId.instance_name, "First");
EXPECT_EQ(domain.elements()[0].nodeIndices,
(std::array<fesa::EntityIndex, 2>{0U, 1U}));
EXPECT_EQ(domain.elements()[1].sourceId.instanceName, "Second");
EXPECT_EQ(domain.elements()[1].sourceId.instance_name, "Second");
EXPECT_EQ(domain.elements()[1].nodeIndices,
(std::array<fesa::EntityIndex, 2>{2U, 3U}));
@@ -412,9 +412,9 @@ OnlySecond, 2, 5.
replaceOnce(minimalDeck(), "Root, 1, 6", "1, 1, 6"),
"Tip, 2, -1.",
"2, 2, -1."));
ASSERT_TRUE(direct.hasValue());
EXPECT_EQ(direct.value().steps()[0].boundaries[0].target, "1");
EXPECT_EQ(direct.value().steps()[0].loads[0].target, "2");
ASSERT_TRUE(direct.HasValue());
EXPECT_EQ(direct.Value().steps()[0].boundaries[0].target, "1");
EXPECT_EQ(direct.Value().steps()[0].loads[0].target, "2");
auto aboveThresholds = mapText(
"above-geometry-thresholds",
@@ -422,7 +422,7 @@ OnlySecond, 2, 5.
replaceOnce(minimalDeck(), "2, 1., 0., 0.", "2, 2e-12, 0., 0."),
"0., 1., 0.",
"1., 2e-12, 0."));
ASSERT_TRUE(aboveThresholds.hasValue());
ASSERT_TRUE(aboveThresholds.HasValue());
auto largeFinite = mapText(
"large-finite-geometry",
@@ -433,7 +433,7 @@ OnlySecond, 2, 5.
"2, 1e308, 1e297, 0."),
"0., 1., 0.",
"1e308, 0., 0."));
ASSERT_TRUE(largeFinite.hasValue());
ASSERT_TRUE(largeFinite.HasValue());
}
TEST(InpDomainMapping, KeepsNodeAndElementSetNamesInSeparateNamespaces) {
@@ -444,8 +444,8 @@ TEST(InpDomainMapping, KeepsNodeAndElementSetNamesInSeparateNamespaces) {
"*Elset, elset=ShellS4\n10",
"*Nset, nset=ShellS4\n1, 2, 3, 4\n*Elset, elset=ShellS4\n10"));
ASSERT_TRUE(sharedName.hasValue());
const auto& domain = sharedName.value();
ASSERT_TRUE(sharedName.HasValue());
const auto& domain = sharedName.Value();
EXPECT_TRUE(std::any_of(
domain.nodeSets().begin(), domain.nodeSets().end(),
[](const fesa::NodeSet& set) { return set.name == "ShellS4"; }));
@@ -460,8 +460,8 @@ TEST(InpDomainMapping, KeepsNodeAndElementSetNamesInSeparateNamespaces) {
"*Elset, elset=ShellS4\n10",
"*Nset, nset=Shared\n1\n*Nset, nset=Shared\n2\n"
"*Elset, elset=ShellS4\n10"));
ASSERT_FALSE(duplicateNodeSet.hasValue());
EXPECT_NE(findDiagnostic(duplicateNodeSet.status(), "duplicate-entity"), nullptr);
ASSERT_FALSE(duplicateNodeSet.HasValue());
EXPECT_NE(findDiagnostic(duplicateNodeSet.GetStatus(), "duplicate-entity"), nullptr);
auto duplicateElementSet = mapText(
"duplicate-part-element-set",
@@ -470,8 +470,8 @@ TEST(InpDomainMapping, KeepsNodeAndElementSetNamesInSeparateNamespaces) {
"*Elset, elset=ShellS4\n10",
"*Elset, elset=Repeated\n10\n*Elset, elset=Repeated\n20\n"
"*Elset, elset=ShellS4\n10"));
ASSERT_FALSE(duplicateElementSet.hasValue());
EXPECT_NE(findDiagnostic(duplicateElementSet.status(), "duplicate-entity"), nullptr);
ASSERT_FALSE(duplicateElementSet.HasValue());
EXPECT_NE(findDiagnostic(duplicateElementSet.GetStatus(), "duplicate-entity"), nullptr);
auto assemblySharedName = mapText(
"separate-assembly-set-namespaces",
@@ -483,8 +483,8 @@ TEST(InpDomainMapping, KeepsNodeAndElementSetNamesInSeparateNamespaces) {
"*Nset, nset=Root, instance=Beam-1\n1",
"*Nset, nset=Root, instance=Beam-1\n1\n"
"*Elset, elset=Root, instance=Beam-1\n1"));
ASSERT_TRUE(assemblySharedName.hasValue());
const auto& assemblyDomain = assemblySharedName.value();
ASSERT_TRUE(assemblySharedName.HasValue());
const auto& assemblyDomain = assemblySharedName.Value();
const auto rootNodeSetCount = std::count_if(
assemblyDomain.nodeSets().begin(), assemblyDomain.nodeSets().end(),
[](const fesa::NodeSet& set) { return set.name == "Root"; });
@@ -502,48 +502,48 @@ TEST(InpDomainMapping, KeepsNodeAndElementSetNamesInSeparateNamespaces) {
TEST(InpDomainMapping, NoOpAllowlistWarnsWithoutSemanticEffect) {
auto plain = mapText("without-no-ops", supportedInventoryDeck(false));
auto withNoOps = mapText("with-no-ops", supportedInventoryDeck(true));
ASSERT_TRUE(plain.hasValue());
ASSERT_TRUE(withNoOps.hasValue());
ASSERT_TRUE(plain.HasValue());
ASSERT_TRUE(withNoOps.HasValue());
EXPECT_TRUE(plain.value().warnings().empty());
ASSERT_EQ(withNoOps.value().warnings().size(), 8U);
EXPECT_EQ(withNoOps.value().warnings()[0].code, "ignored-input-keyword");
EXPECT_EQ(withNoOps.value().warnings()[0].keyword, "PREPRINT");
EXPECT_EQ(withNoOps.value().warnings()[1].keyword,
EXPECT_TRUE(plain.Value().warnings().empty());
ASSERT_EQ(withNoOps.Value().warnings().size(), 8U);
EXPECT_EQ(withNoOps.Value().warnings()[0].code, "ignored-input-keyword");
EXPECT_EQ(withNoOps.Value().warnings()[0].keyword, "PREPRINT");
EXPECT_EQ(withNoOps.Value().warnings()[1].keyword,
"TRANSVERSE SHEAR STIFFNESS");
EXPECT_EQ(withNoOps.value().warnings()[2].keyword, "RESTART");
EXPECT_EQ(withNoOps.value().warnings()[3].keyword, "OUTPUT");
EXPECT_EQ(withNoOps.value().warnings()[4].keyword, "NODE OUTPUT");
EXPECT_EQ(withNoOps.value().warnings()[5].keyword, "ELEMENT OUTPUT");
EXPECT_EQ(withNoOps.value().warnings()[6].keyword, "CONTACT OUTPUT");
EXPECT_EQ(withNoOps.value().warnings()[7].keyword, "OUTPUT");
for (const auto& warning : withNoOps.value().warnings()) {
EXPECT_EQ(warning.severity, fesa::Severity::warning);
EXPECT_EQ(withNoOps.Value().warnings()[2].keyword, "RESTART");
EXPECT_EQ(withNoOps.Value().warnings()[3].keyword, "OUTPUT");
EXPECT_EQ(withNoOps.Value().warnings()[4].keyword, "NODE OUTPUT");
EXPECT_EQ(withNoOps.Value().warnings()[5].keyword, "ELEMENT OUTPUT");
EXPECT_EQ(withNoOps.Value().warnings()[6].keyword, "CONTACT OUTPUT");
EXPECT_EQ(withNoOps.Value().warnings()[7].keyword, "OUTPUT");
for (const auto& warning : withNoOps.Value().warnings()) {
EXPECT_EQ(warning.severity, fesa::Severity::kWarning);
}
EXPECT_EQ(withNoOps.value().nodes().size(), plain.value().nodes().size());
EXPECT_EQ(withNoOps.value().elements().size(), plain.value().elements().size());
EXPECT_EQ(withNoOps.value().materials().size(), plain.value().materials().size());
EXPECT_EQ(withNoOps.value().sections().size(), plain.value().sections().size());
EXPECT_EQ(withNoOps.value().nodeSets().size(), plain.value().nodeSets().size());
EXPECT_EQ(withNoOps.value().elementSets().size(), plain.value().elementSets().size());
EXPECT_EQ(withNoOps.value().steps().size(), plain.value().steps().size());
EXPECT_EQ(withNoOps.value().steps()[0].boundaries.size(),
plain.value().steps()[0].boundaries.size());
EXPECT_EQ(withNoOps.value().steps()[0].loads.size(),
plain.value().steps()[0].loads.size());
EXPECT_EQ(withNoOps.Value().nodes().size(), plain.Value().nodes().size());
EXPECT_EQ(withNoOps.Value().elements().size(), plain.Value().elements().size());
EXPECT_EQ(withNoOps.Value().materials().size(), plain.Value().materials().size());
EXPECT_EQ(withNoOps.Value().sections().size(), plain.Value().sections().size());
EXPECT_EQ(withNoOps.Value().nodeSets().size(), plain.Value().nodeSets().size());
EXPECT_EQ(withNoOps.Value().elementSets().size(), plain.Value().elementSets().size());
EXPECT_EQ(withNoOps.Value().steps().size(), plain.Value().steps().size());
EXPECT_EQ(withNoOps.Value().steps()[0].boundaries.size(),
plain.Value().steps()[0].boundaries.size());
EXPECT_EQ(withNoOps.Value().steps()[0].loads.size(),
plain.Value().steps()[0].loads.size());
}
// MITC4-MAP-001
TEST(InpDomainMapping, MapsS4AndS4rThroughOneMitc4Identity) {
auto result = mapText("mitc4-map-001", shellDeck());
ASSERT_TRUE(result.hasValue());
const fesa::Domain& domain = result.value();
ASSERT_TRUE(result.HasValue());
const fesa::Domain& domain = result.Value();
EXPECT_TRUE(domain.elements().empty());
ASSERT_EQ(domain.shellElements().size(), 4U);
EXPECT_EQ(domain.shellElements()[0].sourceId.instanceName, "First");
EXPECT_EQ(domain.shellElements()[0].sourceId.sourceLabelText, "0010");
EXPECT_EQ(domain.shellElements()[0].sourceId.instance_name, "First");
EXPECT_EQ(domain.shellElements()[0].sourceId.source_label_text, "0010");
EXPECT_EQ(
domain.shellElements()[0].sourceType,
fesa::ShellSourceElementType::s4);
@@ -553,7 +553,7 @@ TEST(InpDomainMapping, MapsS4AndS4rThroughOneMitc4Identity) {
EXPECT_EQ(domain.shellElements()[0].sectionIndex, 0U);
EXPECT_EQ(domain.shellElements()[0].materialIndex, 0U);
EXPECT_EQ(domain.shellElements()[1].sourceId.instanceName, "First");
EXPECT_EQ(domain.shellElements()[1].sourceId.instance_name, "First");
EXPECT_EQ(
domain.shellElements()[1].sourceType,
fesa::ShellSourceElementType::s4r);
@@ -563,11 +563,11 @@ TEST(InpDomainMapping, MapsS4AndS4rThroughOneMitc4Identity) {
EXPECT_EQ(domain.shellElements()[1].sectionIndex, 1U);
EXPECT_EQ(domain.shellElements()[1].materialIndex, 1U);
EXPECT_EQ(domain.shellElements()[2].sourceId.instanceName, "Second");
EXPECT_EQ(domain.shellElements()[2].sourceId.instance_name, "Second");
EXPECT_EQ(
domain.shellElements()[2].nodeIndices,
(std::array<fesa::EntityIndex, 4>{6U, 7U, 8U, 9U}));
EXPECT_EQ(domain.shellElements()[3].sourceId.instanceName, "Second");
EXPECT_EQ(domain.shellElements()[3].sourceId.instance_name, "Second");
EXPECT_EQ(
fesa::kMitc4InternalFormulation,
std::string_view{"FESA-MITC4"});
@@ -612,34 +612,34 @@ TEST(InpDomainMapping, RejectsInvalidShellAssignmentsAndProperties) {
const std::vector<InvalidCase> cases{
{"unresolved-material",
replaceOnce(base, "material=Steel", "material=Missing"),
"unresolved-shell-section", fesa::FailureCategory::input},
"unresolved-shell-section", fesa::FailureCategory::kInput},
{"unresolved-elset",
replaceOnce(base, "elset=ShellS4, material=Steel",
"elset=Missing, material=Steel"),
"unresolved-shell-section", fesa::FailureCategory::input},
"unresolved-shell-section", fesa::FailureCategory::kInput},
{"missing-assignment",
replaceOnce(
base,
"*Shell Section, elset=ShellS4R, material=Aluminum\n0.2\n",
""),
"invalid-shell-section-assignment", fesa::FailureCategory::input},
"invalid-shell-section-assignment", fesa::FailureCategory::kInput},
{"conflicting-assignment",
replaceOnce(base, "elset=ShellS4R, material=Aluminum",
"elset=ShellS4, material=Aluminum"),
"invalid-shell-section-assignment", fesa::FailureCategory::input},
"invalid-shell-section-assignment", fesa::FailureCategory::kInput},
{"invalid-thickness",
replaceOnce(base, "0.2\n*End Part", "0.\n*End Part"),
"invalid-shell-thickness", fesa::FailureCategory::model},
"invalid-shell-thickness", fesa::FailureCategory::kModel},
{"invalid-material",
replaceOnce(base, "70000., 0.25", "70000., 0.5"),
"invalid-shell-material", fesa::FailureCategory::model}};
"invalid-shell-material", fesa::FailureCategory::kModel}};
for (const auto& testCase : cases) {
SCOPED_TRACE(testCase.name);
auto result = mapText("mitc4-map-002-" + testCase.name, testCase.deck);
ASSERT_FALSE(result.hasValue());
EXPECT_EQ(result.status().failureCategory(), testCase.category);
ASSERT_NE(findDiagnostic(result.status(), testCase.expectedCode), nullptr);
ASSERT_FALSE(result.HasValue());
EXPECT_EQ(result.GetStatus().Category(), testCase.category);
ASSERT_NE(findDiagnostic(result.GetStatus(), testCase.expectedCode), nullptr);
}
}
@@ -674,11 +674,11 @@ TEST(InpDomainMapping, RejectsInvalidShellConnectivityOptionsAndMixedModels) {
for (const auto& testCase : cases) {
SCOPED_TRACE(testCase.name);
auto result = mapText("mitc4-map-003-" + testCase.name, testCase.deck);
ASSERT_FALSE(result.hasValue());
ASSERT_FALSE(result.HasValue());
EXPECT_EQ(
result.status().failureCategory(),
fesa::FailureCategory::input);
ASSERT_NE(findDiagnostic(result.status(), testCase.expectedCode), nullptr);
result.GetStatus().Category(),
fesa::FailureCategory::kInput);
ASSERT_NE(findDiagnostic(result.GetStatus(), testCase.expectedCode), nullptr);
}
}
@@ -689,11 +689,11 @@ TEST(InpDomainMapping, PreservesProcedureLoadAndOutputRequestBoundariesForShells
"*End Step\n",
"*Output, field\n*Node Output\nU, RF\n*Element Output\nS\n*End Step\n");
auto valid = mapText("mitc4-map-004-no-ops", withNoOps);
ASSERT_TRUE(valid.hasValue());
ASSERT_EQ(valid.value().warnings().size(), 3U);
EXPECT_EQ(valid.value().warnings()[0].keyword, "OUTPUT");
EXPECT_EQ(valid.value().warnings()[1].keyword, "NODE OUTPUT");
EXPECT_EQ(valid.value().warnings()[2].keyword, "ELEMENT OUTPUT");
ASSERT_TRUE(valid.HasValue());
ASSERT_EQ(valid.Value().warnings().size(), 3U);
EXPECT_EQ(valid.Value().warnings()[0].keyword, "OUTPUT");
EXPECT_EQ(valid.Value().warnings()[1].keyword, "NODE OUTPUT");
EXPECT_EQ(valid.Value().warnings()[2].keyword, "ELEMENT OUTPUT");
struct InvalidCase {
std::string name;
@@ -720,11 +720,11 @@ TEST(InpDomainMapping, PreservesProcedureLoadAndOutputRequestBoundariesForShells
for (const auto& testCase : cases) {
SCOPED_TRACE(testCase.name);
auto result = mapText("mitc4-map-004-" + testCase.name, testCase.deck);
ASSERT_FALSE(result.hasValue());
ASSERT_FALSE(result.HasValue());
EXPECT_EQ(
result.status().failureCategory(),
fesa::FailureCategory::input);
ASSERT_NE(findDiagnostic(result.status(), testCase.expectedCode), nullptr);
result.GetStatus().Category(),
fesa::FailureCategory::kInput);
ASSERT_NE(findDiagnostic(result.GetStatus(), testCase.expectedCode), nullptr);
}
}
@@ -739,116 +739,116 @@ TEST(InpDomainMapping, RejectsUnsupportedAndInvalidPortfolio) {
const std::string base = minimalDeck();
const std::vector<InvalidCase> cases{
{"b31", replaceOnce(base, "type=B33", "type=B31"),
"unsupported-element-formulation", fesa::FailureCategory::input},
"unsupported-element-formulation", fesa::FailureCategory::kInput},
{"transform", replaceOnce(base, "*End Instance\n", "1., 0., 0.\n*End Instance\n"),
"unsupported-instance-transform", fesa::FailureCategory::input},
"unsupported-instance-transform", fesa::FailureCategory::kInput},
{"nested-assembly", replaceOnce(base, "*End Assembly\n", "*Assembly, name=Nested\n*End Assembly\n*End Assembly\n"),
"unsupported-nested-assembly", fesa::FailureCategory::input},
"unsupported-nested-assembly", fesa::FailureCategory::kInput},
{"multiple-step", base + "*Step\n*Static\n1., 1., 1., 1.\n*End Step\n",
"unsupported-multiple-step", fesa::FailureCategory::input},
"unsupported-multiple-step", fesa::FailureCategory::kInput},
{"late-part", replaceOnce(base, "*End Assembly\n*Material", "*End Assembly\n*Part, name=Late\n*End Part\n*Material"),
"invalid-keyword-location", fesa::FailureCategory::input},
"invalid-keyword-location", fesa::FailureCategory::kInput},
{"material-before-assembly", replaceOnce(base, "*Assembly, name=Assembly", "*Material, name=Early\n*Elastic\n50., 0.2\n*Assembly, name=Assembly"),
"invalid-keyword-location", fesa::FailureCategory::input},
"invalid-keyword-location", fesa::FailureCategory::kInput},
{"material-after-model-boundary", replaceOnce(base,
"*Material, name=Steel\n*Elastic\n100., 0.25\n*Boundary\nRoot, 1, 6",
"*Boundary\nRoot, 1, 6\n*Material, name=Steel\n*Elastic\n100., 0.25"),
"invalid-keyword-location", fesa::FailureCategory::input},
"invalid-keyword-location", fesa::FailureCategory::kInput},
{"keyword-after-step", base + "*Preprint, echo=NO\n",
"invalid-keyword-location", fesa::FailureCategory::input},
"invalid-keyword-location", fesa::FailureCategory::kInput},
{"assembly-instance-after-set", replaceOnce(base,
"*Instance, name=Beam-1, part=BeamPart\n*End Instance\n*Nset, nset=Root, instance=Beam-1\n1",
"*Nset, nset=Root, instance=Beam-1\n1\n*Instance, name=Beam-1, part=BeamPart\n*End Instance"),
"invalid-keyword-location", fesa::FailureCategory::input},
"invalid-keyword-location", fesa::FailureCategory::kInput},
{"element-before-node", replaceOnce(base,
"*Node\n1, 0., 0., 0.\n2, 1., 0., 0.\n*Element, type=B33\n1, 1, 2",
"*Element, type=B33\n1, 1, 2\n*Node\n1, 0., 0., 0.\n2, 1., 0., 0."),
"invalid-keyword-location", fesa::FailureCategory::input},
"invalid-keyword-location", fesa::FailureCategory::kInput},
{"shear-before-section-context", replaceOnce(base,
"*Beam General Section",
"*Transverse Shear Stiffness\n1., 2., 3.\n*Beam General Section"),
"invalid-keyword-location", fesa::FailureCategory::input},
"invalid-keyword-location", fesa::FailureCategory::kInput},
{"incomplete-part", replaceOnce(base,
base.substr(base.find("*Part"), base.find("*End Part") + std::string{"*End Part\n"}.size() - base.find("*Part")),
"*Part, name=BeamPart\n*End Part\n"),
"invalid-keyword-location", fesa::FailureCategory::input},
"invalid-keyword-location", fesa::FailureCategory::kInput},
{"empty-assembly", replaceOnce(base,
base.substr(base.find("*Assembly"), base.find("*End Assembly") + std::string{"*End Assembly\n"}.size() - base.find("*Assembly")),
"*Assembly, name=Assembly\n*End Assembly\n"),
"invalid-keyword-location", fesa::FailureCategory::input},
"invalid-keyword-location", fesa::FailureCategory::kInput},
{"cload-before-static", replaceOnce(base,
"*Static\n0.1, 1., 0.01, 1.\n*Cload\nTip, 2, -1.",
"*Cload\nTip, 2, -1.\n*Static\n0.1, 1., 0.01, 1."),
"invalid-keyword-location", fesa::FailureCategory::input},
"invalid-keyword-location", fesa::FailureCategory::kInput},
{"static-after-boundary", replaceOnce(
replaceOnce(base, "*Boundary\nRoot, 1, 6\n*Step", "*Step"),
"*Static\n0.1, 1., 0.01, 1.",
"*Boundary\nRoot, 1, 6\n*Static\n0.1, 1., 0.01, 1."),
"invalid-keyword-location", fesa::FailureCategory::input},
"invalid-keyword-location", fesa::FailureCategory::kInput},
{"boundary-after-cload", replaceOnce(
replaceOnce(base, "*Boundary\nRoot, 1, 6\n*Step", "*Step"),
"Tip, 2, -1.\n*End Step",
"Tip, 2, -1.\n*Boundary\nRoot, 1, 6\n*End Step"),
"invalid-keyword-location", fesa::FailureCategory::input},
"invalid-keyword-location", fesa::FailureCategory::kInput},
{"cload-after-no-op", replaceOnce(base, "*Cload", "*Restart, write, frequency=0\n*Cload"),
"invalid-keyword-location", fesa::FailureCategory::input},
"invalid-keyword-location", fesa::FailureCategory::kInput},
{"step-without-static", replaceOnce(base,
"*Static\n0.1, 1., 0.01, 1.\n*Cload\nTip, 2, -1.\n*End Step",
"*End Step"),
"invalid-keyword-location", fesa::FailureCategory::input},
"invalid-keyword-location", fesa::FailureCategory::kInput},
{"dependent-instance", replaceOnce(base, "part=BeamPart", "part=BeamPart, dependent=YES"),
"unsupported-instance-mesh-semantics", fesa::FailureCategory::input},
"unsupported-instance-mesh-semantics", fesa::FailureCategory::kInput},
{"coupled-section", replaceOnce(base, "1., 1., 0., 1., 1.", "1., 1., 0.5, 1., 1."),
"unsupported-coupled-section", fesa::FailureCategory::model},
"unsupported-coupled-section", fesa::FailureCategory::kModel},
{"zero-length", replaceOnce(base, "2, 1., 0., 0.", "2, 0., 0., 0."),
"invalid-beam-length", fesa::FailureCategory::model},
"invalid-beam-length", fesa::FailureCategory::kModel},
{"parallel-guide", replaceOnce(base, "0., 1., 0.", "1., 0., 0."),
"invalid-beam-guide-vector", fesa::FailureCategory::model},
"invalid-beam-guide-vector", fesa::FailureCategory::kModel},
{"nonpositive-area", replaceOnce(base, "1., 1., 0., 1., 1.", "0., 1., 0., 1., 1."),
"invalid-beam-property", fesa::FailureCategory::model},
"invalid-beam-property", fesa::FailureCategory::kModel},
{"nonpositive-derived-shear", replaceOnce(base, "100., 0.25", "100., -1.25"),
"invalid-beam-property", fesa::FailureCategory::model},
"invalid-beam-property", fesa::FailureCategory::kModel},
{"nonfinite-elastic", replaceOnce(base, "100., 0.25", "inf, 0.25"),
"invalid-beam-property", fesa::FailureCategory::model},
"invalid-beam-property", fesa::FailureCategory::kModel},
{"nonfinite-section-property", replaceOnce(base, "1., 1., 0., 1., 1.", "nan, 1., 0., 1., 1."),
"invalid-beam-property", fesa::FailureCategory::model},
"invalid-beam-property", fesa::FailureCategory::kModel},
{"nonfinite-guide", replaceOnce(base, "0., 1., 0.", "0., inf, 0."),
"invalid-beam-guide-vector", fesa::FailureCategory::model},
"invalid-beam-guide-vector", fesa::FailureCategory::kModel},
{"length-at-threshold", replaceOnce(base, "2, 1., 0., 0.", "2, 1e-12, 0., 0."),
"invalid-beam-length", fesa::FailureCategory::model},
"invalid-beam-length", fesa::FailureCategory::kModel},
{"guide-at-threshold", replaceOnce(base, "0., 1., 0.", "1., 1e-12, 0."),
"invalid-beam-guide-vector", fesa::FailureCategory::model},
"invalid-beam-guide-vector", fesa::FailureCategory::kModel},
{"duplicate-elastic", replaceOnce(base, "100., 0.25\n*Boundary", "100., 0.25\n*Elastic\n100., 0.25\n*Boundary"),
"duplicate-entity", fesa::FailureCategory::input},
"duplicate-entity", fesa::FailureCategory::kInput},
{"duplicate-node-label", replaceOnce(base, "2, 1., 0., 0.", "1, 1., 0., 0."),
"duplicate-entity", fesa::FailureCategory::input},
"duplicate-entity", fesa::FailureCategory::kInput},
{"dangling-connectivity", replaceOnce(base, "1, 1, 2", "1, 1, 9"),
"unresolved-reference", fesa::FailureCategory::input},
"unresolved-reference", fesa::FailureCategory::kInput},
{"invalid-dof", replaceOnce(base, "Root, 1, 6", "Root, 1, 7"),
"invalid-dof", fesa::FailureCategory::input},
"invalid-dof", fesa::FailureCategory::kInput},
{"nonfinite-coordinate", replaceOnce(base, "1., 0., 0.", "nan, 0., 0."),
"invalid-numeric-value", fesa::FailureCategory::input},
"invalid-numeric-value", fesa::FailureCategory::kInput},
{"malformed-node-arity", replaceOnce(base, "1, 0., 0., 0.", "1, 0., 0."),
"invalid-data-arity", fesa::FailureCategory::input},
"invalid-data-arity", fesa::FailureCategory::kInput},
{"nlgeom", replaceOnce(base, "nlgeom=NO", "nlgeom=YES"),
"unsupported-nonlinear-geometry", fesa::FailureCategory::model},
"unsupported-nonlinear-geometry", fesa::FailureCategory::kModel},
{"unknown-keyword", replaceOnce(base, "*Assembly, name=Assembly", "*Density\n1.\n*Assembly, name=Assembly"),
"unsupported-keyword", fesa::FailureCategory::input},
"unsupported-keyword", fesa::FailureCategory::kInput},
{"invalid-static-arity", replaceOnce(base, "0.1, 1., 0.01, 1.", "0.1, 1., 0.01"),
"invalid-static-data", fesa::FailureCategory::input},
"invalid-static-data", fesa::FailureCategory::kInput},
{"invalid-static-range", replaceOnce(base, "0.1, 1., 0.01, 1.", "0.1, 1., 2., 1."),
"invalid-static-data", fesa::FailureCategory::input},
"invalid-static-data", fesa::FailureCategory::kInput},
{"invalid-generate", replaceOnce(base, "*Elset, elset=BeamSet\n1", "*Elset, elset=BeamSet, generate\n1, 1, 0"),
"invalid-set-range", fesa::FailureCategory::input},
"invalid-set-range", fesa::FailureCategory::kInput},
{"nonlanding-generate", replaceOnce(base, "*Elset, elset=BeamSet\n1", "*Elset, elset=BeamSet, generate\n1, 2, 2"),
"invalid-set-range", fesa::FailureCategory::input},
"invalid-set-range", fesa::FailureCategory::kInput},
{"ambiguous-direct-label", replaceOnce(
replaceOnce(base,
"*End Instance\n*Nset, nset=Root",
"*End Instance\n*Instance, name=Beam-2, part=BeamPart\n*End Instance\n*Nset, nset=Root"),
"Root, 1, 6",
"1, 1, 6"),
"unresolved-reference", fesa::FailureCategory::input},
"unresolved-reference", fesa::FailureCategory::kInput},
{"ambiguous-part-set", replaceOnce(
replaceOnce(
replaceOnce(base,
@@ -858,24 +858,24 @@ TEST(InpDomainMapping, RejectsUnsupportedAndInvalidPortfolio) {
"*End Instance\n*Instance, name=Beam-2, part=BeamPart\n*End Instance\n*Nset, nset=Root"),
"Root, 1, 6",
"Local, 1, 6"),
"unresolved-reference", fesa::FailureCategory::input},
"unresolved-reference", fesa::FailureCategory::kInput},
{"direct-set-conflict", replaceOnce(base,
"*Step, name=Load",
"*Boundary\n1, 1, 1, 2.\n*Step, name=Load"),
"conflicting-boundary-condition", fesa::FailureCategory::input},
"conflicting-boundary-condition", fesa::FailureCategory::kInput},
{"dangling-boundary-target", replaceOnce(base, "Root, 1, 6", "Missing, 1, 6"),
"unresolved-reference", fesa::FailureCategory::input},
"unresolved-reference", fesa::FailureCategory::kInput},
{"conflicting-boundary", replaceOnce(base, "*Step, name=Load", "*Boundary\nRoot, 1, 1, 2.\n*Step, name=Load"),
"conflicting-boundary-condition", fesa::FailureCategory::input}};
"conflicting-boundary-condition", fesa::FailureCategory::kInput}};
for (const auto& testCase : cases) {
SCOPED_TRACE(testCase.name);
auto result = mapText("invalid-" + testCase.name, testCase.deck);
ASSERT_FALSE(result.hasValue());
EXPECT_EQ(result.status().failureCategory(), testCase.category);
const auto* diagnostic = findDiagnostic(result.status(), testCase.expectedCode);
ASSERT_FALSE(result.HasValue());
EXPECT_EQ(result.GetStatus().Category(), testCase.category);
const auto* diagnostic = findDiagnostic(result.GetStatus(), testCase.expectedCode);
ASSERT_NE(diagnostic, nullptr);
EXPECT_EQ(diagnostic->severity, fesa::Severity::error);
EXPECT_EQ(diagnostic->severity, fesa::Severity::kError);
EXPECT_FALSE(diagnostic->location.file.empty());
EXPECT_GT(diagnostic->location.line, 0U);
}
@@ -899,16 +899,16 @@ TEST(InpDomainMapping, RejectsUnsupportedAndInvalidPortfolio) {
for (const auto& testCase : sameTokenCases) {
SCOPED_TRACE("same-token-" + testCase.name);
auto result = mapText("same-token-" + testCase.name, testCase.deck);
ASSERT_FALSE(result.hasValue());
ASSERT_FALSE(result.HasValue());
EXPECT_EQ(
result.status().failureCategory(),
fesa::FailureCategory::input);
result.GetStatus().Category(),
fesa::FailureCategory::kInput);
const auto* diagnostic =
findDiagnostic(result.status(), "unresolved-reference");
findDiagnostic(result.GetStatus(), "unresolved-reference");
ASSERT_NE(diagnostic, nullptr);
EXPECT_EQ(diagnostic->severity, fesa::Severity::error);
EXPECT_EQ(diagnostic->severity, fesa::Severity::kError);
EXPECT_EQ(diagnostic->keyword, testCase.expectedKeyword);
EXPECT_EQ(diagnostic->entityIdentity, "1");
EXPECT_EQ(diagnostic->entity_identity, "1");
EXPECT_EQ(diagnostic->location.line, testCase.expectedLine);
EXPECT_EQ(
diagnostic->location.file.filename().string(),
@@ -923,9 +923,9 @@ TEST(InpDomainMapping, RejectsDloadWithoutDistributedLoadObject) {
"*Dload\nBeamSet, PY, -1.\n");
auto result = mapText("dload", deck);
ASSERT_FALSE(result.hasValue());
EXPECT_EQ(result.status().failureCategory(), fesa::FailureCategory::input);
const auto* diagnostic = findDiagnostic(result.status(), "unsupported-keyword");
ASSERT_FALSE(result.HasValue());
EXPECT_EQ(result.GetStatus().Category(), fesa::FailureCategory::kInput);
const auto* diagnostic = findDiagnostic(result.GetStatus(), "unsupported-keyword");
ASSERT_NE(diagnostic, nullptr);
EXPECT_EQ(diagnostic->keyword, "DLOAD");
}
+27 -27
View File
@@ -63,37 +63,37 @@ TEST(InpSyntax, RejectsMalformedOrOrphanData) {
std::filesystem::remove(missingPath, removeError);
const auto unreadable = fesa::AbaqusInputReader{}.read(missingPath);
ASSERT_FALSE(unreadable.hasValue());
ASSERT_FALSE(unreadable.HasValue());
EXPECT_EQ(
unreadable.status().failureCategory(),
fesa::FailureCategory::input);
ASSERT_EQ(unreadable.status().diagnostics().size(), 1U);
EXPECT_EQ(unreadable.status().diagnostics()[0].code,
unreadable.GetStatus().Category(),
fesa::FailureCategory::kInput);
ASSERT_EQ(unreadable.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(unreadable.GetStatus().Diagnostics()[0].code,
"input-file-unreadable");
const TemporaryInputFile malformed{"malformed-keyword", "*, name=value\n"};
const auto malformedResult =
fesa::AbaqusInputReader{}.read(malformed.path());
ASSERT_FALSE(malformedResult.hasValue());
ASSERT_FALSE(malformedResult.HasValue());
EXPECT_EQ(
malformedResult.status().failureCategory(),
fesa::FailureCategory::input);
ASSERT_EQ(malformedResult.status().diagnostics().size(), 1U);
EXPECT_EQ(malformedResult.status().diagnostics()[0].code,
malformedResult.GetStatus().Category(),
fesa::FailureCategory::kInput);
ASSERT_EQ(malformedResult.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(malformedResult.GetStatus().Diagnostics()[0].code,
"malformed-keyword");
EXPECT_EQ(malformedResult.status().diagnostics()[0].location.line, 1U);
EXPECT_EQ(malformedResult.GetStatus().Diagnostics()[0].location.line, 1U);
const TemporaryInputFile orphan{
"orphan-data", "** comment\n\norphan, data\n"};
const auto orphanResult = fesa::AbaqusInputReader{}.read(orphan.path());
ASSERT_FALSE(orphanResult.hasValue());
ASSERT_FALSE(orphanResult.HasValue());
EXPECT_EQ(
orphanResult.status().failureCategory(),
fesa::FailureCategory::input);
ASSERT_EQ(orphanResult.status().diagnostics().size(), 1U);
EXPECT_EQ(orphanResult.status().diagnostics()[0].code,
orphanResult.GetStatus().Category(),
fesa::FailureCategory::kInput);
ASSERT_EQ(orphanResult.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(orphanResult.GetStatus().Diagnostics()[0].code,
"orphan-data-line");
EXPECT_EQ(orphanResult.status().diagnostics()[0].location.line, 3U);
EXPECT_EQ(orphanResult.GetStatus().Diagnostics()[0].location.line, 3U);
}
TEST(InpSyntax, ReadsLegacyCantileverWithoutMutation) {
@@ -105,23 +105,23 @@ TEST(InpSyntax, ReadsLegacyCantileverWithoutMutation) {
const auto result = fesa::AbaqusInputReader{}.read(inputPath);
ASSERT_TRUE(result.hasValue());
EXPECT_EQ(result.value().sourceContentIdentity,
ASSERT_TRUE(result.HasValue());
EXPECT_EQ(result.Value().sourceContentIdentity,
"fnv1a64:04543464cc970405");
EXPECT_EQ(result.value().sourcePath,
EXPECT_EQ(result.Value().sourcePath,
std::filesystem::absolute(inputPath).lexically_normal());
ASSERT_EQ(result.value().blocks.size(), 30U);
EXPECT_EQ(result.value().blocks.front().canonicalName, "HEADING");
EXPECT_EQ(result.value().blocks.front().location.line, 1U);
EXPECT_EQ(result.value().blocks.back().canonicalName, "END STEP");
ASSERT_EQ(result.Value().blocks.size(), 30U);
EXPECT_EQ(result.Value().blocks.front().canonicalName, "HEADING");
EXPECT_EQ(result.Value().blocks.front().location.line, 1U);
EXPECT_EQ(result.Value().blocks.back().canonicalName, "END STEP");
const auto element = std::find_if(
result.value().blocks.begin(),
result.value().blocks.end(),
result.Value().blocks.begin(),
result.Value().blocks.end(),
[](const fesa::KeywordBlock& block) {
return block.canonicalName == "ELEMENT";
});
ASSERT_NE(element, result.value().blocks.end());
ASSERT_NE(element, result.Value().blocks.end());
ASSERT_EQ(element->parameters.size(), 1U);
EXPECT_EQ(element->parameters[0].name, "TYPE");
ASSERT_TRUE(element->parameters[0].value.has_value());
+10 -10
View File
@@ -45,9 +45,9 @@ TEST(InpSyntax, CanonicalizesKeywordAndParameterNamesOnly) {
const auto result = fesa::AbaqusInputReader{}.read(input.path());
ASSERT_TRUE(result.hasValue());
ASSERT_EQ(result.value().blocks.size(), 1U);
const auto& block = result.value().blocks[0];
ASSERT_TRUE(result.HasValue());
ASSERT_EQ(result.Value().blocks.size(), 1U);
const auto& block = result.Value().blocks[0];
EXPECT_EQ(block.canonicalName, "ELEMENT");
EXPECT_EQ(block.originalLine, originalLine);
EXPECT_EQ(block.location.line, 1U);
@@ -73,23 +73,23 @@ TEST(InpSyntax, PreservesDataAndSourceLocations) {
const auto result = fesa::AbaqusInputReader{}.read(input.path());
ASSERT_TRUE(result.hasValue());
ASSERT_TRUE(result.HasValue());
EXPECT_EQ(
result.value().sourcePath,
result.Value().sourcePath,
std::filesystem::absolute(input.path()).lexically_normal());
EXPECT_EQ(result.value().sourceContentIdentity,
EXPECT_EQ(result.Value().sourceContentIdentity,
"fnv1a64:c120b6ed2445be46");
ASSERT_EQ(result.value().blocks.size(), 1U);
const auto& block = result.value().blocks[0];
ASSERT_EQ(result.Value().blocks.size(), 1U);
const auto& block = result.Value().blocks[0];
EXPECT_EQ(block.canonicalName, "NODE");
EXPECT_EQ(block.originalLine, "*NoDe");
EXPECT_EQ(block.location.file, result.value().sourcePath);
EXPECT_EQ(block.location.file, result.Value().sourcePath);
EXPECT_EQ(block.location.line, 3U);
ASSERT_EQ(block.data.size(), 1U);
EXPECT_EQ(
block.data[0].fields,
(std::vector<std::string>{"0007", "Label_A", "", ""}));
EXPECT_EQ(block.data[0].location.file, result.value().sourcePath);
EXPECT_EQ(block.data[0].location.file, result.Value().sourcePath);
EXPECT_EQ(block.data[0].location.line, 4U);
}
+33 -33
View File
@@ -5,7 +5,7 @@
#include "fesa/analysis/analysis_model.hpp"
#include "fesa/analysis/analysis_state.hpp"
#include "fesa/build_info.hpp"
#include "fesa/build_info.h"
#include "fesa/fem/dof_manager.hpp"
#include "fesa/model/domain.hpp"
@@ -158,27 +158,27 @@ WriterFixture makeFixture(
const bool useDefaultCentroid = false) {
auto domainResult = fesa::Domain::create(
makeDefinition(source, useDefaultCentroid));
if (!domainResult.hasValue()) {
if (!domainResult.HasValue()) {
throw std::runtime_error{"Writer fixture Domain construction failed."};
}
auto domain = std::make_unique<fesa::Domain>(
std::move(domainResult.value()));
std::move(domainResult.Value()));
auto modelResult = fesa::AnalysisModel::create(*domain);
if (!modelResult.hasValue()) {
if (!modelResult.HasValue()) {
throw std::runtime_error{"Writer fixture AnalysisModel construction failed."};
}
const fesa::AnalysisModel model = std::move(modelResult.value());
const fesa::AnalysisModel model = std::move(modelResult.Value());
auto dofsResult = fesa::DofManager::create(model);
if (!dofsResult.hasValue()) {
if (!dofsResult.HasValue()) {
throw std::runtime_error{"Writer fixture DofManager construction failed."};
}
auto dofs = std::make_unique<fesa::DofManager>(
std::move(dofsResult.value()));
std::move(dofsResult.Value()));
auto state = std::make_unique<fesa::AnalysisState>(
fesa::AnalysisState::create(*dofs, {"Step-1", 0U}));
for (std::size_t index = 0U; index < state->displacement().size(); ++index) {
for (std::size_t index = 0U; index < state->displacement().Size(); ++index) {
state->displacement()[index] = 0.25 + static_cast<double>(index);
state->externalForce()[index] = 100.0 + static_cast<double>(index);
state->internalForce()[index] = 200.0 + 2.0 * static_cast<double>(index);
@@ -259,25 +259,25 @@ fesa::ModelDefinition makeShellDefinition(const std::filesystem::path& source) {
WriterFixture makeShellFixture(const std::filesystem::path& source) {
auto domainResult = fesa::Domain::create(makeShellDefinition(source));
if (!domainResult.hasValue()) {
if (!domainResult.HasValue()) {
throw std::runtime_error{"Shell writer fixture Domain construction failed."};
}
auto domain = std::make_unique<fesa::Domain>(
std::move(domainResult.value()));
std::move(domainResult.Value()));
auto modelResult = fesa::AnalysisModel::create(*domain);
if (!modelResult.hasValue()) {
if (!modelResult.HasValue()) {
throw std::runtime_error{"Shell writer fixture AnalysisModel construction failed."};
}
const fesa::AnalysisModel model = std::move(modelResult.value());
const fesa::AnalysisModel model = std::move(modelResult.Value());
auto dofsResult = fesa::DofManager::create(model);
if (!dofsResult.hasValue()) {
if (!dofsResult.HasValue()) {
throw std::runtime_error{"Shell writer fixture DofManager construction failed."};
}
auto dofs = std::make_unique<fesa::DofManager>(
std::move(dofsResult.value()));
std::move(dofsResult.Value()));
auto state = std::make_unique<fesa::AnalysisState>(
fesa::AnalysisState::create(*dofs, {"Step-1", 0U}));
for (std::size_t index = 0U; index < state->displacement().size(); ++index) {
for (std::size_t index = 0U; index < state->displacement().Size(); ++index) {
state->displacement()[index] = 0.01 * static_cast<double>(index + 1U);
state->externalForce()[index] = 10.0 + static_cast<double>(index);
state->internalForce()[index] = 20.0 + static_cast<double>(index);
@@ -325,7 +325,7 @@ WriterFixture makeShellFixture(const std::filesystem::path& source) {
candidate.verificationMetrics = {1.0e-13, 2.0e-13, 3.0e-13};
const fesa::Status commit = state->commitShellResults(
{0U}, std::move(candidate));
if (!commit.isOk()) {
if (!commit.IsOk()) {
throw std::runtime_error{"Shell writer fixture state commit failed."};
}
return {std::move(domain), std::move(dofs), std::move(state)};
@@ -830,11 +830,11 @@ std::size_t entryCount(const std::filesystem::path& directory) {
void expectOutputFailure(
const fesa::Status& status, const std::string& expectedCode) {
ASSERT_FALSE(status.isOk());
EXPECT_EQ(status.failureCategory(), fesa::FailureCategory::output);
ASSERT_EQ(status.diagnostics().size(), 1U);
EXPECT_EQ(status.diagnostics()[0U].severity, fesa::Severity::error);
EXPECT_EQ(status.diagnostics()[0U].code, expectedCode);
ASSERT_FALSE(status.IsOk());
EXPECT_EQ(status.Category(), fesa::FailureCategory::kOutput);
ASSERT_EQ(status.Diagnostics().size(), 1U);
EXPECT_EQ(status.Diagnostics()[0U].severity, fesa::Severity::kError);
EXPECT_EQ(status.Diagnostics()[0U].code, expectedCode);
}
} // namespace
@@ -846,7 +846,7 @@ TEST(Hdf5ResultsWriter, WritesExactSchemaShapesAttributesAndIdentity) {
const auto output = directory.path() / "results.h5";
fesa::Hdf5ResultsWriter writer;
ASSERT_TRUE(writer.write(output, *fixture.domain, *fixture.state, {}).isOk());
ASSERT_TRUE(writer.write(output, *fixture.domain, *fixture.state, {}).IsOk());
ASSERT_GT(H5Fis_hdf5(output.string().c_str()), 0);
const auto file = openFile(output);
@@ -874,7 +874,7 @@ TEST(Hdf5ResultsWriter, WritesExactSchemaShapesAttributesAndIdentity) {
"linear-static-3d-euler-beam");
EXPECT_EQ(
readStringAttribute(metadata.get(), "solver_version"),
std::string{fesa::solverVersion()});
std::string{fesa::SolverVersion()});
const std::string normalizedSource =
std::filesystem::absolute(source).lexically_normal().generic_u8string();
EXPECT_EQ(
@@ -1031,7 +1031,7 @@ TEST(Hdf5ResultsWriter, WritesMandatoryOutputsDespiteOutputRequests) {
TempDirectory directory{"mandatory"};
auto fixture = makeFixture(directory.path() / "request-model.inp");
const fesa::Diagnostic ignoredRequest{
fesa::Severity::warning,
fesa::Severity::kWarning,
"ignored-output-request",
{fixture.domain->sourcePath(), 70U},
"*OUTPUT",
@@ -1042,7 +1042,7 @@ TEST(Hdf5ResultsWriter, WritesMandatoryOutputsDespiteOutputRequests) {
fesa::Hdf5ResultsWriter writer;
ASSERT_TRUE(
writer.write(output, *fixture.domain, *fixture.state, {ignoredRequest})
.isOk());
.IsOk());
const auto file = openFile(output);
for (const char* suffix : {
"/nodal/displacement",
@@ -1063,13 +1063,13 @@ TEST(Hdf5ResultsWriter, WritesWarningsAndDefaultCentroid) {
TempDirectory directory{"warnings"};
auto fixture = makeFixture(directory.path() / "centroid.inp", true);
std::vector<fesa::Diagnostic> diagnostics = {
{fesa::Severity::warning,
{fesa::Severity::kWarning,
"ignored-output-request",
{fixture.domain->sourcePath(), 80U},
"*OUTPUT",
"FIELD",
"Ignored output request."},
{fesa::Severity::warning,
{fesa::Severity::kWarning,
"ignored-keyword",
{fixture.domain->sourcePath(), 20U},
"*PREPRINT",
@@ -1078,7 +1078,7 @@ TEST(Hdf5ResultsWriter, WritesWarningsAndDefaultCentroid) {
const auto output = directory.path() / "results.h5";
fesa::Hdf5ResultsWriter writer;
ASSERT_TRUE(writer.write(output, *fixture.domain, *fixture.state, diagnostics).isOk());
ASSERT_TRUE(writer.write(output, *fixture.domain, *fixture.state, diagnostics).IsOk());
const auto file = openFile(output);
auto stressRows = readStressRows(file.get());
ASSERT_EQ(stressRows.size(), 2U);
@@ -1153,7 +1153,7 @@ TEST(Hdf5ResultsWriter, SuccessfullyReplacesExistingFinal) {
writeBytes(final, {'o', 'l', 'd'});
fesa::Hdf5ResultsWriter writer;
ASSERT_TRUE(writer.write(final, *fixture.domain, *fixture.state, {}).isOk());
ASSERT_TRUE(writer.write(final, *fixture.domain, *fixture.state, {}).IsOk());
EXPECT_GT(H5Fis_hdf5(final.string().c_str()), 0);
EXPECT_EQ(entryCount(directory.path()), 1U);
const auto file = openFile(final);
@@ -1171,7 +1171,7 @@ TEST(Hdf5ResultsWriter, WritesExactShellMetadataAndModelIdentity) {
const auto output = directory.path() / "results.h5";
fesa::Hdf5ResultsWriter writer;
ASSERT_TRUE(writer.write(output, *fixture.domain, *fixture.state, {}).isOk());
ASSERT_TRUE(writer.write(output, *fixture.domain, *fixture.state, {}).IsOk());
const auto file = openFile(output);
Hdf5Handle metadata{
@@ -1261,7 +1261,7 @@ TEST(Hdf5ResultsWriter, WritesExactMandatoryShellResultInventory) {
const auto output = directory.path() / "results.h5";
fesa::Hdf5ResultsWriter writer;
ASSERT_TRUE(writer.write(output, *fixture.domain, *fixture.state, {}).isOk());
ASSERT_TRUE(writer.write(output, *fixture.domain, *fixture.state, {}).IsOk());
const auto file = openFile(output);
const std::string shellRoot = std::string{kStepRoot} + "/element/shell";
expectNumericDataset(
@@ -1319,7 +1319,7 @@ TEST(Hdf5ResultsWriter, WritesShellInventoryDespiteRequestsAndOmitsForbiddenPath
TempDirectory directory{"shell-mandatory"};
auto fixture = makeShellFixture(directory.path() / "shell.inp");
const fesa::Diagnostic ignoredRequest{
fesa::Severity::warning,
fesa::Severity::kWarning,
"ignored-output-request",
{fixture.domain->sourcePath(), 80U},
"*ELEMENT OUTPUT",
@@ -1330,7 +1330,7 @@ TEST(Hdf5ResultsWriter, WritesShellInventoryDespiteRequestsAndOmitsForbiddenPath
fesa::Hdf5ResultsWriter writer;
ASSERT_TRUE(
writer.write(output, *fixture.domain, *fixture.state, {ignoredRequest})
.isOk());
.IsOk());
const auto file = openFile(output);
for (const char* suffix : {
"/element/shell/local_frame",
+87 -83
View File
@@ -1,4 +1,4 @@
#include "fesa/math/matrix.hpp"
#include "fesa/math/matrix.h"
#include <gtest/gtest.h>
@@ -10,100 +10,104 @@ namespace fesa {
namespace {
TEST(DenseMath, RowMajorMatrixMatchesKnownGemvGemm) {
const std::size_t wraparoundRows =
(std::numeric_limits<std::size_t>::max)() / 2U + 1U;
EXPECT_THROW(static_cast<void>(Matrix{wraparoundRows, 2}), std::length_error);
const std::size_t wraparound_rows =
(std::numeric_limits<std::size_t>::max)() / 2U + 1U;
EXPECT_THROW(static_cast<void>(Matrix{wraparound_rows, 2}),
std::length_error);
Matrix zeroRows{0, 3};
Vector threeValues{3, 2.0};
const Vector zeroRowProduct = zeroRows.multiply(threeValues);
EXPECT_EQ(zeroRowProduct.size(), 0U);
Matrix zero_rows{0, 3};
Vector three_values{3, 2.0};
const Vector zero_row_product = zero_rows.Multiply(three_values);
EXPECT_EQ(zero_row_product.Size(), 0U);
Matrix zeroColumns{2, 0};
const Vector zeroColumnProduct = zeroColumns.multiply(Vector{0});
ASSERT_EQ(zeroColumnProduct.size(), 2U);
EXPECT_DOUBLE_EQ(zeroColumnProduct[0], 0.0);
EXPECT_DOUBLE_EQ(zeroColumnProduct[1], 0.0);
Matrix zero_columns{2, 0};
const Vector zero_column_product = zero_columns.Multiply(Vector{0});
ASSERT_EQ(zero_column_product.Size(), 2U);
EXPECT_DOUBLE_EQ(zero_column_product[0], 0.0);
EXPECT_DOUBLE_EQ(zero_column_product[1], 0.0);
Matrix zeroInnerRight{0, 3};
const Matrix zeroInnerProduct = zeroColumns.multiply(zeroInnerRight);
EXPECT_EQ(zeroInnerProduct.rows(), 2U);
EXPECT_EQ(zeroInnerProduct.columns(), 3U);
for (std::size_t row = 0; row < zeroInnerProduct.rows(); ++row) {
for (std::size_t column = 0; column < zeroInnerProduct.columns(); ++column) {
EXPECT_DOUBLE_EQ(zeroInnerProduct(row, column), 0.0);
}
Matrix zero_inner_right{0, 3};
const Matrix zero_inner_product = zero_columns.Multiply(zero_inner_right);
EXPECT_EQ(zero_inner_product.Rows(), 2U);
EXPECT_EQ(zero_inner_product.Columns(), 3U);
for (std::size_t row = 0; row < zero_inner_product.Rows(); ++row) {
for (std::size_t column = 0; column < zero_inner_product.Columns();
++column) {
EXPECT_DOUBLE_EQ(zero_inner_product(row, column), 0.0);
}
}
Matrix left{2, 3};
left(0, 0) = 1.0;
left(0, 1) = 2.0;
left(0, 2) = 3.0;
left(1, 0) = 4.0;
left(1, 1) = 5.0;
left(1, 2) = 6.0;
Matrix left{2, 3};
left(0, 0) = 1.0;
left(0, 1) = 2.0;
left(0, 2) = 3.0;
left(1, 0) = 4.0;
left(1, 1) = 5.0;
left(1, 2) = 6.0;
EXPECT_EQ(&left(0, 0) + 1, &left(0, 1));
EXPECT_EQ(&left(0, 0) + 2, &left(0, 2));
EXPECT_EQ(&left(0, 0) + 3, &left(1, 0));
const Matrix& constLeft = left;
EXPECT_DOUBLE_EQ(constLeft(1, 2), 6.0);
EXPECT_EQ(&left(0, 0) + 1, &left(0, 1));
EXPECT_EQ(&left(0, 0) + 2, &left(0, 2));
EXPECT_EQ(&left(0, 0) + 3, &left(1, 0));
const Matrix& const_left = left;
EXPECT_DOUBLE_EQ(const_left(1, 2), 6.0);
Matrix copied{left};
copied(0, 0) = 42.0;
EXPECT_DOUBLE_EQ(left(0, 0), 1.0);
Matrix copied{left};
copied(0, 0) = 42.0;
EXPECT_DOUBLE_EQ(left(0, 0), 1.0);
Matrix copyAssigned{0, 0};
copyAssigned = left;
copyAssigned(1, 2) = -7.0;
EXPECT_DOUBLE_EQ(left(1, 2), 6.0);
Matrix copy_assigned{0, 0};
copy_assigned = left;
copy_assigned(1, 2) = -7.0;
EXPECT_DOUBLE_EQ(left(1, 2), 6.0);
Matrix moved{std::move(copied)};
EXPECT_EQ(copied.rows(), 0U);
EXPECT_EQ(copied.columns(), 0U);
EXPECT_EQ(moved.rows(), 2U);
EXPECT_EQ(moved.columns(), 3U);
EXPECT_DOUBLE_EQ(moved(0, 0), 42.0);
EXPECT_NO_THROW(static_cast<void>(copied.multiply(Vector{0})));
Matrix moved{std::move(copied)};
EXPECT_EQ(copied.Rows(), 0U);
EXPECT_EQ(copied.Columns(), 0U);
EXPECT_EQ(moved.Rows(), 2U);
EXPECT_EQ(moved.Columns(), 3U);
EXPECT_DOUBLE_EQ(moved(0, 0), 42.0);
EXPECT_NO_THROW(static_cast<void>(copied.Multiply(Vector{0})));
Matrix moveAssigned{1, 1, -1.0};
moveAssigned = std::move(copyAssigned);
EXPECT_EQ(copyAssigned.rows(), 0U);
EXPECT_EQ(copyAssigned.columns(), 0U);
EXPECT_EQ(moveAssigned.rows(), 2U);
EXPECT_EQ(moveAssigned.columns(), 3U);
EXPECT_DOUBLE_EQ(moveAssigned(1, 2), -7.0);
Matrix move_assigned{1, 1, -1.0};
move_assigned = std::move(copy_assigned);
EXPECT_EQ(copy_assigned.Rows(), 0U);
EXPECT_EQ(copy_assigned.Columns(), 0U);
EXPECT_EQ(move_assigned.Rows(), 2U);
EXPECT_EQ(move_assigned.Columns(), 3U);
EXPECT_DOUBLE_EQ(move_assigned(1, 2), -7.0);
Vector vector{3};
vector[0] = 7.0;
vector[1] = 8.0;
vector[2] = 9.0;
const Vector matrixVectorProduct = left.multiply(vector);
ASSERT_EQ(matrixVectorProduct.size(), 2U);
EXPECT_DOUBLE_EQ(matrixVectorProduct[0], 50.0);
EXPECT_DOUBLE_EQ(matrixVectorProduct[1], 122.0);
Vector vector{3};
vector[0] = 7.0;
vector[1] = 8.0;
vector[2] = 9.0;
const Vector matrix_vector_product = left.Multiply(vector);
ASSERT_EQ(matrix_vector_product.Size(), 2U);
EXPECT_DOUBLE_EQ(matrix_vector_product[0], 50.0);
EXPECT_DOUBLE_EQ(matrix_vector_product[1], 122.0);
Matrix right{3, 2};
right(0, 0) = 7.0;
right(0, 1) = 8.0;
right(1, 0) = 9.0;
right(1, 1) = 10.0;
right(2, 0) = 11.0;
right(2, 1) = 12.0;
const Matrix matrixProduct = left.multiply(right);
ASSERT_EQ(matrixProduct.rows(), 2U);
ASSERT_EQ(matrixProduct.columns(), 2U);
EXPECT_DOUBLE_EQ(matrixProduct(0, 0), 58.0);
EXPECT_DOUBLE_EQ(matrixProduct(0, 1), 64.0);
EXPECT_DOUBLE_EQ(matrixProduct(1, 0), 139.0);
EXPECT_DOUBLE_EQ(matrixProduct(1, 1), 154.0);
Matrix right{3, 2};
right(0, 0) = 7.0;
right(0, 1) = 8.0;
right(1, 0) = 9.0;
right(1, 1) = 10.0;
right(2, 0) = 11.0;
right(2, 1) = 12.0;
const Matrix matrix_product = left.Multiply(right);
ASSERT_EQ(matrix_product.Rows(), 2U);
ASSERT_EQ(matrix_product.Columns(), 2U);
EXPECT_DOUBLE_EQ(matrix_product(0, 0), 58.0);
EXPECT_DOUBLE_EQ(matrix_product(0, 1), 64.0);
EXPECT_DOUBLE_EQ(matrix_product(1, 0), 139.0);
EXPECT_DOUBLE_EQ(matrix_product(1, 1), 154.0);
EXPECT_THROW(static_cast<void>(left(2, 0)), std::out_of_range);
EXPECT_THROW(static_cast<void>(left(0, 3)), std::out_of_range);
EXPECT_THROW(static_cast<void>(constLeft(2, 0)), std::out_of_range);
EXPECT_THROW(static_cast<void>(left.multiply(Vector{2})), std::invalid_argument);
EXPECT_THROW(static_cast<void>(left.multiply(Matrix{4, 1})), std::invalid_argument);
EXPECT_THROW(static_cast<void>(left(2, 0)), std::out_of_range);
EXPECT_THROW(static_cast<void>(left(0, 3)), std::out_of_range);
EXPECT_THROW(static_cast<void>(const_left(2, 0)), std::out_of_range);
EXPECT_THROW(static_cast<void>(left.Multiply(Vector{2})),
std::invalid_argument);
EXPECT_THROW(static_cast<void>(left.Multiply(Matrix{4, 1})),
std::invalid_argument);
}
} // namespace
} // namespace fesa
} // namespace
} // namespace fesa
+95 -110
View File
@@ -1,6 +1,4 @@
#include "fesa/fem/dof_manager.hpp"
#include "fesa/math/matrix.hpp"
#include "fesa/math/sparse_matrix.hpp"
#include "fesa/math/sparse_matrix.h"
#include <gtest/gtest.h>
@@ -10,6 +8,9 @@
#include <utility>
#include <vector>
#include "fesa/fem/dof_manager.hpp"
#include "fesa/math/matrix.h"
namespace {
using fesa::CooContribution;
@@ -22,128 +23,112 @@ static_assert(
"SparseMatrix must own CSR storage independently of dense Matrix.");
TEST(SparseAssembly, ValidatesKnownCsrAndMultiply) {
const SparsePattern pattern{
{0U, 2U, 2U, 4U, 5U},
{0U, 2U, 1U, 3U, 3U}};
std::vector<CooContribution> contributions{
{2U, 3U, 4.0, 2U, 0U},
{0U, 2U, 2.0, 0U, 1U},
{3U, 3U, 5.0, 3U, 0U},
{0U, 0U, 1.0, 0U, 0U},
{2U, 1U, 3.0, 1U, 0U}};
const SparsePattern pattern{{0U, 2U, 2U, 4U, 5U}, {0U, 2U, 1U, 3U, 3U}};
std::vector<CooContribution> contributions{{2U, 3U, 4.0, 2U, 0U},
{0U, 2U, 2.0, 0U, 1U},
{3U, 3U, 5.0, 3U, 0U},
{0U, 0U, 1.0, 0U, 0U},
{2U, 1U, 3.0, 1U, 0U}};
auto result = SparseMatrix::fromCoo(
4U, 4U, std::move(contributions), pattern);
ASSERT_TRUE(result.hasValue());
const SparseMatrix& matrix = result.value();
auto result =
SparseMatrix::FromCoo(4U, 4U, std::move(contributions), pattern);
ASSERT_TRUE(result.HasValue());
const SparseMatrix& matrix = result.Value();
EXPECT_EQ(matrix.rows(), 4U);
EXPECT_EQ(matrix.columns(), 4U);
EXPECT_EQ(matrix.rowOffsets(), pattern.rowOffsets);
EXPECT_EQ(matrix.columnIndices(), pattern.columnIndices);
EXPECT_EQ(matrix.values(), (std::vector<double>{1.0, 2.0, 3.0, 4.0, 5.0}));
EXPECT_TRUE(matrix.validate().isOk());
EXPECT_EQ(matrix.Rows(), 4U);
EXPECT_EQ(matrix.Columns(), 4U);
EXPECT_EQ(matrix.RowOffsets(), pattern.rowOffsets);
EXPECT_EQ(matrix.ColumnIndices(), pattern.columnIndices);
EXPECT_EQ(matrix.Values(), (std::vector<double>{1.0, 2.0, 3.0, 4.0, 5.0}));
EXPECT_TRUE(matrix.Validate().IsOk());
Vector rhs{4U};
rhs[0U] = 1.0;
rhs[1U] = 2.0;
rhs[2U] = 3.0;
rhs[3U] = 4.0;
const Vector product = matrix.multiply(rhs);
ASSERT_EQ(product.size(), 4U);
EXPECT_DOUBLE_EQ(product[0U], 7.0);
EXPECT_DOUBLE_EQ(product[1U], 0.0);
EXPECT_DOUBLE_EQ(product[2U], 22.0);
EXPECT_DOUBLE_EQ(product[3U], 20.0);
EXPECT_THROW(static_cast<void>(matrix.multiply(Vector{3U})), std::invalid_argument);
Vector rhs{4U};
rhs[0U] = 1.0;
rhs[1U] = 2.0;
rhs[2U] = 3.0;
rhs[3U] = 4.0;
const Vector product = matrix.Multiply(rhs);
ASSERT_EQ(product.Size(), 4U);
EXPECT_DOUBLE_EQ(product[0U], 7.0);
EXPECT_DOUBLE_EQ(product[1U], 0.0);
EXPECT_DOUBLE_EQ(product[2U], 22.0);
EXPECT_DOUBLE_EQ(product[3U], 20.0);
EXPECT_THROW(static_cast<void>(matrix.Multiply(Vector{3U})),
std::invalid_argument);
}
TEST(SparseAssembly, ReducesDuplicatesInFixedTupleOrder) {
const SparsePattern pattern{{0U, 1U}, {0U}};
const std::vector<CooContribution> contributions{
{0U, 0U, 1.0, 2U, 0U},
{0U, 0U, -1.0e16, 1U, 0U},
{0U, 0U, 1.0e16, 0U, 0U}};
const SparsePattern pattern{{0U, 1U}, {0U}};
const std::vector<CooContribution> contributions{{0U, 0U, 1.0, 2U, 0U},
{0U, 0U, -1.0e16, 1U, 0U},
{0U, 0U, 1.0e16, 0U, 0U}};
auto first = SparseMatrix::fromCoo(1U, 1U, contributions, pattern);
ASSERT_TRUE(first.hasValue());
ASSERT_EQ(first.value().values().size(), 1U);
EXPECT_DOUBLE_EQ(first.value().values()[0U], 1.0);
auto first = SparseMatrix::FromCoo(1U, 1U, contributions, pattern);
ASSERT_TRUE(first.HasValue());
ASSERT_EQ(first.Value().Values().size(), 1U);
EXPECT_DOUBLE_EQ(first.Value().Values()[0U], 1.0);
auto reversedContributions = contributions;
std::reverse(reversedContributions.begin(), reversedContributions.end());
auto second = SparseMatrix::fromCoo(
1U, 1U, std::move(reversedContributions), pattern);
ASSERT_TRUE(second.hasValue());
EXPECT_EQ(second.value().rowOffsets(), first.value().rowOffsets());
EXPECT_EQ(second.value().columnIndices(), first.value().columnIndices());
EXPECT_EQ(second.value().values(), first.value().values());
auto reversed_contributions = contributions;
std::reverse(reversed_contributions.begin(), reversed_contributions.end());
auto second =
SparseMatrix::FromCoo(1U, 1U, std::move(reversed_contributions), pattern);
ASSERT_TRUE(second.HasValue());
EXPECT_EQ(second.Value().RowOffsets(), first.Value().RowOffsets());
EXPECT_EQ(second.Value().ColumnIndices(), first.Value().ColumnIndices());
EXPECT_EQ(second.Value().Values(), first.Value().Values());
}
TEST(SparseAssembly, RejectsInvalidIndexPatternAndShape) {
const SparsePattern oneEntry{{0U, 1U}, {0U}};
const auto expectFailure = [](
std::size_t rows,
std::size_t columns,
std::vector<CooContribution> contributions,
const SparsePattern& pattern) {
auto result = SparseMatrix::fromCoo(
rows, columns, std::move(contributions), pattern);
EXPECT_FALSE(result.hasValue());
if (!result.hasValue()) {
EXPECT_FALSE(result.status().isOk());
EXPECT_EQ(result.status().failureCategory(), fesa::FailureCategory::model);
EXPECT_FALSE(result.status().diagnostics().empty());
}
};
const SparsePattern one_entry{{0U, 1U}, {0U}};
const auto expect_failure = [](std::size_t rows, std::size_t columns,
std::vector<CooContribution> contributions,
const SparsePattern& pattern) {
auto result =
SparseMatrix::FromCoo(rows, columns, std::move(contributions), pattern);
EXPECT_FALSE(result.HasValue());
if (!result.HasValue()) {
EXPECT_FALSE(result.GetStatus().IsOk());
EXPECT_EQ(result.GetStatus().Category(), fesa::FailureCategory::kModel);
EXPECT_FALSE(result.GetStatus().Diagnostics().empty());
}
};
expectFailure(2U, 2U, {}, {{0U, 0U}, {}});
expectFailure(1U, 1U, {}, {{1U, 1U}, {0U}});
expectFailure(2U, 2U, {}, {{0U, 1U, 0U}, {0U}});
expectFailure(1U, 2U, {}, {{0U, 2U}, {1U, 0U}});
expectFailure(1U, 1U, {}, {{0U, 2U}, {0U, 0U}});
expectFailure(1U, 1U, {}, {{0U, 1U}, {1U}});
expectFailure(1U, 1U, {{1U, 0U, 1.0, 0U, 0U}}, oneEntry);
expectFailure(1U, 1U, {{0U, 1U, 1.0, 0U, 0U}}, oneEntry);
expectFailure(1U, 2U, {{0U, 1U, 1.0, 0U, 0U}}, {{0U, 1U}, {0U}});
expectFailure(
1U,
1U,
{{0U, 0U, (std::numeric_limits<double>::infinity)(), 0U, 0U}},
oneEntry);
expectFailure(
1U,
1U,
{{0U, 0U, (std::numeric_limits<double>::quiet_NaN)(), 0U, 0U}},
oneEntry);
expectFailure(
1U,
1U,
{{0U, 0U, (std::numeric_limits<double>::max)(), 0U, 0U},
{0U, 0U, (std::numeric_limits<double>::max)(), 1U, 0U}},
oneEntry);
expect_failure(2U, 2U, {}, {{0U, 0U}, {}});
expect_failure(1U, 1U, {}, {{1U, 1U}, {0U}});
expect_failure(2U, 2U, {}, {{0U, 1U, 0U}, {0U}});
expect_failure(1U, 2U, {}, {{0U, 2U}, {1U, 0U}});
expect_failure(1U, 1U, {}, {{0U, 2U}, {0U, 0U}});
expect_failure(1U, 1U, {}, {{0U, 1U}, {1U}});
expect_failure(1U, 1U, {{1U, 0U, 1.0, 0U, 0U}}, one_entry);
expect_failure(1U, 1U, {{0U, 1U, 1.0, 0U, 0U}}, one_entry);
expect_failure(1U, 2U, {{0U, 1U, 1.0, 0U, 0U}}, {{0U, 1U}, {0U}});
expect_failure(1U, 1U,
{{0U, 0U, (std::numeric_limits<double>::infinity)(), 0U, 0U}},
one_entry);
expect_failure(1U, 1U,
{{0U, 0U, (std::numeric_limits<double>::quiet_NaN)(), 0U, 0U}},
one_entry);
expect_failure(1U, 1U,
{{0U, 0U, (std::numeric_limits<double>::max)(), 0U, 0U},
{0U, 0U, (std::numeric_limits<double>::max)(), 1U, 0U}},
one_entry);
}
TEST(SparseAssembly, PreservesExpectedStructuralZeros) {
const SparsePattern pattern{
{0U, 2U, 4U, 5U},
{0U, 2U, 1U, 2U, 0U}};
std::vector<CooContribution> contributions{
{0U, 0U, 2.0, 0U, 0U},
{1U, 1U, 4.0, 0U, 1U},
{1U, 1U, -4.0, 1U, 0U}};
const SparsePattern pattern{{0U, 2U, 4U, 5U}, {0U, 2U, 1U, 2U, 0U}};
std::vector<CooContribution> contributions{
{0U, 0U, 2.0, 0U, 0U}, {1U, 1U, 4.0, 0U, 1U}, {1U, 1U, -4.0, 1U, 0U}};
auto result = SparseMatrix::fromCoo(
3U, 3U, std::move(contributions), pattern);
ASSERT_TRUE(result.hasValue());
EXPECT_EQ(result.value().rowOffsets(), pattern.rowOffsets);
EXPECT_EQ(result.value().columnIndices(), pattern.columnIndices);
EXPECT_EQ(
result.value().values(),
(std::vector<double>{2.0, 0.0, 0.0, 0.0, 0.0}));
EXPECT_EQ(
std::count(result.value().values().begin(), result.value().values().end(), 0.0),
4);
auto result =
SparseMatrix::FromCoo(3U, 3U, std::move(contributions), pattern);
ASSERT_TRUE(result.HasValue());
EXPECT_EQ(result.Value().RowOffsets(), pattern.rowOffsets);
EXPECT_EQ(result.Value().ColumnIndices(), pattern.columnIndices);
EXPECT_EQ(result.Value().Values(),
(std::vector<double>{2.0, 0.0, 0.0, 0.0, 0.0}));
EXPECT_EQ(std::count(result.Value().Values().begin(),
result.Value().Values().end(), 0.0),
4);
}
} // namespace
} // namespace
+64 -63
View File
@@ -1,4 +1,4 @@
#include "fesa/math/vector.hpp"
#include "fesa/math/vector.h"
#include <gtest/gtest.h>
@@ -10,77 +10,78 @@ namespace fesa {
namespace {
TEST(DenseMath, VectorOwnsAndChecksContiguousStorage) {
Vector empty{0};
EXPECT_EQ(empty.size(), 0U);
EXPECT_DOUBLE_EQ(empty.norm(), 0.0);
EXPECT_NO_THROW(empty.scale(3.0));
EXPECT_NO_THROW(empty.axpy(-2.0, Vector{0}));
EXPECT_THROW(static_cast<void>(empty[0]), std::out_of_range);
Vector empty{0};
EXPECT_EQ(empty.Size(), 0U);
EXPECT_DOUBLE_EQ(empty.Norm(), 0.0);
EXPECT_NO_THROW(empty.Scale(3.0));
EXPECT_NO_THROW(empty.Axpy(-2.0, Vector{0}));
EXPECT_THROW(static_cast<void>(empty[0]), std::out_of_range);
Vector original{3};
original[0] = 1.0;
original[1] = -2.0;
original[2] = 3.0;
Vector original{3};
original[0] = 1.0;
original[1] = -2.0;
original[2] = 3.0;
EXPECT_EQ(original.data() + 1, &original[1]);
EXPECT_EQ(original.data() + 2, &original[2]);
const Vector& constOriginal = original;
EXPECT_EQ(constOriginal.data() + 2, &constOriginal[2]);
EXPECT_EQ(original.Data() + 1, &original[1]);
EXPECT_EQ(original.Data() + 2, &original[2]);
const Vector& const_original = original;
EXPECT_EQ(const_original.Data() + 2, &const_original[2]);
Vector copied{original};
EXPECT_NE(copied.data(), original.data());
copied[0] = 99.0;
EXPECT_DOUBLE_EQ(original[0], 1.0);
Vector copied{original};
EXPECT_NE(copied.Data(), original.Data());
copied[0] = 99.0;
EXPECT_DOUBLE_EQ(original[0], 1.0);
Vector copyAssigned{0};
copyAssigned = original;
EXPECT_NE(copyAssigned.data(), original.data());
copyAssigned[1] = 17.0;
EXPECT_DOUBLE_EQ(original[1], -2.0);
Vector copy_assigned{0};
copy_assigned = original;
EXPECT_NE(copy_assigned.Data(), original.Data());
copy_assigned[1] = 17.0;
EXPECT_DOUBLE_EQ(original[1], -2.0);
Vector moved{std::move(copied)};
EXPECT_EQ(copied.size(), 0U);
EXPECT_EQ(moved.size(), 3U);
EXPECT_DOUBLE_EQ(moved[0], 99.0);
EXPECT_NO_THROW(copied.scale(4.0));
Vector moved{std::move(copied)};
EXPECT_EQ(copied.Size(), 0U);
EXPECT_EQ(moved.Size(), 3U);
EXPECT_DOUBLE_EQ(moved[0], 99.0);
EXPECT_NO_THROW(copied.Scale(4.0));
Vector moveAssigned{1, -1.0};
moveAssigned = std::move(copyAssigned);
EXPECT_EQ(copyAssigned.size(), 0U);
EXPECT_EQ(moveAssigned.size(), 3U);
EXPECT_DOUBLE_EQ(moveAssigned[1], 17.0);
Vector move_assigned{1, -1.0};
move_assigned = std::move(copy_assigned);
EXPECT_EQ(copy_assigned.Size(), 0U);
EXPECT_EQ(move_assigned.Size(), 3U);
EXPECT_DOUBLE_EQ(move_assigned[1], 17.0);
Vector rhs{3};
rhs[0] = 4.0;
rhs[1] = 5.0;
rhs[2] = -6.0;
EXPECT_DOUBLE_EQ(original.dot(rhs), -24.0);
EXPECT_NEAR(original.norm(), std::sqrt(14.0), 1.0e-15);
Vector rhs{3};
rhs[0] = 4.0;
rhs[1] = 5.0;
rhs[2] = -6.0;
EXPECT_DOUBLE_EQ(original.Dot(rhs), -24.0);
EXPECT_NEAR(original.Norm(), std::sqrt(14.0), 1.0e-15);
Vector scaled{original};
scaled.scale(-0.5);
EXPECT_DOUBLE_EQ(scaled[0], -0.5);
EXPECT_DOUBLE_EQ(scaled[1], 1.0);
EXPECT_DOUBLE_EQ(scaled[2], -1.5);
Vector scaled{original};
scaled.Scale(-0.5);
EXPECT_DOUBLE_EQ(scaled[0], -0.5);
EXPECT_DOUBLE_EQ(scaled[1], 1.0);
EXPECT_DOUBLE_EQ(scaled[2], -1.5);
Vector accumulated{3};
accumulated[0] = 1.0;
accumulated[1] = 2.0;
accumulated[2] = 3.0;
Vector increment{3};
increment[0] = 4.0;
increment[1] = -1.0;
increment[2] = 0.5;
accumulated.axpy(2.0, increment);
EXPECT_DOUBLE_EQ(accumulated[0], 9.0);
EXPECT_DOUBLE_EQ(accumulated[1], 0.0);
EXPECT_DOUBLE_EQ(accumulated[2], 4.0);
Vector accumulated{3};
accumulated[0] = 1.0;
accumulated[1] = 2.0;
accumulated[2] = 3.0;
Vector increment{3};
increment[0] = 4.0;
increment[1] = -1.0;
increment[2] = 0.5;
accumulated.Axpy(2.0, increment);
EXPECT_DOUBLE_EQ(accumulated[0], 9.0);
EXPECT_DOUBLE_EQ(accumulated[1], 0.0);
EXPECT_DOUBLE_EQ(accumulated[2], 4.0);
EXPECT_THROW(static_cast<void>(original[3]), std::out_of_range);
EXPECT_THROW(static_cast<void>(constOriginal[3]), std::out_of_range);
EXPECT_THROW(static_cast<void>(original.dot(Vector{2})), std::invalid_argument);
EXPECT_THROW(original.axpy(1.0, Vector{2}), std::invalid_argument);
EXPECT_THROW(static_cast<void>(original[3]), std::out_of_range);
EXPECT_THROW(static_cast<void>(const_original[3]), std::out_of_range);
EXPECT_THROW(static_cast<void>(original.Dot(Vector{2})),
std::invalid_argument);
EXPECT_THROW(original.Axpy(1.0, Vector{2}), std::invalid_argument);
}
} // namespace
} // namespace fesa
} // namespace
} // namespace fesa
+27 -27
View File
@@ -59,7 +59,7 @@ fesa::ModelDefinition makeOwnedDefinition() {
1.0,
{"models/owned.inp", 55U}}};
definition.warnings = {{
fesa::Severity::warning,
fesa::Severity::kWarning,
"ignored-output-request",
{"models/owned.inp", 70U},
"*OUTPUT",
@@ -73,17 +73,17 @@ fesa::ModelDefinition makeOwnedDefinition() {
TEST(DomainModel, ImmutableOwnershipPreservesStableOrder) {
auto definition = makeOwnedDefinition();
auto result = fesa::Domain::create(definition);
ASSERT_TRUE(result.hasValue());
ASSERT_TRUE(result.HasValue());
definition.sourcePath = "mutated.inp";
definition.sourceContentIdentity = "mutated";
definition.nodes[0].sourceId.sourceLabelText = "mutated";
definition.nodes[0].sourceId.source_label_text = "mutated";
definition.nodes[0].coordinates[0] = -99.0;
definition.sections[0].sectionPoints[0][0] = -99.0;
definition.steps[0].loads[0].magnitude = 99.0;
definition.warnings[0].code = "mutated";
const fesa::Domain& domain = result.value();
const fesa::Domain& domain = result.Value();
const fesa::Node* const firstNodeAddress = domain.nodes().data();
static_assert(std::is_same_v<
decltype(std::declval<const fesa::Domain&>().nodes()),
@@ -95,10 +95,10 @@ TEST(DomainModel, ImmutableOwnershipPreservesStableOrder) {
EXPECT_EQ(domain.sourcePath(), std::filesystem::path{"models/owned.inp"});
EXPECT_EQ(domain.sourceContentIdentity(), "fnv1a64:fedcba9876543210");
ASSERT_EQ(domain.nodes().size(), 2U);
EXPECT_EQ(domain.nodes()[0].sourceId.sourceLabel, 20);
EXPECT_EQ(domain.nodes()[0].sourceId.sourceLabelText, "0020");
EXPECT_EQ(domain.nodes()[0].sourceId.source_label, 20);
EXPECT_EQ(domain.nodes()[0].sourceId.source_label_text, "0020");
EXPECT_DOUBLE_EQ(domain.nodes()[0].coordinates[0], 2.0);
EXPECT_EQ(domain.nodes()[1].sourceId.sourceLabel, 10);
EXPECT_EQ(domain.nodes()[1].sourceId.source_label, 10);
EXPECT_EQ(domain.nodes().data(), firstNodeAddress);
ASSERT_EQ(domain.elements().size(), 1U);
@@ -162,23 +162,23 @@ TEST(DomainModel, MultipleIdentityInstancesDoNotMerge) {
{"models/two-instances.inp", 60U}}};
auto result = fesa::Domain::create(std::move(definition));
ASSERT_TRUE(result.hasValue());
const fesa::Domain& domain = result.value();
ASSERT_TRUE(result.HasValue());
const fesa::Domain& domain = result.Value();
ASSERT_EQ(domain.nodes().size(), 4U);
EXPECT_EQ(domain.nodes()[0].sourceId.sourceLabel, 1);
EXPECT_EQ(domain.nodes()[2].sourceId.sourceLabel, 1);
EXPECT_EQ(domain.nodes()[0].sourceId.sourceLabelText, "1");
EXPECT_EQ(domain.nodes()[2].sourceId.sourceLabelText, "1");
EXPECT_EQ(domain.nodes()[0].sourceId.source_label, 1);
EXPECT_EQ(domain.nodes()[2].sourceId.source_label, 1);
EXPECT_EQ(domain.nodes()[0].sourceId.source_label_text, "1");
EXPECT_EQ(domain.nodes()[2].sourceId.source_label_text, "1");
EXPECT_NE(
domain.nodes()[0].sourceId.instanceName,
domain.nodes()[2].sourceId.instanceName);
domain.nodes()[0].sourceId.instance_name,
domain.nodes()[2].sourceId.instance_name);
ASSERT_EQ(domain.elements().size(), 2U);
EXPECT_EQ(domain.elements()[0].sourceId.sourceLabel, 1);
EXPECT_EQ(domain.elements()[1].sourceId.sourceLabel, 1);
EXPECT_EQ(domain.elements()[0].sourceId.instanceName, "Instance-A");
EXPECT_EQ(domain.elements()[1].sourceId.instanceName, "Instance-B");
EXPECT_EQ(domain.elements()[0].sourceId.source_label, 1);
EXPECT_EQ(domain.elements()[1].sourceId.source_label, 1);
EXPECT_EQ(domain.elements()[0].sourceId.instance_name, "Instance-A");
EXPECT_EQ(domain.elements()[1].sourceId.instance_name, "Instance-B");
EXPECT_EQ(domain.elements()[0].nodeIndices[0], 0U);
EXPECT_EQ(domain.elements()[1].nodeIndices[0], 2U);
}
@@ -212,13 +212,13 @@ TEST(DomainModel, ShellOwnershipPreservesResolvedAssignmentsAndOptionalFrames) {
{0U, {0.0, 1.0, 0.0}, {1.0, 0.0, 0.0}, {0.0, 0.0, -1.0}}};
auto result = fesa::Domain::create(definition);
ASSERT_TRUE(result.hasValue());
ASSERT_TRUE(result.HasValue());
definition.shellSections[0].thickness = -1.0;
definition.shellElements[0].sourceId.sourceLabelText = "mutated";
definition.shellElements[0].sourceId.source_label_text = "mutated";
definition.shellNodeInitialFrames[0].director[2] = -1.0;
const fesa::Domain& domain = result.value();
const fesa::Domain& domain = result.Value();
static_assert(std::is_same_v<
decltype(std::declval<const fesa::Domain&>().shellElements()),
const std::vector<fesa::Mitc4ShellDefinition>&>);
@@ -230,9 +230,9 @@ TEST(DomainModel, ShellOwnershipPreservesResolvedAssignmentsAndOptionalFrames) {
const std::vector<fesa::ShellNodeInitialFrame>&>);
ASSERT_EQ(domain.shellElements().size(), 2U);
EXPECT_EQ(domain.shellElements()[0].sourceId.sourceLabel, 20);
EXPECT_EQ(domain.shellElements()[0].sourceId.sourceLabelText, "0020");
EXPECT_EQ(domain.shellElements()[1].sourceId.sourceLabel, 10);
EXPECT_EQ(domain.shellElements()[0].sourceId.source_label, 20);
EXPECT_EQ(domain.shellElements()[0].sourceId.source_label_text, "0020");
EXPECT_EQ(domain.shellElements()[1].sourceId.source_label, 10);
EXPECT_EQ(domain.shellElements()[0].sourceType, fesa::ShellSourceElementType::s4r);
EXPECT_EQ(domain.shellElements()[1].sourceType, fesa::ShellSourceElementType::s4);
EXPECT_EQ(domain.shellElements()[0].nodeIndices[3], 7U);
@@ -258,6 +258,6 @@ TEST(DomainModel, ShellOwnershipPreservesResolvedAssignmentsAndOptionalFrames) {
(std::array<double, 3>{0.0, 1.0, 0.0}));
auto noFramesResult = fesa::Domain::create(fesa::ModelDefinition{});
ASSERT_TRUE(noFramesResult.hasValue());
EXPECT_TRUE(noFramesResult.value().shellNodeInitialFrames().empty());
ASSERT_TRUE(noFramesResult.HasValue());
EXPECT_TRUE(noFramesResult.Value().shellNodeInitialFrames().empty());
}
+17 -17
View File
@@ -39,22 +39,22 @@ TEST(DomainModel, SourceAndInternalIdentityRemainDistinct) {
EXPECT_EQ(
std::make_tuple(
firstIdentity.instanceName,
firstIdentity.sourceLabel,
firstIdentity.sourceLabelText),
firstIdentity.instance_name,
firstIdentity.source_label,
firstIdentity.source_label_text),
std::make_tuple(
equalIdentity.instanceName,
equalIdentity.sourceLabel,
equalIdentity.sourceLabelText));
equalIdentity.instance_name,
equalIdentity.source_label,
equalIdentity.source_label_text));
EXPECT_LT(
std::make_tuple(
firstIdentity.instanceName,
firstIdentity.sourceLabel,
firstIdentity.sourceLabelText),
firstIdentity.instance_name,
firstIdentity.source_label,
firstIdentity.source_label_text),
std::make_tuple(
laterIdentity.instanceName,
laterIdentity.sourceLabel,
laterIdentity.sourceLabelText));
laterIdentity.instance_name,
laterIdentity.source_label,
laterIdentity.source_label_text));
const fesa::Node node{firstIdentity, {1.0, 2.0, 3.0}, nodeLocation};
const fesa::LinearElasticMaterial material{
@@ -116,7 +116,7 @@ TEST(DomainModel, SourceAndInternalIdentityRemainDistinct) {
definition.instances = {instance};
definition.steps = {step};
definition.warnings = {{
fesa::Severity::warning,
fesa::Severity::kWarning,
"ignored-output-request",
{"models/beam.inp", 75U},
"*OUTPUT",
@@ -124,7 +124,7 @@ TEST(DomainModel, SourceAndInternalIdentityRemainDistinct) {
"Output request does not alter mandatory FESA results."}};
ASSERT_EQ(definition.nodes.size(), 1U);
EXPECT_EQ(definition.nodes[0].sourceId.sourceLabelText, "0007");
EXPECT_EQ(definition.nodes[0].sourceId.source_label_text, "0007");
EXPECT_EQ(definition.nodes[0].location.line, 11U);
ASSERT_EQ(definition.instances.size(), 1U);
EXPECT_EQ(definition.instances[0].partName, "BeamPart");
@@ -167,9 +167,9 @@ TEST(DomainModel, Mitc4ShellRecordsPreserveSourceAndInternalIdentity) {
fesa::EntityIndex{7},
{"models/shell.inp", 21U}};
EXPECT_EQ(s4Element.sourceId.instanceName, "Shell-Instance");
EXPECT_EQ(s4Element.sourceId.sourceLabel, 41);
EXPECT_EQ(s4Element.sourceId.sourceLabelText, "0041");
EXPECT_EQ(s4Element.sourceId.instance_name, "Shell-Instance");
EXPECT_EQ(s4Element.sourceId.source_label, 41);
EXPECT_EQ(s4Element.sourceId.source_label_text, "0041");
EXPECT_EQ(s4Element.sourceType, fesa::ShellSourceElementType::s4);
EXPECT_EQ(s4rElement.sourceType, fesa::ShellSourceElementType::s4r);
EXPECT_NE(s4Element.sourceType, s4rElement.sourceType);
+19 -19
View File
@@ -89,10 +89,10 @@ const fesa::ShellNodeInitialFrame& frameFor(
void expectFailureCode(
const fesa::Result<fesa::ShellGeometry>& result,
const std::string& code) {
ASSERT_FALSE(result.hasValue());
EXPECT_EQ(result.status().failureCategory(), fesa::FailureCategory::model);
ASSERT_EQ(result.status().diagnostics().size(), 1U);
EXPECT_EQ(result.status().diagnostics()[0].code, code);
ASSERT_FALSE(result.HasValue());
EXPECT_EQ(result.GetStatus().Category(), fesa::FailureCategory::kModel);
ASSERT_EQ(result.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(result.GetStatus().Diagnostics()[0].code, code);
}
} // namespace
@@ -107,12 +107,12 @@ TEST(Mitc4Geometry, BuildsDeterministicFramesForPlanarRotatedAndWarpedElements)
auto planar = fesa::preprocessShellGeometry(
planarNodes, {element(10U, {0U, 1U, 2U, 3U})}, sections());
ASSERT_TRUE(planar.hasValue());
ASSERT_EQ(planar.value().elementData.size(), 1U);
expectVectorNear(planar.value().elementData[0].normalCandidate, {0.0, 0.0, 1.0});
EXPECT_NEAR(planar.value().elementData[0].surfaceAreaWeight, 1.0, 1.0e-12);
ASSERT_EQ(planar.value().nodalFrames.size(), 4U);
for (const auto& frame : planar.value().nodalFrames) {
ASSERT_TRUE(planar.HasValue());
ASSERT_EQ(planar.Value().elementData.size(), 1U);
expectVectorNear(planar.Value().elementData[0].normalCandidate, {0.0, 0.0, 1.0});
EXPECT_NEAR(planar.Value().elementData[0].surfaceAreaWeight, 1.0, 1.0e-12);
ASSERT_EQ(planar.Value().nodalFrames.size(), 4U);
for (const auto& frame : planar.Value().nodalFrames) {
expectVectorNear(frame.director, {0.0, 0.0, 1.0});
expectVectorNear(frame.tangentA, {1.0, 0.0, 0.0});
expectVectorNear(frame.tangentB, {0.0, 1.0, 0.0});
@@ -127,8 +127,8 @@ TEST(Mitc4Geometry, BuildsDeterministicFramesForPlanarRotatedAndWarpedElements)
auto rotated = fesa::preprocessShellGeometry(
rotatedNodes, {element(11U, {0U, 1U, 2U, 3U})}, sections());
ASSERT_TRUE(rotated.hasValue());
const auto& rotatedFrame = frameFor(rotated.value(), 0U);
ASSERT_TRUE(rotated.HasValue());
const auto& rotatedFrame = frameFor(rotated.Value(), 0U);
expectVectorNear(rotatedFrame.director, {1.0, 0.0, 0.0});
expectVectorNear(rotatedFrame.tangentA, {0.0, 1.0, 0.0});
expectVectorNear(rotatedFrame.tangentB, {0.0, 0.0, 1.0});
@@ -142,9 +142,9 @@ TEST(Mitc4Geometry, BuildsDeterministicFramesForPlanarRotatedAndWarpedElements)
auto warped = fesa::preprocessShellGeometry(
warpedNodes, {element(12U, {0U, 1U, 2U, 3U})}, sections());
ASSERT_TRUE(warped.hasValue());
EXPECT_GT(warped.value().elementData[0].surfaceAreaWeight, 2.0);
for (const auto& frame : warped.value().nodalFrames) {
ASSERT_TRUE(warped.HasValue());
EXPECT_GT(warped.Value().elementData[0].surfaceAreaWeight, 2.0);
for (const auto& frame : warped.Value().nodalFrames) {
expectRightHandedFrame(frame);
}
}
@@ -166,13 +166,13 @@ TEST(Mitc4Geometry, AreaWeightsSharedDirectorsInStableSourceIdentityOrder) {
auto second = fesa::preprocessShellGeometry(
nodes, {flat, tilted}, sections());
ASSERT_TRUE(first.hasValue());
ASSERT_TRUE(second.hasValue());
ASSERT_TRUE(first.HasValue());
ASSERT_TRUE(second.HasValue());
const Vector3 expectedSharedDirector{
-1.0 / std::sqrt(5.0), 0.0, 2.0 / std::sqrt(5.0)};
for (const auto sharedNode : {1U, 2U}) {
const auto& firstFrame = frameFor(first.value(), sharedNode);
const auto& secondFrame = frameFor(second.value(), sharedNode);
const auto& firstFrame = frameFor(first.Value(), sharedNode);
const auto& secondFrame = frameFor(second.Value(), sharedNode);
expectVectorNear(firstFrame.director, expectedSharedDirector);
expectVectorNear(firstFrame.director, secondFrame.director, 0.0);
expectVectorNear(firstFrame.tangentA, {0.0, 1.0, 0.0});
+15 -15
View File
@@ -20,12 +20,12 @@ fesa::DofManager makeEmptyDofs() {
{definition.sourcePath, 10U}}};
auto domain = fesa::Domain::create(std::move(definition));
EXPECT_TRUE(domain.hasValue());
auto model = fesa::AnalysisModel::create(domain.value());
EXPECT_TRUE(model.hasValue());
auto dofs = fesa::DofManager::create(model.value());
EXPECT_TRUE(dofs.hasValue());
return std::move(dofs.value());
EXPECT_TRUE(domain.HasValue());
auto model = fesa::AnalysisModel::create(domain.Value());
EXPECT_TRUE(model.HasValue());
auto dofs = fesa::DofManager::create(model.Value());
EXPECT_TRUE(dofs.HasValue());
return std::move(dofs.Value());
}
fesa::ShellResultRow makeShellRow(
@@ -141,15 +141,15 @@ void expectShellCandidateRejectedWithoutMutation(
const fesa::ShellStateCandidate& candidate,
const fesa::ShellStateCandidate& committed) {
const auto status = state.commitShellResults(expectedElements, candidate);
EXPECT_FALSE(status.isOk());
EXPECT_EQ(status.failureCategory(), fesa::FailureCategory::model);
EXPECT_FALSE(status.IsOk());
EXPECT_EQ(status.Category(), fesa::FailureCategory::kModel);
expectShellStateEquals(
state,
committed.rows,
committed.physicalStrainEnergy,
committed.equilibrium,
committed.verificationMetrics);
ASSERT_TRUE(state.commitShellResults(expectedElements, committed).isOk());
ASSERT_TRUE(state.commitShellResults(expectedElements, committed).IsOk());
}
} // namespace
@@ -197,9 +197,9 @@ TEST(AnalysisState, PreservesStepFrameAndStableRowOrder) {
EXPECT_EQ(constState.endpointResults().data(), endpointStorage);
EXPECT_EQ(constState.endpointResults()[0].element, 2U);
EXPECT_EQ(constState.endpointResults()[0].endpoint, -1);
EXPECT_EQ(constState.endpointResults()[0].node.instanceName, "Beam-1");
EXPECT_EQ(constState.endpointResults()[0].node.sourceLabel, 10);
EXPECT_EQ(constState.endpointResults()[0].node.sourceLabelText, "010");
EXPECT_EQ(constState.endpointResults()[0].node.instance_name, "Beam-1");
EXPECT_EQ(constState.endpointResults()[0].node.source_label, 10);
EXPECT_EQ(constState.endpointResults()[0].node.source_label_text, "010");
EXPECT_EQ(
constState.endpointResults()[0].endAction,
(std::array<double, 6>{1.0, 2.0, 3.0, 4.0, 5.0, 6.0}));
@@ -237,7 +237,7 @@ TEST(AnalysisState, OwnsExactShellRowsInStableElementAndLocationOrder) {
const auto status = state.commitShellResults(expectedElements, candidate);
ASSERT_TRUE(status.isOk());
ASSERT_TRUE(status.IsOk());
const fesa::AnalysisState& constState = state;
ASSERT_EQ(constState.shellResults().size(), 8U);
EXPECT_EQ(constState.shellResults()[0].element, 3U);
@@ -283,7 +283,7 @@ TEST(AnalysisState, CommitsFiniteShellGlobalEvidence) {
const auto status = state.commitShellResults(expectedElements, candidate);
ASSERT_TRUE(status.isOk());
ASSERT_TRUE(status.IsOk());
EXPECT_DOUBLE_EQ(state.physicalStrainEnergy(), 35.5);
EXPECT_EQ(
state.equilibrium(),
@@ -299,7 +299,7 @@ TEST(AnalysisState, InvalidShellCandidatesLeavePriorStateUnchanged) {
auto state = fesa::AnalysisState::create(dofs, {"Step-1", 0U});
const std::vector<fesa::EntityIndex> expectedElements{5U};
const auto committed = makeShellCandidate(expectedElements);
ASSERT_TRUE(state.commitShellResults(expectedElements, committed).isOk());
ASSERT_TRUE(state.commitShellResults(expectedElements, committed).IsOk());
auto invalidLocation = makeShellCandidate(expectedElements);
invalidLocation.rows[0].location = fesa::ShellMidsurfaceLocation::gp2;
+73 -73
View File
@@ -117,34 +117,34 @@ RecoveryFixture makeFixture(
reverseSecond,
sectionJump,
nonzeroPrescription));
if (!domainResult.hasValue()) {
if (!domainResult.HasValue()) {
throw std::runtime_error{"Recovery fixture Domain construction failed."};
}
auto domain = std::make_unique<fesa::Domain>(
std::move(domainResult.value()));
std::move(domainResult.Value()));
auto modelResult = fesa::AnalysisModel::create(*domain);
if (!modelResult.hasValue()) {
if (!modelResult.HasValue()) {
throw std::runtime_error{"Recovery fixture AnalysisModel construction failed."};
}
auto model = std::make_unique<fesa::AnalysisModel>(
std::move(modelResult.value()));
std::move(modelResult.Value()));
auto dofsResult = fesa::DofManager::create(*model);
if (!dofsResult.hasValue()) {
if (!dofsResult.HasValue()) {
throw std::runtime_error{"Recovery fixture DofManager construction failed."};
}
auto dofs = std::make_unique<fesa::DofManager>(
std::move(dofsResult.value()));
std::move(dofsResult.Value()));
fesa::SerialParallelFor serial;
auto stiffnessResult = fesa::SparseAssembler::assembleStiffness(
*model, *dofs, serial);
if (!stiffnessResult.hasValue()) {
if (!stiffnessResult.HasValue()) {
throw std::runtime_error{"Recovery fixture stiffness assembly failed."};
}
auto stiffness = std::make_unique<fesa::SparseMatrix>(
std::move(stiffnessResult.value()));
std::move(stiffnessResult.Value()));
return {
std::move(domain),
std::move(model),
@@ -226,38 +226,38 @@ fesa::ModelDefinition makeShellDefinition(
ShellRecoveryFixture makeShellFixture(fesa::ModelDefinition definition) {
auto domainResult = fesa::Domain::create(std::move(definition));
if (!domainResult.hasValue()) {
if (!domainResult.HasValue()) {
throw std::runtime_error{
"Shell recovery fixture Domain construction failed."};
}
auto domain = std::make_unique<fesa::Domain>(
std::move(domainResult.value()));
std::move(domainResult.Value()));
auto modelResult = fesa::AnalysisModel::create(*domain);
if (!modelResult.hasValue()) {
if (!modelResult.HasValue()) {
throw std::runtime_error{
"Shell recovery fixture AnalysisModel construction failed."};
}
auto model = std::make_unique<fesa::AnalysisModel>(
std::move(modelResult.value()));
std::move(modelResult.Value()));
auto dofsResult = fesa::DofManager::create(*model);
if (!dofsResult.hasValue()) {
if (!dofsResult.HasValue()) {
throw std::runtime_error{
"Shell recovery fixture DofManager construction failed."};
}
auto dofs = std::make_unique<fesa::DofManager>(
std::move(dofsResult.value()));
std::move(dofsResult.Value()));
fesa::SerialParallelFor serial;
auto stiffnessResult = fesa::SparseAssembler::assembleStiffness(
*model, *dofs, serial);
if (!stiffnessResult.hasValue()) {
if (!stiffnessResult.HasValue()) {
throw std::runtime_error{
"Shell recovery fixture stiffness assembly failed."};
}
auto stiffness = std::make_unique<fesa::SparseMatrix>(
std::move(stiffnessResult.value()));
std::move(stiffnessResult.Value()));
return {
std::move(domain),
std::move(model),
@@ -290,7 +290,7 @@ fesa::AnalysisState makeShellPhysicalState(
generalized[3U] * x + 0.5 * generalized[5U] * y;
}
state.externalForce() =
fixture.stiffness->multiply(state.displacement());
fixture.stiffness->Multiply(state.displacement());
return state;
}
@@ -299,7 +299,7 @@ fesa::AnalysisState makeAxialEquilibriumState(const RecoveryFixture& fixture) {
*fixture.dofs, {"Step-1", 0U});
state.displacement()[0U] = 0.1;
state.displacement()[6U] = 0.3;
const fesa::Vector internal = fixture.stiffness->multiply(state.displacement());
const fesa::Vector internal = fixture.stiffness->Multiply(state.displacement());
for (const std::size_t fullDof : fixture.dofs->freeDofs()) {
state.externalForce()[fullDof] = internal[fullDof];
}
@@ -321,15 +321,15 @@ fesa::AnalysisState makePatchState(
state.displacement()[9U] = twist * kLength;
state.displacement()[10U] = kappaY * kLength;
state.displacement()[11U] = kappaZ * kLength;
state.externalForce() = fixture.stiffness->multiply(state.displacement());
state.externalForce() = fixture.stiffness->Multiply(state.displacement());
return state;
}
void expectStatusCode(const fesa::Status& status, const std::string& code) {
ASSERT_FALSE(status.isOk());
EXPECT_EQ(status.failureCategory(), fesa::FailureCategory::model);
ASSERT_EQ(status.diagnostics().size(), 1U);
EXPECT_EQ(status.diagnostics()[0U].code, code);
ASSERT_FALSE(status.IsOk());
EXPECT_EQ(status.Category(), fesa::FailureCategory::kModel);
ASSERT_EQ(status.Diagnostics().size(), 1U);
EXPECT_EQ(status.Diagnostics()[0U].code, code);
}
void expectScaledNear(
@@ -371,12 +371,12 @@ TEST(ResultRecovery, ComputesResidualReactionForNonzeroPrescription) {
staleShellEvidence.physicalStrainEnergy = 123.0;
staleShellEvidence.equilibrium = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0};
staleShellEvidence.verificationMetrics = {1.0e-11, 2.0e-11, 3.0e-11};
ASSERT_TRUE(state.commitShellResults({}, staleShellEvidence).isOk());
ASSERT_TRUE(state.commitShellResults({}, staleShellEvidence).IsOk());
const fesa::Status status = fesa::ResultRecovery::recover(
*fixture.model, *fixture.dofs, *fixture.stiffness, state);
ASSERT_TRUE(status.isOk());
ASSERT_TRUE(status.IsOk());
EXPECT_DOUBLE_EQ(state.internalForce()[0U], -20.0);
EXPECT_DOUBLE_EQ(state.internalForce()[6U], 20.0);
EXPECT_DOUBLE_EQ(state.residual()[0U], -20.0);
@@ -422,7 +422,7 @@ TEST(ResultRecovery, EnforcesNormalizedFreeResidual) {
*fixture.dofs,
*fixture.stiffness,
thresholdPass);
ASSERT_TRUE(thresholdStatus.isOk());
ASSERT_TRUE(thresholdStatus.IsOk());
EXPECT_NE(thresholdPass.residual()[6U], 0.0);
EXPECT_DOUBLE_EQ(
thresholdPass.reaction()[6U], thresholdPass.residual()[6U]);
@@ -435,7 +435,7 @@ TEST(ResultRecovery, EnforcesNormalizedFreeResidual) {
*zeroFixture.dofs,
*zeroFixture.stiffness,
zeroEquilibrium)
.isOk());
.IsOk());
auto wrongPrescription = makeAxialEquilibriumState(fixture);
wrongPrescription.displacement()[0U] = 0.0;
@@ -469,7 +469,7 @@ TEST(ResultRecovery, KeepsEndActionSectionAndGaussResultsDistinct) {
ASSERT_TRUE(fesa::ResultRecovery::recover(
*fixture.model, *fixture.dofs, *fixture.stiffness, state)
.isOk());
.IsOk());
ASSERT_EQ(state.endpointResults().size(), 2U);
ASSERT_EQ(state.gaussResults().size(), 2U);
EXPECT_EQ(state.endpointResults()[0U].endpoint, 0);
@@ -497,7 +497,7 @@ TEST(ResultRecovery, MatchesAxialTorsionAndTwoPlaneEndSigns) {
ASSERT_TRUE(fesa::ResultRecovery::recover(
*fixture.model, *fixture.dofs, *fixture.stiffness, state)
.isOk());
.IsOk());
const double shearModulus =
kYoungsModulus / (2.0 * (1.0 + kPoissonRatio));
const std::array<double, 4> expected = {
@@ -529,7 +529,7 @@ TEST(ResultRecovery, OrdersStressPointsAndDefaultCentroid) {
auto state = makePatchState(fixture, 0.01, 0.0, 0.02, -0.03);
ASSERT_TRUE(fesa::ResultRecovery::recover(
*fixture.model, *fixture.dofs, *fixture.stiffness, state)
.isOk());
.IsOk());
ASSERT_EQ(state.stressResults().size(), 4U);
for (std::size_t gauss = 0U; gauss < 2U; ++gauss) {
for (std::size_t point = 0U; point < sectionPoints.size(); ++point) {
@@ -554,7 +554,7 @@ TEST(ResultRecovery, OrdersStressPointsAndDefaultCentroid) {
*defaultFixture.dofs,
*defaultFixture.stiffness,
defaultState)
.isOk());
.IsOk());
ASSERT_EQ(defaultState.stressResults().size(), 2U);
for (const auto& row : defaultState.stressResults()) {
EXPECT_EQ(row.sectionPoint, 0U);
@@ -573,17 +573,17 @@ TEST(ResultRecovery, RequiresInteriorEndpointConsistencyWithoutAveraging) {
auto normalized =
fesa::ResultRecovery::normalizeSectionResultantsToNodeStations(
*fixture.model, rows, tolerances);
ASSERT_TRUE(normalized.hasValue());
ASSERT_EQ(normalized.value().size(), 3U);
EXPECT_EQ(normalized.value()[1U].representativeElement, 0U);
EXPECT_DOUBLE_EQ(normalized.value()[1U].sectionResultant[0U], 5.0);
ASSERT_TRUE(normalized.HasValue());
ASSERT_EQ(normalized.Value().size(), 3U);
EXPECT_EQ(normalized.Value()[1U].representativeElement, 0U);
EXPECT_DOUBLE_EQ(normalized.Value()[1U].sectionResultant[0U], 5.0);
rows[2U].sectionResultant[0U] = 5.0 + 2.0e-6;
auto mismatch =
fesa::ResultRecovery::normalizeSectionResultantsToNodeStations(
*fixture.model, rows, tolerances);
ASSERT_FALSE(mismatch.hasValue());
expectStatusCode(mismatch.status(), "node-station-tolerance-failure");
ASSERT_FALSE(mismatch.HasValue());
expectStatusCode(mismatch.GetStatus(), "node-station-tolerance-failure");
rows = makeStationRows(fixture);
rows[2U].sectionResultant[1U] =
@@ -591,17 +591,17 @@ TEST(ResultRecovery, RequiresInteriorEndpointConsistencyWithoutAveraging) {
auto nonfinite =
fesa::ResultRecovery::normalizeSectionResultantsToNodeStations(
*fixture.model, rows, tolerances);
ASSERT_FALSE(nonfinite.hasValue());
expectStatusCode(nonfinite.status(), "nonfinite-node-station-value");
ASSERT_FALSE(nonfinite.HasValue());
expectStatusCode(nonfinite.GetStatus(), "nonfinite-node-station-value");
auto invalidTolerance =
fesa::ResultRecovery::normalizeSectionResultantsToNodeStations(
*fixture.model,
makeStationRows(fixture),
{1.0e-6, -1.0, 1.0e-6, 1.0e-6});
ASSERT_FALSE(invalidTolerance.hasValue());
ASSERT_FALSE(invalidTolerance.HasValue());
expectStatusCode(
invalidTolerance.status(), "invalid-node-station-tolerance");
invalidTolerance.GetStatus(), "invalid-node-station-tolerance");
const std::filesystem::path source{"models/result-recovery.inp"};
const auto loadedFixture = makeFixture(
@@ -611,8 +611,8 @@ TEST(ResultRecovery, RequiresInteriorEndpointConsistencyWithoutAveraging) {
*loadedFixture.model,
makeStationRows(loadedFixture),
tolerances);
ASSERT_FALSE(loaded.hasValue());
expectStatusCode(loaded.status(), "ineligible-node-station");
ASSERT_FALSE(loaded.HasValue());
expectStatusCode(loaded.GetStatus(), "ineligible-node-station");
const auto reversedFixture = makeFixture(true, {}, {}, true);
auto reversed =
@@ -620,8 +620,8 @@ TEST(ResultRecovery, RequiresInteriorEndpointConsistencyWithoutAveraging) {
*reversedFixture.model,
makeStationRows(reversedFixture),
tolerances);
ASSERT_FALSE(reversed.hasValue());
expectStatusCode(reversed.status(), "ineligible-node-station");
ASSERT_FALSE(reversed.HasValue());
expectStatusCode(reversed.GetStatus(), "ineligible-node-station");
const auto jumpFixture = makeFixture(true, {}, {}, false, true);
auto jumped =
@@ -629,8 +629,8 @@ TEST(ResultRecovery, RequiresInteriorEndpointConsistencyWithoutAveraging) {
*jumpFixture.model,
makeStationRows(jumpFixture),
tolerances);
ASSERT_FALSE(jumped.hasValue());
expectStatusCode(jumped.status(), "ineligible-node-station");
ASSERT_FALSE(jumped.HasValue());
expectStatusCode(jumped.GetStatus(), "ineligible-node-station");
}
// MITC4-REC-001
@@ -647,12 +647,12 @@ TEST(ResultRecovery, RecoversShellRowsInStableElementAndGpOrder) {
state.displacement()[node * 6U + 1U] = -0.05 * y + 0.1 * x;
}
state.externalForce() =
fixture.stiffness->multiply(state.displacement());
fixture.stiffness->Multiply(state.displacement());
const auto status = fesa::ResultRecovery::recover(
*fixture.model, *fixture.dofs, *fixture.stiffness, state);
ASSERT_TRUE(status.isOk());
ASSERT_TRUE(status.IsOk());
ASSERT_EQ(state.shellResults().size(), 8U);
const double gauss = 1.0 / std::sqrt(3.0);
const std::array<fesa::ShellMidsurfaceLocation, 4> locations{
@@ -706,7 +706,7 @@ TEST(ResultRecovery, RecoversDirectBottomMiddleTopShellStress) {
*fixture.dofs,
*fixture.stiffness,
state)
.isOk());
.IsOk());
constexpr std::array<fesa::ShellSectionPosition, 3> positions{
fesa::ShellSectionPosition::bottom,
fesa::ShellSectionPosition::middle,
@@ -744,16 +744,16 @@ TEST(ResultRecovery, SumsOnlyPhysicalShellEnergyInSourceOrder) {
state.displacement()[node * 6U + 5U] = drill[node];
}
state.externalForce() =
fixture.stiffness->multiply(state.displacement());
const double stabilizedEnergy = 0.5 * state.displacement().dot(
fixture.stiffness->multiply(state.displacement()));
fixture.stiffness->Multiply(state.displacement());
const double stabilizedEnergy = 0.5 * state.displacement().Dot(
fixture.stiffness->Multiply(state.displacement()));
ASSERT_TRUE(fesa::ResultRecovery::recover(
*fixture.model,
*fixture.dofs,
*fixture.stiffness,
state)
.isOk());
.IsOk());
EXPECT_NEAR(state.physicalStrainEnergy(), 72.16, 1.0e-12);
EXPECT_GT(stabilizedEnergy, state.physicalStrainEnergy());
}
@@ -772,15 +772,15 @@ TEST(ResultRecovery, KeepsFullResidualAndComputesGlobalShellEquilibrium) {
*fixture.dofs, {"Step-1", 0U});
auto fullLoad = fesa::LoadAssembler::assembleFullNodalLoad(
*fixture.model, *fixture.dofs);
ASSERT_TRUE(fullLoad.hasValue());
state.externalForce() = std::move(fullLoad.value());
ASSERT_TRUE(fullLoad.HasValue());
state.externalForce() = std::move(fullLoad.Value());
ASSERT_TRUE(fesa::ResultRecovery::recover(
*fixture.model,
*fixture.dofs,
*fixture.stiffness,
state)
.isOk());
.IsOk());
ASSERT_EQ(state.shellResults().size(), 4U);
for (std::size_t fullDof = 0U;
fullDof < fixture.dofs->fullDofCount();
@@ -805,7 +805,7 @@ TEST(ResultRecovery, KeepsFullResidualAndComputesGlobalShellEquilibrium) {
*freeFixture.dofs,
*freeFixture.stiffness,
perturbed)
.isOk());
.IsOk());
for (const double metric : perturbed.verificationMetrics()) {
EXPECT_GT(metric, 0.0);
EXPECT_LE(metric, 1.0e-10);
@@ -838,13 +838,13 @@ TEST(ResultRecovery, UsesGlobalOriginForShellMomentBalance) {
*centeredFixture.dofs,
*centeredFixture.stiffness,
centered)
.isOk());
.IsOk());
ASSERT_TRUE(fesa::ResultRecovery::recover(
*translatedFixture.model,
*translatedFixture.dofs,
*translatedFixture.stiffness,
translated)
.isOk());
.IsOk());
std::array<double, 3> centeredForce{};
for (std::size_t component = 0U; component < 3U; ++component) {
centeredForce[component] = centered.equilibrium()[component];
@@ -877,11 +877,11 @@ TEST(ResultRecovery, UsesScaleAwareShellMetricsAndRejectsExcess) {
auto large = makeShellPhysicalState(fixture);
constexpr double subunitScale = 1.0e-6;
constexpr double largeScale = 1.0e6;
subunit.displacement().scale(subunitScale);
subunit.externalForce().scale(subunitScale);
subunit.displacement().Scale(subunitScale);
subunit.externalForce().Scale(subunitScale);
subunit.externalForce()[0U] += 1.0e-9 * subunitScale;
large.displacement().scale(largeScale);
large.externalForce().scale(largeScale);
large.displacement().Scale(largeScale);
large.externalForce().Scale(largeScale);
large.externalForce()[0U] += 1.0e-9 * largeScale;
ASSERT_TRUE(fesa::ResultRecovery::recover(
@@ -889,13 +889,13 @@ TEST(ResultRecovery, UsesScaleAwareShellMetricsAndRejectsExcess) {
*fixture.dofs,
*fixture.stiffness,
subunit)
.isOk());
.IsOk());
ASSERT_TRUE(fesa::ResultRecovery::recover(
*fixture.model,
*fixture.dofs,
*fixture.stiffness,
large)
.isOk());
.IsOk());
for (std::size_t metric = 0U; metric < 3U; ++metric) {
EXPECT_GT(subunit.verificationMetrics()[metric], 0.0);
EXPECT_GT(large.verificationMetrics()[metric], 0.0);
@@ -918,17 +918,17 @@ TEST(ResultRecovery, UsesScaleAwareShellMetricsAndRejectsExcess) {
}
const std::vector<fesa::CooContribution> unbalancedEntry{
{0U, 0U, 1.0, 0U, 0U}};
auto unbalancedStiffness = fesa::SparseMatrix::fromCoo(
auto unbalancedStiffness = fesa::SparseMatrix::FromCoo(
constrainedFixture.dofs->fullDofCount(),
constrainedFixture.dofs->fullDofCount(),
unbalancedEntry,
constrainedFixture.dofs->sparsePattern());
ASSERT_TRUE(unbalancedStiffness.hasValue());
ASSERT_TRUE(unbalancedStiffness.HasValue());
expectStatusCode(
fesa::ResultRecovery::recover(
*constrainedFixture.model,
*constrainedFixture.dofs,
unbalancedStiffness.value(),
unbalancedStiffness.Value(),
rejected),
"global-equilibrium-tolerance-failure");
}
@@ -942,7 +942,7 @@ TEST(ResultRecovery, InvalidLaterShellLeavesEntirePriorStateUnchanged) {
*validFixture.dofs,
*validFixture.stiffness,
state)
.isOk());
.IsOk());
ASSERT_EQ(state.shellResults().size(), 8U);
const auto priorFirstRow = state.shellResults().front();
const double priorEnergy = state.physicalStrainEnergy();
@@ -957,17 +957,17 @@ TEST(ResultRecovery, InvalidLaterShellLeavesEntirePriorStateUnchanged) {
state.displacement()[5U * 6U] =
(std::numeric_limits<double>::max)();
state.externalForce() = fesa::Vector{validFixture.dofs->fullDofCount()};
auto zeroStiffness = fesa::SparseMatrix::fromCoo(
auto zeroStiffness = fesa::SparseMatrix::FromCoo(
validFixture.dofs->fullDofCount(),
validFixture.dofs->fullDofCount(),
{},
validFixture.dofs->sparsePattern());
ASSERT_TRUE(zeroStiffness.hasValue());
ASSERT_TRUE(zeroStiffness.HasValue());
const auto status = fesa::ResultRecovery::recover(
*validFixture.model,
*validFixture.dofs,
zeroStiffness.value(),
zeroStiffness.Value(),
state);
expectStatusCode(status, "invalid-shell-recovery");
+2 -2
View File
@@ -1,8 +1,8 @@
#include "fesa/results/results_writer.hpp"
#include "fesa/analysis/analysis_state.hpp"
#include "fesa/core/diagnostic.hpp"
#include "fesa/core/status.hpp"
#include "fesa/core/diagnostic.h"
#include "fesa/core/status.h"
#include "fesa/model/domain.hpp"
#include <filesystem>
+98 -117
View File
@@ -1,8 +1,4 @@
#include "fesa/solvers/linear/linear_solver.hpp"
#include "fesa/solvers/linear/mkl_pardiso_solver.hpp"
#include "fesa/fem/dof_manager.hpp"
#include "fesa/math/sparse_matrix.hpp"
#include "fesa/solvers/linear/linear_solver.h"
#include <gtest/gtest.h>
@@ -10,137 +6,122 @@
#include <utility>
#include <vector>
#include "fesa/fem/dof_manager.hpp"
#include "fesa/math/sparse_matrix.h"
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
namespace {
fesa::SparseMatrix makeDenseCsr(
const std::size_t rows,
const std::size_t columns,
const std::vector<double>& values) {
EXPECT_EQ(values.size(), rows * columns);
fesa::SparseMatrix MakeDenseCsr(const std::size_t rows,
const std::size_t columns,
const std::vector<double>& values) {
EXPECT_EQ(values.size(), rows * columns);
fesa::SparsePattern pattern;
std::vector<fesa::CooContribution> contributions;
pattern.rowOffsets.reserve(rows + 1U);
pattern.rowOffsets.push_back(0U);
for (std::size_t row = 0U; row < rows; ++row) {
for (std::size_t column = 0U; column < columns; ++column) {
pattern.columnIndices.push_back(column);
contributions.push_back({
row,
column,
values[row * columns + column],
row,
column});
}
pattern.rowOffsets.push_back(pattern.columnIndices.size());
fesa::SparsePattern pattern;
std::vector<fesa::CooContribution> contributions;
pattern.rowOffsets.reserve(rows + 1U);
pattern.rowOffsets.push_back(0U);
for (std::size_t row = 0U; row < rows; ++row) {
for (std::size_t column = 0U; column < columns; ++column) {
pattern.columnIndices.push_back(column);
contributions.push_back(
{row, column, values[row * columns + column], row, column});
}
pattern.rowOffsets.push_back(pattern.columnIndices.size());
}
auto matrix = fesa::SparseMatrix::fromCoo(
rows, columns, std::move(contributions), pattern);
EXPECT_TRUE(matrix.hasValue());
return std::move(matrix.value());
auto matrix = fesa::SparseMatrix::FromCoo(rows, columns,
std::move(contributions), pattern);
EXPECT_TRUE(matrix.HasValue());
return std::move(matrix.Value());
}
void expectSolverFailure(const fesa::Status& status) {
EXPECT_FALSE(status.isOk());
EXPECT_EQ(status.failureCategory(), fesa::FailureCategory::solver);
ASSERT_FALSE(status.diagnostics().empty());
EXPECT_EQ(status.diagnostics().front().severity, fesa::Severity::error);
void ExpectSolverFailure(const fesa::Status& status) {
EXPECT_FALSE(status.IsOk());
EXPECT_EQ(status.Category(), fesa::FailureCategory::kSolver);
ASSERT_FALSE(status.Diagnostics().empty());
EXPECT_EQ(status.Diagnostics().front().severity, fesa::Severity::kError);
}
} // namespace
} // namespace
TEST(MklPardisoSolver, RejectsInvalidCsrStateAndDimensions) {
static_assert(std::is_base_of_v<fesa::LinearSolver, fesa::MklPardisoSolver>);
static_assert(std::has_virtual_destructor_v<fesa::LinearSolver>);
static_assert(std::is_base_of_v<fesa::LinearSolver, fesa::MklPardisoSolver>);
static_assert(std::has_virtual_destructor_v<fesa::LinearSolver>);
fesa::MklPardisoSolver solver;
fesa::Vector untouched{2U};
untouched[0U] = 17.0;
untouched[1U] = -4.0;
const auto beforeFactorize =
solver.solve(fesa::Vector{2U, 1.0}, untouched);
expectSolverFailure(beforeFactorize);
EXPECT_EQ(
beforeFactorize.diagnostics().front().code,
"solver-not-factorized");
EXPECT_DOUBLE_EQ(untouched[0U], 17.0);
EXPECT_DOUBLE_EQ(untouched[1U], -4.0);
fesa::MklPardisoSolver solver;
fesa::Vector untouched{2U};
untouched[0U] = 17.0;
untouched[1U] = -4.0;
const auto before_factorize = solver.Solve(fesa::Vector{2U, 1.0}, untouched);
ExpectSolverFailure(before_factorize);
EXPECT_EQ(before_factorize.Diagnostics().front().code,
"solver-not-factorized");
EXPECT_DOUBLE_EQ(untouched[0U], 17.0);
EXPECT_DOUBLE_EQ(untouched[1U], -4.0);
// A fully constrained model has a valid 0x0 Kff. It still observes the
// factorize-then-solve lifecycle without invoking a numerical backend.
const auto empty = makeDenseCsr(0U, 0U, {});
ASSERT_TRUE(solver.factorize(empty).isOk());
fesa::Vector emptySolution{0U};
EXPECT_TRUE(solver.solve(fesa::Vector{0U}, emptySolution).isOk());
EXPECT_EQ(emptySolution.size(), 0U);
// A fully constrained model has a valid 0x0 Kff. It still observes the
// factorize-then-solve lifecycle without invoking a numerical backend.
const auto empty = MakeDenseCsr(0U, 0U, {});
ASSERT_TRUE(solver.Factorize(empty).IsOk());
fesa::Vector empty_solution{0U};
EXPECT_TRUE(solver.Solve(fesa::Vector{0U}, empty_solution).IsOk());
EXPECT_EQ(empty_solution.Size(), 0U);
// Refactorization from the trivial state must establish ordinary PARDISO
// state rather than retaining a zero-equation shortcut.
const auto spd = makeDenseCsr(2U, 2U, {4.0, 1.0, 1.0, 3.0});
ASSERT_TRUE(solver.factorize(spd).isOk());
fesa::Vector solution{2U};
ASSERT_TRUE(solver.solve(fesa::Vector{2U, 1.0}, solution).isOk());
EXPECT_NEAR(solution[0U], 2.0 / 11.0, 1.0e-14);
EXPECT_NEAR(solution[1U], 3.0 / 11.0, 1.0e-14);
// Refactorization from the trivial state must establish ordinary PARDISO
// state rather than retaining a zero-equation shortcut.
const auto spd = MakeDenseCsr(2U, 2U, {4.0, 1.0, 1.0, 3.0});
ASSERT_TRUE(solver.Factorize(spd).IsOk());
fesa::Vector solution{2U};
ASSERT_TRUE(solver.Solve(fesa::Vector{2U, 1.0}, solution).IsOk());
EXPECT_NEAR(solution[0U], 2.0 / 11.0, 1.0e-14);
EXPECT_NEAR(solution[1U], 3.0 / 11.0, 1.0e-14);
const double solvedFirst = solution[0U];
const double solvedSecond = solution[1U];
expectSolverFailure(solver.solve(fesa::Vector{1U, 1.0}, solution));
EXPECT_DOUBLE_EQ(solution[0U], solvedFirst);
EXPECT_DOUBLE_EQ(solution[1U], solvedSecond);
const double solved_first = solution[0U];
const double solved_second = solution[1U];
ExpectSolverFailure(solver.Solve(fesa::Vector{1U, 1.0}, solution));
EXPECT_DOUBLE_EQ(solution[0U], solved_first);
EXPECT_DOUBLE_EQ(solution[1U], solved_second);
fesa::Vector wrongSolution{1U};
wrongSolution[0U] = 41.0;
expectSolverFailure(solver.solve(fesa::Vector{2U, 1.0}, wrongSolution));
EXPECT_DOUBLE_EQ(wrongSolution[0U], 41.0);
fesa::Vector wrong_solution{1U};
wrong_solution[0U] = 41.0;
ExpectSolverFailure(solver.Solve(fesa::Vector{2U, 1.0}, wrong_solution));
EXPECT_DOUBLE_EQ(wrong_solution[0U], 41.0);
const auto rectangular = makeDenseCsr(
2U, 3U, {2.0, 0.0, 0.0, 0.0, 3.0, 0.0});
const auto rectangularStatus = solver.factorize(rectangular);
expectSolverFailure(rectangularStatus);
EXPECT_EQ(
rectangularStatus.diagnostics().front().code,
"solver-matrix-not-square");
const auto rectangular = MakeDenseCsr(2U, 3U, {2.0, 0.0, 0.0, 0.0, 3.0, 0.0});
const auto rectangular_status = solver.Factorize(rectangular);
ExpectSolverFailure(rectangular_status);
EXPECT_EQ(rectangular_status.Diagnostics().front().code,
"solver-matrix-not-square");
fesa::SparsePattern invalidPattern{{0U, 2U}, {0U}};
auto invalidCsr = fesa::SparseMatrix::fromCoo(
1U,
1U,
{{0U, 0U, 1.0, 0U, 0U}},
invalidPattern);
EXPECT_FALSE(invalidCsr.hasValue());
fesa::SparsePattern invalid_pattern{{0U, 2U}, {0U}};
auto invalid_csr = fesa::SparseMatrix::FromCoo(
1U, 1U, {{0U, 0U, 1.0, 0U, 0U}}, invalid_pattern);
EXPECT_FALSE(invalid_csr.HasValue());
const auto nonsymmetric = makeDenseCsr(2U, 2U, {2.0, 1.0, 0.0, 3.0});
const auto nonsymmetricStatus = solver.factorize(nonsymmetric);
expectSolverFailure(nonsymmetricStatus);
EXPECT_EQ(
nonsymmetricStatus.diagnostics().front().code,
"solver-matrix-not-symmetric");
const auto nonsymmetric = MakeDenseCsr(2U, 2U, {2.0, 1.0, 0.0, 3.0});
const auto nonsymmetric_status = solver.Factorize(nonsymmetric);
ExpectSolverFailure(nonsymmetric_status);
EXPECT_EQ(nonsymmetric_status.Diagnostics().front().code,
"solver-matrix-not-symmetric");
const auto scaledNonsymmetric = makeDenseCsr(
2U, 2U, {2.0e-20, 1.0e-20, 1.1e-20, 3.0e-20});
const auto scaledNonsymmetricStatus =
solver.factorize(scaledNonsymmetric);
// Stop this case before inspecting diagnostics when the production code
// incorrectly accepts the matrix; this keeps the RED failure deterministic.
ASSERT_FALSE(scaledNonsymmetricStatus.isOk());
expectSolverFailure(scaledNonsymmetricStatus);
EXPECT_EQ(
scaledNonsymmetricStatus.diagnostics().front().code,
"solver-matrix-not-symmetric");
fesa::SparsePattern noDiagonalPattern{{0U, 1U, 2U}, {1U, 0U}};
auto noDiagonal = fesa::SparseMatrix::fromCoo(
2U,
2U,
{{0U, 1U, 1.0, 0U, 0U}, {1U, 0U, 1.0, 1U, 0U}},
noDiagonalPattern);
ASSERT_TRUE(noDiagonal.hasValue());
const auto noDiagonalStatus = solver.factorize(noDiagonal.value());
expectSolverFailure(noDiagonalStatus);
EXPECT_EQ(
noDiagonalStatus.diagnostics().front().code,
"solver-missing-diagonal");
const auto scaled_nonsymmetric =
MakeDenseCsr(2U, 2U, {2.0e-20, 1.0e-20, 1.1e-20, 3.0e-20});
const auto scaled_nonsymmetric_status = solver.Factorize(scaled_nonsymmetric);
// Stop this case before inspecting diagnostics when the production code
// incorrectly accepts the matrix; this keeps the RED failure deterministic.
ASSERT_FALSE(scaled_nonsymmetric_status.IsOk());
ExpectSolverFailure(scaled_nonsymmetric_status);
EXPECT_EQ(scaled_nonsymmetric_status.Diagnostics().front().code,
"solver-matrix-not-symmetric");
fesa::SparsePattern no_diagonal_pattern{{0U, 1U, 2U}, {1U, 0U}};
auto no_diagonal = fesa::SparseMatrix::FromCoo(
2U, 2U, {{0U, 1U, 1.0, 0U, 0U}, {1U, 0U, 1.0, 1U, 0U}},
no_diagonal_pattern);
ASSERT_TRUE(no_diagonal.HasValue());
const auto no_diagonal_status = solver.Factorize(no_diagonal.Value());
ExpectSolverFailure(no_diagonal_status);
EXPECT_EQ(no_diagonal_status.Diagnostics().front().code,
"solver-missing-diagonal");
}
@@ -1,7 +1,4 @@
#include "fesa/solvers/linear/mkl_pardiso_solver.hpp"
#include "fesa/fem/dof_manager.hpp"
#include "fesa/math/sparse_matrix.hpp"
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
#include <gtest/gtest.h>
@@ -12,261 +9,242 @@
#include <utility>
#include <vector>
#include "fesa/fem/dof_manager.hpp"
#include "fesa/math/sparse_matrix.h"
namespace {
fesa::SparseMatrix makeDenseCsr(
const std::size_t size,
const std::vector<double>& values) {
EXPECT_EQ(values.size(), size * size);
fesa::SparseMatrix MakeDenseCsr(const std::size_t size,
const std::vector<double>& values) {
EXPECT_EQ(values.size(), size * size);
fesa::SparsePattern pattern;
std::vector<fesa::CooContribution> contributions;
pattern.rowOffsets.reserve(size + 1U);
pattern.rowOffsets.push_back(0U);
for (std::size_t row = 0U; row < size; ++row) {
for (std::size_t column = 0U; column < size; ++column) {
pattern.columnIndices.push_back(column);
contributions.push_back({
row,
column,
values[row * size + column],
row,
column});
}
pattern.rowOffsets.push_back(pattern.columnIndices.size());
fesa::SparsePattern pattern;
std::vector<fesa::CooContribution> contributions;
pattern.rowOffsets.reserve(size + 1U);
pattern.rowOffsets.push_back(0U);
for (std::size_t row = 0U; row < size; ++row) {
for (std::size_t column = 0U; column < size; ++column) {
pattern.columnIndices.push_back(column);
contributions.push_back(
{row, column, values[row * size + column], row, column});
}
pattern.rowOffsets.push_back(pattern.columnIndices.size());
}
auto matrix = fesa::SparseMatrix::fromCoo(
size, size, std::move(contributions), pattern);
EXPECT_TRUE(matrix.hasValue());
return std::move(matrix.value());
auto matrix = fesa::SparseMatrix::FromCoo(size, size,
std::move(contributions), pattern);
EXPECT_TRUE(matrix.HasValue());
return std::move(matrix.Value());
}
fesa::Vector makeVector(const std::initializer_list<double> values) {
fesa::Vector result{values.size()};
std::size_t index = 0U;
for (const double value : values) {
result[index++] = value;
}
return result;
fesa::Vector MakeVector(const std::initializer_list<double> values) {
fesa::Vector result{values.size()};
std::size_t index = 0U;
for (const double value : values) {
result[index++] = value;
}
return result;
}
double normalizedResidual(
const fesa::SparseMatrix& matrix,
const fesa::Vector& solution,
const fesa::Vector& rhs) {
auto residual = matrix.multiply(solution);
residual.axpy(-1.0, rhs);
const double numerator = residual.norm();
const double denominator = rhs.norm();
if (!std::isfinite(numerator) || !std::isfinite(denominator)) {
return (std::numeric_limits<double>::infinity)();
}
if (denominator == 0.0) {
return numerator == 0.0 ? 0.0 :
(std::numeric_limits<double>::infinity)();
}
return numerator / denominator;
double NormalizedResidual(const fesa::SparseMatrix& matrix,
const fesa::Vector& solution,
const fesa::Vector& rhs) {
auto residual = matrix.Multiply(solution);
residual.Axpy(-1.0, rhs);
const double numerator = residual.Norm();
const double denominator = rhs.Norm();
if (!std::isfinite(numerator) || !std::isfinite(denominator)) {
return (std::numeric_limits<double>::infinity)();
}
if (denominator == 0.0) {
return numerator == 0.0 ? 0.0 : (std::numeric_limits<double>::infinity)();
}
return numerator / denominator;
}
double relativeError(
const fesa::Vector& actual,
const fesa::Vector& expected) {
auto difference = actual;
difference.axpy(-1.0, expected);
const double numerator = difference.norm();
const double denominator = expected.norm();
if (!std::isfinite(numerator) || !std::isfinite(denominator)) {
return (std::numeric_limits<double>::infinity)();
}
if (denominator == 0.0) {
return numerator == 0.0 ? 0.0 :
(std::numeric_limits<double>::infinity)();
}
return numerator / denominator;
double RelativeError(const fesa::Vector& actual, const fesa::Vector& expected) {
auto difference = actual;
difference.Axpy(-1.0, expected);
const double numerator = difference.Norm();
const double denominator = expected.Norm();
if (!std::isfinite(numerator) || !std::isfinite(denominator)) {
return (std::numeric_limits<double>::infinity)();
}
if (denominator == 0.0) {
return numerator == 0.0 ? 0.0 : (std::numeric_limits<double>::infinity)();
}
return numerator / denominator;
}
void expectStructuredSolverFailure(const fesa::Status& status) {
EXPECT_FALSE(status.isOk());
EXPECT_EQ(status.failureCategory(), fesa::FailureCategory::solver);
ASSERT_EQ(status.diagnostics().size(), 1U);
EXPECT_EQ(status.diagnostics()[0U].severity, fesa::Severity::error);
EXPECT_FALSE(status.diagnostics()[0U].code.empty());
EXPECT_FALSE(status.diagnostics()[0U].message.empty());
void ExpectStructuredSolverFailure(const fesa::Status& status) {
EXPECT_FALSE(status.IsOk());
EXPECT_EQ(status.Category(), fesa::FailureCategory::kSolver);
ASSERT_EQ(status.Diagnostics().size(), 1U);
EXPECT_EQ(status.Diagnostics()[0U].severity, fesa::Severity::kError);
EXPECT_FALSE(status.Diagnostics()[0U].code.empty());
EXPECT_FALSE(status.Diagnostics()[0U].message.empty());
}
} // namespace
} // namespace
TEST(MklPardisoSolver, SolvesKnownSpdWithNormalizedResidual) {
const auto matrix = makeDenseCsr(3U, {
6.0, 2.0, 1.0,
2.0, 5.0, 2.0,
1.0, 2.0, 4.0});
const auto expected = makeVector({1.0, -2.0, 3.0});
const auto rhs = matrix.multiply(expected);
const auto matrix =
MakeDenseCsr(3U, {6.0, 2.0, 1.0, 2.0, 5.0, 2.0, 1.0, 2.0, 4.0});
const auto expected = MakeVector({1.0, -2.0, 3.0});
const auto rhs = matrix.Multiply(expected);
fesa::MklPardisoSolver concreteSolver;
fesa::LinearSolver& solver = concreteSolver;
ASSERT_TRUE(solver.factorize(matrix).isOk());
fesa::MklPardisoSolver concrete_solver;
fesa::LinearSolver& solver = concrete_solver;
ASSERT_TRUE(solver.Factorize(matrix).IsOk());
fesa::Vector solution{3U};
ASSERT_TRUE(solver.solve(rhs, solution).isOk());
EXPECT_LE(normalizedResidual(matrix, solution, rhs), 1.0e-10);
EXPECT_LE(relativeError(solution, expected), 1.0e-9);
fesa::Vector solution{3U};
ASSERT_TRUE(solver.Solve(rhs, solution).IsOk());
EXPECT_LE(NormalizedResidual(matrix, solution, rhs), 1.0e-10);
EXPECT_LE(RelativeError(solution, expected), 1.0e-9);
}
TEST(MklPardisoSolver, ReusesOneFactorizationForRepeatedRhs) {
const auto matrix = makeDenseCsr(2U, {4.0, 1.0, 1.0, 3.0});
const auto expectedFirst = makeVector({1.0, 2.0});
const auto expectedSecond = makeVector({-2.0, 0.5});
const auto rhsFirst = matrix.multiply(expectedFirst);
const auto rhsSecond = matrix.multiply(expectedSecond);
const auto matrix = MakeDenseCsr(2U, {4.0, 1.0, 1.0, 3.0});
const auto expected_first = MakeVector({1.0, 2.0});
const auto expected_second = MakeVector({-2.0, 0.5});
const auto rhs_first = matrix.Multiply(expected_first);
const auto rhs_second = matrix.Multiply(expected_second);
fesa::MklPardisoSolver solver;
ASSERT_TRUE(solver.factorize(matrix).isOk());
fesa::Vector first{2U};
fesa::Vector second{2U};
ASSERT_TRUE(solver.solve(rhsFirst, first).isOk());
ASSERT_TRUE(solver.solve(rhsSecond, second).isOk());
fesa::MklPardisoSolver solver;
ASSERT_TRUE(solver.Factorize(matrix).IsOk());
fesa::Vector first{2U};
fesa::Vector second{2U};
ASSERT_TRUE(solver.Solve(rhs_first, first).IsOk());
ASSERT_TRUE(solver.Solve(rhs_second, second).IsOk());
EXPECT_LE(relativeError(first, expectedFirst), 1.0e-9);
EXPECT_LE(relativeError(second, expectedSecond), 1.0e-9);
EXPECT_LE(normalizedResidual(matrix, first, rhsFirst), 1.0e-10);
EXPECT_LE(normalizedResidual(matrix, second, rhsSecond), 1.0e-10);
EXPECT_LE(RelativeError(first, expected_first), 1.0e-9);
EXPECT_LE(RelativeError(second, expected_second), 1.0e-9);
EXPECT_LE(NormalizedResidual(matrix, first, rhs_first), 1.0e-10);
EXPECT_LE(NormalizedResidual(matrix, second, rhs_second), 1.0e-10);
}
TEST(MklPardisoSolver, RefactorizesWithoutLeakingState) {
const auto firstMatrix = makeDenseCsr(2U, {4.0, 1.0, 1.0, 3.0});
const auto secondMatrix = makeDenseCsr(2U, {2.0, 0.0, 0.0, 5.0});
const auto firstExpected = makeVector({1.0, 2.0});
const auto secondExpected = makeVector({-3.0, 4.0});
const auto first_matrix = MakeDenseCsr(2U, {4.0, 1.0, 1.0, 3.0});
const auto second_matrix = MakeDenseCsr(2U, {2.0, 0.0, 0.0, 5.0});
const auto first_expected = MakeVector({1.0, 2.0});
const auto second_expected = MakeVector({-3.0, 4.0});
fesa::MklPardisoSolver solver;
ASSERT_TRUE(solver.factorize(firstMatrix).isOk());
fesa::Vector firstSolution{2U};
ASSERT_TRUE(
solver.solve(firstMatrix.multiply(firstExpected), firstSolution).isOk());
EXPECT_LE(relativeError(firstSolution, firstExpected), 1.0e-9);
fesa::MklPardisoSolver solver;
ASSERT_TRUE(solver.Factorize(first_matrix).IsOk());
fesa::Vector first_solution{2U};
ASSERT_TRUE(
solver.Solve(first_matrix.Multiply(first_expected), first_solution)
.IsOk());
EXPECT_LE(RelativeError(first_solution, first_expected), 1.0e-9);
ASSERT_TRUE(solver.factorize(secondMatrix).isOk());
fesa::Vector secondSolution{2U};
const auto secondRhs = secondMatrix.multiply(secondExpected);
ASSERT_TRUE(solver.solve(secondRhs, secondSolution).isOk());
EXPECT_LE(relativeError(secondSolution, secondExpected), 1.0e-9);
EXPECT_LE(
normalizedResidual(secondMatrix, secondSolution, secondRhs), 1.0e-10);
ASSERT_TRUE(solver.Factorize(second_matrix).IsOk());
fesa::Vector second_solution{2U};
const auto second_rhs = second_matrix.Multiply(second_expected);
ASSERT_TRUE(solver.Solve(second_rhs, second_solution).IsOk());
EXPECT_LE(RelativeError(second_solution, second_expected), 1.0e-9);
EXPECT_LE(NormalizedResidual(second_matrix, second_solution, second_rhs),
1.0e-10);
}
TEST(MklPardisoSolver, ClassifiesSingularIndefiniteAndNonfiniteFailures) {
fesa::MklPardisoSolver solver;
const auto singular = MakeDenseCsr(2U, {1.0, 1.0, 1.0, 1.0});
const auto singular_status = solver.Factorize(singular);
ExpectStructuredSolverFailure(singular_status);
EXPECT_TRUE(singular_status.Diagnostics()[0U].code ==
"pardiso-zero-or-negative-pivot" ||
singular_status.Diagnostics()[0U].code ==
"pardiso-singular-diagonal");
EXPECT_NE(
singular_status.Diagnostics()[0U].entity_identity.find("phase=22,error="),
std::string::npos);
const auto indefinite = MakeDenseCsr(2U, {1.0, 2.0, 2.0, 1.0});
const auto indefinite_status = solver.Factorize(indefinite);
ExpectStructuredSolverFailure(indefinite_status);
EXPECT_TRUE(indefinite_status.Diagnostics()[0U].code ==
"pardiso-zero-or-negative-pivot" ||
indefinite_status.Diagnostics()[0U].code ==
"pardiso-singular-diagonal");
EXPECT_NE(indefinite_status.Diagnostics()[0U].entity_identity.find(
"phase=22,error="),
std::string::npos);
const auto spd = MakeDenseCsr(2U, {3.0, 1.0, 1.0, 2.0});
ASSERT_TRUE(solver.Factorize(spd).IsOk());
auto rhs = MakeVector({1.0, 2.0});
rhs[1U] = (std::numeric_limits<double>::infinity)();
fesa::Vector solution{2U};
solution[0U] = 23.0;
solution[1U] = -9.0;
const auto rhs_status = solver.Solve(rhs, solution);
ExpectStructuredSolverFailure(rhs_status);
EXPECT_EQ(rhs_status.Diagnostics()[0U].code, "nonfinite-solver-rhs");
EXPECT_DOUBLE_EQ(solution[0U], 23.0);
EXPECT_DOUBLE_EQ(solution[1U], -9.0);
fesa::SparsePattern pattern{{0U, 1U}, {0U}};
auto nonfinite_matrix = fesa::SparseMatrix::FromCoo(
1U, 1U, {{0U, 0U, (std::numeric_limits<double>::quiet_NaN)(), 0U, 0U}},
pattern);
EXPECT_FALSE(nonfinite_matrix.HasValue());
EXPECT_EQ(nonfinite_matrix.GetStatus().Diagnostics()[0U].code,
"nonfinite-sparse-value");
}
TEST(MklPardisoSolver,
ConditioningSweepPassesResolvedCasesAndFailsUnresolvedCasesExplicitly) {
const std::vector<double> common_scales{1.0e-12, 1.0, 1.0e12};
for (const double scale : common_scales) {
const auto matrix =
MakeDenseCsr(2U, {4.0 * scale, 1.0 * scale, 1.0 * scale, 3.0 * scale});
const auto expected = MakeVector({1.25, -0.75});
const auto rhs = matrix.Multiply(expected);
fesa::MklPardisoSolver solver;
ASSERT_TRUE(solver.Factorize(matrix).IsOk()) << "scale=" << scale;
fesa::Vector solution{2U};
ASSERT_TRUE(solver.Solve(rhs, solution).IsOk()) << "scale=" << scale;
EXPECT_LE(NormalizedResidual(matrix, solution, rhs), 1.0e-10);
EXPECT_LE(RelativeError(solution, expected), 1.0e-9);
}
const double resolved_ratio = 1.0e-8;
const auto resolved_matrix =
MakeDenseCsr(2U, {1.0, 0.0, 0.0, resolved_ratio});
const auto resolved_expected = MakeVector({0.5, -2.0});
const auto resolved_rhs = resolved_matrix.Multiply(resolved_expected);
fesa::MklPardisoSolver resolved_solver;
ASSERT_TRUE(resolved_solver.Factorize(resolved_matrix).IsOk());
fesa::Vector resolved_solution{2U};
ASSERT_TRUE(resolved_solver.Solve(resolved_rhs, resolved_solution).IsOk());
EXPECT_LE(
NormalizedResidual(resolved_matrix, resolved_solution, resolved_rhs),
1.0e-10);
EXPECT_LE(RelativeError(resolved_solution, resolved_expected), 1.0e-9);
const std::vector<double> unresolved_candidates{1.0e-16, 1.0e-300};
for (const double ratio : unresolved_candidates) {
const auto matrix = MakeDenseCsr(2U, {1.0, 0.0, 0.0, ratio});
const auto expected = MakeVector({0.5, -2.0});
const auto rhs = matrix.Multiply(expected);
fesa::MklPardisoSolver solver;
const auto singular = makeDenseCsr(2U, {1.0, 1.0, 1.0, 1.0});
const auto singularStatus = solver.factorize(singular);
expectStructuredSolverFailure(singularStatus);
EXPECT_TRUE(
singularStatus.diagnostics()[0U].code ==
"pardiso-zero-or-negative-pivot" ||
singularStatus.diagnostics()[0U].code ==
"pardiso-singular-diagonal");
EXPECT_NE(
singularStatus.diagnostics()[0U].entityIdentity.find(
"phase=22,error="),
std::string::npos);
const auto factor_status = solver.Factorize(matrix);
if (!factor_status.IsOk()) {
ExpectStructuredSolverFailure(factor_status);
continue;
}
const auto indefinite = makeDenseCsr(2U, {1.0, 2.0, 2.0, 1.0});
const auto indefiniteStatus = solver.factorize(indefinite);
expectStructuredSolverFailure(indefiniteStatus);
EXPECT_TRUE(
indefiniteStatus.diagnostics()[0U].code ==
"pardiso-zero-or-negative-pivot" ||
indefiniteStatus.diagnostics()[0U].code ==
"pardiso-singular-diagonal");
EXPECT_NE(
indefiniteStatus.diagnostics()[0U].entityIdentity.find(
"phase=22,error="),
std::string::npos);
const auto spd = makeDenseCsr(2U, {3.0, 1.0, 1.0, 2.0});
ASSERT_TRUE(solver.factorize(spd).isOk());
auto rhs = makeVector({1.0, 2.0});
rhs[1U] = (std::numeric_limits<double>::infinity)();
fesa::Vector solution{2U};
solution[0U] = 23.0;
solution[1U] = -9.0;
const auto rhsStatus = solver.solve(rhs, solution);
expectStructuredSolverFailure(rhsStatus);
EXPECT_EQ(rhsStatus.diagnostics()[0U].code, "nonfinite-solver-rhs");
EXPECT_DOUBLE_EQ(solution[0U], 23.0);
EXPECT_DOUBLE_EQ(solution[1U], -9.0);
fesa::SparsePattern pattern{{0U, 1U}, {0U}};
auto nonfiniteMatrix = fesa::SparseMatrix::fromCoo(
1U,
1U,
{{0U,
0U,
(std::numeric_limits<double>::quiet_NaN)(),
0U,
0U}},
pattern);
EXPECT_FALSE(nonfiniteMatrix.hasValue());
EXPECT_EQ(
nonfiniteMatrix.status().diagnostics()[0U].code,
"nonfinite-sparse-value");
}
TEST(MklPardisoSolver, ConditioningSweepPassesResolvedCasesAndFailsUnresolvedCasesExplicitly) {
const std::vector<double> commonScales{1.0e-12, 1.0, 1.0e12};
for (const double scale : commonScales) {
const auto matrix = makeDenseCsr(2U, {
4.0 * scale, 1.0 * scale,
1.0 * scale, 3.0 * scale});
const auto expected = makeVector({1.25, -0.75});
const auto rhs = matrix.multiply(expected);
fesa::MklPardisoSolver solver;
ASSERT_TRUE(solver.factorize(matrix).isOk()) << "scale=" << scale;
fesa::Vector solution{2U};
ASSERT_TRUE(solver.solve(rhs, solution).isOk()) << "scale=" << scale;
EXPECT_LE(normalizedResidual(matrix, solution, rhs), 1.0e-10);
EXPECT_LE(relativeError(solution, expected), 1.0e-9);
const auto solve_status = solver.Solve(rhs, solution);
if (!solve_status.IsOk()) {
ExpectStructuredSolverFailure(solve_status);
continue;
}
const double resolvedRatio = 1.0e-8;
const auto resolvedMatrix = makeDenseCsr(
2U, {1.0, 0.0, 0.0, resolvedRatio});
const auto resolvedExpected = makeVector({0.5, -2.0});
const auto resolvedRhs = resolvedMatrix.multiply(resolvedExpected);
fesa::MklPardisoSolver resolvedSolver;
ASSERT_TRUE(resolvedSolver.factorize(resolvedMatrix).isOk());
fesa::Vector resolvedSolution{2U};
ASSERT_TRUE(resolvedSolver.solve(resolvedRhs, resolvedSolution).isOk());
EXPECT_LE(
normalizedResidual(resolvedMatrix, resolvedSolution, resolvedRhs),
1.0e-10);
EXPECT_LE(relativeError(resolvedSolution, resolvedExpected), 1.0e-9);
const std::vector<double> unresolvedCandidates{1.0e-16, 1.0e-300};
for (const double ratio : unresolvedCandidates) {
const auto matrix = makeDenseCsr(2U, {1.0, 0.0, 0.0, ratio});
const auto expected = makeVector({0.5, -2.0});
const auto rhs = matrix.multiply(expected);
fesa::MklPardisoSolver solver;
const auto factorStatus = solver.factorize(matrix);
if (!factorStatus.isOk()) {
expectStructuredSolverFailure(factorStatus);
continue;
}
fesa::Vector solution{2U};
const auto solveStatus = solver.solve(rhs, solution);
if (!solveStatus.isOk()) {
expectStructuredSolverFailure(solveStatus);
continue;
}
EXPECT_LE(normalizedResidual(matrix, solution, rhs), 1.0e-10);
EXPECT_LE(relativeError(solution, expected), 1.0e-9);
}
EXPECT_LE(NormalizedResidual(matrix, solution, rhs), 1.0e-10);
EXPECT_LE(RelativeError(solution, expected), 1.0e-9);
}
}