From 9b19cc1b1887673f37a13cf6db8426dd5f51a468 Mon Sep 17 00:00:00 2001 From: "KOKO\\Mimi" Date: Tue, 1 Sep 2026 15:48:52 +0900 Subject: [PATCH] feat(shell-surface-pressure-load): step 5 - integrate-nonlinear-follower-pressure --- .../analysis/nonlinear_static_procedure.cpp | 102 ++++- tests/CMakeLists.txt | 1 + .../shell_surface_pressure_nonlinear_test.cpp | 430 ++++++++++++++++++ ...hell_surface_pressure_application_test.cpp | 50 ++ .../nonlinear_hdf5_results_writer_test.cpp | 1 + 5 files changed, 580 insertions(+), 4 deletions(-) create mode 100644 tests/integration/analysis/shell_surface_pressure_nonlinear_test.cpp diff --git a/src/fesa/analysis/nonlinear_static_procedure.cpp b/src/fesa/analysis/nonlinear_static_procedure.cpp index 7663655..c503758 100644 --- a/src/fesa/analysis/nonlinear_static_procedure.cpp +++ b/src/fesa/analysis/nonlinear_static_procedure.cpp @@ -15,6 +15,7 @@ #include "fesa/analysis/newton_iteration_log.h" #include "fesa/analysis/nonlinear_static_control.h" #include "fesa/assembly/parallel_for.h" +#include "fesa/assembly/surface_pressure_assembler.h" #include "fesa/constraints/essential_constraint_policy.h" #include "fesa/elements/finite_rotation.h" #include "fesa/elements/mitc4_shell.h" @@ -453,6 +454,76 @@ Result Difference(const SparseMatrix& internal, dofs.GetSparsePattern()); } +/// @brief Adds fixed and follower external candidates in stable CSR order. +Result 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::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::Failure(ProcedureFailure( + FailureCategory::kSolver, "nonfinite-newton-data", {}, + "NONLINEAR_STATIC_PROCEDURE", std::to_string(index), + "Nonlinear fixed and pressure force composition is nonfinite.")); + } + } + + std::vector 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::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 tangent = + SparseMatrix::FromCoo(fixed.generalized_external_tangent.Rows(), + fixed.generalized_external_tangent.Columns(), + std::move(contributions), dofs.GetSparsePattern()); + if (!tangent.HasValue()) { + return Result::Failure(tangent.GetStatus()); + } + + std::vector diagnostics = fixed.diagnostics; + diagnostics.insert(diagnostics.end(), pressure.diagnostics.begin(), + pressure.diagnostics.end()); + return Result::Success( + {std::move(force), std::move(tangent.Value()), std::move(diagnostics)}); +} + Vector SelectFreeComponents(const Vector& full, const DofManager& dofs, const bool rotations) { std::size_t count = 0U; @@ -670,6 +741,11 @@ NonlinearStaticProcedureExecutor::Execute( if (!dofs.HasValue()) { return Result::Failure(dofs.GetStatus()); } + Result analysis_model = AnalysisModel::Create(domain); + if (!analysis_model.HasValue()) { + return Result::Failure( + analysis_model.GetStatus()); + } Result baseline = BuildBaseline(domain); if (!baseline.HasValue()) { return Result::Failure( @@ -732,14 +808,32 @@ NonlinearStaticProcedureExecutor::Execute( } record.last_completed_stage = NewtonIterationStage::kInternalAssembled; - Result external = + Result fixed = AssembleGlobalFixedLoadContribution( domain, dofs.Value(), trial.Value().generalized_coordinates, increment.load_factor); + if (!fixed.HasValue()) { + return Result::Failure( + RecordFailure(log, &record, fixed.GetStatus(), + NewtonIterationStage::kExternalAssembled)); + } + Result pressure = + AssembleSurfacePressure( + analysis_model.Value(), dofs.Value(), + trial.Value().generalized_coordinates, increment.load_factor, + SurfacePressureConfiguration::kCurrent, parallel_for_); + if (!pressure.HasValue()) { + return Result::Failure( + RecordFailure(log, &record, pressure.GetStatus(), + NewtonIterationStage::kExternalAssembled)); + } + Result external = + CombineExternalContributions(fixed.Value(), pressure.Value(), + dofs.Value()); if (!external.HasValue()) { - return Result::Failure(RecordFailure( - log, &record, external.GetStatus(), - NewtonIterationStage::kExternalAssembled)); + return Result::Failure( + RecordFailure(log, &record, external.GetStatus(), + NewtonIterationStage::kExternalAssembled)); } record.last_completed_stage = NewtonIterationStage::kExternalAssembled; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fe178c3..a8580a0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -76,6 +76,7 @@ add_executable( fesa_integration_tests integration/analysis/linear_static_analysis_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/app/fesa_application_test.cpp integration/app/shell_surface_pressure_application_test.cpp diff --git a/tests/integration/analysis/shell_surface_pressure_nonlinear_test.cpp b/tests/integration/analysis/shell_surface_pressure_nonlinear_test.cpp new file mode 100644 index 0000000..0cde0e3 --- /dev/null +++ b/tests/integration/analysis/shell_surface_pressure_nonlinear_test.cpp @@ -0,0 +1,430 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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(text.size())); + if (!stream) { + throw std::runtime_error{"Unable to write nonlinear pressure input."}; + } +} + +std::vector ReadBytes(const std::filesystem::path& path) { + std::ifstream stream{path, std::ios::binary}; + return {std::istreambuf_iterator{stream}, + std::istreambuf_iterator{}}; +} + +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 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 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 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 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& 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& 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 previous{'p', 'r', 'i', 'o', 'r'}; + { + std::ofstream stream{output, std::ios::binary | std::ios::trunc}; + stream.write(previous.data(), + static_cast(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); +} diff --git a/tests/integration/app/shell_surface_pressure_application_test.cpp b/tests/integration/app/shell_surface_pressure_application_test.cpp index e270098..24d77ae 100644 --- a/tests/integration/app/shell_surface_pressure_application_test.cpp +++ b/tests/integration/app/shell_surface_pressure_application_test.cpp @@ -101,6 +101,38 @@ All, 1, 6 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 TEST(ShellSurfacePressureApplication, @@ -143,3 +175,21 @@ TEST(ShellSurfacePressureApplication, 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); +} diff --git a/tests/unit/io/hdf5/nonlinear_hdf5_results_writer_test.cpp b/tests/unit/io/hdf5/nonlinear_hdf5_results_writer_test.cpp index a210b1c..ee8d73b 100644 --- a/tests/unit/io/hdf5/nonlinear_hdf5_results_writer_test.cpp +++ b/tests/unit/io/hdf5/nonlinear_hdf5_results_writer_test.cpp @@ -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/trial_history", H5P_DEFAULT), 0); + EXPECT_EQ(H5Lexists(file.Get(), "/surface_pressure", H5P_DEFAULT), 0); const std::string root = "/steps/Step-1/frames/1"; for (const std::string& suffix :