Integrate Refactor solver sources and dual MSVC CMake builds
This commit is contained in:
@@ -12,7 +12,6 @@
|
||||
#include "fesa/analysis/analysis_model.h"
|
||||
#include "fesa/build_info.h"
|
||||
#include "fesa/core/diagnostic.h"
|
||||
#include "fesa/elements/finite_rotation.h"
|
||||
#include "fesa/fem/dof_manager.h"
|
||||
#include "io/hdf5/hdf5_atomic_file.h"
|
||||
#include "io/hdf5/hdf5_model_writer.h"
|
||||
@@ -24,6 +23,10 @@ namespace {
|
||||
|
||||
using hdf5_internal::Hdf5Failure;
|
||||
using hdf5_internal::Hdf5Handle;
|
||||
using hdf5_internal::CopyVectorValues;
|
||||
using hdf5_internal::FlattenNodalTriads;
|
||||
using hdf5_internal::FlattenNodalDirectors;
|
||||
using hdf5_internal::SpatialReactionValues;
|
||||
|
||||
struct AcceptedFrameRow {
|
||||
std::uint64_t frame_index;
|
||||
@@ -228,11 +231,6 @@ Status Failure(const char* code, const std::string& message) {
|
||||
return hdf5_internal::OutputFailure(code, message);
|
||||
}
|
||||
|
||||
/// @brief Copies one nonempty full-space vector in node-major order.
|
||||
std::vector<double> Values(const Vector& values) {
|
||||
return std::vector<double>{values.Data(), values.Data() + values.Size()};
|
||||
}
|
||||
|
||||
/// @brief Removes inherited metadata before applying the dynamic schema.
|
||||
void DeleteAttribute(const hid_t object, const char* name) {
|
||||
if (H5Aexists(object, name) > 0) {
|
||||
@@ -401,26 +399,6 @@ void ProcessDataset(const hid_t file, const std::string& path,
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Flattens nodal triad axes in A,B,D row order.
|
||||
std::vector<double> FlattenTriad(const DynamicAcceptedFrameState& frame) {
|
||||
std::vector<double> values;
|
||||
values.reserve(frame.nodal_triad.size() * 9U);
|
||||
for (const auto& triad : frame.nodal_triad) {
|
||||
for (const auto& axis : triad)
|
||||
values.insert(values.end(), axis.begin(), axis.end());
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
/// @brief Flattens nodal directors in stable node order.
|
||||
std::vector<double> FlattenDirector(const DynamicAcceptedFrameState& frame) {
|
||||
std::vector<double> values;
|
||||
values.reserve(frame.nodal_director.size() * 3U);
|
||||
for (const auto& director : frame.nodal_director)
|
||||
values.insert(values.end(), director.begin(), director.end());
|
||||
return values;
|
||||
}
|
||||
|
||||
/// @brief Flattens physical recovery in element and Gauss order.
|
||||
std::vector<double> FlattenShell(const DynamicAcceptedFrameState& frame,
|
||||
const int quantity) {
|
||||
@@ -442,27 +420,6 @@ std::vector<double> FlattenShell(const DynamicAcceptedFrameState& frame,
|
||||
return values;
|
||||
}
|
||||
|
||||
/// @brief Maps generalized support moments to work-conjugate spatial moments.
|
||||
std::vector<double> SpatialReaction(const DynamicAcceptedFrameState& frame) {
|
||||
std::vector<double> values = Values(frame.reaction);
|
||||
for (std::size_t node = 0U; node < frame.nodal_internal_order.size();
|
||||
++node) {
|
||||
const std::size_t offset = node * 6U;
|
||||
const Vector3 phi{frame.generalized_coordinates[offset + 3U],
|
||||
frame.generalized_coordinates[offset + 4U],
|
||||
frame.generalized_coordinates[offset + 5U]};
|
||||
const Matrix transform = InverseTransposeLeftJacobianSO3(phi);
|
||||
for (std::size_t row = 0U; row < 3U; ++row) {
|
||||
values[offset + 3U + row] = 0.0;
|
||||
for (std::size_t column = 0U; column < 3U; ++column) {
|
||||
values[offset + 3U + row] +=
|
||||
transform(row, column) * frame.reaction[offset + 3U + column];
|
||||
}
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
/// @brief Writes or reads back all mandatory accepted-endpoint quantities.
|
||||
void WriteFrame(const hid_t file, const Domain& domain,
|
||||
const DynamicAcceptedFrameState& frame,
|
||||
@@ -491,52 +448,55 @@ void WriteFrame(const hid_t file, const Domain& domain,
|
||||
"force,force,force,force*length,force*length,force*length";
|
||||
const std::vector<hsize_t> nodal{nodes, 6U};
|
||||
WriteDataset(file, root + "/nodal/displacement", nodal,
|
||||
Values(frame.generalized_coordinates), "UX,UY,UZ,URX,URY,URZ",
|
||||
CopyVectorValues(frame.generalized_coordinates), "UX,UY,UZ,URX,URY,URZ",
|
||||
"length,length,length,1,1,1", "global-cartesian", "nodal",
|
||||
frame);
|
||||
WriteDataset(file, root + "/nodal/velocity", nodal, Values(frame.velocity),
|
||||
WriteDataset(file, root + "/nodal/velocity", nodal, CopyVectorValues(frame.velocity),
|
||||
"V1,V2,V3,PHIDOT1,PHIDOT2,PHIDOT3",
|
||||
"length/time,length/time,length/time,1/time,1/time,1/time",
|
||||
"fixed-initial-global-chart", "nodal", frame);
|
||||
WriteDataset(
|
||||
file, root + "/nodal/acceleration", nodal, Values(frame.acceleration),
|
||||
file, root + "/nodal/acceleration", nodal, CopyVectorValues(frame.acceleration),
|
||||
"A1,A2,A3,PHIDDOT1,PHIDDOT2,PHIDDOT3",
|
||||
"length/time^2,length/time^2,length/time^2,1/time^2,1/time^2,1/time^2",
|
||||
"fixed-initial-global-chart", "nodal", frame);
|
||||
WriteDataset(
|
||||
file, root + "/nodal/algorithmic_acceleration", nodal,
|
||||
Values(frame.algorithmic_acceleration), "AUX1,AUX2,AUX3,AUX4,AUX5,AUX6",
|
||||
CopyVectorValues(frame.algorithmic_acceleration), "AUX1,AUX2,AUX3,AUX4,AUX5,AUX6",
|
||||
"length/time^2,length/time^2,length/time^2,1/time^2,1/time^2,1/time^2",
|
||||
"fixed-initial-global-chart", "nodal", frame);
|
||||
WriteDataset(file, root + "/nodal/angular_velocity", {nodes, 3U},
|
||||
Values(frame.angular_velocity), "OMEGA1,OMEGA2,OMEGA3",
|
||||
CopyVectorValues(frame.angular_velocity), "OMEGA1,OMEGA2,OMEGA3",
|
||||
"1/time,1/time,1/time", "global-cartesian", "nodal", frame);
|
||||
WriteDataset(file, root + "/nodal/angular_acceleration", {nodes, 3U},
|
||||
Values(frame.angular_acceleration),
|
||||
CopyVectorValues(frame.angular_acceleration),
|
||||
"OMEGADOT1,OMEGADOT2,OMEGADOT3", "1/time^2,1/time^2,1/time^2",
|
||||
"global-cartesian", "nodal", frame);
|
||||
WriteDataset(file, root + "/nodal/triad", {nodes, 3U, 3U},
|
||||
FlattenTriad(frame), "X,Y,Z", "1,1,1", "global-cartesian",
|
||||
FlattenNodalTriads(frame.nodal_triad), "X,Y,Z", "1,1,1", "global-cartesian",
|
||||
"nodal", frame);
|
||||
WriteDataset(file, root + "/nodal/director", {nodes, 3U},
|
||||
FlattenDirector(frame), "D1,D2,D3", "1,1,1", "global-cartesian",
|
||||
FlattenNodalDirectors(frame.nodal_director), "D1,D2,D3", "1,1,1", "global-cartesian",
|
||||
"nodal", frame);
|
||||
WriteDataset(file, root + "/nodal/generalized_inertia_force", nodal,
|
||||
Values(frame.inertia_force), kGeneralized, kGeneralizedUnits,
|
||||
CopyVectorValues(frame.inertia_force), kGeneralized, kGeneralizedUnits,
|
||||
"fixed-initial-global-chart", "nodal", frame);
|
||||
WriteDataset(file, root + "/nodal/generalized_internal_force", nodal,
|
||||
Values(frame.internal_force), kGeneralized, kGeneralizedUnits,
|
||||
CopyVectorValues(frame.internal_force), kGeneralized, kGeneralizedUnits,
|
||||
"fixed-initial-global-chart", "nodal", frame);
|
||||
WriteDataset(file, root + "/nodal/generalized_external_force", nodal,
|
||||
Values(frame.external_force), kGeneralized, kGeneralizedUnits,
|
||||
CopyVectorValues(frame.external_force), kGeneralized, kGeneralizedUnits,
|
||||
"fixed-initial-global-chart", "nodal", frame);
|
||||
WriteDataset(file, root + "/nodal/generalized_residual", nodal,
|
||||
Values(frame.residual), kGeneralized, kGeneralizedUnits,
|
||||
CopyVectorValues(frame.residual), kGeneralized, kGeneralizedUnits,
|
||||
"fixed-initial-global-chart", "nodal", frame);
|
||||
WriteDataset(file, root + "/nodal/generalized_reaction", nodal,
|
||||
Values(frame.reaction), kGeneralized, kGeneralizedUnits,
|
||||
CopyVectorValues(frame.reaction), kGeneralized, kGeneralizedUnits,
|
||||
"fixed-initial-global-chart", "nodal", frame);
|
||||
WriteDataset(file, root + "/nodal/reaction", nodal, SpatialReaction(frame),
|
||||
WriteDataset(file, root + "/nodal/reaction", nodal,
|
||||
SpatialReactionValues(frame.generalized_coordinates,
|
||||
frame.reaction,
|
||||
frame.nodal_internal_order.size()),
|
||||
"RF1,RF2,RF3,RM1,RM2,RM3", kGeneralizedUnits, "global-cartesian",
|
||||
"nodal", frame);
|
||||
const std::string shell = root + "/element/shell/";
|
||||
@@ -810,13 +770,7 @@ void CheckModel(const hid_t file, const Domain& domain,
|
||||
model.prescribed_displacement);
|
||||
std::vector<double> directors;
|
||||
std::vector<double> frames;
|
||||
for (const auto& frame : domain.ShellNodeInitialFrames()) {
|
||||
directors.insert(directors.end(), frame.director.begin(),
|
||||
frame.director.end());
|
||||
frames.insert(frames.end(), frame.tangent_a.begin(), frame.tangent_a.end());
|
||||
frames.insert(frames.end(), frame.tangent_b.begin(), frame.tangent_b.end());
|
||||
frames.insert(frames.end(), frame.director.begin(), frame.director.end());
|
||||
}
|
||||
hdf5_internal::AppendShellReferenceFrames(domain, directors, frames);
|
||||
(void)CheckDoubles(file, "/model/shell/nodal_director", {count, 3U},
|
||||
directors);
|
||||
(void)CheckDoubles(file, "/model/shell/nodal_frame", {count, 3U, 3U}, frames);
|
||||
|
||||
@@ -20,23 +20,16 @@
|
||||
#include "io/hdf5/hdf5_model_writer.h"
|
||||
#include "io/hdf5/hdf5_primitives.h"
|
||||
#include "io/hdf5/hdf5_raii.h"
|
||||
#include "math/finite_values.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
|
||||
constexpr const char* kEigenRoot = "/steps/Step-1/eigen";
|
||||
using math_internal::IsFinite;
|
||||
|
||||
void WriteQuantityAttributes(hid_t dataset, const std::string& components,
|
||||
const std::string& units,
|
||||
const std::string& coordinates,
|
||||
const std::string& location) {
|
||||
hdf5_internal::WriteStringAttribute(dataset, "component_names", components);
|
||||
hdf5_internal::WriteStringAttribute(dataset, "component_unit_dimensions",
|
||||
units);
|
||||
hdf5_internal::WriteStringAttribute(dataset, "coordinate_system",
|
||||
coordinates);
|
||||
hdf5_internal::WriteStringAttribute(dataset, "location", location);
|
||||
}
|
||||
using hdf5_internal::WriteQuantityAttributes;
|
||||
|
||||
constexpr const char* kEigenRoot = "/steps/Step-1/eigen";
|
||||
|
||||
void WriteModalAttributes(hid_t dataset, const std::string& source_step) {
|
||||
hdf5_internal::WriteUint64Attribute(dataset, "eigen_schema_version", 1U);
|
||||
@@ -44,17 +37,6 @@ void WriteModalAttributes(hid_t dataset, const std::string& source_step) {
|
||||
hdf5_internal::WriteStringAttribute(dataset, "domain", "mode");
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!std::isfinite(matrix(row, column))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Status Validate(const Domain& domain, const EigenAnalysisState& state,
|
||||
const std::vector<Diagnostic>& diagnostics) {
|
||||
const FrequencyStepDefinition* frequency = domain.Frequency();
|
||||
|
||||
@@ -489,13 +489,7 @@ bool CheckModelRows(hid_t file, const Domain& domain) {
|
||||
[&](auto i) { return domain.ShellSections()[i].thickness; }))
|
||||
return false;
|
||||
std::vector<double> directors, frames;
|
||||
for (const auto& frame : domain.ShellNodeInitialFrames()) {
|
||||
directors.insert(directors.end(), frame.director.begin(),
|
||||
frame.director.end());
|
||||
frames.insert(frames.end(), frame.tangent_a.begin(), frame.tangent_a.end());
|
||||
frames.insert(frames.end(), frame.tangent_b.begin(), frame.tangent_b.end());
|
||||
frames.insert(frames.end(), frame.director.begin(), frame.director.end());
|
||||
}
|
||||
hdf5_internal::AppendShellReferenceFrames(domain, directors, frames);
|
||||
return CheckArray<double>(file, "/model/shell/nodal_director", H5T_IEEE_F64LE,
|
||||
H5T_NATIVE_DOUBLE,
|
||||
{static_cast<hsize_t>(domain.Nodes().size()), 3U},
|
||||
|
||||
@@ -419,13 +419,7 @@ void WriteShellModelData(const hid_t file, const Domain& domain,
|
||||
directors.reserve(domain.Nodes().size() * 3U);
|
||||
std::vector<double> frames;
|
||||
frames.reserve(domain.Nodes().size() * 9U);
|
||||
for (const auto& frame : domain.ShellNodeInitialFrames()) {
|
||||
directors.insert(directors.end(), frame.director.begin(),
|
||||
frame.director.end());
|
||||
frames.insert(frames.end(), frame.tangent_a.begin(), frame.tangent_a.end());
|
||||
frames.insert(frames.end(), frame.tangent_b.begin(), frame.tangent_b.end());
|
||||
frames.insert(frames.end(), frame.director.begin(), frame.director.end());
|
||||
}
|
||||
hdf5_internal::AppendShellReferenceFrames(domain, directors, frames);
|
||||
WriteModelDoubleDataset(file, "/model/shell/nodal_director",
|
||||
{static_cast<hsize_t>(domain.Nodes().size()), 3U},
|
||||
directors.data(), directors.size(), "D1,D2,D3",
|
||||
|
||||
@@ -5,9 +5,13 @@
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/elements/finite_rotation.h"
|
||||
#include "fesa/math/vector3.h"
|
||||
|
||||
namespace fesa::hdf5_internal {
|
||||
|
||||
Status OutputFailure(const std::string& code, const std::string& message) {
|
||||
@@ -60,10 +64,20 @@ bool IsValidUtf8(const std::string& value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SameIdentity(const SourceEntityId& left, const SourceEntityId& right) {
|
||||
return left.instance_name == right.instance_name &&
|
||||
left.source_label == right.source_label &&
|
||||
left.source_label_text == right.source_label_text;
|
||||
bool SameIdentity(const SourceEntityId& left,
|
||||
const SourceEntityId& right) {
|
||||
return fesa::SourceIdentityEquals(left, right);
|
||||
}
|
||||
|
||||
void AppendShellReferenceFrames(const Domain& domain,
|
||||
std::vector<double>& directors,
|
||||
std::vector<double>& frames) {
|
||||
for (const auto& frame : domain.ShellNodeInitialFrames()) {
|
||||
directors.insert(directors.end(), frame.director.begin(), frame.director.end());
|
||||
frames.insert(frames.end(), frame.tangent_a.begin(), frame.tangent_a.end());
|
||||
frames.insert(frames.end(), frame.tangent_b.begin(), frame.tangent_b.end());
|
||||
frames.insert(frames.end(), frame.director.begin(), frame.director.end());
|
||||
}
|
||||
}
|
||||
|
||||
bool IsShellDomain(const Domain& domain) noexcept {
|
||||
@@ -95,6 +109,55 @@ std::string SourceInputIdentity(const Domain& domain) {
|
||||
";content_identity=" + domain.SourceContentIdentity();
|
||||
}
|
||||
|
||||
std::vector<double> CopyVectorValues(const Vector& source) {
|
||||
std::vector<double> values(source.Size());
|
||||
for (std::size_t index = 0U; index < source.Size(); ++index) {
|
||||
values[index] = source[index];
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
std::vector<double> FlattenNodalTriads(
|
||||
const std::vector<std::array<std::array<double, 3>, 3>>& triads) {
|
||||
std::vector<double> values;
|
||||
values.reserve(triads.size() * 9U);
|
||||
for (const auto& triad : triads) {
|
||||
for (const auto& axis : triad) {
|
||||
values.insert(values.end(), axis.begin(), axis.end());
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
std::vector<double> FlattenNodalDirectors(
|
||||
const std::vector<std::array<double, 3>>& directors) {
|
||||
std::vector<double> values;
|
||||
values.reserve(directors.size() * 3U);
|
||||
for (const auto& director : directors) {
|
||||
values.insert(values.end(), director.begin(), director.end());
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
std::vector<double> SpatialReactionValues(const Vector& rotation,
|
||||
const Vector& reaction,
|
||||
const std::size_t node_count) {
|
||||
std::vector<double> values = CopyVectorValues(reaction);
|
||||
for (std::size_t node = 0U; node < node_count; ++node) {
|
||||
const std::size_t offset = kDofsPerNode * node;
|
||||
const Vector3 phi{rotation[offset + 3U], rotation[offset + 4U],
|
||||
rotation[offset + 5U]};
|
||||
const Vector3 generalized{reaction[offset + 3U], reaction[offset + 4U],
|
||||
reaction[offset + 5U]};
|
||||
const auto moment = SpatialMomentSO3(phi, generalized);
|
||||
if (!moment.HasValue())
|
||||
throw std::runtime_error{"Spatial reaction moment is nonfinite."};
|
||||
for (std::size_t row = 0U; row < 3U; ++row)
|
||||
values[offset + 3U + row] = moment.Value()[row];
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
Hdf5Handle MakeUtf8StringType() {
|
||||
Hdf5Handle type{
|
||||
RequireHdf5Id(H5Tcopy(H5T_C_S1), "Unable to copy an HDF5 string type."),
|
||||
@@ -186,7 +249,7 @@ Hdf5Handle CreateDatasetSpace(const std::vector<hsize_t>& dimensions) {
|
||||
H5Sclose};
|
||||
}
|
||||
|
||||
void WriteResultAttributes(const hid_t dataset,
|
||||
void WriteQuantityAttributes(const hid_t dataset,
|
||||
const std::string& component_names,
|
||||
const std::string& component_units,
|
||||
const std::string& coordinate_system,
|
||||
@@ -195,6 +258,15 @@ void WriteResultAttributes(const hid_t dataset,
|
||||
WriteStringAttribute(dataset, "component_unit_dimensions", component_units);
|
||||
WriteStringAttribute(dataset, "coordinate_system", coordinate_system);
|
||||
WriteStringAttribute(dataset, "location", location);
|
||||
}
|
||||
|
||||
void WriteResultAttributes(const hid_t dataset,
|
||||
const std::string& component_names,
|
||||
const std::string& component_units,
|
||||
const std::string& coordinate_system,
|
||||
const std::string& location) {
|
||||
WriteQuantityAttributes(dataset, component_names, component_units,
|
||||
coordinate_system, location);
|
||||
WriteStringAttribute(dataset, "step_name", kStepName);
|
||||
WriteUint64Attribute(dataset, "frame_index", 0U);
|
||||
}
|
||||
@@ -238,11 +310,8 @@ void WriteModelDoubleDataset(const hid_t file, const std::string& path,
|
||||
const std::string& coordinate_system,
|
||||
const std::string& location) {
|
||||
auto dataset = WriteDoubleValues(file, path, dimensions, values, value_count);
|
||||
WriteStringAttribute(dataset.Get(), "component_names", component_names);
|
||||
WriteStringAttribute(dataset.Get(), "component_unit_dimensions",
|
||||
component_units);
|
||||
WriteStringAttribute(dataset.Get(), "coordinate_system", coordinate_system);
|
||||
WriteStringAttribute(dataset.Get(), "location", location);
|
||||
WriteQuantityAttributes(dataset.Get(), component_names, component_units,
|
||||
coordinate_system, location);
|
||||
}
|
||||
|
||||
void WriteUint8Dataset(const hid_t file, const std::string& path,
|
||||
|
||||
@@ -13,11 +13,15 @@
|
||||
|
||||
#include "fesa/core/source_identity.h"
|
||||
#include "fesa/core/status.h"
|
||||
#include "fesa/math/vector.h"
|
||||
#include "fesa/model/domain.h"
|
||||
#include "io/hdf5/hdf5_raii.h"
|
||||
#include "math/finite_values.h"
|
||||
|
||||
namespace fesa::hdf5_internal {
|
||||
|
||||
using math_internal::IsFinite;
|
||||
|
||||
inline constexpr std::size_t kDofsPerNode = 6U;
|
||||
inline constexpr std::size_t kEndpointCount = 2U;
|
||||
inline constexpr std::size_t kGaussPointCount = 2U;
|
||||
@@ -64,16 +68,11 @@ Status OutputFailure(const std::string& code, const std::string& message);
|
||||
/// @brief Tests exact preserved source identity.
|
||||
bool SameIdentity(const SourceEntityId& left, const SourceEntityId& right);
|
||||
|
||||
/// @brief Tests a fixed component record for finite values.
|
||||
template <std::size_t Size>
|
||||
bool IsFinite(const std::array<double, Size>& values) {
|
||||
for (const double value : values) {
|
||||
if (!std::isfinite(value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/// @brief Appends source-ordered reference directors and a,b,d frame columns.
|
||||
/// @note Callers retain allocation, schema, write/readback and failure ownership.
|
||||
void AppendShellReferenceFrames(const Domain& domain,
|
||||
std::vector<double>& directors,
|
||||
std::vector<double>& frames);
|
||||
|
||||
/// @brief Validates UTF-8 without accepting overlong or surrogate encodings.
|
||||
bool IsValidUtf8(const std::string& value);
|
||||
@@ -93,6 +92,25 @@ std::string NormalizedPathString(const std::filesystem::path& path);
|
||||
/// @brief Returns schema-v0 source path and content identity metadata.
|
||||
std::string SourceInputIdentity(const Domain& domain);
|
||||
|
||||
/// @brief Copies validated vector values in their existing component order.
|
||||
std::vector<double> CopyVectorValues(const Vector& source);
|
||||
|
||||
/// @brief Flattens global nodal A,B,D axes in stable node/axis/component order.
|
||||
std::vector<double> FlattenNodalTriads(
|
||||
const std::vector<std::array<std::array<double, 3>, 3>>& triads);
|
||||
|
||||
/// @brief Flattens global nodal directors in stable node/component order.
|
||||
std::vector<double> FlattenNodalDirectors(
|
||||
const std::vector<std::array<double, 3>>& directors);
|
||||
|
||||
/// @brief Preserves force rows and maps generalized moments to spatial moments.
|
||||
/// @pre Both six-DOF vectors and node_count were validated by the typed writer.
|
||||
/// @note Rotation entries are at offsets 3..5; conversion uses J^T m = q.
|
||||
/// @throws std::runtime_error when the existing spatial-moment solve fails.
|
||||
std::vector<double> SpatialReactionValues(const Vector& rotation,
|
||||
const Vector& reaction,
|
||||
std::size_t node_count);
|
||||
|
||||
Hdf5Handle MakeUtf8StringType();
|
||||
void WriteStringAttribute(hid_t object, const char* name,
|
||||
const std::string& value);
|
||||
@@ -101,6 +119,11 @@ void WriteUint8Attribute(hid_t object, const char* name, std::uint8_t value);
|
||||
void WriteDoubleAttribute(hid_t object, const char* name, double value);
|
||||
Hdf5Handle CreateGroup(hid_t file, const char* path);
|
||||
Hdf5Handle CreateDatasetSpace(const std::vector<hsize_t>& dimensions);
|
||||
/// @brief Writes quantity identity attributes, without family/frame metadata.
|
||||
void WriteQuantityAttributes(hid_t dataset, const std::string& component_names,
|
||||
const std::string& component_units,
|
||||
const std::string& coordinate_system,
|
||||
const std::string& location);
|
||||
void WriteResultAttributes(hid_t dataset, const std::string& component_names,
|
||||
const std::string& component_units,
|
||||
const std::string& coordinate_system,
|
||||
|
||||
@@ -756,27 +756,19 @@ void WriteResultDatasets(const hid_t file, const Domain& domain,
|
||||
WriteStress(file, state);
|
||||
}
|
||||
|
||||
std::vector<double> CopyVector(const Vector& source) {
|
||||
std::vector<double> values(source.Size());
|
||||
for (std::size_t index = 0U; index < source.Size(); ++index) {
|
||||
values[index] = source[index];
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
std::vector<DoubleDatasetPlan> BuildResultDatasetPlans(
|
||||
const Domain& domain, const AnalysisState& state) {
|
||||
std::vector<DoubleDatasetPlan> datasets;
|
||||
const std::vector<std::size_t> nodal_dimensions{domain.Nodes().size(),
|
||||
kDofsPerNode};
|
||||
datasets.push_back({std::string{kStepRoot} + "/nodal/displacement",
|
||||
nodal_dimensions, CopyVector(state.Displacement()),
|
||||
nodal_dimensions, CopyVectorValues(state.Displacement()),
|
||||
"UX,UY,UZ,URX,URY,URZ",
|
||||
"length,length,length,radian,radian,radian",
|
||||
"global-cartesian", "nodal"});
|
||||
datasets.push_back(
|
||||
{std::string{kStepRoot} + "/nodal/reaction", nodal_dimensions,
|
||||
CopyVector(state.Reaction()), "RF1,RF2,RF3,RM1,RM2,RM3",
|
||||
CopyVectorValues(state.Reaction()), "RF1,RF2,RF3,RM1,RM2,RM3",
|
||||
"force,force,force,force*length,force*length,force*length",
|
||||
"global-cartesian", "nodal"});
|
||||
if (IsShellDomain(domain)) {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "fesa/build_info.h"
|
||||
#include "io/hdf5/hdf5_raii.h"
|
||||
#include "io/hdf5/hdf5_primitives.h"
|
||||
|
||||
namespace fesa::hdf5_internal {
|
||||
|
||||
@@ -596,15 +597,7 @@ void SelfCheckFile(const std::filesystem::path& path, const Domain& domain,
|
||||
std::vector<double> frames;
|
||||
directors.reserve(domain.Nodes().size() * 3U);
|
||||
frames.reserve(domain.Nodes().size() * 9U);
|
||||
for (const auto& frame : domain.ShellNodeInitialFrames()) {
|
||||
directors.insert(directors.end(), frame.director.begin(),
|
||||
frame.director.end());
|
||||
frames.insert(frames.end(), frame.tangent_a.begin(),
|
||||
frame.tangent_a.end());
|
||||
frames.insert(frames.end(), frame.tangent_b.begin(),
|
||||
frame.tangent_b.end());
|
||||
frames.insert(frames.end(), frame.director.begin(), frame.director.end());
|
||||
}
|
||||
hdf5_internal::AppendShellReferenceFrames(domain, directors, frames);
|
||||
RequireModelDoubleDataset(file.Get(), "/model/shell/nodal_director",
|
||||
{static_cast<hsize_t>(domain.Nodes().size()), 3U},
|
||||
"D1,D2,D3", "1,1,1", "global-cartesian", "nodal",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "fesa/io/hdf5/nonlinear_hdf5_results_writer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
@@ -14,17 +13,20 @@
|
||||
|
||||
#include "fesa/analysis/analysis_model.h"
|
||||
#include "fesa/build_info.h"
|
||||
#include "fesa/elements/finite_rotation.h"
|
||||
#include "fesa/fem/dof_manager.h"
|
||||
#include "fesa/math/vector3.h"
|
||||
#include "io/hdf5/hdf5_atomic_file.h"
|
||||
#include "io/hdf5/hdf5_primitives.h"
|
||||
#include "io/hdf5/hdf5_raii.h"
|
||||
#include "io/hdf5/hdf5_self_check.h"
|
||||
#include "math/finite_values.h"
|
||||
#include "results/shell_result_recovery.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
|
||||
using math_internal::IsFinite;
|
||||
|
||||
using hdf5_internal::Hdf5Failure;
|
||||
using hdf5_internal::Hdf5Handle;
|
||||
|
||||
@@ -90,43 +92,9 @@ struct DiagnosticWriteRow {
|
||||
const char* message;
|
||||
};
|
||||
|
||||
template <std::size_t Size>
|
||||
bool IsFinite(const std::array<double, Size>& values) {
|
||||
return std::all_of(values.begin(), values.end(),
|
||||
[](const double value) { return std::isfinite(value); });
|
||||
}
|
||||
|
||||
bool IsFinite(const Vector& values) {
|
||||
for (std::size_t index = 0U; index < values.Size(); ++index) {
|
||||
if (!std::isfinite(values[index])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SameIdentity(const SourceEntityId& left, const SourceEntityId& right) {
|
||||
return left.instance_name == right.instance_name &&
|
||||
left.source_label == right.source_label &&
|
||||
left.source_label_text == right.source_label_text;
|
||||
}
|
||||
|
||||
bool IsFinite(const ShellResultRow& row) {
|
||||
if (!IsFinite(row.natural_coordinates) || !IsFinite(row.generalized_strain) ||
|
||||
!IsFinite(row.section_resultant)) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& axis : row.local_frame) {
|
||||
if (!IsFinite(axis)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const auto& stress : row.stress) {
|
||||
if (!std::isfinite(stress.zeta) || !IsFinite(stress.components)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
bool SameIdentity(const SourceEntityId& left,
|
||||
const SourceEntityId& right) {
|
||||
return fesa::SourceIdentityEquals(left, right);
|
||||
}
|
||||
|
||||
Status Failure(const char* code, const std::string& message) {
|
||||
@@ -300,7 +268,8 @@ Status ValidateState(const Domain& domain, const NonlinearAnalysisState& state)
|
||||
const std::size_t element = row_index / kShellLocationsPerElement;
|
||||
const std::size_t location = row_index % kShellLocationsPerElement;
|
||||
if (row.element != element || row.location != locations[location] ||
|
||||
row.natural_coordinates != coordinates[location] || !IsFinite(row)) {
|
||||
row.natural_coordinates != coordinates[location] ||
|
||||
!results_internal::IsFiniteShellRow(row)) {
|
||||
return Failure("invalid-result-state",
|
||||
"Accepted shell rows must preserve element and GP order.");
|
||||
}
|
||||
@@ -315,13 +284,8 @@ void WriteCommonNumericAttributes(const hid_t dataset,
|
||||
const std::string& coordinate_system,
|
||||
const std::string& location) {
|
||||
hdf5_internal::WriteUint64Attribute(dataset, "schema_version", 1U);
|
||||
hdf5_internal::WriteStringAttribute(dataset, "component_names",
|
||||
component_names);
|
||||
hdf5_internal::WriteStringAttribute(dataset, "component_unit_dimensions",
|
||||
component_units);
|
||||
hdf5_internal::WriteStringAttribute(dataset, "coordinate_system",
|
||||
coordinate_system);
|
||||
hdf5_internal::WriteStringAttribute(dataset, "location", location);
|
||||
hdf5_internal::WriteQuantityAttributes(
|
||||
dataset, component_names, component_units, coordinate_system, location);
|
||||
}
|
||||
|
||||
void WriteFrameAttributes(const hid_t dataset, const AcceptedFrameState& frame) {
|
||||
@@ -660,12 +624,7 @@ void WriteModelData(const hid_t file, const Domain& domain,
|
||||
std::vector<double> frames;
|
||||
directors.reserve(domain.Nodes().size() * 3U);
|
||||
frames.reserve(domain.Nodes().size() * 9U);
|
||||
for (const auto& frame : domain.ShellNodeInitialFrames()) {
|
||||
directors.insert(directors.end(), frame.director.begin(), frame.director.end());
|
||||
frames.insert(frames.end(), frame.tangent_a.begin(), frame.tangent_a.end());
|
||||
frames.insert(frames.end(), frame.tangent_b.begin(), frame.tangent_b.end());
|
||||
frames.insert(frames.end(), frame.director.begin(), frame.director.end());
|
||||
}
|
||||
hdf5_internal::AppendShellReferenceFrames(domain, directors, frames);
|
||||
const hsize_t node_count = static_cast<hsize_t>(domain.Nodes().size());
|
||||
(void)WriteModelDoubleDataset(file, "/model/shell/nodal_director",
|
||||
{node_count, 3U}, directors.data(),
|
||||
@@ -772,14 +731,6 @@ void WriteAcceptedFrameTable(const hid_t file,
|
||||
hdf5_internal::WriteUint64Attribute(dataset.Get(), "schema_version", 1U);
|
||||
}
|
||||
|
||||
std::vector<double> CopyVector(const Vector& source) {
|
||||
std::vector<double> values(source.Size());
|
||||
for (std::size_t index = 0U; index < source.Size(); ++index) {
|
||||
values[index] = source[index];
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
std::vector<double> NodalDisplacement(const AcceptedFrameState& frame) {
|
||||
std::vector<double> values(frame.displacement.Size());
|
||||
for (std::size_t node = 0U; node < frame.nodal_internal_order.size(); ++node) {
|
||||
@@ -793,47 +744,6 @@ std::vector<double> NodalDisplacement(const AcceptedFrameState& frame) {
|
||||
return values;
|
||||
}
|
||||
|
||||
std::vector<double> FlattenTriads(const AcceptedFrameState& frame) {
|
||||
std::vector<double> values;
|
||||
values.reserve(frame.nodal_triad.size() * 9U);
|
||||
for (const auto& triad : frame.nodal_triad) {
|
||||
for (const auto& axis : triad) {
|
||||
values.insert(values.end(), axis.begin(), axis.end());
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
std::vector<double> FlattenDirectors(const AcceptedFrameState& frame) {
|
||||
std::vector<double> values;
|
||||
values.reserve(frame.nodal_director.size() * 3U);
|
||||
for (const auto& director : frame.nodal_director) {
|
||||
values.insert(values.end(), director.begin(), director.end());
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
std::vector<double> SpatialReaction(const AcceptedFrameState& frame) {
|
||||
std::vector<double> values = CopyVector(frame.reaction);
|
||||
for (std::size_t node = 0U; node < frame.nodal_internal_order.size(); ++node) {
|
||||
const std::size_t offset = kDofsPerNode * node;
|
||||
const Vector3 phi{frame.rotation_vector[offset + 3U],
|
||||
frame.rotation_vector[offset + 4U],
|
||||
frame.rotation_vector[offset + 5U]};
|
||||
const Matrix transform = InverseTransposeLeftJacobianSO3(phi);
|
||||
const std::array<double, 3> generalized{
|
||||
frame.reaction[offset + 3U], frame.reaction[offset + 4U],
|
||||
frame.reaction[offset + 5U]};
|
||||
for (std::size_t row = 0U; row < 3U; ++row) {
|
||||
values[offset + 3U + row] = 0.0;
|
||||
for (std::size_t column = 0U; column < 3U; ++column) {
|
||||
values[offset + 3U + row] += transform(row, column) * generalized[column];
|
||||
}
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
void WriteShellIdentity(const hid_t dataset, const bool has_section_positions,
|
||||
const std::string& local_frame_path) {
|
||||
hdf5_internal::WriteStringAttribute(dataset, "source_element_type_dataset",
|
||||
@@ -864,35 +774,36 @@ void WriteFrame(const hid_t file, const Domain& domain,
|
||||
file, root + "/nodal/displacement", nodal_dimensions, displacement.data(),
|
||||
displacement.size(), "UX,UY,UZ,URX,URY,URZ", "length,length,length,1,1,1",
|
||||
"global-cartesian", "nodal", frame);
|
||||
const auto triads = FlattenTriads(frame);
|
||||
const auto triads = hdf5_internal::FlattenNodalTriads(frame.nodal_triad);
|
||||
(void)WriteFrameDoubleDataset(file, root + "/nodal/triad",
|
||||
{node_count, 3U, 3U}, triads.data(), triads.size(),
|
||||
"X,Y,Z", "1,1,1", "global-cartesian",
|
||||
"nodal-triad", frame);
|
||||
const auto directors = FlattenDirectors(frame);
|
||||
const auto directors = hdf5_internal::FlattenNodalDirectors(frame.nodal_director);
|
||||
(void)WriteFrameDoubleDataset(file, root + "/nodal/director", {node_count, 3U},
|
||||
directors.data(), directors.size(), "D1,D2,D3",
|
||||
"1,1,1", "global-cartesian", "nodal-director",
|
||||
frame);
|
||||
const auto internal = CopyVector(frame.internal_force);
|
||||
const auto internal = hdf5_internal::CopyVectorValues(frame.internal_force);
|
||||
(void)WriteFrameDoubleDataset(
|
||||
file, root + "/nodal/generalized_internal_force", nodal_dimensions,
|
||||
internal.data(), internal.size(), "FI1,FI2,FI3,MI_PHI1,MI_PHI2,MI_PHI3",
|
||||
"force,force,force,force*length,force*length,force*length",
|
||||
"global-cartesian", "nodal", frame);
|
||||
const auto external = CopyVector(frame.external_force);
|
||||
const auto external = hdf5_internal::CopyVectorValues(frame.external_force);
|
||||
(void)WriteFrameDoubleDataset(
|
||||
file, root + "/nodal/generalized_external_force", nodal_dimensions,
|
||||
external.data(), external.size(), "FE1,FE2,FE3,ME_PHI1,ME_PHI2,ME_PHI3",
|
||||
"force,force,force,force*length,force*length,force*length",
|
||||
"global-cartesian", "nodal", frame);
|
||||
const auto residual = CopyVector(frame.residual);
|
||||
const auto residual = hdf5_internal::CopyVectorValues(frame.residual);
|
||||
(void)WriteFrameDoubleDataset(
|
||||
file, root + "/nodal/generalized_residual", nodal_dimensions,
|
||||
residual.data(), residual.size(), "RU1,RU2,RU3,RPHI1,RPHI2,RPHI3",
|
||||
"force,force,force,force*length,force*length,force*length",
|
||||
"global-cartesian", "nodal", frame);
|
||||
const auto reaction = SpatialReaction(frame);
|
||||
const auto reaction = hdf5_internal::SpatialReactionValues(
|
||||
frame.rotation_vector, frame.reaction, frame.nodal_internal_order.size());
|
||||
(void)WriteFrameDoubleDataset(
|
||||
file, root + "/nodal/reaction", nodal_dimensions, reaction.data(),
|
||||
reaction.size(), "RF1,RF2,RF3,RM1,RM2,RM3",
|
||||
|
||||
@@ -27,11 +27,6 @@ using U = std::vector<std::uint64_t>;
|
||||
using B = std::vector<std::uint8_t>;
|
||||
using D = std::vector<double>;
|
||||
using S = std::vector<std::string>;
|
||||
std::vector<double> Values(const Vector& vector) {
|
||||
D result;
|
||||
for (std::size_t i = 0; i < vector.Size(); ++i) result.push_back(vector[i]);
|
||||
return result;
|
||||
}
|
||||
void Parent(hid_t file, const std::string& path) {
|
||||
const auto parent = path.substr(0, path.find_last_of('/'));
|
||||
if (!parent.empty() && H5Lexists(file, parent.c_str(), H5P_DEFAULT) <= 0)
|
||||
@@ -110,11 +105,11 @@ void FrameData(SolidPlan& plan, const Domain& domain, std::size_t frame,
|
||||
value.doubles = {{dynamic ? "time" : "load_factor", coordinate}};
|
||||
plan.datasets.push_back(std::move(value));
|
||||
};
|
||||
add(Data(root + "/nodal/displacement", {n, 3}, Values(u), "U1,U2,U3",
|
||||
add(Data(root + "/nodal/displacement", {n, 3}, CopyVectorValues(u), "U1,U2,U3",
|
||||
"length", "nodal"));
|
||||
add(Data(root + "/nodal/reaction", {n, 3}, Values(rf), "RF1,RF2,RF3", "force",
|
||||
add(Data(root + "/nodal/reaction", {n, 3}, CopyVectorValues(rf), "RF1,RF2,RF3", "force",
|
||||
"nodal"));
|
||||
add(Data(root + "/nodal/residual", {n, 3}, Values(r), "R1,R2,R3", "force",
|
||||
add(Data(root + "/nodal/residual", {n, 3}, CopyVectorValues(r), "R1,R2,R3", "force",
|
||||
"nodal"));
|
||||
D stress, strain;
|
||||
Check(points.size() == 8 * e, "Solid point count is invalid.");
|
||||
@@ -249,7 +244,7 @@ void Model(SolidPlan& plan, const Domain& domain, const DofManager& dofs) {
|
||||
for (std::size_t i = 0; i < domain.Elements().Size(); ++i) {
|
||||
auto element = factory.Create(domain.Elements()[i], domain);
|
||||
Check(element.HasValue(), "Solid factory reference failed.");
|
||||
const auto* hex = dynamic_cast<const Hex8Element*>(element.Value().get());
|
||||
auto hex = factory.BindSolidTrial(*element.Value());
|
||||
Check(hex != nullptr, "Solid kernel identity failed.");
|
||||
auto response = hex->EvaluateLinear(Vector{24});
|
||||
Check(response.HasValue(), "Solid reference recovery failed.");
|
||||
@@ -426,15 +421,15 @@ Result<SolidPlan> BuildSolidPlan(const Domain& domain, SolidStateView view,
|
||||
plan.datasets.push_back(std::move(data));
|
||||
};
|
||||
add(Data(root + "/nodal/velocity", {domain.Nodes().size(), 3},
|
||||
Values(frame.velocity), "V1,V2,V3", "length/time", "nodal"));
|
||||
CopyVectorValues(frame.velocity), "V1,V2,V3", "length/time", "nodal"));
|
||||
auto a = Data(root + "/nodal/acceleration", {domain.Nodes().size(), 3},
|
||||
Values(frame.acceleration), "A1,A2,A3", "length/time^2",
|
||||
CopyVectorValues(frame.acceleration), "A1,A2,A3", "length/time^2",
|
||||
"nodal");
|
||||
a.strings["acceleration_kind"] = "physical";
|
||||
add(std::move(a));
|
||||
a = Data(root + "/nodal/algorithmic_acceleration",
|
||||
{domain.Nodes().size(), 3},
|
||||
Values(frame.algorithmic_acceleration), "A1,A2,A3",
|
||||
CopyVectorValues(frame.algorithmic_acceleration), "A1,A2,A3",
|
||||
"length/time^2", "nodal");
|
||||
a.strings["acceleration_kind"] = "algorithmic";
|
||||
add(std::move(a));
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/analysis/analysis_model.h"
|
||||
#include "fesa/build_info.h"
|
||||
#include "fesa/fem/dof_manager.h"
|
||||
#include "io/hdf5/hdf5_primitives.h"
|
||||
#include "io/hdf5/hdf5_raii.h"
|
||||
|
||||
@@ -424,8 +426,7 @@ void Frames(hid_t f, const ThermalAnalysisState& s) {
|
||||
Number(g.Get(), "is_initial", H5T_STD_U8LE, H5T_NATIVE_UINT8,
|
||||
static_cast<std::uint8_t>(frame.is_initial));
|
||||
ResultValues(f, root + "/nodal/temperature", {s.NodeOrder().size(), 5},
|
||||
{frame.temperature.Data(),
|
||||
frame.temperature.Data() + frame.temperature.Size()},
|
||||
CopyVectorValues(frame.temperature),
|
||||
"NT11,NT12,NT13,NT14,NT15", "temperature", "scalar",
|
||||
"nodal-thickness-stations");
|
||||
if (frame.is_initial) {
|
||||
@@ -433,9 +434,7 @@ void Frames(hid_t f, const ThermalAnalysisState& s) {
|
||||
Absent(f, root + "/element", "heat_flux");
|
||||
} else {
|
||||
const auto& payload = *frame.completed;
|
||||
std::vector<double> reaction(payload.reaction_heat_rate.Size());
|
||||
for (std::size_t i = 0; i < reaction.size(); ++i)
|
||||
reaction[i] = payload.reaction_heat_rate[i];
|
||||
const auto reaction = CopyVectorValues(payload.reaction_heat_rate);
|
||||
ResultValues(f, root + "/nodal/reaction_heat_rate", {reaction.size()},
|
||||
reaction, "RFL", "power", "scalar",
|
||||
"prescribed-temperature-dofs");
|
||||
|
||||
Reference in New Issue
Block a user