feat(nonlinear-static-newton-raphson): step 11 - equilibrium-projection-and-reference-gate
This commit is contained in:
@@ -3,14 +3,21 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <hdf5.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/elements/finite_rotation.h"
|
||||
#include "fesa/math/vector3.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class TempDirectory {
|
||||
@@ -83,6 +90,53 @@ All, 1, 6
|
||||
)inp";
|
||||
}
|
||||
|
||||
std::string NonzeroPrescribedNonlinearShellDeck() {
|
||||
return R"inp(*Part, name=ShellPart
|
||||
*Node
|
||||
1, 0., 0., 0.
|
||||
2, 1., 0., 0.
|
||||
3, 1., 1., 0.
|
||||
4, 0., 1., 0.
|
||||
*Element, type=S4
|
||||
1, 1, 2, 3, 4
|
||||
*Elset, elset=ShellSet
|
||||
1
|
||||
*Shell Section, elset=ShellSet, material=Steel
|
||||
0.1
|
||||
*End Part
|
||||
*Assembly, name=Assembly
|
||||
*Instance, name=Shell-1, part=ShellPart
|
||||
*End Instance
|
||||
*Nset, nset=N1, instance=Shell-1
|
||||
1
|
||||
*Nset, nset=N2, instance=Shell-1
|
||||
2
|
||||
*Nset, nset=N3, instance=Shell-1
|
||||
3
|
||||
*Nset, nset=N4, instance=Shell-1
|
||||
4
|
||||
*End Assembly
|
||||
*Material, name=Steel
|
||||
*Elastic
|
||||
1000., 0.25
|
||||
*Boundary
|
||||
N1, 1, 6
|
||||
N2, 1, 1, 0.2
|
||||
N2, 2, 3
|
||||
N2, 4, 4, 0.3
|
||||
N2, 5, 6
|
||||
N3, 1, 6
|
||||
N4, 1, 6
|
||||
*Step, name=Step-1, nlgeom=YES
|
||||
*Static
|
||||
0.1, 1., 0.001, 0.1
|
||||
*Cload
|
||||
N2, 3, -3.
|
||||
N2, 4, 2.
|
||||
*End Step
|
||||
)inp";
|
||||
}
|
||||
|
||||
class Hdf5File {
|
||||
public:
|
||||
explicit Hdf5File(const std::filesystem::path& path)
|
||||
@@ -129,6 +183,112 @@ std::string ReadStringAttribute(const hid_t object, const char* name) {
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<double> ReadNodalDataset(const Hdf5File& file,
|
||||
const std::string& path) {
|
||||
const hid_t dataset = H5Dopen2(file.Get(), path.c_str(), H5P_DEFAULT);
|
||||
if (dataset < 0) {
|
||||
throw std::runtime_error{"Unable to open nonlinear nodal dataset."};
|
||||
}
|
||||
std::vector<double> values(24U);
|
||||
const herr_t read = H5Dread(dataset, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL,
|
||||
H5P_DEFAULT, values.data());
|
||||
(void)H5Dclose(dataset);
|
||||
if (read < 0 || !std::all_of(values.begin(), values.end(),
|
||||
[](const double value) {
|
||||
return std::isfinite(value);
|
||||
})) {
|
||||
throw std::runtime_error{"Unable to read nonlinear nodal dataset."};
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> ReadConstraintMask(const Hdf5File& file) {
|
||||
const hid_t dataset =
|
||||
H5Dopen2(file.Get(), "/model/nodal_constraint_mask", H5P_DEFAULT);
|
||||
if (dataset < 0) {
|
||||
throw std::runtime_error{"Unable to open nonlinear constraint mask."};
|
||||
}
|
||||
std::vector<std::uint8_t> values(24U);
|
||||
const herr_t read = H5Dread(dataset, H5T_NATIVE_UINT8, H5S_ALL, H5S_ALL,
|
||||
H5P_DEFAULT, values.data());
|
||||
(void)H5Dclose(dataset);
|
||||
if (read < 0 || !std::all_of(values.begin(), values.end(),
|
||||
[](const std::uint8_t value) {
|
||||
return value <= 1U;
|
||||
})) {
|
||||
throw std::runtime_error{"Unable to read nonlinear constraint mask."};
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
fesa::Vector3 SpatialMoment(const fesa::Vector3& phi,
|
||||
const fesa::Vector3& generalized) {
|
||||
const fesa::Matrix transform = fesa::InverseTransposeLeftJacobianSO3(phi);
|
||||
return {transform(0U, 0U) * generalized.X() +
|
||||
transform(0U, 1U) * generalized.Y() +
|
||||
transform(0U, 2U) * generalized.Z(),
|
||||
transform(1U, 0U) * generalized.X() +
|
||||
transform(1U, 1U) * generalized.Y() +
|
||||
transform(1U, 2U) * generalized.Z(),
|
||||
transform(2U, 0U) * generalized.X() +
|
||||
transform(2U, 1U) * generalized.Y() +
|
||||
transform(2U, 2U) * generalized.Z()};
|
||||
}
|
||||
|
||||
std::array<double, 6> ReconstructGlobalBalance(const Hdf5File& file) {
|
||||
const std::string root = "/steps/Step-1/frames/9/nodal/";
|
||||
const auto displacement = ReadNodalDataset(file, root + "displacement");
|
||||
const auto external =
|
||||
ReadNodalDataset(file, root + "generalized_external_force");
|
||||
const auto residual =
|
||||
ReadNodalDataset(file, root + "generalized_residual");
|
||||
const auto mask = ReadConstraintMask(file);
|
||||
const std::array<fesa::Vector3, 4> initial_positions{
|
||||
fesa::Vector3{0.0, 0.0, 0.0}, fesa::Vector3{1.0, 0.0, 0.0},
|
||||
fesa::Vector3{1.0, 1.0, 0.0}, fesa::Vector3{0.0, 1.0, 0.0}};
|
||||
fesa::Vector3 applied_force_resultant;
|
||||
fesa::Vector3 applied_moment_resultant;
|
||||
fesa::Vector3 reaction_force_resultant;
|
||||
fesa::Vector3 reaction_moment_resultant;
|
||||
for (std::size_t node = 0U; node < initial_positions.size(); ++node) {
|
||||
const std::size_t offset = node * 6U;
|
||||
const fesa::Vector3 current_position =
|
||||
initial_positions[node] +
|
||||
fesa::Vector3{displacement[offset], displacement[offset + 1U],
|
||||
displacement[offset + 2U]};
|
||||
const fesa::Vector3 applied_force{external[offset], external[offset + 1U],
|
||||
external[offset + 2U]};
|
||||
const fesa::Vector3 reaction_force{
|
||||
mask[offset] == 1U ? residual[offset] : 0.0,
|
||||
mask[offset + 1U] == 1U ? residual[offset + 1U] : 0.0,
|
||||
mask[offset + 2U] == 1U ? residual[offset + 2U] : 0.0};
|
||||
const fesa::Vector3 phi{displacement[offset + 3U],
|
||||
displacement[offset + 4U],
|
||||
displacement[offset + 5U]};
|
||||
const fesa::Vector3 applied_moment = SpatialMoment(
|
||||
phi, {external[offset + 3U], external[offset + 4U],
|
||||
external[offset + 5U]});
|
||||
const fesa::Vector3 reaction_moment = SpatialMoment(
|
||||
phi, {mask[offset + 3U] == 1U ? residual[offset + 3U] : 0.0,
|
||||
mask[offset + 4U] == 1U ? residual[offset + 4U] : 0.0,
|
||||
mask[offset + 5U] == 1U ? residual[offset + 5U] : 0.0});
|
||||
applied_force_resultant = applied_force_resultant + applied_force;
|
||||
applied_moment_resultant =
|
||||
applied_moment_resultant + current_position.Cross(applied_force) +
|
||||
applied_moment;
|
||||
reaction_force_resultant = reaction_force_resultant + reaction_force;
|
||||
reaction_moment_resultant =
|
||||
reaction_moment_resultant + current_position.Cross(reaction_force) +
|
||||
reaction_moment;
|
||||
}
|
||||
const fesa::Vector3 force_balance =
|
||||
applied_force_resultant + reaction_force_resultant;
|
||||
const fesa::Vector3 moment_balance =
|
||||
applied_moment_resultant + reaction_moment_resultant;
|
||||
return {force_balance.X(), force_balance.Y(), force_balance.Z(),
|
||||
moment_balance.X(), moment_balance.Y(), moment_balance.Z()};
|
||||
}
|
||||
|
||||
// NSNR-PROC-004, NSNR-PROC-007, and NSNR-PROC-010.
|
||||
TEST(NonlinearStaticProcedure,
|
||||
AllConstrainedNlgeomRunWritesSchemaV1AfterAcceptedFrames) {
|
||||
@@ -154,4 +314,33 @@ TEST(NonlinearStaticProcedure,
|
||||
0);
|
||||
}
|
||||
|
||||
// NSNR-PROC-019.
|
||||
TEST(NonlinearStaticProcedure,
|
||||
NonzeroPrescribedMotionWritesCurrentArmSpatialAppliedPlusReactionBalance) {
|
||||
TempDirectory directory{"current-arm-balance"};
|
||||
const auto input = directory.Path() / "shellnl-current-arm.inp";
|
||||
const auto output = directory.Path() / "shellnl-current-arm_results.h5";
|
||||
WriteText(input, NonzeroPrescribedNonlinearShellDeck());
|
||||
|
||||
testing::internal::CaptureStderr();
|
||||
const int exit_code = fesa::FesaApplication{}.Run({input.string()});
|
||||
const std::string standard_error = testing::internal::GetCapturedStderr();
|
||||
|
||||
ASSERT_EQ(exit_code, 0) << standard_error;
|
||||
const Hdf5File file{output};
|
||||
const hid_t dataset = H5Dopen2(
|
||||
file.Get(), "/steps/Step-1/frames/9/global/equilibrium", H5P_DEFAULT);
|
||||
ASSERT_GE(dataset, 0);
|
||||
std::array<double, 6> actual{};
|
||||
ASSERT_GE(H5Dread(dataset, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL,
|
||||
H5P_DEFAULT, actual.data()),
|
||||
0);
|
||||
(void)H5Dclose(dataset);
|
||||
const auto expected = ReconstructGlobalBalance(file);
|
||||
for (std::size_t component = 0U; component < actual.size(); ++component) {
|
||||
EXPECT_NEAR(actual[component], expected[component], 1.0e-10)
|
||||
<< "component=" << component;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Reference in New Issue
Block a user