feat(cpp-object-oriented-modular-refactoring): step 3 - foundation-google-style
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user