feat(shell-surface-pressure-load): step 5 - integrate-nonlinear-follower-pressure
This commit is contained in:
@@ -15,6 +15,7 @@
|
|||||||
#include "fesa/analysis/newton_iteration_log.h"
|
#include "fesa/analysis/newton_iteration_log.h"
|
||||||
#include "fesa/analysis/nonlinear_static_control.h"
|
#include "fesa/analysis/nonlinear_static_control.h"
|
||||||
#include "fesa/assembly/parallel_for.h"
|
#include "fesa/assembly/parallel_for.h"
|
||||||
|
#include "fesa/assembly/surface_pressure_assembler.h"
|
||||||
#include "fesa/constraints/essential_constraint_policy.h"
|
#include "fesa/constraints/essential_constraint_policy.h"
|
||||||
#include "fesa/elements/finite_rotation.h"
|
#include "fesa/elements/finite_rotation.h"
|
||||||
#include "fesa/elements/mitc4_shell.h"
|
#include "fesa/elements/mitc4_shell.h"
|
||||||
@@ -453,6 +454,76 @@ Result<SparseMatrix> Difference(const SparseMatrix& internal,
|
|||||||
dofs.GetSparsePattern());
|
dofs.GetSparsePattern());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief Adds fixed and follower external candidates in stable CSR order.
|
||||||
|
Result<GlobalFixedLoadContribution> CombineExternalContributions(
|
||||||
|
const GlobalFixedLoadContribution& fixed,
|
||||||
|
const SurfacePressureAssemblyCandidate& pressure, const DofManager& dofs) {
|
||||||
|
if (fixed.generalized_external_force.Size() !=
|
||||||
|
pressure.full_external_force.Size() ||
|
||||||
|
fixed.generalized_external_tangent.Rows() !=
|
||||||
|
pressure.full_external_tangent.Rows() ||
|
||||||
|
fixed.generalized_external_tangent.Columns() !=
|
||||||
|
pressure.full_external_tangent.Columns() ||
|
||||||
|
fixed.generalized_external_tangent.RowOffsets() !=
|
||||||
|
pressure.full_external_tangent.RowOffsets() ||
|
||||||
|
fixed.generalized_external_tangent.ColumnIndices() !=
|
||||||
|
pressure.full_external_tangent.ColumnIndices()) {
|
||||||
|
return Result<GlobalFixedLoadContribution>::Failure(ProcedureFailure(
|
||||||
|
FailureCategory::kSolver, "nonfinite-newton-data", {},
|
||||||
|
"NONLINEAR_STATIC_PROCEDURE", {},
|
||||||
|
"Nonlinear fixed and pressure external candidates must share the "
|
||||||
|
"stable full-space dimension and pattern."));
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector force{fixed.generalized_external_force.Size()};
|
||||||
|
for (std::size_t index = 0U; index < force.Size(); ++index) {
|
||||||
|
force[index] = fixed.generalized_external_force[index] +
|
||||||
|
pressure.full_external_force[index];
|
||||||
|
if (!std::isfinite(force[index])) {
|
||||||
|
return Result<GlobalFixedLoadContribution>::Failure(ProcedureFailure(
|
||||||
|
FailureCategory::kSolver, "nonfinite-newton-data", {},
|
||||||
|
"NONLINEAR_STATIC_PROCEDURE", std::to_string(index),
|
||||||
|
"Nonlinear fixed and pressure force composition is nonfinite."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<CooContribution> contributions;
|
||||||
|
contributions.reserve(fixed.generalized_external_tangent.Values().size());
|
||||||
|
for (std::size_t row = 0U; row < fixed.generalized_external_tangent.Rows();
|
||||||
|
++row) {
|
||||||
|
for (std::size_t offset =
|
||||||
|
fixed.generalized_external_tangent.RowOffsets()[row];
|
||||||
|
offset < fixed.generalized_external_tangent.RowOffsets()[row + 1U];
|
||||||
|
++offset) {
|
||||||
|
const double tangent =
|
||||||
|
fixed.generalized_external_tangent.Values()[offset] +
|
||||||
|
pressure.full_external_tangent.Values()[offset];
|
||||||
|
if (!std::isfinite(tangent)) {
|
||||||
|
return Result<GlobalFixedLoadContribution>::Failure(ProcedureFailure(
|
||||||
|
FailureCategory::kSolver, "nonfinite-newton-data", {},
|
||||||
|
"NONLINEAR_STATIC_PROCEDURE", std::to_string(row),
|
||||||
|
"Nonlinear fixed and pressure tangent composition is nonfinite."));
|
||||||
|
}
|
||||||
|
contributions.push_back(
|
||||||
|
{row, fixed.generalized_external_tangent.ColumnIndices()[offset],
|
||||||
|
tangent, row, offset});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Result<SparseMatrix> tangent =
|
||||||
|
SparseMatrix::FromCoo(fixed.generalized_external_tangent.Rows(),
|
||||||
|
fixed.generalized_external_tangent.Columns(),
|
||||||
|
std::move(contributions), dofs.GetSparsePattern());
|
||||||
|
if (!tangent.HasValue()) {
|
||||||
|
return Result<GlobalFixedLoadContribution>::Failure(tangent.GetStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Diagnostic> diagnostics = fixed.diagnostics;
|
||||||
|
diagnostics.insert(diagnostics.end(), pressure.diagnostics.begin(),
|
||||||
|
pressure.diagnostics.end());
|
||||||
|
return Result<GlobalFixedLoadContribution>::Success(
|
||||||
|
{std::move(force), std::move(tangent.Value()), std::move(diagnostics)});
|
||||||
|
}
|
||||||
|
|
||||||
Vector SelectFreeComponents(const Vector& full, const DofManager& dofs,
|
Vector SelectFreeComponents(const Vector& full, const DofManager& dofs,
|
||||||
const bool rotations) {
|
const bool rotations) {
|
||||||
std::size_t count = 0U;
|
std::size_t count = 0U;
|
||||||
@@ -670,6 +741,11 @@ NonlinearStaticProcedureExecutor::Execute(
|
|||||||
if (!dofs.HasValue()) {
|
if (!dofs.HasValue()) {
|
||||||
return Result<NonlinearStaticExecutionResult>::Failure(dofs.GetStatus());
|
return Result<NonlinearStaticExecutionResult>::Failure(dofs.GetStatus());
|
||||||
}
|
}
|
||||||
|
Result<AnalysisModel> analysis_model = AnalysisModel::Create(domain);
|
||||||
|
if (!analysis_model.HasValue()) {
|
||||||
|
return Result<NonlinearStaticExecutionResult>::Failure(
|
||||||
|
analysis_model.GetStatus());
|
||||||
|
}
|
||||||
Result<NonlinearStateBaselineInput> baseline = BuildBaseline(domain);
|
Result<NonlinearStateBaselineInput> baseline = BuildBaseline(domain);
|
||||||
if (!baseline.HasValue()) {
|
if (!baseline.HasValue()) {
|
||||||
return Result<NonlinearStaticExecutionResult>::Failure(
|
return Result<NonlinearStaticExecutionResult>::Failure(
|
||||||
@@ -732,14 +808,32 @@ NonlinearStaticProcedureExecutor::Execute(
|
|||||||
}
|
}
|
||||||
record.last_completed_stage = NewtonIterationStage::kInternalAssembled;
|
record.last_completed_stage = NewtonIterationStage::kInternalAssembled;
|
||||||
|
|
||||||
Result<GlobalFixedLoadContribution> external =
|
Result<GlobalFixedLoadContribution> fixed =
|
||||||
AssembleGlobalFixedLoadContribution(
|
AssembleGlobalFixedLoadContribution(
|
||||||
domain, dofs.Value(), trial.Value().generalized_coordinates,
|
domain, dofs.Value(), trial.Value().generalized_coordinates,
|
||||||
increment.load_factor);
|
increment.load_factor);
|
||||||
|
if (!fixed.HasValue()) {
|
||||||
|
return Result<NonlinearStaticExecutionResult>::Failure(
|
||||||
|
RecordFailure(log, &record, fixed.GetStatus(),
|
||||||
|
NewtonIterationStage::kExternalAssembled));
|
||||||
|
}
|
||||||
|
Result<SurfacePressureAssemblyCandidate> pressure =
|
||||||
|
AssembleSurfacePressure(
|
||||||
|
analysis_model.Value(), dofs.Value(),
|
||||||
|
trial.Value().generalized_coordinates, increment.load_factor,
|
||||||
|
SurfacePressureConfiguration::kCurrent, parallel_for_);
|
||||||
|
if (!pressure.HasValue()) {
|
||||||
|
return Result<NonlinearStaticExecutionResult>::Failure(
|
||||||
|
RecordFailure(log, &record, pressure.GetStatus(),
|
||||||
|
NewtonIterationStage::kExternalAssembled));
|
||||||
|
}
|
||||||
|
Result<GlobalFixedLoadContribution> external =
|
||||||
|
CombineExternalContributions(fixed.Value(), pressure.Value(),
|
||||||
|
dofs.Value());
|
||||||
if (!external.HasValue()) {
|
if (!external.HasValue()) {
|
||||||
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
|
return Result<NonlinearStaticExecutionResult>::Failure(
|
||||||
log, &record, external.GetStatus(),
|
RecordFailure(log, &record, external.GetStatus(),
|
||||||
NewtonIterationStage::kExternalAssembled));
|
NewtonIterationStage::kExternalAssembled));
|
||||||
}
|
}
|
||||||
record.last_completed_stage = NewtonIterationStage::kExternalAssembled;
|
record.last_completed_stage = NewtonIterationStage::kExternalAssembled;
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ add_executable(
|
|||||||
fesa_integration_tests
|
fesa_integration_tests
|
||||||
integration/analysis/linear_static_analysis_test.cpp
|
integration/analysis/linear_static_analysis_test.cpp
|
||||||
integration/analysis/shell_surface_pressure_linear_test.cpp
|
integration/analysis/shell_surface_pressure_linear_test.cpp
|
||||||
|
integration/analysis/shell_surface_pressure_nonlinear_test.cpp
|
||||||
integration/analysis/nonlinear_static_procedure_test.cpp
|
integration/analysis/nonlinear_static_procedure_test.cpp
|
||||||
integration/app/fesa_application_test.cpp
|
integration/app/fesa_application_test.cpp
|
||||||
integration/app/shell_surface_pressure_application_test.cpp
|
integration/app/shell_surface_pressure_application_test.cpp
|
||||||
|
|||||||
@@ -0,0 +1,430 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <hdf5.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iterator>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "fesa/app/fesa_application.h"
|
||||||
|
#include "fesa/loads/shell_surface_pressure.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class TempDirectory {
|
||||||
|
public:
|
||||||
|
explicit TempDirectory(const std::string& label) {
|
||||||
|
static std::atomic<unsigned long long> sequence{0U};
|
||||||
|
const auto tick =
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch().count();
|
||||||
|
path_ =
|
||||||
|
std::filesystem::temp_directory_path() /
|
||||||
|
("fesa-surface-pressure-nonlinear-" + label + "-" +
|
||||||
|
std::to_string(tick) + "-" + std::to_string(sequence.fetch_add(1U)));
|
||||||
|
std::error_code error;
|
||||||
|
if (!std::filesystem::create_directory(path_, error) || error) {
|
||||||
|
throw std::runtime_error{"Unable to create nonlinear pressure fixture."};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TempDirectory(const TempDirectory&) = delete;
|
||||||
|
TempDirectory& operator=(const TempDirectory&) = delete;
|
||||||
|
|
||||||
|
~TempDirectory() {
|
||||||
|
std::error_code ignored;
|
||||||
|
std::filesystem::remove_all(path_, ignored);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::filesystem::path& Path() const noexcept { return path_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::filesystem::path path_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Hdf5File {
|
||||||
|
public:
|
||||||
|
explicit Hdf5File(const std::filesystem::path& path)
|
||||||
|
: handle_{H5Fopen(path.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT)} {
|
||||||
|
if (handle_ < 0) {
|
||||||
|
throw std::runtime_error{"Unable to open nonlinear pressure results."};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Hdf5File(const Hdf5File&) = delete;
|
||||||
|
Hdf5File& operator=(const Hdf5File&) = delete;
|
||||||
|
|
||||||
|
~Hdf5File() { (void)H5Fclose(handle_); }
|
||||||
|
|
||||||
|
hid_t Get() const noexcept { return handle_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
hid_t handle_;
|
||||||
|
};
|
||||||
|
|
||||||
|
void WriteText(const std::filesystem::path& path, const std::string& text) {
|
||||||
|
std::ofstream stream{path, std::ios::binary | std::ios::trunc};
|
||||||
|
stream.write(text.data(), static_cast<std::streamsize>(text.size()));
|
||||||
|
if (!stream) {
|
||||||
|
throw std::runtime_error{"Unable to write nonlinear pressure input."};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<char> ReadBytes(const std::filesystem::path& path) {
|
||||||
|
std::ifstream stream{path, std::ios::binary};
|
||||||
|
return {std::istreambuf_iterator<char>{stream},
|
||||||
|
std::istreambuf_iterator<char>{}};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string NonlinearPressureDeck(const bool include_pressure,
|
||||||
|
const bool collapsed) {
|
||||||
|
const std::string surface = include_pressure
|
||||||
|
? "*Surface, TYPE=ELEMENT, NAME=Pressure\n"
|
||||||
|
"Shell-1.1, SPOS\n"
|
||||||
|
: "";
|
||||||
|
const std::string dsload =
|
||||||
|
include_pressure ? "*Dsload\nPressure, P, 4.0\n" : "";
|
||||||
|
const std::string node_three =
|
||||||
|
collapsed ? "3, 1., 0., 0.\n" : "3, 1., 1., 0.\n";
|
||||||
|
const std::string constraints =
|
||||||
|
"N1, 1, 6\n"
|
||||||
|
"N2, 1, 6\n"
|
||||||
|
"N3, 2, 6\n"
|
||||||
|
"N3, 1, 1, 1.0\n"
|
||||||
|
"N4, 1, 6\n";
|
||||||
|
return std::string{R"inp(*Part, name=ShellPart
|
||||||
|
*Node
|
||||||
|
1, 0., 0., 0.
|
||||||
|
2, 1., 0., 0.
|
||||||
|
)inp"} + node_three +
|
||||||
|
R"inp(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
|
||||||
|
)inp" + surface +
|
||||||
|
R"inp(*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
|
||||||
|
)inp" + constraints +
|
||||||
|
R"inp(*Step, name=Step-1, nlgeom=YES
|
||||||
|
*Static
|
||||||
|
0.1, 1., 0.001, 0.1
|
||||||
|
)inp" + dsload +
|
||||||
|
"*End Step\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<double> ReadFrameDataset(const Hdf5File& file,
|
||||||
|
const std::uint32_t frame_index,
|
||||||
|
const std::string& suffix) {
|
||||||
|
const std::string path =
|
||||||
|
"/steps/Step-1/frames/" + std::to_string(frame_index) + suffix;
|
||||||
|
const hid_t dataset = H5Dopen2(file.Get(), path.c_str(), H5P_DEFAULT);
|
||||||
|
if (dataset < 0) {
|
||||||
|
throw std::runtime_error{"Unable to open nonlinear pressure 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 pressure dataset."};
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<double, 6> ReadEquilibrium(const Hdf5File& file,
|
||||||
|
const std::uint32_t frame_index) {
|
||||||
|
const std::string path = "/steps/Step-1/frames/" +
|
||||||
|
std::to_string(frame_index) + "/global/equilibrium";
|
||||||
|
const hid_t dataset = H5Dopen2(file.Get(), path.c_str(), H5P_DEFAULT);
|
||||||
|
if (dataset < 0) {
|
||||||
|
throw std::runtime_error{"Unable to open nonlinear equilibrium."};
|
||||||
|
}
|
||||||
|
std::array<double, 6> values{};
|
||||||
|
const herr_t read = H5Dread(dataset, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL,
|
||||||
|
H5P_DEFAULT, values.data());
|
||||||
|
(void)H5Dclose(dataset);
|
||||||
|
if (read < 0) {
|
||||||
|
throw std::runtime_error{"Unable to read nonlinear equilibrium."};
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
double SumTranslation(const std::vector<double>& values,
|
||||||
|
const std::size_t component) {
|
||||||
|
double result = 0.0;
|
||||||
|
for (std::size_t node = 0U; node < 4U; ++node) {
|
||||||
|
result += values[node * 6U + component];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
double MaximumAbs(const std::array<double, 6>& values) {
|
||||||
|
double result = 0.0;
|
||||||
|
for (const double value : values) {
|
||||||
|
result = (std::max)(result, std::abs(value));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
fesa::ShellSurfacePressureInput CurrentPressureInput(
|
||||||
|
const double node_three_x_displacement) {
|
||||||
|
return {
|
||||||
|
{{{0.0, 0.0, 0.0}, {1.0, 0.0, 0.0}, {1.0, 1.0, 0.0}, {0.0, 1.0, 0.0}}},
|
||||||
|
{{{0.0, 0.0, 0.0},
|
||||||
|
{0.0, 0.0, 0.0},
|
||||||
|
{node_three_x_displacement, 0.0, 0.0},
|
||||||
|
{0.0, 0.0, 0.0}}},
|
||||||
|
fesa::ShellSurfaceSide::kSpos,
|
||||||
|
4.0,
|
||||||
|
1.0,
|
||||||
|
fesa::SurfacePressureConfiguration::kCurrent,
|
||||||
|
{},
|
||||||
|
"unsymmetric"};
|
||||||
|
}
|
||||||
|
|
||||||
|
const Hdf5File RunNonlinearPressure(const std::filesystem::path& input,
|
||||||
|
const std::filesystem::path& output) {
|
||||||
|
testing::internal::CaptureStderr();
|
||||||
|
const int exit_code = fesa::FesaApplication{}.Run({input.string()});
|
||||||
|
const std::string standard_error = testing::internal::GetCapturedStderr();
|
||||||
|
EXPECT_EQ(exit_code, 0) << standard_error;
|
||||||
|
EXPECT_TRUE(std::filesystem::exists(output));
|
||||||
|
EXPECT_GT(H5Fis_hdf5(output.string().c_str()), 0);
|
||||||
|
return Hdf5File{output};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(NonlinearShellSurfacePressure,
|
||||||
|
AssemblesFollowerForceFromCurrentTrialGeometry) {
|
||||||
|
TempDirectory directory{"current-force"};
|
||||||
|
const auto input = directory.Path() / "current-force.inp";
|
||||||
|
const auto output = directory.Path() / "current-force_results.h5";
|
||||||
|
WriteText(input, NonlinearPressureDeck(true, false));
|
||||||
|
|
||||||
|
const Hdf5File file = RunNonlinearPressure(input, output);
|
||||||
|
const auto external =
|
||||||
|
ReadFrameDataset(file, 9U, "/nodal/generalized_external_force");
|
||||||
|
|
||||||
|
EXPECT_NEAR(SumTranslation(external, 2U), -6.0, 1.0e-12);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NonlinearShellSurfacePressure,
|
||||||
|
FormsResidualAndTangentWithApprovedExternalSigns) {
|
||||||
|
TempDirectory directory{"external-sign"};
|
||||||
|
const auto input = directory.Path() / "external-sign.inp";
|
||||||
|
const auto output = directory.Path() / "external-sign_results.h5";
|
||||||
|
WriteText(input, NonlinearPressureDeck(true, false));
|
||||||
|
|
||||||
|
const Hdf5File file = RunNonlinearPressure(input, output);
|
||||||
|
const auto external =
|
||||||
|
ReadFrameDataset(file, 9U, "/nodal/generalized_external_force");
|
||||||
|
const auto residual =
|
||||||
|
ReadFrameDataset(file, 9U, "/nodal/generalized_residual");
|
||||||
|
|
||||||
|
EXPECT_NEAR(SumTranslation(external, 2U), -6.0, 1.0e-12);
|
||||||
|
EXPECT_NEAR(SumTranslation(residual, 2U), 6.0, 1.0e-12);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NonlinearShellSurfacePressure, AppliesIncrementFactorExactlyOnce) {
|
||||||
|
TempDirectory directory{"factor-once"};
|
||||||
|
const auto input = directory.Path() / "factor-once.inp";
|
||||||
|
const auto output = directory.Path() / "factor-once_results.h5";
|
||||||
|
WriteText(input, NonlinearPressureDeck(true, false));
|
||||||
|
|
||||||
|
const Hdf5File file = RunNonlinearPressure(input, output);
|
||||||
|
const auto external =
|
||||||
|
ReadFrameDataset(file, 0U, "/nodal/generalized_external_force");
|
||||||
|
|
||||||
|
EXPECT_NEAR(SumTranslation(external, 2U), -0.42, 1.0e-12);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NonlinearShellSurfacePressure, UsesFullUnsymmetricTangentSolverPath) {
|
||||||
|
TempDirectory directory{"unsymmetric"};
|
||||||
|
const auto input = directory.Path() / "unsymmetric.inp";
|
||||||
|
const auto output = directory.Path() / "unsymmetric_results.h5";
|
||||||
|
WriteText(input, NonlinearPressureDeck(true, false));
|
||||||
|
|
||||||
|
const Hdf5File file = RunNonlinearPressure(input, output);
|
||||||
|
const auto external =
|
||||||
|
ReadFrameDataset(file, 9U, "/nodal/generalized_external_force");
|
||||||
|
const auto residual =
|
||||||
|
ReadFrameDataset(file, 9U, "/nodal/generalized_residual");
|
||||||
|
|
||||||
|
EXPECT_NE(SumTranslation(external, 2U), 0.0);
|
||||||
|
EXPECT_NE(SumTranslation(residual, 2U), 0.0);
|
||||||
|
|
||||||
|
const auto central =
|
||||||
|
fesa::EvaluateShellSurfacePressure(CurrentPressureInput(1.0));
|
||||||
|
const auto forward =
|
||||||
|
fesa::EvaluateShellSurfacePressure(CurrentPressureInput(1.0 + 1.0e-6));
|
||||||
|
const auto backward =
|
||||||
|
fesa::EvaluateShellSurfacePressure(CurrentPressureInput(1.0 - 1.0e-6));
|
||||||
|
ASSERT_TRUE(central.HasValue());
|
||||||
|
ASSERT_TRUE(forward.HasValue());
|
||||||
|
ASSERT_TRUE(backward.HasValue());
|
||||||
|
|
||||||
|
bool has_antisymmetric_entry = false;
|
||||||
|
for (std::size_t row = 0U; row < 24U; ++row) {
|
||||||
|
const double finite_difference =
|
||||||
|
(forward.Value().force[row] - backward.Value().force[row]) / 2.0e-6;
|
||||||
|
EXPECT_NEAR(finite_difference,
|
||||||
|
central.Value().external_tangent[row * 24U + 12U], 1.0e-8)
|
||||||
|
<< "row=" << row;
|
||||||
|
for (std::size_t column = row + 1U; column < 24U; ++column) {
|
||||||
|
has_antisymmetric_entry =
|
||||||
|
has_antisymmetric_entry ||
|
||||||
|
std::abs(central.Value().external_tangent[row * 24U + column] -
|
||||||
|
central.Value().external_tangent[column * 24U + row]) >
|
||||||
|
1.0e-12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EXPECT_TRUE(has_antisymmetric_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NonlinearShellSurfacePressure,
|
||||||
|
ConvergesOpenPatchAndPreservesAntisymmetricEvidence) {
|
||||||
|
TempDirectory directory{"open-patch"};
|
||||||
|
const auto input = directory.Path() / "open-patch.inp";
|
||||||
|
const auto output = directory.Path() / "open-patch_results.h5";
|
||||||
|
WriteText(input, NonlinearPressureDeck(true, false));
|
||||||
|
|
||||||
|
const Hdf5File file = RunNonlinearPressure(input, output);
|
||||||
|
const auto external =
|
||||||
|
ReadFrameDataset(file, 9U, "/nodal/generalized_external_force");
|
||||||
|
|
||||||
|
EXPECT_NEAR(SumTranslation(external, 2U), -6.0, 1.0e-12);
|
||||||
|
EXPECT_GT(H5Lexists(file.Get(), "/steps/Step-1/accepted_frames", H5P_DEFAULT),
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NonlinearShellSurfacePressure,
|
||||||
|
SatisfiesAcceptedFreeResidualAndFactorOneGlobalBalance) {
|
||||||
|
TempDirectory directory{"balance"};
|
||||||
|
const auto input = directory.Path() / "balance.inp";
|
||||||
|
const auto output = directory.Path() / "balance_results.h5";
|
||||||
|
WriteText(input, NonlinearPressureDeck(true, false));
|
||||||
|
|
||||||
|
const Hdf5File file = RunNonlinearPressure(input, output);
|
||||||
|
const auto equilibrium = ReadEquilibrium(file, 9U);
|
||||||
|
|
||||||
|
EXPECT_LE(MaximumAbs(equilibrium), 1.0e-8);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NonlinearShellSurfacePressure,
|
||||||
|
IsObjectiveUnderRigidMotionAndTracksCurrentArea) {
|
||||||
|
fesa::ShellSurfacePressureInput input{
|
||||||
|
{{{0.0, 0.0, 0.0}, {1.0, 0.0, 0.0}, {1.0, 1.0, 0.0}, {0.0, 1.0, 0.0}}},
|
||||||
|
{{{0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, {0.0, -1.0, 1.0}, {0.0, -1.0, 1.0}}},
|
||||||
|
fesa::ShellSurfaceSide::kSpos,
|
||||||
|
4.0,
|
||||||
|
1.0,
|
||||||
|
fesa::SurfacePressureConfiguration::kCurrent,
|
||||||
|
{},
|
||||||
|
"objectivity"};
|
||||||
|
|
||||||
|
const auto contribution = fesa::EvaluateShellSurfacePressure(input);
|
||||||
|
|
||||||
|
ASSERT_TRUE(contribution.HasValue());
|
||||||
|
double force_x = 0.0;
|
||||||
|
double force_y = 0.0;
|
||||||
|
double force_z = 0.0;
|
||||||
|
for (std::size_t node = 0U; node < 4U; ++node) {
|
||||||
|
force_x += contribution.Value().force[node * 6U];
|
||||||
|
force_y += contribution.Value().force[node * 6U + 1U];
|
||||||
|
force_z += contribution.Value().force[node * 6U + 2U];
|
||||||
|
}
|
||||||
|
EXPECT_NEAR(force_x, 0.0, 1.0e-12);
|
||||||
|
EXPECT_NEAR(force_y, 4.0, 1.0e-12);
|
||||||
|
EXPECT_NEAR(force_z, 0.0, 1.0e-12);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NonlinearShellSurfacePressure,
|
||||||
|
ReducesSerialAndParallelContributionsDeterministically) {
|
||||||
|
TempDirectory directory{"deterministic"};
|
||||||
|
const auto first_input = directory.Path() / "first.inp";
|
||||||
|
const auto first_output = directory.Path() / "first_results.h5";
|
||||||
|
const auto second_input = directory.Path() / "second.inp";
|
||||||
|
const auto second_output = directory.Path() / "second_results.h5";
|
||||||
|
const std::string deck = NonlinearPressureDeck(true, false);
|
||||||
|
WriteText(first_input, deck);
|
||||||
|
WriteText(second_input, deck);
|
||||||
|
|
||||||
|
const Hdf5File first = RunNonlinearPressure(first_input, first_output);
|
||||||
|
const Hdf5File second = RunNonlinearPressure(second_input, second_output);
|
||||||
|
const auto first_force =
|
||||||
|
ReadFrameDataset(first, 9U, "/nodal/generalized_external_force");
|
||||||
|
const auto second_force =
|
||||||
|
ReadFrameDataset(second, 9U, "/nodal/generalized_external_force");
|
||||||
|
|
||||||
|
EXPECT_EQ(first_force, second_force);
|
||||||
|
EXPECT_NE(SumTranslation(first_force, 2U), 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NonlinearShellSurfacePressure,
|
||||||
|
InvalidTrialRollsBackCommittedFrameAndFinalOutput) {
|
||||||
|
TempDirectory directory{"rollback"};
|
||||||
|
const auto input = directory.Path() / "rollback.inp";
|
||||||
|
const auto output = directory.Path() / "rollback_results.h5";
|
||||||
|
WriteText(input, NonlinearPressureDeck(true, true));
|
||||||
|
const std::vector<char> previous{'p', 'r', 'i', 'o', 'r'};
|
||||||
|
{
|
||||||
|
std::ofstream stream{output, std::ios::binary | std::ios::trunc};
|
||||||
|
stream.write(previous.data(),
|
||||||
|
static_cast<std::streamsize>(previous.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
const int exit_code = fesa::FesaApplication{}.Run({input.string()});
|
||||||
|
|
||||||
|
EXPECT_NE(exit_code, 0);
|
||||||
|
EXPECT_EQ(ReadBytes(output), previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NonlinearShellSurfacePressure,
|
||||||
|
WritesSchemaOneExternalInternalResidualReactionAndEquilibrium) {
|
||||||
|
TempDirectory directory{"schema"};
|
||||||
|
const auto input = directory.Path() / "schema.inp";
|
||||||
|
const auto output = directory.Path() / "schema_results.h5";
|
||||||
|
WriteText(input, NonlinearPressureDeck(true, false));
|
||||||
|
|
||||||
|
const Hdf5File file = RunNonlinearPressure(input, output);
|
||||||
|
const std::string root = "/steps/Step-1/frames/9";
|
||||||
|
for (const std::string& suffix :
|
||||||
|
{"/nodal/generalized_internal_force",
|
||||||
|
"/nodal/generalized_external_force", "/nodal/generalized_residual",
|
||||||
|
"/nodal/reaction", "/global/equilibrium"}) {
|
||||||
|
EXPECT_GT(H5Lexists(file.Get(), (root + suffix).c_str(), H5P_DEFAULT), 0)
|
||||||
|
<< suffix;
|
||||||
|
}
|
||||||
|
const auto external =
|
||||||
|
ReadFrameDataset(file, 9U, "/nodal/generalized_external_force");
|
||||||
|
EXPECT_NEAR(SumTranslation(external, 2U), -6.0, 1.0e-12);
|
||||||
|
EXPECT_EQ(H5Lexists(file.Get(), "/surface_pressure", H5P_DEFAULT), 0);
|
||||||
|
}
|
||||||
@@ -101,6 +101,38 @@ All, 1, 6
|
|||||||
dsload + "*End Step\n";
|
dsload + "*End Step\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string NonlinearShellDeck() {
|
||||||
|
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=All, instance=Shell-1
|
||||||
|
1, 2, 3, 4
|
||||||
|
*End Assembly
|
||||||
|
*Material, name=Steel
|
||||||
|
*Elastic
|
||||||
|
1000., 0.25
|
||||||
|
*Boundary
|
||||||
|
All, 1, 6
|
||||||
|
*Step, name=Step-1, nlgeom=YES
|
||||||
|
*Static
|
||||||
|
0.1, 1., 0.001, 0.1
|
||||||
|
*End Step
|
||||||
|
)inp";
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST(ShellSurfacePressureApplication,
|
TEST(ShellSurfacePressureApplication,
|
||||||
@@ -143,3 +175,21 @@ TEST(ShellSurfacePressureApplication,
|
|||||||
0);
|
0);
|
||||||
EXPECT_GE(H5Fclose(file), 0);
|
EXPECT_GE(H5Fclose(file), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(ShellSurfacePressureApplication,
|
||||||
|
InputWithoutPressureRetainsExistingNonlinearBehavior) {
|
||||||
|
TempDirectory directory{"nonlinear-no-pressure"};
|
||||||
|
const auto input = directory.Path() / "nonlinear-no-pressure.inp";
|
||||||
|
const auto output = directory.Path() / "nonlinear-no-pressure_results.h5";
|
||||||
|
WriteText(input, NonlinearShellDeck());
|
||||||
|
|
||||||
|
EXPECT_EQ(fesa::FesaApplication{}.Run({input.string()}), 0);
|
||||||
|
ASSERT_TRUE(std::filesystem::exists(output));
|
||||||
|
ASSERT_GT(H5Fis_hdf5(output.string().c_str()), 0);
|
||||||
|
const hid_t file =
|
||||||
|
H5Fopen(output.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT);
|
||||||
|
ASSERT_GE(file, 0);
|
||||||
|
EXPECT_GT(H5Lexists(file, "/steps/Step-1/accepted_frames", H5P_DEFAULT), 0);
|
||||||
|
EXPECT_EQ(H5Lexists(file, "/surface_pressure", H5P_DEFAULT), 0);
|
||||||
|
EXPECT_GE(H5Fclose(file), 0);
|
||||||
|
}
|
||||||
|
|||||||
@@ -478,6 +478,7 @@ TEST(NonlinearHdf5ResultsWriter,
|
|||||||
EXPECT_EQ(H5Lexists(file.Get(), "/steps/Step-1/frames/2", H5P_DEFAULT), 0);
|
EXPECT_EQ(H5Lexists(file.Get(), "/steps/Step-1/frames/2", H5P_DEFAULT), 0);
|
||||||
EXPECT_EQ(H5Lexists(file.Get(), "/steps/Step-1/trial_history", H5P_DEFAULT),
|
EXPECT_EQ(H5Lexists(file.Get(), "/steps/Step-1/trial_history", H5P_DEFAULT),
|
||||||
0);
|
0);
|
||||||
|
EXPECT_EQ(H5Lexists(file.Get(), "/surface_pressure", H5P_DEFAULT), 0);
|
||||||
|
|
||||||
const std::string root = "/steps/Step-1/frames/1";
|
const std::string root = "/steps/Step-1/frames/1";
|
||||||
for (const std::string& suffix :
|
for (const std::string& suffix :
|
||||||
|
|||||||
Reference in New Issue
Block a user