feat(nonlinear-static-newton-raphson): step 10 - prepared-analysis-flow-and-full-procedure

This commit is contained in:
KOKO\Mimi
2026-08-24 15:49:08 +09:00
parent 3a443e08af
commit f0f38b9803
16 changed files with 1373 additions and 223 deletions
@@ -16,6 +16,7 @@
#include "fesa/analysis/analysis.h"
#include "fesa/assembly/parallel_for.h"
#include "fesa/math/sparse_matrix.h"
#include "fesa/results/results_writer.h"
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
@@ -0,0 +1,157 @@
#include "fesa/app/fesa_application.h"
#include <gtest/gtest.h>
#include <hdf5.h>
#include <atomic>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <stdexcept>
#include <string>
#include <vector>
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-step10-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 procedure 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_;
};
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 procedure input."};
}
}
std::string AllConstrainedNonlinearShellDeck() {
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";
}
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 results HDF5."};
}
}
Hdf5File(const Hdf5File&) = delete;
Hdf5File& operator=(const Hdf5File&) = delete;
~Hdf5File() { (void)H5Fclose(handle_); }
hid_t Get() const noexcept { return handle_; }
private:
hid_t handle_;
};
std::string ReadStringAttribute(const hid_t object, const char* name) {
const hid_t attribute = H5Aopen(object, name, H5P_DEFAULT);
const hid_t type = attribute < 0 ? -1 : H5Aget_type(attribute);
if (attribute < 0 || type < 0) {
if (type >= 0) {
(void)H5Tclose(type);
}
if (attribute >= 0) {
(void)H5Aclose(attribute);
}
throw std::runtime_error{"Unable to read nonlinear metadata attribute."};
}
char* value = nullptr;
const herr_t read = H5Aread(attribute, type, &value);
const std::string result = read >= 0 && value != nullptr ? value : "";
if (value != nullptr) {
(void)H5free_memory(value);
}
(void)H5Tclose(type);
(void)H5Aclose(attribute);
if (read < 0) {
throw std::runtime_error{"Unable to read nonlinear metadata text."};
}
return result;
}
// NSNR-PROC-004, NSNR-PROC-007, and NSNR-PROC-010.
TEST(NonlinearStaticProcedure,
AllConstrainedNlgeomRunWritesSchemaV1AfterAcceptedFrames) {
TempDirectory directory{"schema-v1"};
const auto input = directory.Path() / "shellnl.inp";
const auto output = directory.Path() / "shellnl_results.h5";
WriteText(input, AllConstrainedNonlinearShellDeck());
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;
ASSERT_TRUE(std::filesystem::exists(output));
ASSERT_GT(H5Fis_hdf5(output.string().c_str()), 0);
const Hdf5File file{output};
const hid_t metadata = H5Gopen2(file.Get(), "/metadata", H5P_DEFAULT);
ASSERT_GE(metadata, 0);
EXPECT_EQ(ReadStringAttribute(metadata, "feature_id"),
"nonlinear-static-newton-raphson");
(void)H5Gclose(metadata);
EXPECT_GT(H5Lexists(file.Get(), "/steps/Step-1/accepted_frames", H5P_DEFAULT),
0);
}
} // namespace
@@ -368,6 +368,20 @@ TEST(LinearStaticCli, DefaultOutputUsesInputSiblingDerivedName) {
EXPECT_FALSE(std::filesystem::exists(directory.Path() / "results.h5"));
}
// NSNR-PROC-001: log lifecycle begins after leasing and before parse/map.
TEST(FesaApplication, OpensDerivedLogBeforeInputMapping) {
TempDirectory directory{"log-before-input"};
const auto input = directory.Path() / "missing.inp";
const auto log = directory.Path() / "missing_log.txt";
const auto result = RunApplication({input.string()});
EXPECT_EQ(result.exit_code, 3);
ExpectDiagnosticFieldOrder(result.standard_error);
ASSERT_TRUE(std::filesystem::exists(log));
EXPECT_EQ(std::filesystem::file_size(log), 0U);
}
TEST(LinearStaticCli, ReturnsEveryExactExitCodeAndOrderedDiagnostic) {
TempDirectory directory{"exit-codes"};
const auto valid_input = directory.Path() / "valid.inp";