feat(cpp-object-oriented-modular-refactoring): step 3 - foundation-google-style
This commit is contained in:
@@ -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,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 {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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_
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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,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,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,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,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,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/core/source_identity.hpp"
|
||||
#include "fesa/core/source_identity.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/core/status.hpp"
|
||||
#include "fesa/core/status.h"
|
||||
#include "fesa/model/model_types.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/core/status.hpp"
|
||||
#include "fesa/core/status.h"
|
||||
#include "fesa/model/model_types.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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."};
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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] =
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/core/status.hpp"
|
||||
#include "fesa/core/status.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user